@erclx/canon 4.52.0 → 4.54.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,6 +10,14 @@ canon is a CLI and Claude Code plugin that stops your AI conventions drifting ap
10
10
 
11
11
  The counts above are read from the catalogs when the image is built, so they're what the repo actually ships today.
12
12
 
13
+ ## It runs on itself
14
+
15
+ The workflow this toolkit ships is the workflow that built it. Several Claude Code sessions run at once, each in its own git worktree on its own branch, and each opens its own pull request.
16
+
17
+ ![The canon landing page's session list, with the orchestrator pinned above bands of working and completed sessions, workers and planners in both, each naming what it is doing and the pull request it opened, and one row moving from Working into Completed](demos/agent-view.gif)
18
+
19
+ That is the landing page's own agent view, recorded from a local build by `canon demo run`. The session rows behind it are a real `canon sessions list --json` read. The activity text and the pull request number are transcribed, because no verb here reports either.
20
+
13
21
  ## Why
14
22
 
15
23
  If you work across more than one repository and your AI setup has started to drift between them, this is for you. Every AI coding setup accumulates the same assets. Prompts to reuse, rules agents should follow, slash commands, skills, seed docs, sync scripts. Once you have enough projects, your copies drift and your agents stop getting consistent signals.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "canon",
3
3
  "description": "Automated governance, versioning, and discovery tools for Claude Code.",
4
- "version": "4.52.0",
4
+ "version": "4.54.0",
5
5
  "author": {
6
6
  "name": "Eric Le",
7
7
  "url": "https://github.com/erclx"
@@ -150,12 +150,17 @@ the regen rebuilt, and close the same way. The heading stays outside the
150
150
  `## Review` family so the close-out's equality test on the first line never
151
151
  matches it.
152
152
 
153
- Before posting, run the scan in
154
- `${CLAUDE_SKILL_DIR}/../../standards/publish.md`
155
- against the reply. The hook skips `.canon/tmp/`, so this scan is the
156
- only gate on the published reply. Post it to the PR and capture the posted
157
- comment's id, since Step 7 edits this exact comment rather than trusting
158
- whichever one `gh` considers last:
153
+ Before posting, follow `${CLAUDE_SKILL_DIR}/../../standards/publish.md`
154
+ and run its scan against the reply:
155
+
156
+ ```bash
157
+ canon labels scan --body-file .canon/tmp/address-review/reply-<number>.md
158
+ ```
159
+
160
+ The hook skips `.canon/tmp/`, so this scan is the only gate on the published
161
+ reply. Fix any hit the standard names, then post the reply to the PR and
162
+ capture the posted comment's id, since Step 7 edits this exact comment rather
163
+ than trusting whichever one `gh` considers last:
159
164
 
160
165
  ```bash
161
166
  comment_url=$(gh pr comment <number> --body-file .canon/tmp/address-review/reply-<number>.md)
@@ -182,13 +187,25 @@ carries none is false on a surface nothing else checks:
182
187
  printf '\n✅ Rebased onto origin/main, CI green. No review findings were open.\n' >> .canon/tmp/address-review/reply-<number>.md
183
188
  ```
184
189
 
185
- Re-run the `${CLAUDE_SKILL_DIR}/../../standards/publish.md` scan against the
186
- updated file, since the appended line is new content the Step 6 scan never saw.
190
+ Re-run the scan against the updated file, since the appended line is new
191
+ content the Step 6 scan never saw:
192
+
193
+ ```bash
194
+ canon labels scan --body-file .canon/tmp/address-review/reply-<number>.md
195
+ ```
196
+
187
197
  Then edit the exact comment Step 6 posted, read back from the id it saved,
188
- rather than posting a second comment:
198
+ rather than posting a second comment. A shell variable assigned from `cat` and
199
+ handed to `gh` still carries a runtime value the isolation rule cannot verify,
200
+ so read the id as its own plain command and write the printed value as a
201
+ literal in the `gh api` call rather than a substitution or a variable:
202
+
203
+ ```bash
204
+ cat .canon/tmp/address-review/reply-<number>.id
205
+ ```
189
206
 
190
207
  ```bash
191
- gh api -X PATCH "repos/{owner}/{repo}/issues/comments/$(cat .canon/tmp/address-review/reply-<number>.id)" \
208
+ gh api -X PATCH "repos/{owner}/{repo}/issues/comments/<id printed above>" \
192
209
  -F body=@.canon/tmp/address-review/reply-<number>.md
193
210
  ```
194
211
 
@@ -226,7 +243,7 @@ Do not merge. Hand back to the orchestrator for re-review.
226
243
 
227
244
  ## Post-review findings
228
245
 
229
- Not everything worth reaching the reviewing session surfaces inside the numbered flow above. A worker that settled a risk, filed a follow-up, or found something else worth reporting after Step 7 already closed the review posts it directly rather than waiting on a review pass that has nothing left to trigger it. Write the body the way Step 6 writes a reply: load `write-human` for voice, follow `${CLAUDE_SKILL_DIR}/../../standards/markdown.md` for the banned words, and run the `${CLAUDE_SKILL_DIR}/../../standards/publish.md` scan before posting.
246
+ Not everything worth reaching the reviewing session surfaces inside the numbered flow above. A worker that settled a risk, filed a follow-up, or found something else worth reporting after Step 7 already closed the review posts it directly rather than waiting on a review pass that has nothing left to trigger it. Write the body the way Step 6 writes a reply: load `write-human` for voice, follow `${CLAUDE_SKILL_DIR}/../../standards/markdown.md` for the banned words, and run the `${CLAUDE_SKILL_DIR}/../../standards/publish.md` scan before posting with `canon labels scan --body-file .canon/tmp/address-review/reply-<number>.md`.
230
247
 
231
248
  Open with `## Post-review findings` rather than `## Review response`, since nothing on the thread is being answered. `claude-pr-review` states the full heading set this belongs to and routes it the same as a response: `claude-orchestrate`'s poll picks it up and sends the reviewing session back for a pass. Close the body with `🤖 Addressed by Claude Code` on its own line, matching the reply's footer.
232
249
 
@@ -93,7 +93,7 @@ Omit empty sections. Do not print `None identified.` in chat.
93
93
 
94
94
  Derive a 2-to-4-word kebab-case slug from the feature description. Write the full plan to `.canon/plans/feature-<slug>.md` at the main worktree root, not the current worktree. Resolve that root the way `claude-worktree` does. Create the directory if it does not exist.
95
95
 
96
- From a linked worktree the file-editing tools refuse that path, so the plan goes out through `Bash`. Send the `mkdir -p` and the heredoc as two plain commands rather than joining them with `&&`, which is refused as compound.
96
+ From a linked worktree, or from a background session sitting at the main root with none entered, the file-editing tools refuse that path, so the plan goes out through `Bash`. Send the `mkdir -p` and the heredoc as two plain commands rather than joining them with `&&`, which is refused as compound.
97
97
 
98
98
  The file follows the template in `${CLAUDE_SKILL_DIR}/../../standards/plan.md`. Copy the shape from there rather than from this body, so one edit to the standard moves every plan.
99
99
 
@@ -26,7 +26,7 @@ This gate runs ahead of the two collision checks because it is the cheapest read
26
26
 
27
27
  It also reads the plan rather than a cell describing one, which is the input the gate below it does not have. The disjointness gate compares the sets a dispatcher wrote into the constraints and the Touches column, so a cell omitting a file clears a check the tree would fail. That happened on 2026-08-31, when two rows were cleared against each other with one constraints block leaving out the context entry both were about to write, and what caught it was a worker sending a message rather than any check.
28
28
 
29
- A blank `- Answer:` is not an unanswered question. `${CLAUDE_SKILL_DIR}/../../standards/plan.md` fixes an empty slot as accepting the `- Suggested:` line above it, which is what makes a plan decision-ready in one pass. The narrow case this reads is `- Suggested: needs your call, <why>` over an empty slot, the form that same standard writes where the answer turns on preference rather than on a technical default. A gate reading every blank slot as open would refuse every plan in the folder.
29
+ A blank `- Answer:` is not an unanswered question. `${CLAUDE_SKILL_DIR}/../../standards/plan.md` fixes an empty slot as accepting the `- Suggested:` line above it, which is what makes a plan decision-ready in one pass. The narrow case this reads is `- Suggested: needs your call, <why>` and its two demonstrated paraphrases, `needs operator's call` and `needs the operator's call`, over an empty slot, the form that same standard writes where the answer turns on preference rather than on a technical default. A gate reading every blank slot as open would refuse every plan in the folder.
30
30
 
31
31
  What it prevents is a halt nobody is watching for. `claude-worker` instructs a session to stop on a question written as needing the operator's call, correctly and by its own body, so a dispatch that never reads the plan lands a worker in a wait for a person who does not know it is waiting. The worker's halt is not the defect, and the dispatch that made it necessary is.
32
32
 
@@ -227,6 +227,9 @@ build shape above reaches `claude-worker`.
227
227
  No branch and no worktree exist here and none is created. A planner writes one
228
228
  gitignored file at the main worktree root, so this shape names the row's task
229
229
  file rather than a branch and opens with the role instead of a worktree call.
230
+ That write meets the isolation guard the same way a linked worktree's
231
+ main-root write does, with no worktree here to redirect it to, so
232
+ `claude-planner` sends it as a `Bash` heredoc rather than through `Write`.
230
233
 
231
234
  ```bash
232
235
  claude --bg --model <model> -n "planner-<project>-<slug>" "Run /canon:claude-planner, then /canon:claude-feature <task>. Your controller is the session whose sessionId is <dispatcher-id>. Resolve its current name from that id through canon sessions list --json, which carries sessionId per row, at the moment you send, and never resolve an addressee by name prefix. Message it when the plan lands, carrying the path and what the task file got wrong, and message it again if you stop on a question."
@@ -21,7 +21,7 @@ from here.
21
21
 
22
22
  ## Where the session stands
23
23
 
24
- - Write one file, the plan, at `.canon/plans/feature-<slug>.md` on the main worktree root. Everything else this session touches is a read.
24
+ - Write one file, the plan, at `.canon/plans/feature-<slug>.md` on the main worktree root. Everything else this session touches is a read. This session holds no worktree, so send the file as a plain `Bash` heredoc past the same main-root refusal a linked worktree meets. The route skips the write-matched hooks, and they are no-ops on a plan file.
25
25
  - Never enter a worktree, never create a branch, and never write a tracked file. A planner that builds has stopped being one, and the row loses the independent read the worker's own session was going to bring to it.
26
26
  - Resolve `.canon/plans/`, `.canon/tasks/`, and `.canon/review/` at the main worktree root. Those folders are gitignored, so a copy beside a linked worktree is absent rather than empty.
27
27
  - Plan the row the launch named. Report a second row met while reading rather than planning it, since which rows run is the controller's call.
@@ -9,84 +9,85 @@ Full help: `canon <command> --help`. Behavior notes for the install and sync ver
9
9
 
10
10
  ## Project-level
11
11
 
12
- | Command | Purpose |
13
- | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
14
- | `canon init [path]` | Bootstrap a project with selected toolkit domains |
15
- | `canon sync [path]` | Sync all installed domains in a target project |
16
- | `canon sync --check` | Report toolkit drift and the installed version against the newest published (`--json`, `--exit-code`) |
17
- | `canon sandbox [cat:cmd]` | Run sandbox scenarios (interactive or routed), toolkit-only like the tree it reads |
18
- | `canon sandbox reset` | Reset sandbox to baseline |
19
- | `canon sandbox clean` | Wipe the sandbox |
20
- | `canon sandbox check` | Score a provisioned sandbox against a scenario expectation (`--json` for the verdict) |
21
- | `canon sandbox coverage` | Report which scenarios declare expectations (`--json`, `--strict`, `--skills`) |
22
- | `canon indexes regen` | Regenerate `index.md` files from sibling frontmatter |
23
- | `canon docs [topic]` | Emit toolkit reference docs (`list`, or a topic by name) |
24
- | `canon design render` | Render `.claude/DESIGN.md` tokens to HTML and CSS |
25
- | `canon design regen` | Rewrite this repository's `.claude/DESIGN.md` and `src/design/base.css` from `src/design/tokens.ts` |
26
- | `canon design css` | Emit the design tokens and components as CSS on stdout (`--no-components` for properties alone) |
27
- | `canon design install` | Install the base stylesheet into a project at `.claude/design/base.css` |
28
- | `canon design sync` | Update a base stylesheet already installed under `.claude/design/` |
29
- | `canon slides render` | Render a `.claude/SLIDES.md` source into a PowerPoint deck, reporting any unrecognized layout name on stderr |
30
- | `canon slides list` | List the available slide layouts (`--json` for the catalog) |
31
- | `canon feedback` | Write toolkit feedback from stdin to `.canon/review/feedback/`, or open a GitHub issue with `--github`, refusing either when a required field is absent |
32
- | `canon transcripts <url>` | Fetch a YouTube transcript with metadata frontmatter (needs `yt-dlp`) |
33
- | `canon tasks archive` | Move a shipped task and its plan off the board, clear its ordering row, and regenerate the index |
34
- | `canon tasks pull-request` | Record a pull request number on the task a branch closes, by stem or `--plan` (`--json`) |
35
- | `canon tasks plan-link` | Write or correct a task's `Plan:` line to point at a plan, by stem and plan path or slug (`--json`) |
36
- | `canon tasks outcome` | Mark outcomes `[x]` on a task by position, repeating `--close` (`--json`) |
37
- | `canon tasks validate` | Report board rows whose shape, order, plan, task file, group, file set, or blocker does not hold (`--json`) |
38
- | `canon intake list` | Report intake folder counts, or one folder's items, keeping what is unread with `--unread` (`--json`) |
39
- | `canon intake answer` | Write selections into one cluster's answer slots, repeating `--set <item>=<answer>` (`--json`) |
40
- | `canon teach list` | Report learning workspaces and the ordinal a new one takes, or what one workspace holds and what its records schedule (`--json`) |
41
- | `canon teach open` | Open a workspace at the next ordinal with its mission, resources, and glossary files (`--json`) |
42
- | `canon teach resource` | Record sources and leads in a workspace, repeating `--read` or `--lead` as `<title>=<url>` (`--json`) |
43
- | `canon teach glossary` | Add terms to a workspace glossary alphabetically, repeating `--term <term>=<definition>` (`--json`) |
44
- | `canon teach stylesheet` | Seed a workspace stylesheet from the design source, leaving an existing one alone without `--force` (`--json`) |
45
- | `canon teach nav` | Rewrite the teach-root listing, a workspace's contents page, and each lesson's chrome from its four marker regions (`--json`) |
46
- | `canon records validate` | Report a session record or a standard against the standard governing it, per kind (`--json`) |
47
- | `canon records migrate` | Rewrite the records a validate finding names a recoverable transform for (`--write`, `--json`) |
48
- | `canon records size` | Report what each record folder holds and how much of it is recent, heaviest first (`--json`) |
49
- | `canon records push` | Commit the nine backed record folders and push them to a private records remote (`--json`) |
50
- | `canon records pull` | Fetch the records remote and write it back, refusing rather than discarding unpushed records (`--json`) |
51
- | `canon migrate rename` | Rewrite every unprotected `aitk` token to `canon` and move the paths that carry the name, reporting the plan without `--write` (`--scope`, `--json`) |
52
- | `canon migrate records` | Move the gitignored session records to `.canon/` and repoint every tracked citation, reporting the plan without `--write` (`--root`, `--json`) |
53
- | `canon migrate record-tree` | Repoint the old-root citations inside the records themselves, scoped to the live folders and reporting every line without `--write` (`--root`, `--json`) |
54
- | `canon migrate rule-layout` | Move a target's installed rules from the flat `.claude/rules/<subdir>/` layout to `.claude/rules/canon/<subdir>/`, reporting the plan without `--write` (`--root`, `--json`) |
55
- | `canon sessions list` | Resolve live sessions to the worktree and branch each holds, filtered by `--branch` (`--json`) |
56
- | `canon worktrees list` | Report which worktrees are reclaimable, keyed on the pull request having merged, with every refusal and the removal route named (`--json`) |
57
- | `canon worktrees reclaim` | Remove every reclaimable worktree and the branch behind it, reporting without acting under `--dry-run` (`--json`) |
58
- | `canon comments scan` | Measure comment density by language and comment kind, with a trend recomputed from git |
59
- | `canon context audit` | Report required sections, length, cited paths, reference form, catalog tables, provenance, superseded-decision narration, and index drift |
60
- | `canon markdown audit` | Fail any markdown path on a banned character, word, or spelling, or a dead relative link, and report the structural checkpoints |
61
- | `canon claude skills audit` | Report both skill corpora against the mechanical rules in `standards/skill.md` |
62
- | `canon standards audit` | Report the corpus against the success-criterion gate `standards/standard.md` states, failing only on a standard new to the branch (`--json`, `--arrivals-only`) |
63
- | `canon claude skills drift` | Name the shipped skill bodies rewritten between a given ref and `HEAD`, and the installed version against the newest published (`--json`) |
64
- | `canon claude skills reach` | Report the bodies in either skill corpus citing a toolkit path no target project receives, exiting 2 on an unqualified one |
65
- | `canon claude skills rank` | Score either skill corpus's descriptions against a case corpus by TF-IDF cosine similarity, reporting rank-one and top-three (`--cases <path>`) |
66
- | `canon claude routing` | Report per `CLAUDE.md` section how many bullets name a path and how many of those a path-scoped rule already covers (`--json`) |
67
- | `canon gov test-order` | Report where an implementation reached history ahead of the test covering it (`--json`) |
68
- | `canon gov superseded` | Report where the tree still asserts a value a changed convention no longer produces, keyed on the value and on the family stem behind a templated citation (`--json`) |
69
- | `canon gov restated` | Report every instruction the always-loaded file or a rule shares with the seed, a shipped skill body, or another rule, classed and with its anchors named (`--json`) |
70
- | `canon gov citations` | Resolve every path a rule cites and every internal frontmatter glob, failing on one reaching nothing (`--json`) |
71
- | `canon secrets scan` | Report credential-shaped values in the tree the package ships, keyed on issued values rather than on words (`--json`) |
72
- | `canon deps audit` | Report published advisories against the resolved dependency set, refusing rather than reporting clean when the index is unreachable (`--json`) |
73
- | `canon labels audit` | Report the labels a changed set earns from the pull request label map and the paths no row reaches (`--json`) |
74
- | `canon labels scan` | Fail a pull request or a posted review whose title, body, or review comment carries a phase label, a label a code span quotes, a gitignored record path, a session link, or a title word no dictionary holds (`--event`, `--json`) |
75
- | `canon autoship classify` | Decide whether a changed set needs the review pass, naming the file and the test that decided it (`--json`) |
76
- | `canon pr key-changes` | Compare the files a pull request body's Key Changes names against its own diff, in both directions (`--body`, `--base`, `--json`) |
77
- | `canon pr head` | Compare the head a pull request object reports against the branch tip the remote carries, naming both shas (`--root`, `--json`) |
78
- | `canon pr checks` | Report the check runs belonging to the branch tip, reading pending for a tip carrying none rather than clean (`--root`, `--json`) |
79
- | `canon repo metadata propose` | Compare a description, homepage, and topic set computed from the README and `package.json` against what the remote carries, writing nothing (`--root`, `--json`) |
80
- | `canon repo metadata apply` | Write an explicitly supplied description, homepage, or topic set to the remote through `gh repo edit` (`--description`, `--homepage`, `--topics`, `--root`, `--json`) |
81
- | `canon census [path]` | Report tracked file count, a breakdown by extension, and a line total that skips whatever reads as binary (`--json`) |
82
- | `canon audits run` | Run every audit as one set, report per check under one verdict, and compare each count to the recorded baseline (`--json`, `--record`, `--corpus`) |
83
- | `canon audits list` | List every audit the set runs, with the corpus each reads and whether it gates (`--json`) |
84
- | `canon gate run` | Run every stage that guards a branch here, scoping shell, types, and tests to the changed set (`--all`, `--no-write`, `--nested`, `--json`) |
85
- | `canon inventory [subject]` | Walk every route a project declares and group its elements by the property each computes, as a listing rather than a gate (`--json`) |
86
- | `canon drive <url> <run>` | Walk a page through named interactions and measure each state it reaches, reporting findings rather than gating (`--json`) |
87
- | `canon capture [source]` | Render HTML sources to PNG and prove the font each one declares resolved (`--selector` required, `--out`) |
88
- | `canon serve [dir]` | Serve a directory on the loopback interface and print the link that opens it, running until interrupted (`--port`, `--entry`, `--json`) |
89
- | `canon upgrade` | Reinstall the CLI globally with the package manager the install path names (`--json`) |
12
+ | Command | Purpose |
13
+ | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
14
+ | `canon init [path]` | Bootstrap a project with selected toolkit domains |
15
+ | `canon sync [path]` | Sync all installed domains in a target project |
16
+ | `canon sync --check` | Report toolkit drift and the installed version against the newest published (`--json`, `--exit-code`) |
17
+ | `canon sandbox [cat:cmd]` | Run sandbox scenarios (interactive or routed), toolkit-only like the tree it reads |
18
+ | `canon sandbox reset` | Reset sandbox to baseline |
19
+ | `canon sandbox clean` | Wipe the sandbox |
20
+ | `canon sandbox check` | Score a provisioned sandbox against a scenario expectation (`--json` for the verdict) |
21
+ | `canon sandbox coverage` | Report which scenarios declare expectations (`--json`, `--strict`, `--skills`) |
22
+ | `canon indexes regen` | Regenerate `index.md` files from sibling frontmatter |
23
+ | `canon indexes list [path]` | Flatten every folder index under a path into one `{path, title, description}` catalog (`--json`) |
24
+ | `canon docs [topic]` | Emit toolkit reference docs (`list`, or a topic by name) |
25
+ | `canon design render` | Render `.claude/DESIGN.md` tokens to HTML and CSS |
26
+ | `canon design regen` | Rewrite this repository's `.claude/DESIGN.md` and `src/design/base.css` from `src/design/tokens.ts` |
27
+ | `canon design css` | Emit the design tokens and components as CSS on stdout (`--no-components` for properties alone) |
28
+ | `canon design install` | Install the base stylesheet into a project at `.claude/design/base.css` |
29
+ | `canon design sync` | Update a base stylesheet already installed under `.claude/design/` |
30
+ | `canon slides render` | Render a `.claude/SLIDES.md` source into a PowerPoint deck, reporting any unrecognized layout name on stderr |
31
+ | `canon slides list` | List the available slide layouts (`--json` for the catalog) |
32
+ | `canon feedback` | Write toolkit feedback from stdin to `.canon/review/feedback/`, or open a GitHub issue with `--github`, refusing either when a required field is absent |
33
+ | `canon transcripts <url>` | Fetch a YouTube transcript with metadata frontmatter (needs `yt-dlp`) |
34
+ | `canon tasks archive` | Move a shipped task and its plan off the board, clear its ordering row, and regenerate the index |
35
+ | `canon tasks pull-request` | Record a pull request number on the task a branch closes, by stem or `--plan` (`--json`) |
36
+ | `canon tasks plan-link` | Write or correct a task's `Plan:` line to point at a plan, by stem and plan path or slug (`--json`) |
37
+ | `canon tasks outcome` | Mark outcomes `[x]` on a task by position, repeating `--close` (`--json`) |
38
+ | `canon tasks validate` | Report board rows whose shape, order, plan, task file, group, file set, or blocker does not hold (`--json`) |
39
+ | `canon intake list` | Report intake folder counts, or one folder's items, keeping what is unread with `--unread` (`--json`) |
40
+ | `canon intake answer` | Write selections into one cluster's answer slots, repeating `--set <item>=<answer>` (`--json`) |
41
+ | `canon teach list` | Report learning workspaces and the ordinal a new one takes, or what one workspace holds and what its records schedule (`--json`) |
42
+ | `canon teach open` | Open a workspace at the next ordinal with its mission, resources, and glossary files (`--json`) |
43
+ | `canon teach resource` | Record sources and leads in a workspace, repeating `--read` or `--lead` as `<title>=<url>` (`--json`) |
44
+ | `canon teach glossary` | Add terms to a workspace glossary alphabetically, repeating `--term <term>=<definition>` (`--json`) |
45
+ | `canon teach stylesheet` | Seed a workspace stylesheet from the design source, leaving an existing one alone without `--force` (`--json`) |
46
+ | `canon teach nav` | Rewrite the teach-root listing, a workspace's contents page, and each lesson's chrome from its four marker regions (`--json`) |
47
+ | `canon records validate` | Report a session record or a standard against the standard governing it, per kind (`--json`) |
48
+ | `canon records migrate` | Rewrite the records a validate finding names a recoverable transform for (`--write`, `--json`) |
49
+ | `canon records size` | Report what each record folder holds and how much of it is recent, heaviest first (`--json`) |
50
+ | `canon records push` | Commit the nine backed record folders and push them to a private records remote (`--json`) |
51
+ | `canon records pull` | Fetch the records remote and write it back, refusing rather than discarding unpushed records (`--json`) |
52
+ | `canon migrate rename` | Rewrite every unprotected `aitk` token to `canon` and move the paths that carry the name, reporting the plan without `--write` (`--scope`, `--json`) |
53
+ | `canon migrate records` | Move the gitignored session records to `.canon/` and repoint every tracked citation, reporting the plan without `--write` (`--root`, `--json`) |
54
+ | `canon migrate record-tree` | Repoint the old-root citations inside the records themselves, scoped to the live folders and reporting every line without `--write` (`--root`, `--json`) |
55
+ | `canon migrate rule-layout` | Move a target's installed rules from the flat `.claude/rules/<subdir>/` layout to `.claude/rules/canon/<subdir>/`, reporting the plan without `--write` (`--root`, `--json`) |
56
+ | `canon sessions list` | Resolve live sessions to the worktree and branch each holds, filtered by `--branch` (`--json`) |
57
+ | `canon worktrees list` | Report which worktrees are reclaimable, keyed on the pull request having merged, with every refusal and the removal route named (`--json`) |
58
+ | `canon worktrees reclaim` | Remove every reclaimable worktree and the branch behind it, reporting without acting under `--dry-run` (`--json`) |
59
+ | `canon comments scan` | Measure comment density by language and comment kind, with a trend recomputed from git |
60
+ | `canon context audit` | Report required sections, length, cited paths, reference form, catalog tables, provenance, superseded-decision narration, and index drift |
61
+ | `canon markdown audit` | Fail any markdown path on a banned character, word, or spelling, or a dead relative link, and report the structural checkpoints |
62
+ | `canon claude skills audit` | Report both skill corpora against the mechanical rules in `standards/skill.md` |
63
+ | `canon standards audit` | Report the corpus against the success-criterion gate `standards/standard.md` states, failing only on a standard new to the branch (`--json`, `--arrivals-only`) |
64
+ | `canon claude skills drift` | Name the shipped skill bodies rewritten between a given ref and `HEAD`, and the installed version against the newest published (`--json`) |
65
+ | `canon claude skills reach` | Report the bodies in either skill corpus citing a toolkit path no target project receives, exiting 2 on an unqualified one |
66
+ | `canon claude skills rank` | Score either skill corpus's descriptions against a case corpus by TF-IDF cosine similarity, reporting rank-one and top-three (`--cases <path>`) |
67
+ | `canon claude routing` | Report per `CLAUDE.md` section how many bullets name a path and how many of those a path-scoped rule already covers (`--json`) |
68
+ | `canon gov test-order` | Report where an implementation reached history ahead of the test covering it (`--json`) |
69
+ | `canon gov superseded` | Report where the tree still asserts a value a changed convention no longer produces, keyed on the value and on the family stem behind a templated citation (`--json`) |
70
+ | `canon gov restated` | Report every instruction the always-loaded file or a rule shares with the seed, a shipped skill body, or another rule, classed and with its anchors named (`--json`) |
71
+ | `canon gov citations` | Resolve every path a rule cites and every internal frontmatter glob, failing on one reaching nothing (`--json`) |
72
+ | `canon secrets scan` | Report credential-shaped values in the tree the package ships, keyed on issued values rather than on words (`--json`) |
73
+ | `canon deps audit` | Report published advisories against the resolved dependency set, refusing rather than reporting clean when the index is unreachable (`--json`) |
74
+ | `canon labels audit` | Report the labels a changed set earns from the pull request label map and the paths no row reaches (`--json`) |
75
+ | `canon labels scan` | Fail a pull request or a posted review whose title, body, or review comment carries a phase label, a label a code span quotes, a gitignored record path, a session link, or a title word no dictionary holds (`--event`, `--body-file`, `--json`) |
76
+ | `canon autoship classify` | Decide whether a changed set needs the review pass, naming the file and the test that decided it (`--json`) |
77
+ | `canon pr key-changes` | Compare the files a pull request body's Key Changes names against its own diff, in both directions (`--body`, `--base`, `--json`) |
78
+ | `canon pr head` | Compare the head a pull request object reports against the branch tip the remote carries, naming both shas (`--root`, `--json`) |
79
+ | `canon pr checks` | Report the check runs belonging to the branch tip, reading pending for a tip carrying none rather than clean (`--root`, `--json`) |
80
+ | `canon repo metadata propose` | Compare a description, homepage, and topic set computed from the README and `package.json` against what the remote carries, writing nothing (`--root`, `--json`) |
81
+ | `canon repo metadata apply` | Write an explicitly supplied description, homepage, or topic set to the remote through `gh repo edit` (`--description`, `--homepage`, `--topics`, `--root`, `--json`) |
82
+ | `canon census [path]` | Report tracked file count, a breakdown by extension, and a line total that skips whatever reads as binary (`--json`) |
83
+ | `canon audits run` | Run every audit as one set, report per check under one verdict, and compare each count to the recorded baseline (`--json`, `--record`, `--corpus`) |
84
+ | `canon audits list` | List every audit the set runs, with the corpus each reads and whether it gates (`--json`) |
85
+ | `canon gate run` | Run every stage that guards a branch here, scoping shell, types, and tests to the changed set (`--all`, `--no-write`, `--nested`, `--json`) |
86
+ | `canon inventory [subject]` | Walk every route a project declares and group its elements by the property each computes, as a listing rather than a gate (`--json`) |
87
+ | `canon drive <url> <run>` | Walk a page through named interactions and measure each state it reaches, reporting findings rather than gating (`--json`) |
88
+ | `canon capture [source]` | Render HTML sources to PNG and prove the font each one declares resolved (`--selector` required, `--out`) |
89
+ | `canon serve [dir]` | Serve a directory on the loopback interface and print the link that opens it, running until interrupted (`--port`, `--entry`, `--json`) |
90
+ | `canon upgrade` | Reinstall the CLI globally with the package manager the install path names (`--json`) |
90
91
 
91
92
  `canon serve` drives no browser, which is what separates it from the four that do. All four ship now that `capture` does, so the line between them is the engine rather than the package. A generated page loses its script to an editor preview and to a `file://` open, so the link is the delivery rather than a convenience, and every generated surface here reaches a reader through one. A teach lesson's stylesheet is embedded rather than linked, so only its script still needs a server. It binds `127.0.0.1` and never a wildcard, because what it is pointed at is routinely a gitignored record tree. It sends `cache-control: no-store`, since a preview exists to be edited and reloaded and a cached stylesheet reads as a fix that did not work.
92
93
 
@@ -38,19 +38,22 @@ A step's caption from the draft renders as an overlay while its hold plays, so t
38
38
 
39
39
  When `ffmpeg` is on PATH, the run also writes an mp4 beside the webm, since webm plays in a `<video>` tag but nothing else accepts it. A target without `ffmpeg` still gets the webm and a line naming what to install, and the run does not fail over the missing converter.
40
40
 
41
- | Option | Behavior |
42
- | ---------------- | ----------------------------------------------------------- |
43
- | `--out <dir>` | Directory to write into, overriding what the plan names |
44
- | `--cursor <dir>` | Cursor theme folder to draw the pointer from |
45
- | `--no-video` | Write only the still |
46
- | `--no-still` | Write only the recording |
47
- | `--json` | Add a record on stdout carrying both paths and the duration |
41
+ `--gif` adds a third file from the same converter, and it is opt-in where the mp4 is not. A gif runs several times the size of the webm it derives from, and the destination that needs one is a README on a host that strips `<video>`, which GitHub does. The filter generates a palette from the source and then applies it, rather than quantizing per frame, since a per-frame palette is what makes a recording of flat interface colors band and shimmer. It applies that palette without dithering, because the command records applications rather than photographs and the dither writes noise the encoder then stores. One recording measured 3,193,715 bytes dithered against 2,701,941 without, with the same text region cropped from both and read as identical. A missing converter is reported the same way it is for the mp4, so an absent `ffmpeg` costs the gif and not the run.
42
+
43
+ | Option | Behavior |
44
+ | ---------------- | ------------------------------------------------------------------- |
45
+ | `--out <dir>` | Directory to write into, overriding what the plan names |
46
+ | `--cursor <dir>` | Cursor theme folder to draw the pointer from |
47
+ | `--no-video` | Write only the still |
48
+ | `--no-still` | Write only the recording |
49
+ | `--gif` | Also write a gif, for a host that strips video |
50
+ | `--json` | Add a record on stdout carrying every path written and the duration |
48
51
 
49
52
  A step waits on its `waitFor` selector becoming visible and then holds for its own `holdMs`, which is what puts a finished state on screen long enough to read. `navigate` uses the plan's URL unless the step names its own.
50
53
 
51
54
  ## The pointer is painted inside the page
52
55
 
53
- The browser engine's own annotation draws a red dot at the moment of a click and an action label in a corner. It paints no cursor, so a run without more looks like the pointer teleports between targets.
56
+ The browser engine offers an annotation of its own that draws a dot on the interacted element and a title naming the API call it made, and this recorder does not turn it on. It paints no cursor, so a run relying on it looks like the pointer teleports between targets, and the two overlays below supersede it: a real cursor where the dot is a marker, and the beat's narration where the title reads `Mouse move`. Running both put four overlays on the frame, and the two the engine drew were the two a viewer reads as noise.
54
57
 
55
58
  The recorder injects a pointer element before navigation and moves it through the engine's pointer with interpolated steps rather than through the element-clicking helper, which resolves a target and jumps to it. The step count is the whole difference between a cursor that travels and one that appears, and how many steps a move takes is derived from the machine's own round-trip cost rather than fixed, so the same plan glides at roughly the same pace on a loaded machine as an idle one. It also reads the element under it on every move and switches between an arrow, a hand, and a text beam, so it reflects the page the way a real cursor does.
56
59
 
@@ -20,7 +20,7 @@ CLI catalog and invocation rules for agents, split by command domain. Start with
20
20
  - [Docs](docs.md): How canon docs resolves the toolkit's own reference surface from an install root, and how a split domain is named
21
21
  - [Driver](driver.md): Walking a page through named interactions, the probe catalog and the false finding each one carries, why viewport heights are never defaulted, and what each refusal reports
22
22
  - [Merge gate](gate.md): Running the gate this repository verifies a branch with, what the stage table holds and what stays a script, how the changed set scopes three stages, and why a stage that cannot read its input reports rather than passing
23
- - [Indexes](indexes.md): Flags, exit codes, and JSON shape for canon indexes regen, plus when it auto-stages what it rewrote
23
+ - [Indexes](indexes.md): Flags, exit codes, and JSON shape for canon indexes regen and canon indexes list, plus when regen auto-stages what it rewrote
24
24
  - [Install and sync](install-and-sync.md): What each install and sync verb writes, refuses, or leaves alone, and how drift is attributed in a target project
25
25
  - [Intake](intake.md): Reading intake folder counts and items, the three read states an item can be in, landing a batch of selections in one cluster, the refusal reasons, and why a call is scoped to one file
26
26
  - [Key Changes bijection](key-changes.md): Comparing the files a pull request body's Key Changes names against its own diff, the two directions and the split inside each that decides what is worth raising, the span rules the extractor was measured into, and the three refusals that separate a clean pass from a read that produced nothing
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: Indexes
3
- description: Flags, exit codes, and JSON shape for canon indexes regen, plus when it auto-stages what it rewrote
3
+ description: Flags, exit codes, and JSON shape for canon indexes regen and canon indexes list, plus when regen auto-stages what it rewrote
4
4
  ---
5
5
 
6
6
  # Indexes
@@ -32,4 +32,20 @@ Skills can parse drift without branching on exit code:
32
32
  canon indexes regen --dry-run --json | jq '.results[] | select(.action == "would-write")'
33
33
  ```
34
34
 
35
+ ## List
36
+
37
+ `canon indexes list [path]` walks every folder index under `path` (default: cwd) and flattens each folder's own `title`/`subtitle` plus every sibling's `title`/`description` into one catalog, sorted by path relative to the walk root.
38
+
39
+ | Option | Behavior |
40
+ | -------- | ------------------------------------------ |
41
+ | `--json` | Emit `{ root, entries, errors }` on stdout |
42
+
43
+ Each entry is `{ path, title, description }`. `path` is relative to the resolved root, and an `index.md` file's own row uses its `subtitle` as `description`.
44
+
45
+ A folder failing frontmatter validation lands its message in `errors` and drops out of `entries` rather than failing the whole walk, matching `regen`'s per-folder isolation. Every mode writes one `ERROR:` line per error to stderr.
46
+
47
+ Exit codes: `0` no errors, `1` root not a directory, or a folder failed frontmatter validation.
48
+
49
+ A git-ignored indexed folder, such as `.canon/tasks/` or `.canon/memory/`, never appears in the output. `listIndexes` filters candidates through `git check-ignore` the same way `regen`'s whole-repo walk does, which is the right default for a documentation lookup since those rows are session scratch, but it is a gap against `regen`'s positional-path mode, which bypasses that filter for those two folders.
50
+
35
51
  For the system rationale, frontmatter contract, when to adopt, and bootstrap path, see `.claude/context/indexes.md`.
@@ -92,7 +92,7 @@ A relative path resolves against the project root first and against `.canon/task
92
92
 
93
93
  The record carries `launchable` and `open`, the questions still waiting, each with the `label` that names it and the `why` its suggestion gave for needing a person. Exit codes: `0` launchable, `1` refused with `no-plan`, `archived`, or `bad-input`, `2` waiting on the operator.
94
94
 
95
- A blank `- Answer:` is not a waiting question. The plan standard fixes an empty slot as accepting the `- Suggested:` line above it, so the one shape this reads is `- Suggested: needs your call, <why>` over an empty slot, which is what that standard writes where the answer turns on preference rather than on a technical default. A verb reading every blank slot as open would report every plan in the folder.
95
+ A blank `- Answer:` is not a waiting question. The plan standard fixes an empty slot as accepting the `- Suggested:` line above it, so the shapes this reads are `- Suggested: needs your call, <why>` and the two demonstrated paraphrases, `needs operator's call` and `needs the operator's call`, over an empty slot, which is what that standard writes where the answer turns on preference rather than on a technical default. A verb reading every blank slot as open would report every plan in the folder.
96
96
 
97
97
  The question block is read through the same parser `canon tasks validate` runs, so the gate and the conformance check cannot drift into disagreeing about what a question is. A question carrying no suggestion at all is that check's finding rather than this one's, and it goes unread here.
98
98
 
@@ -15,3 +15,5 @@ description: Route tracked-file writes and shared session scratch correctly from
15
15
  - From a linked worktree, `Edit` and `Write` are refused for every main-root path, session scratch included. The refusal names session isolation and points at the worktree copy, which is a second gitignored file no later session reads, so never take that redirect.
16
16
  - `Read` resolves against the main root normally from a linked worktree. A main-root write reaches it only through `Bash`, as one plain command rather than a compound one, which is refused for complexity.
17
17
  - Route a main-root write by what it does to the file. Creating a whole file goes out as one plain `Bash` command carrying a heredoc. Changing a line inside a file that already exists goes through a command that resolves the main root in-process, because the shell route for that case is the stream editor this file bans.
18
+ - A background session that entered no worktree at all meets the identical refusal writing to the main root, since the guard gates on isolation rather than on sitting inside a linked worktree. A planner writing its plan file from no worktree takes the same heredoc route for that reason.
19
+ - The heredoc route costs nothing on `.canon/plans/` and `.canon/review/`, which no `PostToolUse` hook watches, and costs the index on `.canon/tasks/` and `.canon/memory/`, since a hook matching `Write|Edit|MultiEdit` never fires on `Bash`. Regenerate positionally: in-process where a verb such as `canon tasks archive` already calls it, elsewhere by an explicit `canon indexes regen` call.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/canon",
3
3
  "type": "module",
4
- "version": "4.52.0",
4
+ "version": "4.54.0",
5
5
  "description": "Infrastructure and quality tooling for developer workflows",
6
6
  "license": "MIT",
7
7
  "bin": {