@erclx/canon 5.54.0 → 5.56.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.
Files changed (102) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/claude/.claude-plugin/plugin.json +1 -1
  3. package/claude/skills/api-design/SKILL.md +8 -10
  4. package/claude/skills/auto-ship/SKILL.md +4 -5
  5. package/claude/skills/auto-ship/references/failure-recovery.md +37 -17
  6. package/claude/skills/auto-ship/references/tier-failures.md +9 -7
  7. package/claude/skills/auto-ship/references/worktree-name.md +8 -5
  8. package/claude/skills/backlog-triage/SKILL.md +10 -10
  9. package/claude/skills/backlog-triage/references/apply.md +6 -6
  10. package/claude/skills/context-fold/references/classify.md +17 -4
  11. package/claude/skills/create-rule/references/rule.md +4 -0
  12. package/claude/skills/create-skill/SKILL.md +3 -3
  13. package/claude/skills/deploy-app/SKILL.md +2 -0
  14. package/claude/skills/deprecation-migration/SKILL.md +7 -9
  15. package/claude/skills/document-health/SKILL.md +6 -6
  16. package/claude/skills/document-health/references/axes.md +1 -1
  17. package/claude/skills/document-repair/SKILL.md +5 -5
  18. package/claude/skills/git-followup/SKILL.md +16 -5
  19. package/claude/skills/git-issue/references/issue.md +9 -11
  20. package/claude/skills/git-pr/references/publish.md +4 -5
  21. package/claude/skills/git-split/SKILL.md +11 -4
  22. package/claude/skills/markdown-craft/SKILL.md +1 -1
  23. package/claude/skills/markdown-craft/references/markdown.md +2 -2
  24. package/claude/skills/markdown-craft/references/tables.md +9 -7
  25. package/claude/skills/plan-feature/SKILL.md +2 -0
  26. package/claude/skills/plan-feature/references/plan.md +2 -0
  27. package/claude/skills/review-address/SKILL.md +9 -7
  28. package/claude/skills/review-craft/SKILL.md +7 -9
  29. package/claude/skills/review-craft/references/adopted.md +10 -8
  30. package/claude/skills/review-pr/SKILL.md +2 -0
  31. package/claude/skills/review-pr/references/key-changes.md +8 -6
  32. package/claude/skills/review-ui/references/driving.md +10 -8
  33. package/claude/skills/role-orchestrator/references/orchestrator-parked.md +6 -4
  34. package/claude/skills/role-orchestrator/references/orchestrator-refill.md +9 -3
  35. package/claude/skills/role-reviewer/SKILL.md +5 -5
  36. package/claude/skills/search-craft/SKILL.md +5 -7
  37. package/claude/skills/session-worktree/SKILL.md +2 -0
  38. package/claude/skills/session-worktree/references/main-root-writes.md +2 -0
  39. package/claude/skills/systematic-debugging/SKILL.md +9 -9
  40. package/claude/skills/systematic-debugging/references/adopted.md +1 -1
  41. package/claude/skills/target-check/SKILL.md +6 -8
  42. package/claude/skills/target-check/references/domains.md +1 -4
  43. package/claude/skills/target-setup/SKILL.md +8 -8
  44. package/claude/skills/target-update/SKILL.md +5 -5
  45. package/claude/skills/task-board/SKILL.md +2 -4
  46. package/claude/skills/task-board/references/tasks.md +5 -5
  47. package/claude/skills/task-complete/references/archive.md +5 -2
  48. package/claude/skills/test-craft/SKILL.md +2 -0
  49. package/claude/skills/test-craft/references/adopted.md +2 -0
  50. package/claude/skills/ui-checklist/SKILL.md +8 -9
  51. package/claude/skills/write-doc/SKILL.md +8 -8
  52. package/claude/skills/write-doc/references/readme.md +2 -0
  53. package/claude/skills/write-doc/references/wiki-kind.md +11 -4
  54. package/docs/agents/context-audit.md +16 -14
  55. package/docs/agents/install-and-sync.md +2 -2
  56. package/docs/agents/markdown-audit.md +11 -4
  57. package/docs/agents/migrate.md +15 -13
  58. package/docs/agents/output-shape.md +13 -10
  59. package/docs/target/migrations.md +3 -3
  60. package/docs/target/sync.md +1 -1
  61. package/docs/workflow/operating-model.md +2 -0
  62. package/package.json +1 -1
  63. package/scripts/core/install-check.sh +1 -1
  64. package/scripts/lib/ui.sh +12 -65
  65. package/src/audits/run.ts +1 -1
  66. package/src/claude/routing.ts +1 -1
  67. package/src/cli/run.ts +5 -6
  68. package/src/cli/ui.ts +87 -28
  69. package/src/cli.ts +6 -0
  70. package/src/commands/claude.ts +2 -3
  71. package/src/commands/design.ts +8 -13
  72. package/src/commands/gate.ts +5 -3
  73. package/src/commands/gov.ts +11 -21
  74. package/src/commands/labels.ts +1 -1
  75. package/src/commands/markdown.ts +25 -29
  76. package/src/commands/pr.ts +1 -1
  77. package/src/commands/sync.ts +1 -1
  78. package/src/commands/tooling.ts +5 -8
  79. package/src/commands/transcripts.ts +6 -11
  80. package/src/gate/sequencer.ts +1 -1
  81. package/src/gate/stages.ts +0 -10
  82. package/src/hooks/post-merge.ts +2 -5
  83. package/src/labels/density.ts +5 -46
  84. package/src/markdown/density.ts +41 -6
  85. package/src/markdown/scope.ts +6 -1
  86. package/src/markdown/structure.ts +18 -9
  87. package/src/markdown/tables.ts +32 -18
  88. package/src/process/harness.ts +11 -4
  89. package/src/process/seeded-package.ts +21 -0
  90. package/src/sandbox/equivalence.ts +0 -1
  91. package/src/sandbox/provision.ts +8 -13
  92. package/src/sandbox/scenario.ts +6 -12
  93. package/src/serve/static.ts +2 -60
  94. package/src/sync/engine.ts +3 -8
  95. package/src/sync/workflow.ts +3 -5
  96. package/src/tooling/verify-stack.ts +1 -1
  97. package/tooling/base/configs/.husky/post-merge +6 -10
  98. package/tooling/base/reference.md +7 -6
  99. package/tooling/claude/reference.md +7 -5
  100. package/tooling/claude/seeds/.claude/hooks/standards-audit.sh +7 -6
  101. package/scripts/core/check-color-source.sh +0 -51
  102. package/src/serve/live.ts +0 -113
