Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
60c1337
📚 docs: contract: condition deferred SDK promises
pascalandy Nov 3, 2026
4e06c32
📚 docs: contract: Record the remaining verbose help copy
pascalandy Nov 4, 2026
784be76
📚 docs: readme: move the extension template overview
pascalandy Nov 3, 2026
c6bd3b4
📚 docs: contract: move extension version pins
pascalandy Nov 3, 2026
95e6cb9
📚 docs: rules: give formal models their own page
pascalandy Nov 3, 2026
38fc439
📚 docs: skill: move the bundled skill specification
pascalandy Nov 3, 2026
82b61cf
♻️ refactor: template: move repository fixture checks
pascalandy Nov 3, 2026
ef4e50e
🚑 fix: docs: pin extension links to their release
pascalandy Nov 3, 2026
cfab204
📚 docs: template: Complete the extension overview
pascalandy Nov 4, 2026
ab4799a
🚑 fix: transcript: classify the final yt-dlp error
pascalandy Nov 3, 2026
6e38718
🚑 fix: transcript: validate unnamed runs before locking
pascalandy Nov 3, 2026
25d5c91
📚 docs: transcript: Describe unnamed preflight refusals
pascalandy Nov 4, 2026
7f4c7ee
🧪 test: transcript: Preserve generated-ID collision answers
pascalandy Nov 4, 2026
3095089
🚑 fix: kit: bind privacy cases to accepted limits
pascalandy Nov 3, 2026
039206c
♻️ refactor: template: Separate acceptance proof rules
pascalandy Nov 4, 2026
2509c1f
🚑 fix: kit: Require reciprocal privacy limit declarations
pascalandy Nov 4, 2026
c5ff0e0
🧰 skill: template: Teach accepted privacy exceptions
pascalandy Nov 4, 2026
5887217
🚑 fix: kit: Require descriptions for accepted privacy limits
pascalandy Nov 4, 2026
7718a15
🚑 fix: kit: Refuse placeholders beside privacy limit text
pascalandy Nov 4, 2026
a172b1c
📚 docs: kit: State the accepted privacy grammar precisely
pascalandy Nov 4, 2026
6be5949
📚 docs: kit: Name the accepted output outcome
pascalandy Nov 4, 2026
ad67390
♻️ refactor: docs: Separate pinned documentation rules
pascalandy Nov 3, 2026
59edd99
🚑 fix: sdk: Isolate updater Git state
pascalandy Nov 3, 2026
e1a5a5b
🧪 test: sdk: Keep updater fixture Git config private
pascalandy Nov 4, 2026
95d54c9
📚 docs: sdk: Correct the updater documentation scope
pascalandy Nov 4, 2026
54bb169
📚 docs: sdk: State the shared environment filter scope
pascalandy Nov 4, 2026
314ac75
📚 docs: sdk: State unconditional caller Git filtering
pascalandy Nov 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .agents/skills/verify-nicely/features/extensions-skill.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

- `template-command` gives the command that creates an extension repository, in the `nicely-extensions` skill, with the VERSION of a development build
- `template-install-command` gives the commands that install an extension repository at a release tag, in the `nicely-extensions` skill
- `revision-links` use the release tag for bundled documentation, or main in a development build
- `template-remove-command` gives the steps that remove an extension, in the `nicely-extensions` skill

## How to get to it (user POV)
Expand All @@ -23,6 +24,8 @@ Preconditions:
- **Install command.** In the output of `skill view nicely-extensions`, the skill holds the section `## Install an extension`. It quotes the install rule, in a line that starts with `> A user installs a fixed version with`. Its code block holds the lines `git clone --branch TAG https://github.com/OWNER/REPO` and `cd REPO && just install`, and the text names `ncly describe ROOT --json`.
- **Remove command.** In the output of `skill view nicely-extensions`, the skill holds the section `## Remove an extension`. It names `ncly doctor extensions --json`, the four files `ncly-SLUG`, `ncly-SLUG.toml`, `ncly-SLUG.locales/`, and `ncly-SLUG.skill/`, `just uninstall`, `NOT_FOUND`, and `GRANT_UNUSED`.

