@erclx/canon 5.35.0 → 5.37.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 (238) hide show
  1. package/README.md +11 -11
  2. package/claude/.claude-plugin/plugin.json +1 -1
  3. package/claude/skills/api-design/SKILL.md +6 -0
  4. package/claude/skills/backlog-triage/SKILL.md +7 -7
  5. package/claude/skills/build-in-slices/SKILL.md +6 -0
  6. package/claude/skills/build-in-slices/references/adopted.md +3 -0
  7. package/claude/skills/canon-cli/REQUIREMENT.md +2 -2
  8. package/claude/skills/canon-cli/SKILL.md +5 -1
  9. package/claude/skills/canon-feedback/SKILL.md +5 -0
  10. package/claude/skills/canon-feedback-triage/SKILL.md +3 -0
  11. package/claude/skills/canvas-board/REQUIREMENT.md +1 -1
  12. package/claude/skills/canvas-board/SKILL.md +5 -2
  13. package/claude/skills/canvas-board/references/canvas.md +12 -0
  14. package/claude/skills/canvas-board/references/design.md +53 -36
  15. package/claude/skills/ci-workflow/SKILL.md +5 -0
  16. package/claude/skills/code-craft/SKILL.md +6 -0
  17. package/claude/skills/codebase-layout/REQUIREMENT.md +1 -1
  18. package/claude/skills/codebase-layout/SKILL.md +2 -2
  19. package/claude/skills/context-fold/references/context.md +48 -34
  20. package/claude/skills/create-rule/REQUIREMENT.md +6 -8
  21. package/claude/skills/create-rule/SKILL.md +19 -17
  22. package/claude/skills/create-rule/references/rule.md +17 -23
  23. package/claude/skills/create-skill/SKILL.md +11 -2
  24. package/claude/skills/create-skill/references/skill-disclosure.md +26 -0
  25. package/claude/skills/create-skill/references/skill-paths.md +3 -0
  26. package/claude/skills/create-skill/references/skill-reference.md +12 -0
  27. package/claude/skills/create-skill/references/skill.md +26 -25
  28. package/claude/skills/deprecation-migration/SKILL.md +6 -0
  29. package/claude/skills/deprecation-migration/references/adopted.md +3 -0
  30. package/claude/skills/design-taste/REQUIREMENT.md +6 -6
  31. package/claude/skills/design-taste/SKILL.md +8 -2
  32. package/claude/skills/design-taste/references/craft.md +4 -1
  33. package/claude/skills/design-taste/references/preflight.md +18 -2
  34. package/claude/skills/design-taste/references/tells.md +1 -1
  35. package/claude/skills/docs-sync/references/docs.md +6 -0
  36. package/claude/skills/docs-sync/references/readme.md +15 -0
  37. package/claude/skills/draft-and-pick/SKILL.md +12 -0
  38. package/claude/skills/draft-doc/references/context.md +1 -1
  39. package/claude/skills/draft-doc/references/readme.md +3 -0
  40. package/claude/skills/draft-figure/references/mermaid.md +6 -0
  41. package/claude/skills/draft-identity/SKILL.md +3 -2
  42. package/claude/skills/draft-slides/SKILL.md +3 -0
  43. package/claude/skills/git-branch/references/branch.md +16 -11
  44. package/claude/skills/git-commit/references/commit.md +19 -11
  45. package/claude/skills/git-commit/references/versioning.md +9 -0
  46. package/claude/skills/git-issue/references/issue.md +12 -9
  47. package/claude/skills/git-pr/SKILL.md +5 -0
  48. package/claude/skills/git-pr/references/pr.md +24 -6
  49. package/claude/skills/git-ship/SKILL.md +3 -0
  50. package/claude/skills/git-split/SKILL.md +6 -0
  51. package/claude/skills/git-stage/SKILL.md +3 -0
  52. package/claude/skills/git-worktree/SKILL.md +6 -6
  53. package/claude/skills/markdown-craft/references/markdown.md +27 -8
  54. package/claude/skills/plan-feature/SKILL.md +10 -1
  55. package/claude/skills/plan-feature/references/plan.md +20 -8
  56. package/claude/skills/plan-groundwork/SKILL.md +6 -0
  57. package/claude/skills/plan-groundwork/references/groundwork.md +10 -7
  58. package/claude/skills/plan-intake/references/intake.md +6 -6
  59. package/claude/skills/read-frames/SKILL.md +3 -0
  60. package/claude/skills/review-craft/SKILL.md +3 -0
  61. package/claude/skills/review-craft/references/ui.md +1 -1
  62. package/claude/skills/review-ui/SKILL.md +6 -3
  63. package/claude/skills/role-orchestrator/SKILL.md +3 -0
  64. package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +7 -4
  65. package/claude/skills/role-orchestrator/references/orchestrator-refill.md +3 -0
  66. package/claude/skills/role-planner/SKILL.md +3 -0
  67. package/claude/skills/role-worker/SKILL.md +12 -0
  68. package/claude/skills/search-craft/SKILL.md +3 -0
  69. package/claude/skills/systematic-debugging/SKILL.md +6 -0
  70. package/claude/skills/target-check/references/domains.md +5 -0
  71. package/claude/skills/target-setup/SKILL.md +5 -0
  72. package/claude/skills/task-board/references/tasks.md +9 -6
  73. package/claude/skills/task-complete/references/archive.md +3 -0
  74. package/claude/skills/teach-workspace/SKILL.md +3 -0
  75. package/claude/skills/teach-workspace/references/glossary.md +3 -0
  76. package/claude/skills/teach-workspace/references/lesson-build.md +3 -0
  77. package/claude/skills/teach-workspace/references/lesson-craft.md +3 -0
  78. package/claude/skills/teach-workspace/references/teach.md +8 -8
  79. package/claude/skills/test-craft/SKILL.md +7 -2
  80. package/claude/skills/ux-audit/REQUIREMENT.md +1 -1
  81. package/claude/skills/ux-audit/SKILL.md +2 -2
  82. package/claude/skills/ux-measure/REQUIREMENT.md +1 -1
  83. package/claude/skills/video-craft/SKILL.md +6 -0
  84. package/claude/skills/write-human/SKILL.md +9 -0
  85. package/claude/skills/write-human/references/machine-tells.md +12 -0
  86. package/docs/agents/capture.md +2 -0
  87. package/docs/agents/commands.md +59 -56
  88. package/docs/agents/comments.md +1 -1
  89. package/docs/agents/context-audit-checks.md +6 -0
  90. package/docs/agents/context-audit.md +8 -8
  91. package/docs/agents/context-classify.md +2 -0
  92. package/docs/agents/drift-surfaces.md +5 -5
  93. package/docs/agents/gate.md +6 -7
  94. package/docs/agents/gov-detect.md +1 -1
  95. package/docs/agents/index.md +2 -2
  96. package/docs/agents/install-and-sync.md +6 -6
  97. package/docs/agents/markdown-audit-weight.md +11 -3
  98. package/docs/agents/markdown-audit.md +14 -2
  99. package/docs/agents/output-shape.md +1 -1
  100. package/docs/agents/overview.md +2 -2
  101. package/docs/agents/pr-frames.md +17 -3
  102. package/docs/agents/rule-citations.md +5 -10
  103. package/docs/agents/scripting.md +1 -1
  104. package/docs/agents/sessions-transfer.md +8 -6
  105. package/docs/agents/skills-audit.md +9 -1
  106. package/docs/target/projects.md +2 -2
  107. package/docs/target/scenarios.md +5 -5
  108. package/docs/target/standards-moved.md +20 -18
  109. package/docs/workflow/ai-workflow.md +4 -1
  110. package/docs/workflow/visual-design-workflow.md +8 -3
  111. package/governance/detect.toml +32 -32
  112. package/governance/renames.toml +44 -0
  113. package/governance/rules/lib/{305-e2e-reliability.md → e2e-reliability.md} +1 -1
  114. package/governance/rules/lib/{306-test-scope.md → test-scope.md} +2 -2
  115. package/governance/rules/ui/{410-a11y.md → a11y.md} +1 -1
  116. package/governance/rules/ui/{440-surface-capture.md → surface-capture.md} +3 -3
  117. package/governance/stacks/base.toml +6 -6
  118. package/governance/stacks/go.toml +1 -1
  119. package/governance/stacks/node.toml +1 -1
  120. package/governance/stacks/php.toml +1 -1
  121. package/governance/stacks/python.toml +1 -1
  122. package/package.json +2 -2
  123. package/scripts/core/install-check.sh +2 -7
  124. package/src/capture/frame-values.ts +3 -6
  125. package/src/cli/help-all.ts +96 -0
  126. package/src/cli/help.ts +28 -27
  127. package/src/cli.ts +8 -1
  128. package/src/commands/claude.ts +29 -0
  129. package/src/commands/context.ts +29 -4
  130. package/src/commands/design.ts +22 -1
  131. package/src/commands/gate.ts +1 -8
  132. package/src/commands/gov.ts +25 -3
  133. package/src/commands/init.ts +10 -1
  134. package/src/commands/markdown.ts +148 -3
  135. package/src/commands/pr.ts +17 -10
  136. package/src/commands/records.ts +1 -1
  137. package/src/commands/sync.ts +11 -0
  138. package/src/commands/tooling.ts +8 -0
  139. package/src/context/audit.ts +83 -0
  140. package/src/context/classify/prompts.ts +6 -1
  141. package/src/context/gate.ts +6 -3
  142. package/src/design/budget.ts +14 -10
  143. package/src/design/document.ts +5 -5
  144. package/src/design/parse.ts +21 -4
  145. package/src/design/render.ts +3 -3
  146. package/src/gate/measures.ts +187 -2
  147. package/src/gate/sequencer.ts +0 -4
  148. package/src/gate/stages.ts +32 -27
  149. package/src/git/files.ts +30 -0
  150. package/src/gov/adapter.ts +2 -2
  151. package/src/gov/citations.ts +10 -33
  152. package/src/gov/install.ts +2 -2
  153. package/src/gov/list.ts +1 -1
  154. package/src/gov/payload.ts +1 -1
  155. package/src/gov/stacks.ts +4 -4
  156. package/src/inventory/subjects.ts +1 -1
  157. package/src/markdown/ceiling.ts +6 -1
  158. package/src/markdown/colons.ts +86 -0
  159. package/src/markdown/gate.ts +25 -0
  160. package/src/markdown/growth.ts +43 -0
  161. package/src/markdown/heading-case.ts +74 -0
  162. package/src/markdown/stacks.ts +74 -0
  163. package/src/markdown/structure.ts +72 -4
  164. package/src/migrate/rule-layout.ts +12 -12
  165. package/src/pr/frames.ts +66 -1
  166. package/src/shipped/references.ts +14 -14
  167. package/src/sync/check.ts +23 -23
  168. package/src/sync/copy.ts +4 -5
  169. package/tooling/astro/configs/tsconfig.json +1 -1
  170. package/tooling/astro/manifest.toml +0 -1
  171. package/tooling/astro/reference.md +2 -10
  172. package/tooling/base/configs/scripts/verify.json +1 -1
  173. package/tooling/base/configs/scripts/verify.ts +23 -13
  174. package/tooling/base/manifest.toml +6 -5
  175. package/tooling/base/reference.md +15 -22
  176. package/tooling/base/seeds/.github/workflows/verify.yml +0 -6
  177. package/tooling/base/seeds/.lintstagedrc +1 -2
  178. package/tooling/base/seeds/canon/context/ci.md +7 -8
  179. package/tooling/base/seeds/canon/context/development.md +10 -10
  180. package/tooling/claude/seeds/.claude/hooks/scratch-guard.sh +9 -1
  181. package/tooling/claude/seeds/.claude/hooks/standards-audit.sh +27 -2
  182. package/tooling/claude/seeds/CLAUDE.md +1 -1
  183. package/tooling/claude/seeds/canon/DESIGN.md +24 -37
  184. package/tooling/shell/configs/.github/workflows/shell.yml +30 -0
  185. package/tooling/shell/manifest.toml +12 -0
  186. package/tooling/shell/reference.md +46 -0
  187. package/tooling/vite-react/configs/tsconfig.json +1 -1
  188. package/tooling/vite-react/configs/vitest.config.ts +1 -1
  189. package/tooling/vite-react/manifest.toml +0 -1
  190. package/tooling/vite-react/reference.md +2 -10
  191. package/tooling/web/configs/.github/workflows/readme-screenshot.yml +3 -5
  192. package/tooling/web/configs/.github/workflows/verify.yml +0 -6
  193. package/tooling/web/configs/src/test/setup.ts +1 -1
  194. package/tooling/web/manifest.toml +4 -4
  195. package/tooling/web/reference.md +4 -4
  196. package/tooling/base/configs/scripts/verify.sh +0 -2
  197. /package/governance/rules/canon/{606-git.md → git.md} +0 -0
  198. /package/governance/rules/canon/{611-indexes.md → indexes.md} +0 -0
  199. /package/governance/rules/canon/{604-scratch.md → scratch.md} +0 -0
  200. /package/governance/rules/canon/{602-tasks.md → task-scope.md} +0 -0
  201. /package/governance/rules/canon/{605-worktrees.md → worktrees.md} +0 -0
  202. /package/governance/rules/claude/{566-output.md → output.md} +0 -0
  203. /package/governance/rules/code/{000-code.md → routing.md} +0 -0
  204. /package/governance/rules/code/{010-testing.md → testing.md} +0 -0
  205. /package/governance/rules/framework/{210-astro.md → astro.md} +0 -0
  206. /package/governance/rules/framework/{220-fastapi.md → fastapi.md} +0 -0
  207. /package/governance/rules/framework/{230-nextjs.md → nextjs.md} +0 -0
  208. /package/governance/rules/framework/{200-react.md → react.md} +0 -0
  209. /package/governance/rules/framework/{260-shadcn.md → shadcn.md} +0 -0
  210. /package/governance/rules/framework/{250-tailwind.md → tailwind.md} +0 -0
  211. /package/governance/rules/lang/{130-go.md → go.md} +0 -0
  212. /package/governance/rules/lang/{140-php.md → php.md} +0 -0
  213. /package/governance/rules/lang/{110-python.md → python.md} +0 -0
  214. /package/governance/rules/lang/{100-typescript.md → typescript.md} +0 -0
  215. /package/governance/rules/lib/{370-database.md → database.md} +0 -0
  216. /package/governance/rules/lib/{340-pydantic.md → pydantic.md} +0 -0
  217. /package/governance/rules/lib/{360-security-server.md → security-server.md} +0 -0
  218. /package/governance/rules/lib/{350-security-web.md → security-web.md} +0 -0
  219. /package/governance/rules/lib/{320-tanstack-query.md → tanstack-query.md} +0 -0
  220. /package/governance/rules/lib/{335-testing-go.md → testing-go.md} +0 -0
  221. /package/governance/rules/lib/{336-testing-php.md → testing-php.md} +0 -0
  222. /package/governance/rules/lib/{330-testing-py.md → testing-py.md} +0 -0
  223. /package/governance/rules/lib/{300-testing-ts.md → testing-ts.md} +0 -0
  224. /package/governance/rules/lib/{310-zod.md → zod.md} +0 -0
  225. /package/governance/rules/tooling/{700-ci-workflow.md → ci-workflow.md} +0 -0
  226. /package/governance/rules/tooling/{790-code-comments.md → code-comments.md} +0 -0
  227. /package/governance/rules/tooling/{780-config-comments.md → config-comments.md} +0 -0
  228. /package/governance/rules/tooling/{775-dependencies.md → dependencies.md} +0 -0
  229. /package/governance/rules/ui/{480-canvas.md → canvas.md} +0 -0
  230. /package/governance/rules/ui/{400-ui.md → copy.md} +0 -0
  231. /package/governance/rules/ui/{460-design-taste.md → design-taste.md} +0 -0
  232. /package/governance/rules/ui/{420-forms.md → forms.md} +0 -0
  233. /package/governance/rules/ui/{465-interface-casing.md → interface-casing.md} +0 -0
  234. /package/governance/rules/ui/{450-link-behavior.md → link-behavior.md} +0 -0
  235. /package/governance/rules/ui/{470-motion.md → motion.md} +0 -0
  236. /package/governance/rules/ui/{430-ux-completeness.md → ux-completeness.md} +0 -0
  237. /package/tooling/{base → shell}/configs/.editorconfig +0 -0
  238. /package/tooling/{base → shell}/configs/.shellcheckrc +0 -0
