@erclx/canon 5.48.0 → 5.50.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 (89) hide show
  1. package/claude/.claude-plugin/plugin.json +1 -1
  2. package/claude/skills/auto-ship/SKILL.md +1 -1
  3. package/claude/skills/canvas-board/REQUIREMENT.md +4 -3
  4. package/claude/skills/canvas-board/SKILL.md +8 -9
  5. package/claude/skills/context-fold/references/context.md +4 -3
  6. package/claude/skills/design-record/REQUIREMENT.md +33 -0
  7. package/claude/skills/design-record/SKILL.md +84 -0
  8. package/claude/skills/design-record/references/folder.md +69 -0
  9. package/claude/skills/design-taste/SKILL.md +1 -1
  10. package/claude/skills/markdown-craft/REQUIREMENT.md +1 -0
  11. package/claude/skills/markdown-craft/SKILL.md +6 -1
  12. package/claude/skills/markdown-craft/references/markdown.md +3 -3
  13. package/claude/skills/markdown-craft/references/tables.md +30 -0
  14. package/claude/skills/pick-by-eye/SKILL.md +10 -8
  15. package/claude/skills/plan-feature/SKILL.md +31 -16
  16. package/claude/skills/plan-feature/references/batches.md +5 -5
  17. package/claude/skills/plan-feature/references/plan.md +82 -33
  18. package/claude/skills/plan-groundwork/SKILL.md +8 -0
  19. package/claude/skills/plan-groundwork/references/groundwork.md +2 -1
  20. package/claude/skills/plan-intake/SKILL.md +8 -0
  21. package/claude/skills/plan-intake/references/intake.md +9 -1
  22. package/claude/skills/plan-ready/REQUIREMENT.md +3 -3
  23. package/claude/skills/plan-ready/SKILL.md +3 -3
  24. package/claude/skills/plan-ready/references/assembly.md +7 -7
  25. package/claude/skills/plan-ready/references/ready.md +8 -6
  26. package/claude/skills/review-ui/REQUIREMENT.md +1 -1
  27. package/claude/skills/review-ui/SKILL.md +4 -11
  28. package/claude/skills/review-ui/references/close-out.md +1 -1
  29. package/claude/skills/review-ui/references/drive-copy.md +38 -0
  30. package/claude/skills/review-ui/references/post.md +24 -3
  31. package/claude/skills/review-ui/references/verb-absent.md +18 -0
  32. package/claude/skills/role-orchestrator/SKILL.md +3 -2
  33. package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +1 -1
  34. package/claude/skills/role-orchestrator/references/orchestrator-hand-launch.md +60 -0
  35. package/claude/skills/role-orchestrator/references/orchestrator-launch.md +5 -0
  36. package/claude/skills/role-orchestrator/references/orchestrator-parked.md +1 -1
  37. package/claude/skills/role-orchestrator/references/orchestrator-reviewer-launch.md +1 -1
  38. package/claude/skills/role-planner/SKILL.md +1 -1
  39. package/claude/skills/role-worker/SKILL.md +4 -2
  40. package/claude/skills/session-compact/references/handoff-note.md +3 -1
  41. package/claude/skills/session-map/references/session.md +1 -1
  42. package/claude/skills/session-worktree/SKILL.md +1 -1
  43. package/claude/skills/task-board/references/board.md +2 -2
  44. package/claude/skills/task-board/references/tasks.md +1 -1
  45. package/docs/agents/index.md +1 -1
  46. package/docs/agents/markdown-audit.md +5 -2
  47. package/docs/agents/records.md +1 -1
  48. package/docs/target/migrations.md +7 -0
  49. package/docs/target/projects.md +1 -1
  50. package/docs/workflow/parallel-features.md +2 -2
  51. package/docs/workflow/skill-map.md +2 -1
  52. package/docs/workflow/visual-design-workflow.md +2 -1
  53. package/governance/ledger/ui/design-reference.md +1 -0
  54. package/governance/rules/ui/design-reference.md +1 -0
  55. package/package.json +1 -1
  56. package/src/claude/cases/misc.ts +5 -0
  57. package/src/commands/claude.ts +26 -1
  58. package/src/commands/gate.ts +57 -16
  59. package/src/commands/markdown.ts +98 -19
  60. package/src/commands/pr.ts +411 -23
  61. package/src/commands/records.ts +45 -8
  62. package/src/commands/tasks.ts +12 -0
  63. package/src/context/audit.ts +6 -5
  64. package/src/design/budget.ts +1 -1
  65. package/src/design/components.ts +1 -1
  66. package/src/design/document.ts +1 -1
  67. package/src/design/regen.ts +1 -1
  68. package/src/design/render.ts +1 -1
  69. package/src/gate/hand-suite.ts +52 -0
  70. package/src/gate/measures.ts +9 -2
  71. package/src/gate/sequencer.ts +17 -8
  72. package/src/hooks/post-merge.ts +11 -0
  73. package/src/markdown/gated.ts +5 -7
  74. package/src/markdown/scope.ts +60 -10
  75. package/src/markdown/structure.ts +1 -1
  76. package/src/markdown/tables.ts +150 -0
  77. package/src/pr/drive-copy.ts +218 -0
  78. package/src/pr/local.ts +26 -0
  79. package/src/pr/rest.ts +2 -0
  80. package/src/records/structure.ts +147 -0
  81. package/src/records/validate.ts +113 -12
  82. package/src/tasks/archive.ts +137 -4
  83. package/src/tasks/board.ts +68 -13
  84. package/src/tasks/validate.ts +54 -14
  85. package/tooling/base/configs/.github/workflows/pr-local-preview.yml +1 -1
  86. package/tooling/claude/reference.md +1 -0
  87. package/tooling/claude/seeds/.claude/hooks/standards-audit.sh +11 -1
  88. package/tooling/claude/seeds/CLAUDE.md +9 -0
  89. /package/claude/skills/{canvas-board → design-record}/references/design.md +0 -0