- **Revision links.** In a development or pseudo-version build, each Nicely `blob/` link in `skill view nicely-extensions` uses `main`. For a release-version check, build the same source into `$run/build/ncly` with `go build -ldflags '-X github.com/pascalandy/nicely/internal/cli.version=v0.26.0' -o "$run/build/ncly" ./cmd/ncly`. Run `just verify step "$run" extensions-skill --version`, then `just verify step "$run" extensions-skill skill view nicely-extensions`. Every Nicely `blob/` link now uses `v0.26.0`. Quote both outputs and the build flag; this is a test build, not a published release.

## Gotchas

- The skill comes with the `ncly` of the run, so this feature needs no `prepare`
2 changes: 2 additions & 0 deletions .agents/skills/verify-nicely/features/template.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

- `template-loop` sends each command of the rendered repository to its `nicely-extension` skill, which builds it in rounds until `just finish` passes
- `template-install` renders the template, and installs the rendered repository with `just install` in the scratch data folder
- `template-links` pin documentation to the selected tag
- `template-rule-index` counts the rules of the rule index in `just check` of the rendered repository
- `template-publication` refuses in `just check` a command that writes a user file with no row in `## Publication` of `spec.md`
- `template-acceptance` reads the answer of `probe inspect` in its acceptance case, and refuses a count of 30 lines for 3
Expand Down Expand Up @@ -38,6 +39,7 @@ Preconditions:
- `uv`, `just`, and a `python3` of 3.9 or later, which answers under the scratch home

- **Render and install.** Run `just verify prepare "$run" template`. It exits 0. Its log names `just install` of `probe`, the line `installed <run>/scratch/home/.local/share/nicely/extensions/ncly-probe`, and the `python3` that it puts first in the PATH of the wrapper. Then run `ls "$run/scratch/home/.local/share/nicely/extensions"`: it lists `ncly-probe`, `ncly-probe.locales`, `ncly-probe.skill`, and `ncly-probe.toml`, beside the four files of `hello`. Report the output of `prepare` and of `ls` beside the quote.
- **Links.** Run `rg --hidden 'nicely/blob/' "$run/scratch/ncly-probe" -g '*.md'`. Each link uses `blob/v0.0.0/`.
- **Release and install text.** Run `grep -n -A12 '^## Release$' "$run/scratch/ncly-probe/AGENTS.md"` and `grep -n -A3 '^git clone' "$run/scratch/ncly-probe/README.md"`. Both exit 0. `AGENTS.md` names `just release vX.Y.Z` and the release receipt. `README.md` holds the lines `git clone --branch TAG https://example.com/ncly-probe ncly-probe`, `cd ncly-probe`, `just install`, and `ncly describe probe --json`. Report both outputs beside the quote.
- **Loop text.** Run `grep -n -A12 '^## Done when$' "$run/scratch/ncly-probe/AGENTS.md"`, then `grep -n '^## Open lines$\|^## Case matrix$' "$run/scratch/ncly-probe/spec.md"`. Both exit 0, and `spec.md` lists both parts. "Done when" names `just finish`, the hosted checks, 5 rounds, and a "full stop" request that runs only after it. Then run `grep -n '^##' "$run/scratch/ncly-probe/.agents/skills/nicely-extension/SKILL.md"`. It lists `## Intake`, `## Acceptance lines`, `## Test files`, `## Rounds`, and `## Readiness review`. Then run `grep -n '^##' "$run/scratch/ncly-probe/.agents/skills/nicely-extension/intake.md"`. It lists `## Intake of a script`, `## Intake of a program`, and `## Evidence of the intake`. Report the four outputs.
- **Finish gate.** Run `(cd "$run/scratch/ncly-probe" && just finish)`. Exit 1. Stderr names each `Fill:` line of `vision.md`, `spec.md`, `qa.md`, and `intake.toml` as `<file>:<line>: this line of the form is not filled`, and `review.toml: no readiness review is recorded`. Then run `just records` there. Exit 0, and it names `vision.md`. Report both outputs.
Expand Down
2 changes: 2 additions & 0 deletions .agents/skills/verify-nicely/features/transcript-run.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