@@ -147,11 +147,11 @@ A task with no origin is lost context or work nobody decided to do. The reverse
147
147
 
148
148
  `Plan:` points at `../plans/feature-<slug>.md` while the task is open. Retarget both halves of the link as things move rather than dropping it, so a completed task still leads to its reasoning.
149
149
 
150
- | State | Target |
151
- | ----------------------------- | --------------------------------------- |
152
- | Open | `../plans/feature-<slug>.md` |
153
- | Shipped, plan archived | `../plans/archive/feature-<slug>.md` |
154
- | Task archived a folder deeper | `../../plans/archive/feature-<slug>.md` |
150
+ - Open: `../plans/feature-<slug>.md`
151
+ - Shipped, plan archived: `../plans/archive/feature-<slug>.md`
152
+ - Task archived a folder deeper: `../../plans/archive/feature-<slug>.md`
153
+
154
+ Three rules cover the line itself:
155
155
 
156
156
  - One plan per task, or one per slice as distinct plans on one line. A plan cited by two tasks is a misfile.
157
157
  - `canon tasks plan-link <task> <plan>` writes or corrects the line, and `--add` appends a slice's plan, so it is never hand-edited
@@ -56,8 +56,11 @@ An empty `plans` means the task cited no live plan, or a sibling still holds eve
56
56
  Each reason has one resolution, and none of them is to archive around it.
57
57
 
58
58
  - `open-outcomes`: the named outcomes are unmarked or genuinely open. Run `task-complete` when the work shipped unmarked, and leave the task on the board when the outcome is real.
59
- - An outcome only the operator can check is marked `- [ ] Operator check: <outcome>` rather than struck, which the archive passes and the record lists under `operatorChecks`. The mark has to land before the pull request opens.
60
- - Abandoned work, an outcome another repository owes, and an operator outcome nobody marked in time take the strike. The orchestrator's merge sweep strikes it with its reason and lists it in the report, per `${CLAUDE_SKILL_DIR}/../role-orchestrator/references/orchestrator-refill.md`, and the archive then runs. An outcome this repository owes is never struck for that.
59
+ - An outcome only the operator can check is marked `- [ ] Operator check: <outcome>` rather than struck, so the archive passes it and lists it under `operatorChecks`
60
+ - The mark has to land before the pull request opens
61
+ - Abandoned work, an outcome another repository owes, and an operator outcome nobody marked in time take the strike
62
+ - The orchestrator's merge sweep strikes it with its reason and lists it in the report, per `${CLAUDE_SKILL_DIR}/../role-orchestrator/references/orchestrator-refill.md`, and the archive then runs
63
+ - An outcome this repository owes is never struck for that
61
64
  - `ambiguous`: two tasks name one pull request, the misfile `${CLAUDE_SKILL_DIR}/references/tasks.md` rules out. Resolve the citation by hand, since no sweep repairs it.
62
65
  - `earlier-slice`: the number is listed on the task but is not the last. Leave the task on the board, and its last slice's merge archives it.
63
66
 
@@ -31,6 +31,8 @@ Size a test by what it touches rather than by what it covers. Pick the first row
31
31
  | Rendered behavior: states, interactions, conditional rendering, validation, focus | Component |
32
32
  | A journey across routes, or what only a real browser renders: layout, scroll, motion | End to end |
33
33
 
34
+ Four rules govern how a session reads the table.
35
+
34
36
  - Read the row off the behavior, never off the file. A component file holding a pure formatter takes a unit test for the formatter.
35
37
  - Split a behavior that spans rows. Test the copy at unit and the render at component, rather than asserting both in one browser run.
36
38
  - Write the lower test when a higher one catches a break no lower test did. A red end to end run with every unit and component test green names a missing lower test.
@@ -19,6 +19,8 @@ Three sources settle which layer a test belongs at and how the decision table re
19
19
  | Sizing by what a test touches | Google's small, medium, and large sizes ([Software Engineering at Google, ch. 11](https://abseil.io/resources/swe-book/html/ch11.html)) |