@@ -38,10 +38,11 @@ No surface carries cross-version sequencing, so report none, since reasoning spa
38
38
 
39
39
  ## Runbooks on request
40
40
 
41
- A compaction is a moment this skill cannot detect, so the human asks for each side of it and this skill reads the matching runbook.
41
+ The human asks for each side of a compaction, and this skill reads the runbook.
42
42
 
43
43
  - On a request to write the handoff, follow `${CLAUDE_SKILL_DIR}/references/orchestrator-handoff.md`. It sends the generic half to `canon:session-map` and adds decisions taken under delegated authority.
44
44
  - That map follows `${CLAUDE_SKILL_DIR}/../session-map/references/session.md`. Write nothing to the handoff that the board, a task file, or a groundwork folder already carries.
45
+ - Hand launch: `${CLAUDE_SKILL_DIR}/references/orchestrator-hand-launch.md`
45
46
  - A lesson about how to work goes to the owning task's Findings, as a bold-labeled line per `${CLAUDE_SKILL_DIR}/../task-board/references/tasks.md`
46
47
  - On a request to resume after a compaction, read `${CLAUDE_SKILL_DIR}/references/orchestrator-resume.md`, which reads that file back with the board and the groundwork behind the live work
47
48
 
@@ -108,7 +109,7 @@ The handback runbook holds how the message is addressed and worded, what to do w
108
109
 
109
110
  ### Constraints from the tracks in flight
110
111
 
111
- A plan written here targets a tree several branches are changing, so it names each in-flight track's file set as a constraint, read from that track's Touches column. State for each set which of the two acts it forbids, per Constraints in `${CLAUDE_SKILL_DIR}/../plan-feature/references/plan.md`.
112
+ A plan written here targets a tree several branches are changing, so it names each in-flight track's file set as a constraint, read from the plan its row links. State for each set which of the two acts it forbids, per Constraints in `${CLAUDE_SKILL_DIR}/../plan-feature/references/plan.md`.
112
113
 
113
114
  - A bare path list leaves the worker guessing, and can end up forbidding the repair of a citation the change broke
114
115
  - A shared path forbids an act only where the conflict check in `orchestrator-dispatch.md` holds the pair. Otherwise it names what the second branch to merge rebases over.
@@ -125,7 +125,7 @@ No practitioner source serializes on a shared file. Each builds in parallel and
125
125
  Read `canon sessions list --json` for the branches in flight as well as the board, since a hand-launched track carries no row, and read the plan of any branch no row names.
126
126
 
127
127
  - `canon tasks validate` and `canon tasks plan-reach` report the paths two rows share, a candidate list for the five holds and the paths a dispatch names
128
- - A shared folder means a Touches cell claimed it bare, so narrow that cell rather than reading the folder as a sweep
128
+ - A shared folder means a plan declared it bare, so narrow that plan's list rather than reading the folder as a sweep
129
129
  - A declared set predicts rather than bounds, since `canon:context-fold`, `canon:docs-sync`, and the drift stages write past every plan
130
130
  - Such a crossing changes what the second branch rebases over, not whether a row holds, and `canon tasks plan-reach` at `canon:git-ship` step 5 names it
131
131
 