- `transcript-grant` grants the key of `deepgram` to transcript after a typed `y` on a real terminal
- `transcript-fake-key` runs one transcription with a fake key: Deepgram refuses it, and the item answers `AUTH_REJECTED` under `RUNTIME`, with its result folder
- `transcript-sign-in` stops a YouTube sign-in refusal with `TRANSCRIPT_DOWNLOAD_FAILED` and the browser-setting hint
- `transcript-record` records the run with its failed step, and the same call starts nothing
- `transcript-other-call` refuses another URL under the run ID of that run with `USAGE_INVALID`, before anything starts
- `transcript-stop` stops a run with Ctrl-C during the real download: exit 130, no process left, and the step pending
Expand All @@ -28,6 +29,7 @@ In `just verify step "$run" transcript-run <args>`, `transcript-run` names this

- **Grant.** Run `just verify tmux "$run" transcript-run --answer '[y/N]' y auth grant deepgram transcript`. Exit 0. Before Enter, the terminal shows `? Grant the key of deepgram to transcript?`, then ` Path ` and the path of `ncly-transcript` in `<run>/scratch/home/.local/share/nicely/extensions/`, then ` [y/N] y`. After Enter, it shows `Granted the key of deepgram to transcript.`
- **Fake key.** Run `just verify step "$run" transcript-run transcript run youtube --url 'https://www.youtube.com/watch?v=EIEc43CxIvY' --output "$run/scratch/home/videos" --run-id verify-fake --json`. Exit 1 within a minute, and stdout is empty. The first error of stderr is `RUNTIME`, and the answer holds `"run_id":"verify-fake"`. Its one item has `"ok":false`, `"transcript":null`, and an `output_dir` in `<run>/scratch/home/videos/`, named after the title of the video. The item's one error has the code `AUTH_REJECTED`, the message `Deepgram refused the key.`, and the hint `ncly auth login deepgram`.
- **YouTube sign-in refusal.** YouTube may refuse the download before the fake key reaches Deepgram. In that case, require exit 1 and item code `TRANSCRIPT_DOWNLOAD_FAILED`, with a hint naming the `browser` setting in `[transcript]`. Read `run view verify-fake --json` and require one attempt. Report that login blocks the remaining `AUTH_REJECTED` assertions; do not read personal browser cookies or attempt a browser login for this proof. The stub scenarios cover the final-error classification independently of that live precondition.
- **Result folder.** Run `ls -A` on the `output_dir` of Fake key. It prints `meta.txt` alone, since Deepgram answered no transcript. Report its output beside the quote.
- **Record.** Run `just verify step "$run" transcript-run run view verify-fake --json`. Exit 0. The run has `"status":"failed"`, `"attempts":1`, and `"tools"` with `"transcript":"1.0.0"` and the version of yt-dlp. Its step `transcribe` has `"effect":"paid"`, `"status":"failed"`, no artifact, and the error `AUTH_REJECTED`. Its `output_dir` is the one of Fake key.
- **Same call.** Run the call of Fake key again. Exit 1 within a few seconds, and stderr holds the code `RESUME_UNSAFE`, `"run_id":"verify-fake"`, and the hint `ncly run view verify-fake`, because the audio reached Deepgram once.
Expand Down
6 changes: 5 additions & 1 deletion .agents/skills/verify-nicely/features/transcript.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- `transcript-doctor` passes `transcript.uv` and `transcript.python3`, the programs that the manifest requires, then `transcript.cookies`
- `transcript-cookies` warns when the config sets `browser = "arc"` and no cookie file of Arc's default profile opens, and passes once one does
- `transcript-skill` serves the skill installed beside the extension
- `transcript-lock-refusals` creates no run lock when an unnamed execution fails its output or key check
- `transcript-refusals` refuses a URL that names no single video and a missing output folder with `USAGE_INVALID`, before the engine is extracted
- `transcript-dry-run` runs the engine through `uv` from an empty uv cache and answers the plan as JSON and as text. It creates no record and no result folder