@@ -20,5 +20,5 @@ paths:
20
20
 
21
21
  - Answer a question about the running page with a script against the dev server rather than with the suite.
22
22
  - Do not enable `fullyParallel` in `playwright.config.ts`.
23
- - Follow `.claude/rules/canon/ui/440-surface-capture.md` for capture scope.
24
- - Follow `.claude/rules/canon/lib/305-e2e-reliability.md` for waits and guards.
23
+ - Follow `.claude/rules/canon/ui/surface-capture.md` for capture scope.
24
+ - Follow `.claude/rules/canon/lib/e2e-reliability.md` for waits and guards.
@@ -60,4 +60,4 @@ paths:
60
60
 
61
61
  - Let content reflow at a viewport 320 CSS pixels wide with no loss of content or function and no two-dimensional scrolling. Only content whose meaning depends on both axes, such as a data table, a map, or a diagram, may scroll sideways (WCAG Reflow, 1.4.10, Level AA).
62
62
  - Treat 320 as the criterion's own test condition rather than a device width. It is equivalent to a 1280 pixel window at 400 percent zoom, so a layout that breaks there breaks for a person who zooms.
63
- - Hold the same 320 floor for the content region beside persistent chrome a person can resize. The criterion measures the viewport, so this extension is this rule's own rather than the criterion's: a panel widened to 480 in a 768 window leaves 288 for the content, under the floor at a window the criterion passes. `440-surface-capture` states where the frame for it gets taken.
63
+ - Hold the same 320 floor for the content region beside persistent chrome a person can resize. The criterion measures the viewport, so this extension is this rule's own rather than the criterion's: a panel widened to 480 in a 768 window leaves 288 for the content, under the floor at a window the criterion passes. `surface-capture` states where the frame for it gets taken.
@@ -26,12 +26,12 @@ paths:
26
26
  ## What a capture covers