@@ -0,0 +1,60 @@
1
+ ---
2
+ title: Orchestrator hand launch
3
+ description: The one lean prompts file the orchestrator writes when the operator launches sessions by hand, with its path, overwrite rule, shape, and a filled example
4
+ ---
5
+
6
+ Run this when the operator launches sessions by hand rather than through `claude --bg`. The rules every launch shares stay in `orchestrator-launch.md`.
7
+
8
+ # Hand launch
9
+
10
+ When the operator launches sessions by hand, write every prompt to one file, `.canon/tmp/dispatch/prompts.md`, and overwrite it at each wave. It lists every row with a session not yet launched. A row launched from an older copy and again from a newer one would build twice, and the branch check in `orchestrator-dispatch.md` catches that.
11
+
12
+ The operator opens the file, reads one lead line, and pastes one fence per session. The shape is fixed:
13
+
14
+ - An H1, `# Dispatch prompts`, then one lead line carrying the measured head, the merge order, and the folder to open each session in
15
+ - An H2 per task as `## <label>: <title>`, an H3 per role as `### Worker` or `### Reviewer`, a `Model: <model>.` line, and a `text` fence
16
+ - In the fence, the command at position zero, then `Controller: <sessionId>`, then only context the plan does not carry, one line each
17
+
18
+ Keep the command at position zero per `### Position zero` in `orchestrator-launch.md`, and carry only what `### What the brief may carry` there allows. Leave out the answer-slot default and anything else the plan states.
19
+
20
+ The `Controller:` line replaces the background template's message clauses, which `role-worker` and `session-relay` already own. `auto-ship` Step 0 loads `role-worker`, so the fence needs nothing more.
21
+
22
+ - A reviewer launched by hand takes the `### Reviewer` H3 under its row's H2, with the sibling facts `orchestrator-reviewer-launch.md` lists
23
+ - Its `<number>` exists once the worker opened its pull request, so a later wave carries `### Reviewer` alone under the row, and the launched worker's H3 is gone
24
+
25
+ The first wave carries the worker. The wave after its pull request opens carries the reviewer alone.
26
+
27
+ ````markdown
28
+ # Dispatch prompts
29
+
30
+ Head `<head>`. Merge `<label-a>` first, then `<label-b>`. Open each session in the repository root.
31
+
32
+ ## <label-b>: <title>
33
+
34
+ ### Worker
35
+
36
+ Model: opus.
37
+
38
+ ```text
39
+ /canon:auto-ship .canon/plans/feature-<slug>.md
40
+ Controller: <sessionId>
41
+ <branch-a> edits the section of <file> this plan leaves alone. Rebase over it when told.
42
+ ```
43
+ ````
44
+
45
+ ````markdown
46
+ # Dispatch prompts
47
+
48
+ Head `<head>`. Merge `<label-a>` first, then `<label-b>`. Open each session in the repository root.
49
+
50
+ ## <label-b>: <title>
51
+
52
+ ### Reviewer
53
+
54
+ Model: opus.
55
+
56
+ ```text
57
+ Run /canon:role-reviewer, then /canon:review-pr <number>
58
+ Controller: <sessionId>
59
+ ```
60
+ ````
@@ -12,6 +12,7 @@ Run this once `orchestrator-dispatch.md` has cleared a row, or from its planning
12
12
  - [Dispatch to address a review](#dispatch-to-address-a-review)
13
13
  - [Dispatch to plan a row](#dispatch-to-plan-a-row)
14
14
  - [Dispatch a talk session](#dispatch-a-talk-session)
15
+ - [Hand launch](#hand-launch)
15
16
  - [Dispatch a reviewer](#dispatch-a-reviewer)
16
17
 
17
18
  ## Dispatch
@@ -156,6 +157,10 @@ Read the session's `sessionId` off the launch output, carry it in this session's
156
157
 
157
158
  The map exists only once the handoff writes it, so this session's own launches, read with the prior map, are what the first wind-down reads talk sessions from.
158
159
 
160
+ ## Hand launch
161
+
162
+ Read `${CLAUDE_SKILL_DIR}/references/orchestrator-hand-launch.md` when the operator launches sessions by hand. It states the single prompts file at `.canon/tmp/dispatch/prompts.md`, the lean shape of each fence, and a filled example, while the background templates above keep their full controller paragraph.
163
+
159
164
  ## Dispatch a reviewer
160
165
 
161
166
  Read `${CLAUDE_SKILL_DIR}/references/orchestrator-reviewer-launch.md` when `orchestrator-review-dispatch.md` sends a pull request to a dispatched reviewer, for the code, cloud, and UI reviewer shapes.
@@ -32,7 +32,7 @@ The blocker cell states what the row waits on, and each kind is tested different
32
32
 
33
33
  ### A hold behind a track in flight
34
34
 
35
- The validator tests the file the cell cites against the Touches column of every `## Run now` row. A track that merged since the row was parked is no longer in flight, whatever the sets still share.
35
+ The validator tests the file the cell cites against the files every `## Run now` row's plans declare. A track that merged since the row was parked is no longer in flight, whatever the sets still share.
36
36
 
37
37
  - Re-test the cell under the conflict check in `orchestrator-dispatch.md` too, since a row parked on a shared file alone holds on nothing and moves up
38
38
  - A cell naming the file in prose rather than backticks cites nothing, so rewrite the hold in the board's spelling and the next run picks it up
@@ -9,7 +9,7 @@ Run this once `orchestrator-review-dispatch.md` has sent a pull request to a dis
9
9
 
10
10
  `review-pr` is a single pass rather than a role, so a launch naming it alone reaches no `role-reviewer` and owes no message. Reach the role first on this launch, the way the planning shape in `orchestrator-launch.md` reaches `role-planner`. `orchestrator-review-dispatch.md` decides whether a pull request takes this shape at all.
11
11
 
12
- No branch and no worktree are entered here. The reviewer reads the pull request at the head `review-pr` resolves and writes the comment and its body file alone.
12
+ No branch and no worktree are entered here. The reviewer reads the pull request at the head `review-pr` resolves and writes the comment and its body file alone. A reviewer the operator launches by hand takes the `### Reviewer` H3 that `orchestrator-hand-launch.md` states.
13
13
 
14
14
  ```bash
15
15
  claude --bg --model <model> -n "reviewer-<project>-<number>" "Run /canon:role-reviewer, then /canon:review-pr <number>. Your controller is the session whose sessionId is <dispatcher-id>. Resolve its current name from that id through canon sessions list --json, which carries sessionId per row, at the moment you send, and never resolve an addressee by name prefix. When the pass posts under ## Review, message the worker holding the branch to run review-address, carrying your own sessionId, and send it to the controller instead when no worker resolves. When it posts ## Review closed, run canon pr lift <number> --json and message the controller with the heading, the count line, and the reason the verb returned, unless that reason names the UI review as still open or missing. Message the controller again if you stop on a question."
@@ -39,7 +39,7 @@ Each item below is something a record needed and a launch string did not carry.
39
39
  - The task file, and its `## Findings` before deciding anything, since a row can carry its own disproof under a title still stating the original claim
40
40
  - The source files themselves, opened rather than summarized. Never a count quoted from the task file, which goes wrong or stale.
41
41
  - `CLAUDE.md` and the context entries the row touches, for the decision and the alternative it was taken against rather than for the decision alone
42
- - `.canon/tasks/priority.md` for the row's Touches column and its blocker. Read it for the shared-path lines and holds of the constraint block, never as a gate a plan must clear.
42
+ - `.canon/tasks/priority.md` for the row's blocker and the plan each running row links. Read those plans for the constraint block's shared paths and holds, never as a gate a plan must clear.
43
43
 
44
44
  ### Reads for a feature plan alone
45
45
 
@@ -50,6 +50,8 @@ A painted pull request needs a server in this worktree for `canon pr local` to f
50
50
  - Wait up to 60 seconds for the verb to read `ok`, then ship either way. A stack with no `dev` script gets no start, and `pr-evidence` reports `no-server`.
51
51
  - Never detach it with `nohup`, `setsid`, or `disown`, since the owning session is the stop. `canon worktrees reclaim` ends any listener left in a worktree it removes.
52
52
 
53
+ Run `canon pr drive-copy --json` before starting `dev`. On `copied`, start it with `env` set to `copyRoot`, so the operator's address serves the copy. On `no-config`, start it as it is. On a refusal, start nothing and send the controller the `reason`, since a broken config would send drives into tracked folders.
54
+
53
55
  A hand-run `git-ship` or `git-pr` starts no server, since only a worker has a lifetime to own one.
54
56
 
55
57
  ### Building against a design reference
@@ -86,7 +88,7 @@ The controlling session cannot watch this one build, so the messages below are o
86
88
 
87
89
  ### The pull request announcement
88
90
 
89
- Announce the pull request as the ship chain's pull request step returns, carrying the number, the branch, and the task it closes. That transition is the one moment only this session knows, and the controller's review poll no longer starts on a dispatch.
91
+ Announce the pull request as the ship chain's pull request step returns, carrying the number, the branch, the head sha, the CI state, the task it closes, and each departure from the plan. That transition is the one moment only this session knows, and the controller's review poll no longer starts on a dispatch.
90
92
 
91
93
  - Carry the reach in that message, being the paths this branch wrote that another live plan or `## Run now` row holds, and who holds each, which `canon tasks plan-reach` reports as `claimed`
92
94
  - Report each as a rebase the controller should order, never as a breach, since sharing a path is not a hold
@@ -123,7 +125,7 @@ A worker that improvised this read polled issue comments, never saw a UI close p
123
125
 
124
126
  ### Addressing
125
127
 
126
- Address the session the launch named, and the reviewer by its dispatch's sessionId, each as a `sessionId` rather than a name. `canon:session-relay` turns that id into an address and carries the send, falling back to a copyable block where no send tool exists.
128
+ Address the session the launch named, and the reviewer by its dispatch's sessionId, each as a `sessionId` rather than a name. `canon:session-relay` turns that id into an address and carries the send, falling back to a copyable block where no send tool exists. A `Controller: <sessionId>` line names the controller.
127
129
 
128
130
  Ask the operator when the launch named nobody and a person is there, putting candidate rows through the structured question surface. The halt is the cheaper error, since a message sent to the wrong session reports success and is lost.
129
131
 
@@ -39,7 +39,9 @@ Name what was not touched too. A session that changed no source says so, since a
39
39
 
40
40
  One bullet per decision, each naming what it beat. A decision with no rejected alternative reads as arbitrary and gets reopened by the next session that dislikes it.
41
41
 
42
- Where a decision rests on a measurement, give the number rather than the conclusion. `27 spacing values became 7` survives a reader disagreeing with it, where `the spacing was tidied` does not.
42
+ - Keep each bullet under 200 characters, and give a decision needing more an H3 of its own and a short paragraph
43
+ - The note holds to the six criteria under `### Structure` in `${CLAUDE_SKILL_DIR}/../plan-feature/references/plan.md`
44
+ - Where a decision rests on a measurement, give the number rather than the conclusion. `27 spacing values became 7` survives a reader disagreeing with it, where `the spacing was tidied` does not.
43
45
 
44
46
  ### `## What is open`
45
47
 
@@ -66,7 +66,7 @@ Three sections form the core, in this order, and every session fills all three.
66
66
  - `## Mistakes worth not repeating`: what went wrong and the rule it yields
67
67
  - `## Standing cautions`: commands that lie, tools that measure the wrong tree, and anything unbacked
68
68
 
69
- Add a section only for content that fits none of the three and would otherwise be lost.
69
+ Add a section only for content that fits none of the three and would otherwise be lost. Every section holds to the six criteria under `### Structure` in `${CLAUDE_SKILL_DIR}/../plan-feature/references/plan.md`.
70
70
 
71
71
  ## Writing one
72
72
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: session-worktree
3
- description: Enters a Claude Code worktree at `.claude/worktrees/<name>/` with a name derived from the active plan or branch. Use when asked to "enter a worktree", "start a worktree", "work in a worktree", or at the plan-to-execute boundary after `/plan-feature`. Also use when an `Edit` or `Write` to a main-root file such as a plan, a task, or a review report was refused for session isolation. Do NOT use to list, clean up, or rotate worktrees (use `git-worktree`).
3
+ description: Enters a Claude Code worktree at `.claude/worktrees/<name>/` with a name derived from the active plan or branch. Use when asked to "enter a worktree", "start a worktree", "work in a worktree", or before building an approved plan in this session. Also use when an `Edit` or `Write` to a main-root file such as a plan, a task, or a review report was refused for session isolation. Do NOT use to list, clean up, or rotate worktrees (use `git-worktree`).
4
4
  metadata:
5
5
  family: build
6
6
  ---
@@ -91,9 +91,9 @@ A collision with running work blocks a task only where no merge settles it, bein
91
91
 
92
92
  ### The `Touches` cell
93
93
 
94
- A `Touches` cell is the first three paths of its plan's `**Files to touch:**` and a count of the rest, the union in line order for a sliced task. It states what the branch sets out to write, and a running branch outgrows it.
94
+ A `Touches` cell counts its plans' declared files and names the two fullest folders, as in ``12 files under `src/tasks/`, `claude/skills/` ``. The linked plan holds the file list, which the overlap checks read.
95
95
 
96
- - `canon tasks plan-reach <plan>` reads the current reach off the branch's diff
96
+ - `canon tasks plan-reach <plan>` reads the reach a running branch grew to
97
97
  - Change the plan's list to change the cell. A worker never edits this board.
98
98
 
99
99
  ### Cell width
@@ -83,7 +83,7 @@ Two headings, `## Outcomes` and `## Findings`. Outcomes are future and checkable
83
83
 
84
84
  - Add no third heading. Status stays inline on an outcome rather than becoming an "In progress" section.
85
85
  - Outcomes stay a checklist, since the board tools read them by their `- [ ]` line
86
- - Open each finding with a bold label naming its source and date, as in `- **planner, 2026-10-11:** <finding>`. Keep it to a few sentences, moving anything longer into the plan or groundwork folder.
86
+ - Open each finding with a bold label naming its source and date, as in `- **planner, 2026-10-11:** <finding>`. Past 200 characters it becomes a paragraph, and the plan holds anything longer.
87
87
 
88
88
  ### Sizing outcomes
89
89
 
@@ -12,7 +12,7 @@ CLI catalog and invocation rules for agents, split by command domain. Start with
12
12
  - [Context audit](context-audit.md): Running the context audit, its folder scope, the exit codes, every check with the folders it reaches and whether it gates, the seed gate, and the citation exclusions
13
13
  - [Indexes](indexes.md): How canon indexes regen and canon indexes list resolve paths, what they stage, and the record and exit codes each emits
14
14
  - [Install and sync](install-and-sync.md): What each install and sync verb writes, refuses, or leaves alone, the drift report and its seven side sections, the rule detection record, and version skew states
15
- - [Markdown audit](markdown-audit.md): Running the markdown audit over any path, each finding it reports with whether it gates, the six density limits, the advisory measures, the exit codes, and where the audit runs
15
+ - [Markdown audit](markdown-audit.md): Running the markdown audit over any path, each finding with whether it gates, the density and table limits, the advisory measures, the exit codes, and where the audit runs
16
16
  - [Migrate](migrate.md): What each canon migrate verb moves, the markers that keep an old spelling, and what each one refuses
17
17
  - [Output shape](output-shape.md): The framed output shape every command renders into, the JSON and color modes, the exit discipline, the runtime catalogs, and a headless invocation per domain
18
18
  - [Overview](overview.md): What this folder covers, the invocation rules every command inherits, and how canon docs resolves a topic name
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: Markdown audit
3
- description: Running the markdown audit over any path, each finding it reports with whether it gates, the six density limits, the advisory measures, the exit codes, and where the audit runs
3
+ description: Running the markdown audit over any path, each finding with whether it gates, the density and table limits, the advisory measures, the exit codes, and where the audit runs
4
4
  kind: reference
5
5
  ---
6
6
 
@@ -28,11 +28,14 @@ The ban set and checkpoints ship in `src/markdown/bans.ts` and `src/markdown/str
28
28
  | Links | A relative link resolving to nothing on disk | Always |
29
29
  | Density | The six 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
32
  | Colons | A colon followed by more text in a prose line, under `colons` | Never |
32
33
  | Heading case | A capital after the first word of a heading or `title`, under `headingCase` | Never |
33
34
 
34
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.
35
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.
38
+
36
39
  | Density limit | Finding |
37
40
  | ------------------- | ---------------------------------------------------------- |
38
41
  | Paragraph length | A prose paragraph past 350 characters |
@@ -45,7 +48,7 @@ A page marked `kind: reference` holds its opening, then `Output`, `Refusals`, an
45
48
  - A paragraph under a list item counts toward that bullet, never as a paragraph
46
49
  - A table, fence, list, heading, or bold section marker ends a run of paragraphs
47
50
  - A run whose every item opens with a bold label or a link is exempt, and numbered items never count
48
- - Only context entries, docs, skill bodies, references, and requirements, tooling seeds and stack references, the wiki, `README.md`, and `CONTRIBUTING.md` take a finding
51
+ - Only context entries, docs, skills, tooling seeds and references, the wiki, `README.md`, and `CONTRIBUTING.md` take a finding, plus the Key paths of `CLAUDE.md` on the run and H3 limits
49
52
  - A requirement gates only once the gated list below names its own file, never through its skill's prefix and never on a bullet run alone
50
53
 
51
54
  | Root | A density or reference shape finding gates |
@@ -57,5 +57,5 @@ Branch on `reason` in the `--json` record, not the exit code.
57
57
  ## See also
58
58
 
59
59
  - `canon records ordinal <kind> <slug> --claim`: reserve the next intake or groundwork ordinal
60
- - `canon records validate <kind> --help`: where a record folder disagrees with its standard
60
+ - `canon records validate <kind> [record] --help`: where a record disagrees with its standard, plus a `structure` report that never sets the exit
61
61
  - `canon records stale --help`: when the requirements record was last reviewed
@@ -180,3 +180,10 @@ A project holding `.claude/rules/canon/ui/a11y.md` adds the new rule alone:
180
180
  ```bash
181
181
  canon gov install --add keyboard --yes .
182
182
  ```
183
+
184
+ ## Group a long paths section in `CLAUDE.md`, once
185
+
186
+ The `## Key paths` section of `CLAUDE.md` now keeps to 5 bullets in a row, and every other section stays exempt. `canon claude sync` never rewrites a `CLAUDE.md` you hold, so it warns with the line of each longer run.
187
+
188
+ - Regroup the section by hand under H3s, each opening on a one-sentence lead.
189
+ - List `CLAUDE.md` in `canon/config/markdown.toml` to gate it from then on.
@@ -49,7 +49,7 @@ The chain runs these phases:
49
49
 
50
50
  An existing project goes to `canon:target-check` first. For the Claude layer alone, run `canon claude init` and then `canon:target-setup indexes`. Run `canon:target-setup verify deep` by hand for the heavier smoke and screenshot pass.
51
51
 
52
- Keep the `## Scripts` table in `canon/context/development/local-loop.md` current as scripts are added. A split entry keeps it in `overview.md`. <!-- audit-ignore-citations: canon/context/development/local-loop.md -->
52
+ Keep the `## Scripts` table in `canon/context/development/local-loop.md` current as scripts are added. A split entry keeps it in any entry directly under `development/`, grouping a long set into short tables under lead sentences. <!-- audit-ignore-citations: canon/context/development/local-loop.md -->
53
53
 
54
54
  ### From scaffold to first feature
55
55
 
@@ -32,7 +32,7 @@ Assert the orchestrator role in one warm session with `canon:role-orchestrator`.
32
32
 
33
33
  - To pick otherwise, rewrite that line as `overridden at execution to <pick>,` plus the measurement, and add one line to the open task's `## Findings`
34
34
 
35
- - `canon records validate plans` reports a plan breaking its standard, and takes `groundwork`, `intake`, and `teach`
35
+ - `canon records validate plans [plan]` reports a plan breaking its standard, plus `structure` misses that never set the exit, and takes `groundwork`, `intake`, and `teach`
36
36
  - `canon records push` and `canon records pull` carry `.canon/` to and from a private repository. See [records](../agents/records.md).
37
37
  - `canon records size`, `canon records prune-tmp`, and `canon records stale canonical` report folder size, idle scratch, and releases since the requirements review
38
38
  - `canon tasks archive` moves a shipped plan to `.canon/plans/archive/` and retargets the task's `Plan:` line
@@ -58,6 +58,6 @@ Merge order matters only when two branches touch the same file. Expect to rebase
58
58
  - A parked row is reported when its cited task reached the trunk, whether the cell links it or names its bare label
59
59
  - A group header must be the columns the group owns, a `Touches` cell must match its plan, and a `Waiting on` cell renders at most 120 characters
60
60
 
61
- `canon:task-board` creates a task file, and `canon:task-complete` marks outcomes. The `post-merge` hook archives the task a merged pull request closed, with its plan, and a `post-rewrite` hook does the same for a rebase pull. Both archive only when the merge carried a PR number and the task is closed.
61
+ `canon:task-board` creates a task file, and `canon:task-complete` marks outcomes. The `post-merge` hook archives the task a merged pull request closed, with its plan, and a `post-rewrite` hook does the same for a rebase pull. Both archive a closed task whose merge carried a PR number, and a slice merged before then moves only its own plan.
62
62
 
63
63
  `CANON_SKIP_RECLAIM`, `CANON_SKIP_UPGRADE`, and `CANON_SKIP_PLUGIN_UPDATE` turn off the three hook steps that act on the machine.
@@ -35,7 +35,7 @@ Groups run in the order a project meets them. Every name `canon claude skills li
35
35
 
36
36
  | Skill | When to use |
37
37
  | ----------------------------- | -------------------------------------------------- |
38
- | `canon:session-worktree` | At plan-to-execute, for an isolated tree |
38
+ | `canon:session-worktree` | Before building a plan in this session |
39
39
  | `canon:auto-ship` | After plan approval, to chain implement through PR |
40
40
  | `canon:test-first` | Before implementing a planned change |
41
41
  | `canon:test-craft` | Writing or changing a test |
@@ -64,6 +64,7 @@ Groups run in the order a project meets them. Every name `canon claude skills li
64
64
  | --------------------- | --------------------------------------------- |
65
65
  | `canon:git-ship` | To run the post-feature chain through open PR |
66
66
  | `canon:context-fold` | The diff left a context entry untrue |
67
+ | `canon:design-record` | A picked design needs landing in the record |
67
68
  | `canon:task-complete` | After the fold, to mark shipped outcomes |
68
69
  | `canon:docs-sync` | A change left `README.md` or `docs/` stale |
69
70
  | `canon:git-stage` | Staged changes span several concerns |
@@ -20,7 +20,8 @@ These skills cover the tier:
20
20
 
21
21
  - `canon:ui-checklist`, `canon:ux-audit`, and `canon:ux-measure` review and measure a UI
22
22
  - `canon:pick-by-eye` drafts candidates as frames on the canvas and takes your pick
23
- - `canon:canvas-board` works a direction out as HTML frames you edit in the browser. Only the pick moves into `canon/DESIGN.md`.
23
+ - `canon:canvas-board` works a direction out as HTML frames you edit in the browser, and hands the pick on
24
+ - `canon:design-record` lands a pick in `canon/design/` and carries its values into `canon/DESIGN.md`
24
25
  - `canon:design-identity` drafts a logo mark and social card
25
26
 
26
27
  Pick tier 0 when the UI is not the core product, decisions fit in sentences, and stakeholders review code.
@@ -12,6 +12,7 @@ The rule states canon's own design handoff, generalized from Frameboard's projec
12
12
  | Bullet | Source | Read |
13
13
  | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- |
14
14
  | Build against the tracked copy | House choice: a canvas changes under the build with no history, so a reviewer cannot tell which version a surface was built against | 2026-10-18 |
15
+ | Load the keeper skill | House choice: the rule governs an edit and carries no procedure, so a session without the skill copies frames in by hand or skips the index row | 2026-10-21 |
15
16
  | Land a design first | House choice: a design landing inside a feature pull request is reviewed as code, and a second feature building against it meanwhile reads a design nobody accepted | 2026-10-18 |
16
17
  | No hand edits | House choice: a reference edited by hand drifts from its canvas source, and the next sync overwrites the edit or reports it as a change nobody made | 2026-10-18 |
17
18
  | Index row before the sync | House choice: the sync writes only the surfaces the index names, so a missing row drops the new surface's reference, and a stale row keeps a removed surface's | 2026-10-18 |
@@ -17,6 +17,7 @@ paths:
17
17
  ## Where the design lives
18
18
 
19
19
  - Build UI against `canon/design/`, never against the canvas directly.
20
+ - Load the `canon:design-record` skill before adding, redrawing, or removing a reference under `canon/design/`, and report it rather than proceeding silently when it does not resolve.
20
21
  - Land a picked or changed canvas design in `canon/design/` through the project's design sync, as its own pull request, before any feature builds against it.
21
22
  - Never copy, edit, or delete a reference file under `canon/design/` by hand.
22
23
  - Add a new surface's row to the index in `canon/design/` before running the sync, and remove it only in the change that removes the surface.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/canon",
3
3
  "type": "module",
4
- "version": "5.48.0",
4
+ "version": "5.50.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": {
@@ -39,6 +39,11 @@ export const MISC_CASES: readonly SkillCase[] = [
39
39
  'Draw the signup screen as a frame on the canvas, then restyle the element I just selected there.',
40
40
  expect: 'canvas-board',
41
41
  },
42
+ {
43
+ prompt:
44
+ 'The operator picked the new settings panel, so land it in canon/design with an index row for it.',
45
+ expect: 'design-record',
46
+ },
42
47
  {
43
48
  prompt:
44
49
  "We're about to hit the context limit, write the handoff before we lose state.",
@@ -71,6 +71,9 @@ import {
71
71
  serializeSettings,
72
72
  writeSettings,
73
73
  } from '@/claude/settings'
74
+ import { bodyLines } from '@/markdown/scan'
75
+ import { scopedDensityFindings } from '@/markdown/scope'
76
+ import { CAP } from '@/markdown/stacks'
74
77
  import { readSessionDefaults } from '@/migrate/session-defaults'
75
78
  import { copyPreservingMode } from '@/sync/copy'
76
79
  import { checkoutMismatchWarning, PROJECT_ROOT } from '@/roots/project'
@@ -176,6 +179,22 @@ export function seededDirPath(resolved: string, name: string): string {
176
179
  ? surfaceDir(resolved, name)
177
180
  : recordDir(resolved, name)
178
181
  }
182
+
183
+ /**
184
+ * Each bullet run past the limit in the target's `CLAUDE.md` Key paths, read
185
+ * through the scope the audit gates on. Sync warns rather than gates, since a
186
+ * target gates only the prefixes its own config lists.
187
+ */
188
+ export async function longKeyPathsRuns(
189
+ resolved: string,
190
+ ): Promise<{ line: number; count: number }[]> {
191
+ const path = join(resolved, 'CLAUDE.md')
192
+ if (!existsSync(path)) return []
193
+ const lines = bodyLines(await readFile(path, 'utf8'))
194
+ return scopedDensityFindings('CLAUDE.md', lines)
195
+ .filter((finding) => finding.kind === 'bullet-run')
196
+ .map((finding) => ({ line: finding.line, count: finding.value ?? 0 }))
197
+ }
179
198
  const USER_DIR = join('tooling', 'claude', 'user')
180
199
  const STATUSLINE = 'statusline-command.sh'
181
200
  const PLUGIN_MANIFEST = join('claude', '.claude-plugin', 'plugin.json')
@@ -244,7 +263,8 @@ export function register(program: Command): void {
244
263
  'Writes .gitignore alone: adds the entries the claude manifest lists',
245
264
  'and drops ones under its headers that the manifest no longer lists.',
246
265
  'A seeded file is reported, never rewritten, and a missing one points',
247
- 'at `canon claude init`.',
266
+ 'at `canon claude init`. A CLAUDE.md Key paths running past 5 bullets',
267
+ 'is reported with the H3 grouping that fixes it.',
248
268
  '',
249
269
  'Exit codes:',
250
270
  ' 0 synced, already up to date, or the prompt was cancelled',
@@ -665,6 +685,11 @@ async function runSync(target: string): Promise<number> {
665
685
  if (isDirectory(seededDirPath(resolved, name))) logInfo(`${name}/`)
666
686
  else logWarn(`${name}/ missing. Run \`canon claude init\``)
667
687
  }
688
+ for (const run of await longKeyPathsRuns(resolved)) {
689
+ logWarn(
690
+ `CLAUDE.md:${run.line} runs ${run.count} Key paths bullets past ${CAP}. Group them under H3s, each opening on a lead sentence`,
691
+ )
692
+ }
668
693
 
669
694
  // Sync never rewrites an existing settings file, so a target seeded before
670
695
  // the session defaults learns of them only here.
@@ -130,22 +130,9 @@ async function runGate(opts: RunCommandOptions): Promise<number> {
130
130
  process.stderr.write(`${failed.label}: ${failed.failure}\n`)
131
131
  }
132
132
  process.stdout.write(
133
- `${JSON.stringify({
134
- ok: code === 0,
135
- root,
136
- scoped: changed.scoped,
137
- changed: changed.files.length,
138
- summary,
139
- ms: results.reduce((total, result) => total + result.ms, 0),
140
- stages: results.map(({ id, label, status, failure, ms, queuedMs }) => ({
141
- id,
142
- label,
143
- status,
144
- failure,
145
- ms,
146
- queuedMs,
147
- })),
148
- })}\n`,
133
+ `${JSON.stringify(
134
+ gateRecord({ ok: code === 0, root, changed, summary, results }),
135
+ )}\n`,
149
136
  )
150
137
  return code
151
138
  }
@@ -154,6 +141,60 @@ async function runGate(opts: RunCommandOptions): Promise<number> {
154
141
  return code
155
142
  }
156
143
 
144
+ /** Lines of a failed stage's output the record keeps, counted from the end. */
145
+ const OUTPUT_TAIL_LINES = 200
146
+
147
+ interface GateRecordInput {
148
+ readonly ok: boolean
149
+ readonly root: string
150
+ readonly changed: {
151
+ readonly scoped: boolean
152
+ readonly files: readonly string[]
153
+ }
154
+ readonly summary: Summary
155
+ readonly results: readonly StageResult[]
156
+ }
157
+
158
+ /**
159
+ * The `--json` record. A failed stage carries the tail of the output its
160
+ * checks printed, since a caller reading the record alone otherwise reruns
161
+ * the stage by hand to learn which test failed. The tail rather than a parse
162
+ * of the `FAIL` lines, so a generic stage stays free of one runner's format,
163
+ * and a dot-reporter run prints its failures and summary last.
164
+ */
165
+ export function gateRecord(input: GateRecordInput) {
166
+ const { ok, root, changed, summary, results } = input
167
+ return {
168
+ ok,
169
+ root,
170
+ scoped: changed.scoped,
171
+ changed: changed.files.length,
172
+ summary,
173
+ ms: results.reduce((total, result) => total + result.ms, 0),
174
+ stages: results.map(
175
+ ({ id, label, status, failure, ms, queuedMs, emissions }) => ({
176
+ id,
177
+ label,
178
+ status,
179
+ failure,
180
+ ms,
181
+ queuedMs,
182
+ output: status === 'failed' ? outputTail(emissions) : undefined,
183
+ }),
184
+ ),
185
+ }
186
+ }
187
+
188
+ function outputTail(emissions: StageResult['emissions']): string {
189
+ const lines = emissions
190
+ .filter((emission) => emission.kind === 'output')
191
+ .map((emission) => emission.text.trimEnd())
192
+ .filter((text) => text !== '')
193
+ .join('\n')
194
+ .split('\n')
195
+ return lines.slice(-OUTPUT_TAIL_LINES).join('\n')
196
+ }
197
+
157
198
  function report(result: StageResult): void {
158
199
  logStep(result.label)
159
200
  for (const emission of result.emissions) {