Expand Down Expand Up @@ -48,10 +49,13 @@ In `just verify step "$run" transcript <args>`, `transcript` names this feature,
- **No record.** Run `ls "$run/scratch/home/.local/state/nicely/runs"`, then `ls -A "$run/scratch/home/videos"`. The first fails and the second prints nothing, because a dry run records nothing and creates no result folder. Report both outputs beside the quote.
- **Dry run as text.** Run the call of Dry run without `--json`. Exit 0, and stdout holds two lines: `Would transcribe https://www.youtube.com/watch?v=EIEc43CxIvY into a new folder in <run>/scratch/home/videos.`, then `pending auth.deepgram Authentication is checked only during execution.`

- **Unnamed key refusal.** Before the grant of transcript-run, create `$run/scratch/home/.local/state/nicely/runs` and record its empty `ls -A` output. Run `just verify step "$run" transcript transcript run youtube --url 'https://www.youtube.com/watch?v=EIEc43CxIvY' --output "$run/scratch/home/videos" --json`. Exit 78, with `KEY_NOT_GRANTED` and no `run_id`. The wrapper supplies only a fake key, and no grant lets it reach the extension. A second `ls -A` of the runs folder is empty.
- **Unnamed output refusal.** Run the same call with `--output "$run/scratch/home/missing"`. Exit 2, with `USAGE_INVALID` and no `run_id`. The runs folder is still empty. Quote both CLI calls and report all three directory observations; neither call reaches YouTube or Deepgram.

## Gotchas

- Run `prepare` before any `transcript` step. Before it, the wrapper runs the build of `just verify`, without `ncly-transcript`. Its `PATH` may also start with a shim of `uv` that fails under the scratch home
- Call `ncly` only through `just verify step` or `just verify tmux`, whose wrapper forces the fake key. Core reads `DEEPGRAM_API_KEY` before the keychain, so another call could bill a real key. Never run `ncly auth login deepgram`: it writes the real keychain, where it would replace a real key
- Every call of `transcript run youtube` here passes `--dry-run`, so none reaches YouTube or Deepgram
- The unnamed output and key refusals execute preparation and stop before the engine or network. The other `transcript run youtube` calls here use `--dry-run`
- The dry run reaches PyPI through `uv` only. Nicely's cache in the scratch home holds the engine and the uv cache, and the cleanup removes both
- The wrapper keeps the caller's variables, so a `UV_OFFLINE` or `UV_INDEX_URL` of the caller reaches `uv`
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,22 @@ The current build brings:
- The `agent` extension, which runs one trusted task through Codex and records it
- The `transcript` extension, which transcribes one YouTube video with Deepgram, records the run, replays it, and stops cleanly on Ctrl-C
- `ncly run list` and `ncly run view`, which read the records of the runs
- The extension template, which creates the repository of an extension outside Nicely, with its tests and its recipes. Its `AGENTS.md` explains how to add a first command, remove the example, and release a version. Its `nicely-extension` skill builds each command in rounds, until `just finish` passes. `just finish` reads the intake and the reviews only in the typed records `intake.toml` and `review.toml`. An extension of an older template adopts `just finish` through `just update`, and its first build starts after the update commit. Its `just check` counts the rules of its rule index that wait for a check. Its `spec.md` declares each command that writes a user file. The test kit proves that a dry run predicts its run, and on Linux, a run without a hard link. Each acceptance case reads the output with a reader that is not the wrapped program, and its bad variant must fail. Its `README.md` tells a user how to install a release
- The [extension template](README/extension-template.md), which creates an extension repository with its tests and recipes
- SDK checks that explain broken manifest rules and missing catalog IDs in English, with their repository file names

YouTube sign-in refusals stop automatic retries with `TRANSCRIPT_DOWNLOAD_FAILED`.
Their hint names the transcript `browser` setting.
Output or key refusals leave no empty run-lock files when `--run-id` is omitted.