20
20
  | The component layer carrying UI weight | Kent C. Dodds's trophy ([Write tests](https://kentcdodds.com/blog/write-tests)) and Testing Library's [guiding principles](https://testing-library.com/docs/guiding-principles) |
21
21
 
22
+ Each source below is read for what it settles.
23
+
22
24
  - Fowler writes a lower test when a higher one catches what no lower one did. It is the body's layer rule and the decision table's reason.
23
25
  - Google's sizes name what a test may touch rather than cover. The table keys on the same, so it transfers across stacks without naming a runner.
24
26
  - Testing Library holds that tests resembling use give more confidence
@@ -33,15 +33,14 @@ The evidence comment already heads the list with `## What to look at`, so the fi
33
33
  - [ ] <route>[, <width>]: <verb> <target> → <expected visual result>
34
34
  ```
35
35
 
36
- Write each box so a session driving the running app can run it without guessing:
37
-
38
- | Part | Rule |
39
- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
40
- | Route | A path or the control that reaches it. A box naming none is driven from the address the driver opens. Write "from the page the app opens on" for a state reached by a sequence. |
41
- | Width | Only when the change can differ by width, such as layout, spacing, or anything responsive. Copy, color, or a state rendering the same everywhere leaves it off. |
42
- | Verb | An action a driver performs, such as "scroll to", "click", "resize to", or "tab to". Never "read" or "look at", which name no action. |
43
- | Taste | End the line with `(taste)` when the result is a judgment of look or feel. A driver reports such a box as needing a person's eyes rather than passing it. |
44
- | Setup | State inline any setup the page does not open in. End the line with `(local only)` when a hosted preview cannot reach that state, such as a stopped service or a seeded record. |
36
+ Write each box so a session driving the running app can run it without guessing, with one rule for each part:
37
+
38
+ - **Route.** A path or the control that reaches it. A box naming none is driven from the address the driver opens.
39
+ - Write "from the page the app opens on" for a state reached by a sequence.
40
+ - **Width.** Only when the change can differ by width, such as layout, spacing, or anything responsive. Copy, color, or a state rendering the same everywhere leaves it off.
41
+ - **Verb.** An action a driver performs, such as "scroll to", "click", "resize to", or "tab to". Never "read" or "look at", which name no action.
42
+ - **Taste.** End the line with `(taste)` when the result is a judgment of look or feel. A driver reports such a box as needing a person's eyes rather than passing it.
43
+ - **Setup.** State inline any setup the page does not open in. End the line with `(local only)` when a hosted preview cannot reach that state, such as a stopped service or a seeded record.
45
44
 
46
45
  When the plan cites `canon/design/<surface>/` under `**Design references:**`, name the reference as the comparison on each box for that surface, such as "→ matches `canon/design/<surface>/` in light and dark". The reviewer then judges the build against the accepted design instead of against taste.
47
46
 
@@ -13,12 +13,12 @@ Drafts one brand-new project document end to end: resolve its kind, read the sta
13
13
 
14
14
  Each kind has one standard and one reference. Load the reference for the resolved kind and no other, since every step below that differs by kind lives there.
15
15
 
16
- | Kind | Lands at | Standard | Reference |
17
- | ------- | -------------------------------------------------------- | ----------------------------------------------------------- | --------------------------------------------- |
18
- | docs | a page under `docs/` | `${CLAUDE_SKILL_DIR}/../docs-sync/references/docs.md` | `${CLAUDE_SKILL_DIR}/references/docs.md` |
19
- | context | an entry under `canon/context/` | `${CLAUDE_SKILL_DIR}/../context-fold/references/context.md` | `${CLAUDE_SKILL_DIR}/references/context.md` |
20
- | wiki | a reference page on a subject owned outside this project | `${CLAUDE_SKILL_DIR}/references/wiki.md` | `${CLAUDE_SKILL_DIR}/references/wiki-kind.md` |
21
- | readme | a `README.md`, at the root or in a folder | `${CLAUDE_SKILL_DIR}/../docs-sync/references/readme.md` | `${CLAUDE_SKILL_DIR}/references/readme.md` |
16
+ - **docs** lands at a page under `docs/`. Standard `${CLAUDE_SKILL_DIR}/../docs-sync/references/docs.md`, reference `${CLAUDE_SKILL_DIR}/references/docs.md`.
17
+ - **context** lands at an entry under `canon/context/`. Standard `${CLAUDE_SKILL_DIR}/../context-fold/references/context.md`, reference `${CLAUDE_SKILL_DIR}/references/context.md`.
18
+ - **wiki** lands at a reference page on a subject owned outside this project. Standard `${CLAUDE_SKILL_DIR}/references/wiki.md`, reference `${CLAUDE_SKILL_DIR}/references/wiki-kind.md`.
19
+ - **readme** lands at a `README.md`, at the root or in a folder. Standard `${CLAUDE_SKILL_DIR}/../docs-sync/references/readme.md`, reference `${CLAUDE_SKILL_DIR}/references/readme.md`.
20
+
21
+ Resolve the kind from the request by the rules below.
22
22
 
23
23
  - Read the kind off the destination the request names: a path, a folder, or the document's own name, such as "a context entry" or "a wiki page".
24
24
  - Ask which kind when the request names no destination, offering the candidates the subject fits with the likeliest first
@@ -30,10 +30,10 @@ Each kind has one standard and one reference. Load the reference for the resolve
30
30
 
31
31
  Read these in parallel, from the project root where a path is relative:
32
32
 
33
- - The kind's standard from the table above: the reader the document serves, its frontmatter, its structure, and what it links out to rather than restates
33
+ - The kind's standard from the kind list above: the reader the document serves, its frontmatter, its structure, and what it links out to rather than restates
34
34
  - `${CLAUDE_SKILL_DIR}/../markdown-craft/references/markdown.md`: punctuation and formatting for all generated text
35
35
  - The `write-human` skill: voice, rhythm, and sentence construction for all generated text
36
- - The kind's reference from the table above
36
+ - The kind's reference from the kind list above
37
37
 
38
38
  ## Guards
39
39
 
@@ -23,6 +23,8 @@ Read the target if it exists, and branch on its headings.
23
23
  | Carries no H1, or only headings restating the scaffolding tool | Generator output | Continue, drafting over it rather than syncing its sections |
24
24
  | Does not exist | Nothing to read | Continue |
25
25
 
26
+ Three notes qualify those routes.
27
+
26
28
  - On the authored branch, report rather than offering to overwrite it or to conform it, since the project's own citations into its README are invisible from here
27
29
  - A hand-authored README opening with a badge block, or a title the H1 test misses, falls into the no-H1 branch the same as a scaffold page
28
30
  - The Confirm step catches that case before the write, so it is load-bearing. Never skip it on the reasoning that the guard already decided.
@@ -17,10 +17,17 @@ If `<wiki-root>` does not resolve, say so and ask whether to create it before dr
17
17
 
18
18
  The standard sets two tests and a page has to pass both. Run them before drafting anything, since a page failing either is non-conforming however well it is written.
19
19
 
20
- | Test | Fails on | Refusal |
21
- | ------------------------------------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
22
- | Owned outside this repository | A subject describing how this repository works | `❌ <subject> is owned by this project, so it belongs in the docs surface or a context entry. Re-run write-doc naming that destination.` |
23
- | Owned by the vendor the folder split covers | A third-party tool or a vendor-neutral concept, however reference-like it reads | `❌ <subject> is outside the wiki's folder split. Route it to the docs surface or a skill body rather than adding a second folder for it.` |
20
+ The first test is owned outside this repository, and it fails on a subject describing how this repository works. Refuse with this line:
21
+
22
+ ```plaintext
23
+ ❌ <subject> is owned by this project, so it belongs in the docs surface or a context entry. Re-run write-doc naming that destination.
24
+ ```
25
+
26
+ The second test is owned by the vendor the folder split covers, and it fails on a third-party tool or a vendor-neutral concept, however reference-like it reads. Refuse with this line:
27
+
28
+ ```plaintext
29
+ ❌ <subject> is outside the wiki's folder split. Route it to the docs surface or a skill body rather than adding a second folder for it.
30
+ ```
24
31
 
25
32
  On the first test, offer the docs or context kind of this skill. The standard names one owner and one subfolder for the second.
26
33
 
@@ -22,19 +22,19 @@ canon context audit tooling/base/seeds --gate
22
22
 
23
23
  ## Output
24
24
 
25
- | Check | Reports | Reaches | Finding |
26
- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | ----------------------------------------------------------- |
27
- | Citations | A path into an audited folder that resolves nowhere | Every audited folder | Gates always |
28
- | Requirements record | Words past the cap the record states as `This record holds at most 600 words.` | `canon/REQUIREMENTS.md` | Gates past a stated cap, never under `--citations-only` |
29
- | Required sections | An entry missing `Overview` or `Layout`. A split folder answers by any sibling | `canon/context/` | Gates under `--gate` |
30
- | Closed set | An H2 outside `Overview`, `Layout`, and `Gotchas`, under `extraSections` | `canon/context/` | Gates under `--gate` |
31
- | Shape | `retired-section`, `gotcha-count`, `gotcha-heading`, `gotcha-paragraph`, `layout-heading`, `layout-bullets`, `layout-paragraphs`, `layout-depth` | `canon/context/` | Gates under `--gate` in a `SHAPE_GATED_FOLDERS` folder only |
32
- | Index drift | An entry its index does not link, or a link resolving to nothing | Every audited folder | Gates under `--gate` |
33
- | Coverage | A folder directly under a code root that no Layout names, one with a Layout H3 in two entries, and a declared root matching nothing, under `coverage` | `canon/context/` | Gates under `--gate`, never under `--citations-only` |
34
- | Reference form | A backticked bare filename matching a sibling entry, under `bareReferences` | Split folders | Reports |
35
- | Length | An entry past about 150 rendered lines at 80 columns, with its `causes` | Every audited folder | Reports |
36
- | Tables | Six or more body rows whose first column mostly carries a path, command, or link | Every audited folder | Reports |
37
- | Provenance | A date, change number, or release label. A date after measured, verified, driven, passed, or fired is excluded | `canon/context/` | Reports |
25
+ | Check | Reports | Reaches | Finding |
26
+ | ------------------- | ----------------------------------------------------------------------------------------------------------------------- | ----------------------- | ----------------------------------------------------------- |
27
+ | Citations | A path into an audited folder that resolves nowhere | Every audited folder | Gates always |
28
+ | Requirements record | Words past the cap the record states as `This record holds at most 600 words.` | `canon/REQUIREMENTS.md` | Gates past a stated cap, never under `--citations-only` |
29
+ | Required sections | An entry missing `Overview` or `Layout`. A split folder answers by any sibling | `canon/context/` | Gates under `--gate` |
30
+ | Closed set | An H2 outside `Overview`, `Layout`, and `Gotchas`, under `extraSections` | `canon/context/` | Gates under `--gate` |
31
+ | Shape | A break of the entry shape, in any kind listed under the table | `canon/context/` | Gates under `--gate` in a `SHAPE_GATED_FOLDERS` folder only |
32
+ | Index drift | An entry its index does not link, or a link resolving to nothing | Every audited folder | Gates under `--gate` |
33
+ | Coverage | A folder directly under a code root no Layout names, a Layout H3 in two entries, or an unmatched root, under `coverage` | `canon/context/` | Gates under `--gate`, never under `--citations-only` |
34
+ | Reference form | A backticked bare filename matching a sibling entry, under `bareReferences` | Split folders | Reports |
35
+ | Length | An entry past about 150 rendered lines at 80 columns, with its `causes` | Every audited folder | Reports |
36
+ | Tables | Six or more body rows whose first column mostly carries a path, command, or link | Every audited folder | Reports |
37
+ | Provenance | A date, change number, or release label. A date after measured, verified, driven, passed, or fired is excluded | `canon/context/` | Reports |
38
38
 
39
39
  The coverage roots come from `roots` in `canon/config/context.toml`, a list of folders or globs such as `packages/*/src`.
40
40
 
@@ -42,7 +42,7 @@ The coverage roots come from `roots` in `canon/config/context.toml`, a list of f
42
42
  - A file that is not valid TOML, or holds no list of strings under `roots`, gates under `--gate`
43
43
  - With no declaration and no `src/`, the audit reports no code root and never gates
44
44
 
45
- A shape break is any of these:
45
+ The shape kinds are `retired-section`, `gotcha-count`, `gotcha-heading`, `gotcha-paragraph`, `layout-heading`, `layout-bullets`, `layout-paragraphs`, and `layout-depth`. A shape break is any of these:
46
46
 
47
47
  - A `## Decisions` H2, or a `Gotchas` section past five bullets or holding a heading or paragraph
48
48
  - A `Layout` H3 titled neither a code-spanned path nor `Other folders`, or a heading below H3 under `Layout`
@@ -56,6 +56,8 @@ The development entry may carry a `Scripts` H3 exempt from both folder caps. `SH
56
56
  | `1` | Refused, with the reason on stderr |
57
57
  | `2` | A gating finding for the mode the run took |
58
58
 
59
+ The exit code follows the mode the run took. The bullets cover where the gate runs, what refuses, and how a seed opts out.
60
+
59
61
  - `bun run check` runs `--gate` against every `tooling/<stack>/seeds/` directory carrying a `.claude/` folder
60
62
  - A root resolving no audited folder refuses with `1` and warns, a root measuring no entry warns, and a seed short a section fails with `2`
61
63
  - A seed omitting a required section on purpose sets `stub: true` in its frontmatter. Only the value `true` counts, and both install paths strip the field.
@@ -15,8 +15,8 @@ Any flag skips the confirmation prompt, but the `base` default of `--stack` does
15
15
 
16
16
  ## Governance install
17
17
 
18
- - `canon gov install <stack> [target]` needs the stack under `CANON_NON_INTERACTIVE=1`, else lists the valid names on stderr and exits 1
19
- - `--yes` answers the install confirm with no TTY and no env var. Without it, a headless call refuses and the message names `--yes`. It does not pick a stack.
18
+ - `canon gov install <stack> [target]` needs the stack on a call with no terminal, else lists the valid names on stderr and exits 1
19
+ - A headless call takes Yes at the install confirm, and `--yes` answers it at a terminal too. Neither picks a stack.
20
20
  - It refuses the toolkit root and a missing path
21
21
  - `--add <rules>` with no stack installs into the one positional, or the current directory, keeping the recorded chain. Pass `./node` for a target named like a stack.
22
22
 
@@ -26,24 +26,31 @@ The ban set and checkpoints ship in `src/markdown/bans.ts` and `src/markdown/str
26
26
  | --------------- | ------------------------------------------------------------------------------------ | ----------------- |
27
27
  | Bans | The two characters `markdown.md` bans, outside frontmatter, fences, spans, and links | Always |
28
28
  | Links | A relative link resolving to nothing on disk | Always |
29
- | Density | The six limits below, under `density` | On a gated prefix |
29
+ | Density | The six limits and two table limits below, under `density` | On a gated prefix |
30
30
  | Reference shape | A `kind: reference` page breaking its shape, under `referenceShape` | On a gated prefix |
31
- | Tables | The four table limits below, under `tables` | Never |
31
+ | Tables | The row count and wide token limits below, under `tables` | Never |
32
32
  | Colons | A colon followed by more text in a prose line, under `colons` | Never |
33
33
  | Heading case | A capital after the first word of a heading or `title`, under `headingCase` | Never |
34
34
 
35
35
  A page marked `kind: reference` holds its opening, then `Output`, `Refusals`, and `See also` as H2s, each at most once and in that order. It takes no H3 and no table headed `Flag` or `Option`. An unmarked page is never measured.
36
36
 
37
- Tables take the four limits in the `markdown-craft` skill's `tables.md`, on the density paths but never `CLAUDE.md`. A cell past 120 visible characters reports as `table-cell`, more than 8 body rows as `table-rows`, a list after a table as `table-then-list`, and a code span with a run past 30 characters as `table-token`. None sets the exit code.
37
+ Tables take the four limits in the `markdown-craft` skill's `tables.md`, on the density paths but never `CLAUDE.md`.
38
+
39
+ - A cell past 120 visible characters reports as `table-cell` and a list after a table as `table-then-list`, both under `density`
40
+ - More than 8 body rows reports as `table-rows` and a code span with a run past 30 characters as `table-token`, both under `tables` and neither setting the exit code
38
41
 
39
42
  | Density limit | Finding |
40
43
  | ------------------- | ---------------------------------------------------------- |
41
44
  | Paragraph length | A prose paragraph past 350 characters |
42
45
  | Paragraphs in a row | More than 2 paragraphs with nothing else between them |
43
- | Bullet length | A top-level bullet past 200 characters |
46
+ | Bullet length | A bullet past 200 characters, a nested one on its own |
44
47
  | Bullets in a row | More than 5 plain top-level bullets |
45
48
  | Heading depth | Any heading past H3 |
46
49
  | H3 opening | An H3 whose first block is neither a paragraph nor a table |
50
+ | Table cell | A table cell past 120 visible characters |
51
+ | List after table | A list as the block directly after a table |
52
+
53
+ Five notes fix how the limits count and where they apply.
47
54
 
48
55
  - A paragraph under a list item counts toward that bullet, never as a paragraph
49
56
  - A table, fence, list, heading, or bold section marker ends a run of paragraphs
@@ -8,21 +8,23 @@ kind: reference
8
8
 
9
9
  `canon migrate <verb>` moves a project off a retired name or layout. Each verb reports its plan and writes nothing until `--write`, takes `--json`, and, except `rename`, takes `--root`. A second run rewrites nothing, which confirms the exclusions and markers fired.
10
10
 
11
- | Verb | Moves |
12
- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- |
13
- | `status` | Nothing. It lists every migration the project still needs, in run order. |
14
- | `rename` | The retired `aitk` name. `--scope target` rewrites toolkit-owned folders only. |
15
- | `skill-names` | Plugin skills off their `claude-` and `draft-` prefixes and other retired names, with every citation |
16
- | `records` | Session records from `.claude/` to `.canon/`, with every tracked citation |
17
- | `surface-roots` | `context/`, `wireframes/`, `ARCHITECTURE.md`, `REQUIREMENTS.md`, `DESIGN.md`, and the stamp folder from `.claude/` to `canon/`, by `git mv` |
18
- | `record-tree` | Citations inside `.canon/` folders a session still follows: `diagrams`, `memory`, `plans`, `proposals`, `review`, `tasks`, `teach` |
19
- | `rule-layout` | A flat installed-rule layout onto the `canon/`-wrapped one |
20
- | `scratch-evidence` | A folder a durable record cites out of `.tmp/`, with its citations |
21
- | `record-layout` | Review receipts, memory archives, feedback, renders, picks, and evidence into their current folders |
22
- | `session-defaults` | The seeded session defaults into an existing `.claude/settings.json`: `Agent`, `Task`, and both merge commands denied, auto memory off |
11
+ | Verb | Moves |
12
+ | ------------------ | --------------------------------------------------------------------------------------------------------------- |
13
+ | `status` | Nothing. It lists every migration the project still needs, in run order. |
14
+ | `rename` | The retired `aitk` name. `--scope target` rewrites toolkit-owned folders only. |
15
+ | `skill-names` | Plugin skills off their `claude-` and `draft-` prefixes and other retired names, with every citation |
16
+ | `records` | Session records from `.claude/` to `.canon/`, with every tracked citation |
17
+ | `surface-roots` | Records such as `context/` and `REQUIREMENTS.md`, and the stamp folder, from `.claude/` to `canon/` by `git mv` |
18
+ | `record-tree` | Citations inside the `.canon/` folders a session still follows, listed below the table |
19
+ | `rule-layout` | A flat installed-rule layout onto the `canon/`-wrapped one |
20
+ | `scratch-evidence` | A folder a durable record cites out of `.tmp/`, with its citations |
21
+ | `record-layout` | Review receipts, memory archives, feedback, renders, picks, and evidence into their current folders |
22
+ | `session-defaults` | The seeded session defaults into an existing `.claude/settings.json`, listed below the table |
23
+
24
+ `record-tree` follows citations in `diagrams`, `memory`, `plans`, `proposals`, `review`, `tasks`, and `teach`. `session-defaults` denies `Agent`, `Task`, and both merge commands, and turns auto memory off. The bullets below add detail per verb.
23
25
 
24
26
  - `skill-names` takes no `--scope`, and skips the changelog, eval transcripts, and the pointer each renamed `draft-` skill leaves at its old name
25
- - `surface-roots` leaves `rules/`, `skills/`, `hooks/`, and `settings.json` where Claude Code reads them
27
+ - `surface-roots` moves `context/`, `wireframes/`, `ARCHITECTURE.md`, `REQUIREMENTS.md`, `DESIGN.md`, and the stamp folder, and leaves `rules/`, `skills/`, `hooks/`, and `settings.json` in place
26
28
  - `rule-layout` classifies each flat file against the stamp as clean, edited, or unclaimed. Clean and edited files move untouched, and an unclaimed name stays.
27
29
  - `record-layout` labels each evidence folder `(pick)` or `(evidence)` in the dry run
28
30
  - `session-defaults` adds a key only when absent, merge entries included, and each older project runs it once. `--write` restores a removed `Agent` or `Task` and never removes a deny entry.
@@ -22,18 +22,19 @@ Data goes to stdout. UI and logs go to stderr. Help is the exception and prints
22
22
  - `└` closes the frame on stderr
23
23
  - An error renders as `│ ✗ message` inside the frame, never as a lone line
24
24
  - Help skips the banner and puts `Usage:` directly on `├`
25
+ - A stderr that is not a terminal gets plain lines instead, with the banner and section titles bare and each item behind its mark alone
25
26
 
26
27
  A help request naming an unknown subcommand, under a group taking no operand, writes its refusal to stderr and exits 1.
27
28
 
28
29
  ## JSON and names modes
29
30
 
30
- `--json` and `--names` keep stdout pure data. The frame still renders on stderr, and diagnostics reach stderr in every mode. A JSON refusal carries an action and a reason, so branch on the record rather than on stderr.
31
+ `--json` and `--names` keep stdout pure data. A `--json` run writes no progress to stderr, only its warnings and errors, since diagnostics reach stderr in every mode. A JSON refusal carries an action and a reason, so branch on the record rather than on stderr.
31
32
 
32
33
  A shell profile may wrap `canon` in a function that takes its status from a later command. Where a verb documents a record field, read the field rather than the exit.
33
34
 
34
35
  ## Color
35
36
 
36
- Escape sequences reach a destination that renders them and nowhere else, decided per stream. `NO_COLOR` set to any non-empty value turns color off, and so does a destination that is not a terminal. The frame glyphs and the `✓ ! + - ✗` marks survive both.
37
+ Escape sequences reach a destination that renders them and nowhere else, decided per stream. `NO_COLOR` set to any non-empty value turns color off, and so does a destination that is not a terminal. The `✓ ! + - ✗` marks survive both. The frame glyphs need a terminal.
37
38
 
38
39
  ## Exit discipline
39
40
 
@@ -53,22 +54,24 @@ Discover names at runtime rather than hardcoding them.
53
54
  | `canon docs list --json` | Consumer docs plus per-domain context |
54
55
  | `canon audits list --json` | Audits the set runs, the corpus each reads, and whether it gates |
55
56
 
57
+ The table names the verbs, and three notes cover what some of them carry.
58
+
56
59
  - `gov list` expands each stack to its rules and carries `unreferenced`, the rules no stack reaches, which install only through `--add`
57
60
  - `claude skills list` reports shipped skills only. Each carries `family` (or `null`) and `requirement`, and a `false` requirement is a gap to close. `--names` and `--by-family` change the render.
58
61
  - A skill with unparseable frontmatter returns an empty description rather than failing the listing
59
62
 
60
63
  ## Headless invocation
61
64
 
62
- Set `CANON_NON_INTERACTIVE=1` to refuse every prompt.
65
+ A call with no terminal on stdin is headless and needs no prefix. Each prompt takes its documented default or refuses and names the flag to pass.
63
66
 
64
67
  ```bash
65
- CANON_NON_INTERACTIVE=1 canon init --stack node --skip records /path/to/project
66
- CANON_NON_INTERACTIVE=1 canon sync /path/to/project
67
- CANON_NON_INTERACTIVE=1 canon gov install node --add astro,shadcn /path/to/project
68
- CANON_NON_INTERACTIVE=1 canon gov sync /path/to/project
69
- CANON_NON_INTERACTIVE=1 canon gov build /path/to/project
70
- CANON_NON_INTERACTIVE=1 canon tooling diff astro /path/to/project
71
- CANON_NON_INTERACTIVE=1 canon tooling sync astro /path/to/project --write
68
+ canon init --stack node --skip records /path/to/project
69
+ canon sync /path/to/project
70
+ canon gov install node --add astro,shadcn /path/to/project
71
+ canon gov sync /path/to/project
72
+ canon gov build /path/to/project
73
+ canon tooling diff astro /path/to/project
74
+ canon tooling sync astro /path/to/project --write
72
75
  canon tooling inject base /path/to/project --configs --seeds
73
76
  canon tooling prune-gitignore base /path/to/project
74
77
  canon tooling verify vite-react
@@ -62,19 +62,19 @@ The seven `draft-` skills took verb-first names for what each makes. `canon migr
62
62
  | `draft-ready` | `plan-ready` <!-- canon-keep-retired --> |
63
63
  | `draft-screencast` | `script-screencast` <!-- canon-keep-retired --> |
64
64
 
65
+ Replace a citation of a skill with no successor by hand, or delete the sentence.
66
+
65
67
  - `canvas` is now `canvas-board`. The sweep rewrites only `canon:canvas` and `skills/canvas`, so replace a bare prose mention by hand. <!-- canon-keep-retired -->
66
68
  - `draft-ready` is rewritten only as `canon:draft-ready` and `skills/draft-ready`, since bare it also describes a pull request. Replace a bare mention of the skill by hand. <!-- canon-keep-retired -->
67
69
  - `design-extract` and `sketch-design` are replaced by `canon:canvas-board`. <!-- canon-keep-retired -->
68
70
  - `review-branch`, `seed-sync`, `restate-plainly`, `bash-cli-script`, `canon-operator`, and `canon-rollout` have no successor <!-- canon-keep-retired -->
69
71
  - `create-standard`, `markdown-propose`, `memory-review`, `project-commands`, `repo-metadata`, and `ux-walkthrough` have no successor <!-- canon-keep-retired -->
70
72
 
71
- Replace a citation of a skill with no successor by hand, or delete the sentence.
73
+ The `memory` record kind left `canon records`, so `canon records validate memory` refuses as an unknown kind and `canon records migrate` is gone.
72
74
 
73
75
  - `canon repo metadata`, `canon autoship`, and the `120-bash` rule are gone, and `canon gov sync` removes the rule <!-- canon-keep-retired -->
74
76
  - `memory-capture`, the `603-memory` rule, and the `memory` standard are gone, so send a lesson to the context entry that owns the fact <!-- canon-keep-retired -->
75
77
 
76
- The `memory` record kind left `canon records`, so `canon records validate memory` refuses as an unknown kind and `canon records migrate` is gone.
77
-
78
78
  Two held files run, `.husky/post-merge` and `.claude/hooks/pr-create-log.sh`, and a stale copy calls a missing binary or names a removed skill. Resync what the toolkit owns before sweeping what the project wrote, and run the second line once per stack.
79
79
 
80
80
  ```bash
@@ -51,4 +51,4 @@ Try a scenario from [sandbox](../../canon/context/development/sandbox.md) before
51
51
 
52
52
  ## Running sync from an agent session
53
53
 
54
- `canon sync .` applies every domain sync, then offers a commit and pull request that needs a terminal. Under `CANON_NON_INTERACTIVE=1` the syncs apply, the git step is refused, and the command exits 0. Commit yourself. Sync refuses a dirty working tree, and `canon sync --check .` does not.
54
+ `canon sync .` applies every domain sync, then offers a commit and pull request that needs a terminal. With no terminal on stdin the syncs apply, the git step is refused, and the command exits 0. Commit yourself. Sync refuses a dirty working tree, and `canon sync --check .` does not.
@@ -17,6 +17,8 @@ One warm session holds the cross-feature call and reviews. Cold worker sessions
17
17
  | Planner | One session per row, warm or cold | Measure the row against the tree, write one plan | Enter a worktree, write the board |
18
18
  | Worker | One cold worktree session per feature | Implement, self-check, open PR, answer the reviewer | Write the shared board, merge |
19
19
 
20
+ Each role keeps to its own writes, and five rules cover how the roles meet.
21
+
20
22
  - The orchestrator loads `role-orchestrator`, a worker loads `role-worker` through `auto-ship` Step 0, and a planner and a reviewer load theirs from the launch that dispatches them
21
23
  - The orchestrator decides which rows conflict, what merges first, and whether a row runs at all. Per-row planning can go cold.
22
24
  - A dispatched planner writes only the plan, and reports what the task file got wrong rather than repairing it
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/canon",
3
3
  "type": "module",
4
- "version": "5.54.0",
4
+ "version": "5.56.0",
5
5
  "description": "Runs your agent's work and keeps its code good, on one setup for every repo.",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -88,7 +88,7 @@ mkdir -p "$TARGET_DIR"
88
88
  log_info "Initialized git in $TARGET_DIR"
89
89
 
90
90
  log_step "Run canon init"
91
- (cd "$TARGET_DIR" && CANON_NON_INTERACTIVE=1 bun run "$EXTRACT_DIR/src/cli.ts" init --stack base 2>&1 | pipe_output) || log_error "canon init failed"
91
+ (cd "$TARGET_DIR" && bun run "$EXTRACT_DIR/src/cli.ts" init --stack base </dev/null 2>&1 | pipe_output) || log_error "canon init failed"
92
92
  log_info "canon init completed"
93
93
 
94
94
  log_step "Assert scaffold"
package/scripts/lib/ui.sh CHANGED
@@ -6,93 +6,40 @@ if [ "${BASH_VERSINFO[0]:-0}" -lt 4 ]; then
6
6
  exit 1
7
7
  fi
8
8
 
9
- # `NO_COLOR` follows the published convention, where any non-empty value turns
10
- # color off whatever the value says.
11
- supports_color() {
12
- [ -z "${NO_COLOR:-}" ] && [ -t "$1" ]
13
- }
14
-
15
- # Assigns the six palette names for one file descriptor. The blank palette keeps
16
- # every frame character and drops only the escapes, so a captured run still
17
- # reads as one block.
18
- #
19
- # Bash returns a string and nothing richer, so the palette arrives through the
20
- # caller's own scope. A writer declaring all six `local` before calling this
21
- # keeps the assignment to itself, which is what leaves the source-time answer
22
- # below standing for anything that sources this file.
23
- set_palette() {
24
- if supports_color "$1"; then
25
- GREEN='\033[0;32m'
26
- RED='\033[0;31m'
27
- YELLOW='\033[0;33m'
28
- WHITE='\033[1;37m'
29
- GREY='\033[0;90m'
30
- NC='\033[0m'
31
- else
32
- GREEN=''
33
- RED=''
34
- YELLOW=''
35
- WHITE=''
36
- GREY=''
37
- NC=''
38
- fi
39
- }
40
-
41
- # Every writer below frames to stderr and asks about that stream at write time,
42
- # so no answer is frozen before a caller's redirect is in place. This one answers
43
- # for stdout instead, because it is read by a sourcing script writing its own
44
- # frame with a bare `echo`, and that is the stream a bare `echo` reaches.
45
- set_palette 1
9
+ # The frame writes no color. The scripts left on it run as setup and gate steps,
10
+ # where a pipe or a log reads the output more often than a terminal does.
46
11
 
47
12
  log_info() {
48
- local GREEN RED YELLOW WHITE GREY NC
49
- set_palette 2
50
- echo -e "${GREY}│${NC} ${GREEN}✓${NC} $1" >&2
13
+ echo -e "│ ✓ $1" >&2
51
14
  }
52
15
  log_warn() {
53
- local GREEN RED YELLOW WHITE GREY NC
54
- set_palette 2
55
- echo -e "${GREY}│${NC} ${YELLOW}!${NC} $1" >&2
16
+ echo -e "│ ! $1" >&2
56
17
  }
57
18
  log_error() {
58
- local GREEN RED YELLOW WHITE GREY NC
59
- set_palette 2
60
- echo -e "${GREY}│${NC} ${RED}✗${NC} $1" >&2
19
+ echo -e "│ ✗ $1" >&2
61
20
  exit 1
62
21
  }
63
22
  log_step() {
64
- local GREEN RED YELLOW WHITE GREY NC
65
- set_palette 2
66
- echo -e "${GREY}│${NC}\n${GREY}├${NC} ${WHITE}$1${NC}" >&2
23
+ echo -e "│\n├ $1" >&2
67
24
  }
68
25
  log_add() {
69
- local GREEN RED YELLOW WHITE GREY NC
70
- set_palette 2
71
- echo -e "${GREY}│${NC} ${GREEN}+${NC} $1" >&2
26
+ echo -e "│ + $1" >&2
72
27
  }
73
28
  log_rem() {
74
- local GREEN RED YELLOW WHITE GREY NC
75
- set_palette 2
76
- echo -e "${GREY}│${NC} ${RED}-${NC} $1" >&2
29
+ echo -e "│ - $1" >&2
77
30
  }
78
31
 
79
32
  pipe_output() {
80
- local GREEN RED YELLOW WHITE GREY NC
81
- set_palette 2
82
- while IFS= read -r line; do echo -e "${GREY}│${NC} $line" >&2; done
33
+ while IFS= read -r line; do echo -e "│ $line" >&2; done
83
34
  }
84
35
 
85
36
  open_timeline() {
86
- local GREEN RED YELLOW WHITE GREY NC
87
- set_palette 2
88
- echo -e "${GREY}┌${NC}" >&2
37
+ echo -e "┌" >&2
89
38
  if [ -n "${1:-}" ]; then
90
- echo -e "${GREY}│${NC} ${WHITE}$1${NC}" >&2
39
+ echo -e "│ $1" >&2
91
40
  fi
92
41
  }
93
42
 
94
43
  close_timeline() {
95
- local GREEN RED YELLOW WHITE GREY NC
96
- set_palette 2
97
- echo -e "${GREY}└${NC}" >&2
44
+ echo -e "└" >&2
98
45
  }
package/src/audits/run.ts CHANGED
@@ -74,7 +74,7 @@ export function spawnAudit(root: string): Spawn {
74
74
  // extending it. Spreading `process.env` alongside would put back the very
75
75
  // variables it strips, and a hook's `GIT_DIR` would then point every
76
76
  // history-reading verb at a repository nobody asked about.
77
- env: { ...gitEnv(), CANON_NON_INTERACTIVE: '1' },
77
+ env: gitEnv(),
78
78
  extendEnv: false,
79
79
  })
80
80
 
@@ -9,7 +9,7 @@ const TOKEN = /`([^`\s]+)`/g
9
9
 
10
10
  /**
11
11
  * A path a reader could open. Admits no `$`, `*`, or `=`, so an assignment
12
- * (`CANON_NON_INTERACTIVE=1`) and a variable are both excluded.
12
+ * (`NAME=value`) and a variable are both excluded.
13
13
  */
14
14
  const PATH_TOKEN = /^[.A-Za-z0-9_][A-Za-z0-9._/-]*$/
15
15
 
package/src/cli/run.ts CHANGED
@@ -2,7 +2,7 @@ import { join } from 'node:path'
2
2
  import { execa } from 'execa'
3
3
 
4
4
  export interface CliRunOptions {
5
- /** Sets `CANON_NON_INTERACTIVE=1` for the child. Defaults to false. */
5
+ /** Closes the child's stdin, which makes it headless. Defaults to false. */
6
6
  readonly nonInteractive?: boolean
7
7
  /** Defaults to `inherit`, so a child prompt still reaches the operator. */
8
8
  readonly stdin?: 'ignore' | 'inherit'
@@ -28,13 +28,12 @@ export function cliRun(
28
28
  options: CliRunOptions = {},
29
29
  ): () => Promise<boolean> {
30
30
  return async () => {
31
- const env = options.nonInteractive
32
- ? { ...process.env, CANON_NON_INTERACTIVE: '1' }
33
- : process.env
31
+ const stdin = options.nonInteractive
32
+ ? 'ignore'
33
+ : (options.stdin ?? 'inherit')
34
34
 
35
35
  const result = await execa('bun', [path, ...args], {
36
- stdio: [options.stdin ?? 'inherit', 'inherit', 'inherit'],
37
- env,
36
+ stdio: [stdin, 'inherit', 'inherit'],
38
37
  reject: false,
39
38
  })
40
39