27
27
 
28
28
  - Capture the full page. Do not capture a component in isolation.
29
- - Declare the widths a layout case captures. Always include 320, the reflow floor from the reflow criterion in `410-a11y`, then add each breakpoint bucket the project writes rules for, read from the `## Layout` section of `DESIGN.md`.
29
+ - Declare the widths a layout case captures. Always include 320, the reflow floor from the reflow criterion in `a11y`, then add each breakpoint bucket the project writes rules for, read from the `## Layout` section of `DESIGN.md`.
30
30
  - Fall back to the 320 floor alone when the project has no `DESIGN.md` or its record carries no `## Layout` section. Do not impose another project's breakpoints.
31
31
  - Add a layout case at each bound when a surface renders persistent chrome a person can resize, such as a draggable sidebar or a split pane, one with the chrome at its narrowest and one at its widest. Take both at the narrowest declared width where the chrome stays persistent rather than collapsing, since a frame where it has turned into an overlay shows none of the width it takes.
32
- - Read those bounds from the code that clamps the chrome, since `## Layout` has no slot for them. Judge each frame by the content width left beside the chrome rather than by the window width, against the 320 floor `410-a11y` holds for that region.
32
+ - Read those bounds from the code that clamps the chrome, since `## Layout` has no slot for them. Judge each frame by the content width left beside the chrome rather than by the window width, against the 320 floor `a11y` holds for that region.
33
33
  - Apply the declaration to a case that covers layout only. A case driving a menu open or an answer chosen tests a state, so it takes one width rather than the full set.