Extension manifests declare errors and warnings with optional command scopes, execution modes, and coverage exemptions linked to issues.
`extension.Check` rejects invalid scopes with TOML line and column positions.
The [test kit](docs/north-star/contract/test-kit.md#declared-codes) requires each declared code in every selected command and mode.
It lists coverage exemptions for the UAT report.
Accepted privacy limits use [a declared case](docs/north-star/contract/acceptance-cases.md#accepted-privacy-limits), with `reads -case '<case>' -limit <id>` and an independent reader.

[The current build](docs/status.md) describes each command. The commands below are planned work.
[The current build](docs/status.md) describes each command.

## Planned commands
## Commands and extensions

Nicely is a small core that extensions add commands to, in the spirit of Pi.
Core brings `describe`, `doctor`, `auth`, and `run`. It bundles `skill`, which agents use to learn Nicely.
Expand All @@ -55,6 +60,7 @@ It uses dry runs where available and prints skip reasons in plain `just check` o
The kit, `just signoff`, `just release`, and `just acceptance` run in [owned runs](docs/north-star/contract/owned-runs.md), without settings of the caller such as `NCLY_CONFIG`.
`just verify-models` checks the TLA+ models of `verification/tlc.json` with a pinned TLC, whose SHA-256 it checks before Java starts. `just check` reads only their [record](docs/north-star/contract/formal-models.md), with no Java and no network.
`just update VERSION` runs the target version's updater.
It removes the caller's `GIT_*` settings from update programs, including Copier's Git calls, so a hook cannot redirect the update to another repository.
Older repositories use the bootstrap in [Update an extension](docs/north-star/contract/extension-repository.md#update-an-extension).
Further work lives in [docs hub](docs/backlog/XT-104-docs-hub.md), [Canadian French](docs/backlog/COR-102-canadian-french.md),
[Markdown view](docs/backlog/XT-103-markdown-view.md), and [video and image jobs](docs/backlog/XT-602-video-image-jobs.md).
Expand Down
6 changes: 6 additions & 0 deletions README/extension-template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Extension template

The extension template creates the repository of an extension outside Nicely, with its tests and recipes. Its `AGENTS.md` explains how to add a first command, remove the example, and release a version. Its `nicely-extension` skill builds each command in rounds, until `just finish` passes. `just finish` reads the intake and the reviews only in the typed records `intake.toml` and `review.toml`. An extension of an older template adopts `just finish` through `just update`, and its first build starts after the update commit. Its `just check` counts the rules of its rule index that wait for a check. Its `spec.md` declares each command that writes a user file. The test kit proves that a dry run predicts its run, and on Linux, a run without a hard link. Each acceptance case reads the output with a reader that is not the wrapped program, and its bad variant must fail. Its `README.md` tells a user how to install a release.

The template's documentation links use its selected revision.
The bundled `nicely-extensions` skill links to the running release's documentation, or `main` for a development build.
13 changes: 13 additions & 0 deletions docs/backlog/COR-629-os-trash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
id: COR-629
core: "OS trash"
state: "Idea from #447. Revisit with the first command that deletes a user file"
---

# COR-629 OS trash

The SDK would send deleted user files to the OS trash on macOS and Linux.
No user command deletes a file today.

The first such command must add this SDK support and prove that the deleted file can be recovered.
[Principle 4](../north-star/principles.md) records the condition.
16 changes: 16 additions & 0 deletions docs/backlog/COR-630-core-diagnostics.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
id: COR-630
core: "core diagnostics"
state: "Idea from #447. Revisit with the first core command that needs debug or verbose details"
---

# COR-630 core diagnostics

The SDK would let core commands add verbose step details and debug diagnostics on stderr.
Core adds neither today.
The current `--verbose` help entry says `Add step details on stderr`, although core adds none.
Revisit that catalog entry with the shared support.
An extension can already add diagnostics when it needs them, as [Global flags](../north-star/contract/global-flags.md) describes for transcript.

The first core command that needs these details must add shared SDK support and prove its output.
Keys must stay out of diagnostics.
12 changes: 12 additions & 0 deletions docs/backlog/COR-631-tool-credit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
id: COR-631
core: "tool credit"
state: "Idea from #447. Revisit with the next wrapping command"
---

# COR-631 tool credit

The [wrap helper](../../sdk/wrap/wrap.go) would declare the wrapped tool so that command help names it.
The next wrapping command must add this support to `sdk/wrap` and prove the credit in its help.

[Principle 10](../north-star/principles.md) records the condition.
Loading
Loading