34
- - Read a capture at 320 as proof that a frame was taken there, not that the page reflows correctly. The criterion is stated in `410-a11y` and nothing here enforces it.
34
+ - Read a capture at 320 as proof that a frame was taken there, not that the page reflows correctly. The criterion is stated in `a11y` and nothing here enforces it.
35
35
  - Write the capture into a folder named `evidence`. The image is collected for the pull request comment when any segment of its path is literally `evidence` and its filename carries an image extension, so the folder name is fixed and its position in the tree is the project's own.
36
36
  - Several `evidence/` folders in one project is the ordinary shape rather than a problem to consolidate. One per surface is how a project with more than one rendered surface ends up.
37
37
  - Drive a state a screenshot cannot reach. An initial render reports nothing about a menu that opens, an answer that is chosen, or a rail that tracks scrolling.
@@ -3,12 +3,12 @@ extends = ""
3
3
  # `canon` or `claude` reaches no project until it is added here or proposed by
4
4
  # `canon gov detect`. Every other rule is opt-in.
5
5
  rules = [
6
- "566-output",
7
- "602-tasks",
8
- "604-scratch",
9
- "605-worktrees",
10
- "606-git",
11
- "611-indexes",
6
+ "output",
7
+ "task-scope",
8
+ "scratch",
9
+ "worktrees",
10
+ "git",
11
+ "indexes",
12
12
  "context",
13
13
  "tasks",
14
14
  "plan",
@@ -1,2 +1,2 @@
1
1
  extends = "base"
2
- rules = ["code", "130-go", "335-testing-go"]
2
+ rules = ["code", "go", "testing-go"]
@@ -1,2 +1,2 @@
1
1
  extends = "base"
2
- rules = ["code", "100-typescript", "300-testing-ts"]
2
+ rules = ["code", "typescript", "testing-ts"]
@@ -1,2 +1,2 @@
1
1
  extends = "base"
2
- rules = ["code", "140-php", "336-testing-php"]
2
+ rules = ["code", "php", "testing-php"]
@@ -1,2 +1,2 @@
1
1
  extends = "base"
2
- rules = ["code", "110-python", "330-testing-py"]
2
+ rules = ["code", "python", "testing-py"]
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/canon",
3
3
  "type": "module",
4
- "version": "5.35.0",
4
+ "version": "5.37.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": {
@@ -32,7 +32,7 @@
32
32
  "test": "bun --bun vitest run",
33
33
  "prepare": "husky",
34
34
  "check": "bun src/cli.ts gate run",
35
- "check:ci": "bun src/cli.ts gate run --all --no-write",
35
+ "check:ci": "bun src/cli.ts gate run --all",
36
36
  "update": "bun update --interactive && bun run check",
37
37
  "clean": "rm -rf node_modules && bun pm cache rm && bun install",
38
38
  "bootstrap": "./scripts/core/bootstrap.sh",
@@ -101,18 +101,13 @@ log_step "Assert scaffold"
101
101
  # skills rather than installing into a target. The two
102
102
  # rule paths stand for the `claude` and `canon` groups `base` takes whole.
103
103
  for path in "CLAUDE.md" ".claude/wiki/index.md" ".claude" "canon/context/index.md" \
104
- ".prettierrc" ".editorconfig" ".lintstagedrc" ".husky/pre-commit" ".github/workflows/verify.yml" "scripts/verify.sh" \
105
- ".claude/rules/canon/claude/566-output.md" ".claude/rules/canon/canon/604-scratch.md"; do
104
+ ".prettierrc" ".lintstagedrc" ".husky/pre-commit" ".github/workflows/verify.yml" "scripts/verify.ts" \
105
+ ".claude/rules/canon/claude/output.md" ".claude/rules/canon/canon/scratch.md"; do
106
106
  if [ ! -e "$TARGET_DIR/$path" ]; then
107
107
  log_error "Missing after canon init: $path"
108
108
  fi
109
109
  log_info "Found: $path"
110
110
  done
111
111
 
112
- if [ ! -x "$TARGET_DIR/scripts/verify.sh" ]; then
113
- log_error "Not executable after canon init: scripts/verify.sh"
114
- fi
115
- log_info "Executable: scripts/verify.sh"
116
-
117
112
  log_step "Verification passed"
118
113
  log_info "Manual check still needed: bun link and global canon invocation"
@@ -121,9 +121,6 @@ const plural = (count: number, noun: string): string =>
121
121
  const frameRow = (cells: string): string =>
122
122
  `<span class="frame">│</span> <span class="ok">✓</span> ${cells}`
123
123
 
124
- /** Rule names carry a numeric prefix that orders the load, not the identity. */
125
- const slug = (name: string): string => name.replace(/^\d+-/, '')
126
-
127
124
  /** Indented to the depth a rule inside the `<style>` block sits at. */
128
125
  const indentTokens = (css: string): string =>
129
126
  css
@@ -194,13 +191,13 @@ export function buildFrameValues(catalogs: FrameCatalogs): FrameValues {
194
191
  }
195
192
 
196
193
  const tokenCss = indentTokens(catalogs.tokenCss)
197
- const rules = gov.rules.map((entry) => slug(entry.name))
194
+ const rules = gov.rules.map((entry) => entry.name)
198
195
  // A rule no stack names is opt-in behind `--add`, so featuring one advertises
199
196
  // an entry no ordinary `canon gov install` delivers. The column samples the
200
197
  // stack-reached subset while its count and remainder describe the whole.
201
198
  const stacked = new Set(gov.stacks.flatMap((stack) => stack.rules))
202
199
  const deliveredEntries = gov.rules.filter((entry) => stacked.has(entry.name))
203
- const deliveredRules = deliveredEntries.map((entry) => slug(entry.name))
200
+ const deliveredRules = deliveredEntries.map((entry) => entry.name)
204
201
 
205
202
  for (const [label, list] of [
206
203
  ['skills', skills],
@@ -236,7 +233,7 @@ export function buildFrameValues(catalogs: FrameCatalogs): FrameValues {
236
233
  const scope =
237
234
  globs.length > 0 ? clip(globs.join(' '), GLOB_WIDTH) : 'every session'
238
235
  return frameRow(
239
- `<span class="name">${escape(pad(slug(entry.name), 24))}</span>` +
236
+ `<span class="name">${escape(pad(entry.name, 24))}</span>` +
240
237
  `<span class="domain">${escape(pad(`[${entry.domain}]`, 12))}</span>` +
241
238
  `<span class="muted">${escape(scope)}</span>`,
242
239
  )
@@ -0,0 +1,96 @@
1
+ import type { Command } from 'commander'
2
+
3
+ export type HelpRoute =
4
+ | { kind: 'parse' }
5
+ | { kind: 'top' }
6
+ | { kind: 'all'; command: Command }
7
+ | { kind: 'unknown'; name: string }
8
+
9
+ const HELP_FLAGS = new Set(['-h', '--help'])
10
+
11
+ // Commander exposes no getter for a disabled help option, and `canon sandbox`
12
+ // disables its own to forward `--help` to a bash script.
13
+ function hasHelpOption(command: Command): boolean {
14
+ return command.helpInformation().includes('--help')
15
+ }
16
+
17
+ // A group with its own action, or with a default subcommand such as
18
+ // `inventory run`, takes an operand that names no subcommand. Commander keeps
19
+ // both on private fields.
20
+ function takesOperand(command: Command): boolean {
21
+ const internals = command as unknown as {
22
+ _actionHandler: unknown
23
+ _defaultCommandName: unknown
24
+ }
25
+ return (
26
+ command.registeredArguments.length > 0 ||
27
+ internals._actionHandler !== null ||
28
+ internals._defaultCommandName !== null
29
+ )
30
+ }
31
+
32
+ function visibleChildren(command: Command): Command[] {
33
+ return command
34
+ .createHelp()
35
+ .visibleCommands(command)
36
+ .filter((child) => command.commands.includes(child))
37
+ }
38
+
39
+ function leaves(command: Command): Command[] {
40
+ return visibleChildren(command).flatMap((child) =>
41
+ visibleChildren(child).length === 0 ? [child] : leaves(child),
42
+ )
43
+ }
44
+
45
+ // `outputHelp` rather than `helpInformation`, since only the former emits the
46
+ // blocks `addHelpText` registers, which carry every verb's exit codes and
47
+ // examples.
48
+ function captureHelp(command: Command): string {
49
+ const config = command.configureOutput()
50
+ const { writeOut } = config
51
+ let text = ''
52
+ command.configureOutput({ writeOut: (chunk) => (text += chunk) })
53
+ try {
54
+ command.outputHelp()
55
+ } finally {
56
+ command.configureOutput({ writeOut })
57
+ }
58
+ return text.trimEnd()
59
+ }
60
+
61
+ // Each block opens on its own `Usage:` line, so a blank line is the whole
62
+ // separator and a reader splits on the usage header.
63
+ export function renderAll(command: Command): string {
64
+ const blocks = [command, ...leaves(command)].map(captureHelp)
65
+ return `${blocks.join('\n\n')}\n`
66
+ }
67
+
68
+ // Reads argv ahead of commander, which prints a group's help for an unknown
69
+ // subcommand because it answers `--help` before it resolves operands. Walking
70
+ // stops at the first option, so an option's value is never read as a name.
71
+ export function routeHelp(program: Command, args: string[]): HelpRoute {
72
+ if (!args.some((arg) => HELP_FLAGS.has(arg))) return { kind: 'parse' }
73
+
74
+ let command = program
75
+ let depth = 0
76
+ for (const arg of args) {
77
+ if (arg.startsWith('-')) break
78
+ const child = command.commands.find((c) => c.name() === arg)
79
+ if (child === undefined) {
80
+ const isGroupOnly =
81
+ command !== program &&
82
+ command.commands.length > 0 &&
83
+ !takesOperand(command) &&
84
+ arg !== 'help'
85
+ if (isGroupOnly) return { kind: 'unknown', name: arg }
86
+ break
87
+ }
88
+ command = child
89
+ depth += 1
90
+ }
91
+
92
+ if (!args.includes('--all')) return { kind: 'parse' }
93
+ if (depth === 0) return { kind: 'top' }
94
+ if (!hasHelpOption(command)) return { kind: 'parse' }
95
+ return { kind: 'all', command }
96
+ }
package/src/cli/help.ts CHANGED
@@ -20,61 +20,61 @@ export const COMMAND_GROUPS: { title: string; rows: CommandRow[] }[] = [
20
20
  ['sync [path]', 'Sync all installed domains in a project'],
21
21
  ['upgrade', 'Reinstall the CLI with the manager that installed it'],
22
22
  ['migrate [cmd]', 'Move a project off a retired name or layout'],
23
- ['targets [cmd]', 'Projects this toolkit installed into (list, pulls)'],
23
+ ['targets [cmd]', 'Projects this toolkit installed into'],
24
24
  ],
25
25
  },
26
26
  {
27
27
  title: 'Domains',
28
28
  rows: [
29
- ['gov [cmd]', 'Governance rules (install, sync, list)'],
30
- ['tooling [cmd]', 'Tooling stacks (sync, ref, create)'],
31
- ['claude [cmd]', 'Claude workflow (init, sync, setup)'],
32
- ['wiki [cmd]', 'Wiki pages (init)'],
33
- ['indexes [cmd]', 'Regenerate index.md files (regen)'],
34
- ['docs [cmd|topic]', 'Emit toolkit reference docs (list, <topic>)'],
29
+ ['gov [cmd]', 'Governance rules'],
30
+ ['tooling [cmd]', 'Tooling stacks'],
31
+ ['claude [cmd]', 'Claude workflow'],
32
+ ['wiki [cmd]', 'Wiki pages'],
33
+ ['indexes [cmd]', 'Regenerate index.md files'],
34
+ ['docs [cmd|topic]', 'Emit toolkit reference docs'],
35
35
  ],
36
36
  },
37
37
  {
38
38
  title: 'Author and render',
39
39
  rows: [
40
- ['design [cmd]', 'Design system (render, regen)'],
41
- ['slides [cmd]', 'Slide decks (render, list)'],
40
+ ['design [cmd]', 'Design system'],
41
+ ['slides [cmd]', 'Slide decks'],
42
42
  ['capture [source]', 'Render HTML capture sources to PNG'],
43
43
  ['serve [dir]', 'Serve a directory on localhost, print the link'],
44
- ['demo [cmd]', 'Record a running app (compile, run)'],
44
+ ['demo [cmd]', 'Record a running app'],
45
45
  ['inventory [subj]', 'Report one computed property across every route'],
46
46
  ['drive <url> <run>', 'Walk a page through interactions, measure each'],
47
47
  ['transcripts <url>', 'Fetch a YouTube transcript with frontmatter'],
48
- ['teach [cmd]', 'Learning workspaces (list, open, resource, glossary)'],
49
- ['canvas [cmd]', 'Local canvas of pages and frames (serve, list)'],
48
+ ['teach [cmd]', 'Learning workspaces'],
49
+ ['canvas [cmd]', 'Local canvas of pages and frames'],
50
50
  ],
51
51
  },
52
52
  {
53
53
  title: 'Session and ship',
54
54
  rows: [
55
55
  ['sandbox [cat:cmd]', 'Provision and run sandbox scenarios'],
56
- ['tasks [cmd]', 'Task board (validate, archive)'],
57
- ['intake [cmd]', 'Intake folders under .canon/intake/ (list, answer)'],
58
- ['records [cmd]', 'Session records (validate, size, push, pull)'],
56
+ ['tasks [cmd]', 'Task board'],
57
+ ['intake [cmd]', 'Intake folders under .canon/intake/'],
58
+ ['records [cmd]', 'Session records'],
59
59
  ['sessions [cmd]', 'Resolve live sessions to worktree and branch'],
60
60
  ['worktrees [cmd]', 'Reclaim worktrees whose branches merged'],
61
- ['hooks [cmd]', 'Run the steps a git hook drives (post-merge)'],
62
- ['pr [cmd]', 'Read a pull request (key-changes, head, checks)'],
63
- ['upstream [cmd]', 'Read Claude Code releases (fetch, catalog, advance)'],
61
+ ['hooks [cmd]', 'Run the steps a git hook drives'],
62
+ ['pr [cmd]', 'Read a pull request'],
63
+ ['upstream [cmd]', 'Read Claude Code releases'],
64
64
  ['feedback', 'File toolkit feedback as a GitHub issue'],
65
65
  ],
66
66
  },
67
67
  {
68
68
  title: 'Repo reports',
69
69
  rows: [
70
- ['audits [cmd]', 'Run every health check as one set (run, list)'],
71
- ['gate [cmd]', 'Run the merge gate stage by stage (run)'],
72
- ['secrets [cmd]', 'Scan the shipped tree for credentials (scan)'],
73
- ['deps [cmd]', 'Read the dependency set for advisories (audit)'],
74
- ['labels [cmd]', 'Read a changed set against the label map (audit)'],
75
- ['comments [cmd]', 'Measure comment density and trend (scan)'],
76
- ['context [cmd]', 'Report context folder health (audit)'],
77
- ['markdown [cmd]', 'Report markdown against the shipped bans (audit)'],
70
+ ['audits [cmd]', 'Run every health check as one set'],
71
+ ['gate [cmd]', 'Run the merge gate stage by stage'],
72
+ ['secrets [cmd]', 'Scan the shipped tree for credentials'],
73
+ ['deps [cmd]', 'Read the dependency set for advisories'],
74
+ ['labels [cmd]', 'Read a changed set against the label map'],
75
+ ['comments [cmd]', 'Measure comment density and trend'],
76
+ ['context [cmd]', 'Report context folder health'],
77
+ ['markdown [cmd]', 'Report markdown against the shipped bans'],
78
78
  ['census [path]', 'Report file count, extensions, and line totals'],
79
79
  ],
80
80
  },
@@ -111,6 +111,7 @@ const EXAMPLES = [
111
111
  'canon drive http://localhost:4173 run.json --json',
112
112
  'pbpaste | canon feedback',
113
113
  'canon transcripts https://youtu.be/VIDEO_ID',
114
+ 'canon tasks --help --all',
114
115
  'canon tasks archive --pull-request 673 --json',
115
116
  'canon intake list toolkit-overview --unread --json',
116
117
  'canon teach list --json',
@@ -129,7 +130,7 @@ const EXAMPLES = [
129
130
  'canon labels audit --json',
130
131
  'canon census --json',
131
132
  'canon audits run --json',
132
- 'canon gate run --all --no-write',
133
+ 'canon gate run --all',
133
134
  'canon upgrade --json',
134
135
  'canon upstream fetch --since 2.1.256 --json',
135
136
  ]
package/src/cli.ts CHANGED
@@ -43,6 +43,7 @@ import { register as targets } from '@/commands/targets'
43
43
  import { register as upgrade } from '@/commands/upgrade'
44
44
  import { readInstalled, UNKNOWN_LABEL } from '@/version/installed'
45
45
  import { renderHelp } from '@/cli/help'
46
+ import { renderAll, routeHelp } from '@/cli/help-all'
46
47
 
47
48
  function showHelp(): void {
48
49
  console.log(renderHelp(process.stdout))
@@ -102,4 +103,10 @@ audits(program)
102
103
  gate(program)
103
104
  upgrade(program)
104
105
 
105
- program.parse()
106
+ const route = routeHelp(program, process.argv.slice(2))
107
+ if (route.kind === 'top') showHelp()
108
+ else if (route.kind === 'all') process.stdout.write(renderAll(route.command))
109
+ else if (route.kind === 'unknown') {
110
+ console.error(`error: unknown command '${route.name}'`)
111
+ process.exitCode = 1
112
+ } else program.parse()
@@ -212,6 +212,20 @@ export function register(program: Command): void {
212
212
  .description('Seed .claude/ workflow docs into a project')
213
213
  .argument('[target]', 'Target directory', '.')
214
214
  .helpOption('-h, --help', 'Show this help message')
215
+ .addHelpText(
216
+ 'after',
217
+ [
218
+ '',
219
+ 'Copies each workflow seed the target lacks, CLAUDE.md included, and',
220
+ 'leaves one already present alone. Then adds the .gitignore entries',
221
+ 'the claude manifest lists.',
222
+ '',
223
+ 'Exit codes:',
224
+ ' 0 initialized, already initialized, or the prompt was cancelled',
225
+ ' 1 the target is missing or is the toolkit root',
226
+ '',
227
+ ].join('\n'),
228
+ )
215
229
  .action(async (target: string) => {
216
230
  process.exitCode = await runInit(target)
217
231
  })
@@ -221,6 +235,21 @@ export function register(program: Command): void {
221
235
  .description('Reconcile .gitignore against the claude manifest')
222
236
  .argument('[target]', 'Target directory', '.')
223
237
  .helpOption('-h, --help', 'Show this help message')
238
+ .addHelpText(
239
+ 'after',
240
+ [
241
+ '',
242
+ 'Writes .gitignore alone: adds the entries the claude manifest lists',
243
+ 'and drops ones under its headers that the manifest no longer lists.',
244
+ 'A seeded file is reported, never rewritten, and a missing one points',
245
+ 'at `canon claude init`.',
246
+ '',
247
+ 'Exit codes:',
248
+ ' 0 synced, already up to date, or the prompt was cancelled',
249
+ ' 1 the target is missing or is the toolkit root',
250
+ '',
251
+ ].join('\n'),
252
+ )
224
253
  .action(async (target: string) => {
225
254
  process.exitCode = await runSync(target)
226
255
  })
@@ -11,11 +11,14 @@ import {
11
11
  LENGTH_CHECKPOINT,
12
12
  matchesSiblings,
13
13
  measureFolders,
14
+ extraSections,
14
15
  missingSections,
15
16
  PROVENANCE_FOLDER,
17
+ ALLOWED_SECTIONS_BY_FOLDER,
16
18
  REQUIRED_SECTIONS,
17
19
  REQUIRED_SECTIONS_BY_FOLDER,
18
20
  requiredSections,
21
+ type ExtraSectionFinding,
19
22
  type SectionFinding,
20
23
  } from '@/context/audit'
21
24
  import { auditCitations, type CitationReport } from '@/context/citations'
@@ -640,6 +643,7 @@ async function runAudit(
640
643
  const entries = gateOnly ? [] : await measureFolders(root, folders)
641
644
  const drift = gateOnly ? [] : await auditIndexes(folders)
642
645
  const sections = gateOnly ? [] : missingSections(root, folders, entries)
646
+ const extras = gateOnly ? [] : extraSections(entries)
643
647
  const length = gateOnly ? undefined : lengthFindings(entries)
644
648
  // Absent under `--citations-only` and null when the project carries no
645
649
  // record. A run that never looked and a project with nothing to look at are
@@ -653,7 +657,7 @@ async function runAudit(
653
657
  reportScope(folders, unresolved)
654
658
  reportCitations(citations, cited)
655
659
  reportReferenceForm(entries, folders)
656
- reportSections(sections, folders)
660
+ reportSections(sections, extras, folders)
657
661
  reportLength(length ?? [])
658
662
  reportTables(entries)
659
663
  reportProvenance(entries, folders)
@@ -691,6 +695,7 @@ async function runAudit(
691
695
  // never opened an entry.
692
696
  length,
693
697
  missingSections: sections,
698
+ extraSections: extras,
694
699
  indexDrift: drift,
695
700
  // Null says the run opened the project and found no record, which a
696
701
  // target that never wrote one is entitled to. Absent says the run
@@ -705,6 +710,7 @@ async function runAudit(
705
710
  // caller that read it first, since removing it would break that
706
711
  // caller without an error.
707
712
  requiredSectionsByFolder: REQUIRED_SECTIONS_BY_FOLDER,
713
+ allowedSectionsByFolder: ALLOWED_SECTIONS_BY_FOLDER,
708
714
  },
709
715
  })}\n`,
710
716
  )
@@ -715,6 +721,7 @@ async function runAudit(
715
721
  requirementsOverWords:
716
722
  requirements !== undefined && isOverWordCap(requirements),
717
723
  sections,
724
+ extraSections: extras,
718
725
  drift,
719
726
  widened,
720
727
  })
@@ -905,6 +912,7 @@ function reportReferenceForm(
905
912
  */
906
913
  function reportSections(
907
914
  missing: readonly SectionFinding[],
915
+ extras: readonly ExtraSectionFinding[],
908
916
  folders: readonly AuditedFolder[],
909
917
  ): void {
910
918
  logStep('Sections')
@@ -938,13 +946,30 @@ function reportSections(
938
946
 
939
947
  if (missing.length === 0) {
940
948
  logInfo('Every entry declares each required section.')
949
+ } else {
950
+ logWarn(`${plural(missing.length, 'path')} short a required section`)
951
+ pipeOutput(
952
+ missing
953
+ .map((found) => `${found.rel} missing: ${found.missing.join(', ')}`)
954
+ .join('\n'),
955
+ )
956
+ }
957
+
958
+ logInfo(
959
+ `The set is closed: ${Object.entries(ALLOWED_SECTIONS_BY_FOLDER)
960
+ .map(([name, set]) => `canon/${name}/ allows ${set.join(', ')}`)
961
+ .join('. ')} as H2. Any other topic is an H3 under one of them.`,
962
+ )
963
+
964
+ if (extras.length === 0) {
965
+ logInfo('No entry carries an H2 outside the set.')
941
966
  return
942
967
  }
943
968
 
944
- logWarn(`${plural(missing.length, 'path')} short a required section`)
969
+ logWarn(`${plural(extras.length, 'path')} carrying an H2 outside the set`)
945
970
  pipeOutput(
946
- missing
947
- .map((found) => `${found.rel} missing: ${found.missing.join(', ')}`)
971
+ extras
972
+ .map((found) => `${found.rel} extra: ${found.headings.join(', ')}`)
948
973
  .join('\n'),
949
974
  )
950
975
  }
@@ -161,7 +161,13 @@ export function register(program: Command): void {
161
161
  `overrides a value by writing ${join(DESIGN_INSTALL_DIR, DESIGN_PROJECT_SUBDIR)}/`,
162
162
  'itself, which sync never touches. Nothing arrives on a project that',
163
163
  'has not run this, so a design value reaches a target on an install',
164
- 'rather than on the next sync.',
164
+ 'rather than on the next sync. A base stylesheet already installed is',
165
+ 'replaced.',
166
+ '',
167
+ 'Exit codes:',
168
+ ' 0 the stylesheet was installed',
169
+ ' 1 the target is missing or is the toolkit root, or the toolkit',
170
+ ' source is absent',
165
171
  '',
166
172
  ].join('\n'),
167
173
  )
@@ -176,6 +182,21 @@ export function register(program: Command): void {
176
182
  )
177
183
  .argument('[target]', 'Target directory', '.')
178
184
  .helpOption('-h, --help', 'Show this help message')
185
+ .addHelpText(
186
+ 'after',
187
+ [
188
+ '',
189
+ 'Replaces the shipped design files under .claude/design/ with the',
190
+ 'toolkit copy, edited or not, and leaves',
191
+ `${join(DESIGN_INSTALL_DIR, DESIGN_PROJECT_SUBDIR)}/ alone. It deletes nothing it finds no`,
192
+ 'source for.',
193
+ '',
194
+ 'Exit codes:',
195
+ ' 0 synced, already up to date, or the prompt was cancelled',
196
+ ' 1 the target is missing or is the toolkit root',
197
+ '',
198
+ ].join('\n'),
199
+ )
179
200
  .action(async (target: string) => {
180
201
  process.exitCode = await runDomainSync(
181
202
  createDesignAdapter(PROJECT_ROOT),
@@ -27,7 +27,6 @@ import { repairBareFlag } from '@/worktrees/bare-flag'
27
27
 
28
28
  interface RunCommandOptions {
29
29
  readonly all?: boolean
30
- readonly write?: boolean
31
30
  readonly nested?: boolean
32
31
  readonly json?: boolean
33
32
  }
@@ -48,10 +47,6 @@ export function register(program: Command): void {
48
47
  '--all',
49
48
  'Run every stage instead of scoping shell, types, and tests to changed files',
50
49
  )
51
- .option(
52
- '--no-write',
53
- 'Check formatting instead of applying it, which is what a merge gate wants',
54
- )
55
50
  .option(
56
51
  '--nested',
57
52
  'Suppress the outer frame when another script opened one',
@@ -77,7 +72,7 @@ export function register(program: Command): void {
77
72
  '',
78
73
  'Examples:',
79
74
  ' canon gate run',
80
- ' canon gate run --all --no-write',
75
+ ' canon gate run --all',
81
76
  ' canon gate run --json',
82
77
  '',
83
78
  ].join('\n'),
@@ -92,7 +87,6 @@ async function runGate(opts: RunCommandOptions): Promise<number> {
92
87
  const mismatch = checkoutMismatchWarning(process.cwd())
93
88
  const emitJson = opts.json ?? false
94
89
  const nested = opts.nested ?? false
95
- const write = opts.write ?? true
96
90
  const run = commandRunner(root)
97
91
 
98
92
  if (!emitJson && !nested) intro('canon gate run')
@@ -111,7 +105,6 @@ async function runGate(opts: RunCommandOptions): Promise<number> {
111
105
  ci: process.env.CI === 'true',
112
106
  run,
113
107
  cli: cliRunner(root),
114
- write,
115
108
  changed: changed.scoped ? changed.files : undefined,
116
109
  onQueue: (label, holder) => {
117
110
  const text = `${label} is waiting for the suite pid ${holder.pid} runs at ${holder.root}`
@@ -154,13 +154,21 @@ export function register(program: Command): void {
154
154
  'Examples:',
155
155
  ' canon gov install node',
156
156
  ' canon gov install node ../my-app',
157
- ' canon gov install node --add 210-astro,260-shadcn ../my-app',
157
+ ' canon gov install node --add astro,shadcn ../my-app',
158
158
  ' canon gov install --add tasks,plan ../my-app',
159
159
  '',
160
160
  'With --add and no stack, the named rules install alone. A first',
161
161
  'positional naming a stack is read as the stack, so pass . or a path',
162
162
  'carrying a slash to force a target that shares a stack name.',
163
163
  '',
164
+ 'Copies each rule into .claude/rules/canon/, replacing a copy already',
165
+ 'there. Rules under .claude/rules/project/ are never read.',
166
+ '',
167
+ 'Exit codes:',
168
+ ' 0 installed, the stack held no rules, or the prompt was cancelled',
169
+ ' 1 refused: no stack named headlessly, an unknown stack, no --add',
170
+ ' rule resolved, or a missing target or the toolkit root',
171
+ '',
164
172
  ].join('\n'),
165
173
  )
166
174
  .action(
@@ -178,6 +186,21 @@ export function register(program: Command): void {
178
186
  .description('Update rules already installed under .claude/rules/canon/')
179
187
  .argument('[target]', 'Target directory', '.')
180
188
  .helpOption('-h, --help', 'Show this help message')
189
+ .addHelpText(
190
+ 'after',
191
+ [
192
+ '',
193
+ 'Replaces an edited rule under .claude/rules/canon/ with the shipped',
194
+ 'copy, deletes a rule the toolkit retired, and moves a renamed one.',
195
+ 'It adds no new rule, which is `canon gov install`, and never touches',
196
+ '.claude/rules/project/.',
197
+ '',
198
+ 'Exit codes:',
199
+ ' 0 synced, already up to date, or the prompt was cancelled',
200
+ ' 1 the target is missing or is the toolkit root',
201
+ '',
202
+ ].join('\n'),
203
+ )
181
204
  .action(async (target: string) => {
182
205
  process.exitCode = await runDomainSync(
183
206
  createGovAdapter(PROJECT_ROOT),
@@ -440,10 +463,9 @@ export function register(program: Command): void {
440
463
  'internal corpus ships nowhere, which makes the tree it governs the tree',
441
464
  'present and the question answerable.',
442
465
  '',
443
- 'Forms read, with where each resolves:',
466
+ 'Form read, with where it resolves:',
444
467
  ' path a backticked path carrying a directory segment and a file',
445
468
  ' extension, against the root',
446
- " sibling a bare <nnn>-<slug>.md , against the citing rule's folder",
447
469
  '',
448
470
  'Not read, each a shape the corpus writes and none of them a citation:',
449
471
  ' a placeholder or glob segment, which describes a shape rather than',