@erclx/canon 5.55.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 (67) hide show
  1. package/CHANGELOG.md +8 -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 +2 -2
  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/references/axes.md +1 -1
  16. package/claude/skills/git-followup/SKILL.md +16 -5
  17. package/claude/skills/git-issue/references/issue.md +9 -11
  18. package/claude/skills/git-pr/references/publish.md +4 -5
  19. package/claude/skills/git-split/SKILL.md +11 -4
  20. package/claude/skills/markdown-craft/SKILL.md +1 -1
  21. package/claude/skills/markdown-craft/references/markdown.md +2 -2
  22. package/claude/skills/markdown-craft/references/tables.md +9 -7
  23. package/claude/skills/plan-feature/SKILL.md +2 -0
  24. package/claude/skills/plan-feature/references/plan.md +2 -0
  25. package/claude/skills/review-address/SKILL.md +9 -7
  26. package/claude/skills/review-craft/SKILL.md +7 -9
  27. package/claude/skills/review-craft/references/adopted.md +10 -8
  28. package/claude/skills/review-pr/SKILL.md +2 -0
  29. package/claude/skills/review-pr/references/key-changes.md +8 -6
  30. package/claude/skills/review-ui/references/driving.md +10 -8
  31. package/claude/skills/role-orchestrator/references/orchestrator-parked.md +6 -4
  32. package/claude/skills/role-orchestrator/references/orchestrator-refill.md +9 -3
  33. package/claude/skills/role-reviewer/SKILL.md +5 -5
  34. package/claude/skills/search-craft/SKILL.md +5 -7
  35. package/claude/skills/session-worktree/SKILL.md +2 -0
  36. package/claude/skills/session-worktree/references/main-root-writes.md +2 -0
  37. package/claude/skills/systematic-debugging/SKILL.md +9 -9
  38. package/claude/skills/systematic-debugging/references/adopted.md +1 -1
  39. package/claude/skills/task-board/SKILL.md +2 -4
  40. package/claude/skills/task-board/references/tasks.md +5 -5
  41. package/claude/skills/task-complete/references/archive.md +5 -2
  42. package/claude/skills/test-craft/SKILL.md +2 -0
  43. package/claude/skills/test-craft/references/adopted.md +2 -0
  44. package/claude/skills/ui-checklist/SKILL.md +8 -9
  45. package/claude/skills/write-doc/SKILL.md +8 -8
  46. package/claude/skills/write-doc/references/readme.md +2 -0
  47. package/claude/skills/write-doc/references/wiki-kind.md +11 -4
  48. package/docs/agents/context-audit.md +16 -14
  49. package/docs/agents/markdown-audit.md +11 -4
  50. package/docs/agents/migrate.md +15 -13
  51. package/docs/agents/output-shape.md +2 -0
  52. package/docs/target/migrations.md +3 -3
  53. package/docs/workflow/operating-model.md +2 -0
  54. package/package.json +1 -1
  55. package/src/commands/markdown.ts +25 -29
  56. package/src/labels/density.ts +5 -46
  57. package/src/markdown/density.ts +41 -6
  58. package/src/markdown/scope.ts +6 -1
  59. package/src/markdown/structure.ts +18 -9
  60. package/src/markdown/tables.ts +32 -18
  61. package/src/process/harness.ts +8 -0
  62. package/src/process/seeded-package.ts +21 -0
  63. package/src/serve/static.ts +2 -60
  64. package/tooling/base/reference.md +7 -6
  65. package/tooling/claude/reference.md +7 -5
  66. package/tooling/claude/seeds/.claude/hooks/standards-audit.sh +7 -6
  67. package/src/serve/live.ts +0 -113
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## [5.56.0](https://github.com/erclx/canon/compare/v5.55.0...v5.56.0) (2026-10-23)
4
+
5
+
6
+ ### Features
7
+
8
+ * **markdown:** gate table cell and trailing list as density kinds ([#2630](https://github.com/erclx/canon/issues/2630)) ([1d1cbd3](https://github.com/erclx/canon/commit/1d1cbd3f78009dc6f748aee9579fd0245022aaf0))
9
+ * **markdown:** measure nested bullets against the length cap ([#2632](https://github.com/erclx/canon/issues/2632)) ([b5b8f2b](https://github.com/erclx/canon/commit/b5b8f2b7765c801735b52f8bc22275f3ae478f74))
10
+
3
11
  ## [5.55.0](https://github.com/erclx/canon/compare/v5.54.0...v5.55.0) (2026-10-21)
4
12
 
5
13
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "canon",
3
3
  "description": "Runs your agent's work and keeps its code good, on one setup for every repo.",
4
- "version": "5.55.0",
4
+ "version": "5.56.0",
5
5
  "author": {
6
6
  "name": "Eric Le",
7
7
  "url": "https://github.com/erclx"
@@ -61,16 +61,14 @@ Read `${CLAUDE_SKILL_DIR}/references/adopted.md` only when extending this guidan
61
61
 
62
62
  ## Excuses and rebuttals
63
63
 
64
- | Excuse | Rebuttal |
65
- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- |
66
- | Nobody depends on that field | You can see the callers you opened. A script parsing the output or a project importing the module is the one you did not. |
67
- | It's internal, so renaming it is fine | Internal to whom? If anything outside the module reads it, it is a surface. Add the new name beside the old. |
68
- | The old name is wrong, so keeping it is clutter | Clutter costs a line. A rename breaks every caller you did not open, and they learn about it from a failure. |
69
- | Returning the record directly saves a mapping | The mapping is the contract. Without it, a change to the storage layout is a breaking change to every caller. |
70
- | The caller already validates, so checking again is redundant | The boundary checks once for every caller, including the next one, which may not. |
71
- | Retries are rare, so the write does not need a key | Rare is a duplicate charge, a double send, or a second row once a week. The key is cheaper than the cleanup. |
72
- | The error message is clear, so callers can match on it | A message is written for a person and reworded freely. Give the caller a code it can branch on. |
73
- | I'll update every caller in this change, so renaming is safe | That holds only when every caller lives in the change. Check that no caller outside the repository reads the surface before you rely on it. |
64
+ - **Nobody depends on that field.** You can see the callers you opened. A script parsing the output or a project importing the module is the one you did not.
65
+ - **It's internal, so renaming it is fine.** Internal to whom? If anything outside the module reads it, it is a surface. Add the new name beside the old.
66
+ - **The old name is wrong, so keeping it is clutter.** Clutter costs a line. A rename breaks every caller you did not open, and they learn about it from a failure.
67
+ - **Returning the record directly saves a mapping.** The mapping is the contract. Without it, a change to the storage layout is a breaking change to every caller.
68
+ - **The caller already validates, so checking again is redundant.** The boundary checks once for every caller, including the next one, which may not.
69
+ - **Retries are rare, so the write does not need a key.** Rare is a duplicate charge, a double send, or a second row once a week. The key is cheaper than the cleanup.
70
+ - **The error message is clear, so callers can match on it.** A message is written for a person and reworded freely. Give the caller a code it can branch on.
71
+ - **I'll update every caller in this change, so renaming is safe.** That holds only when every caller lives in the change. Check that nothing outside the repository reads the surface first.
74
72
 
75
73
  ## Red flags
76
74
 
@@ -47,11 +47,10 @@ Resolve `<plan>` in this order, stopping at the first match. Only a path reaches
47
47
 
48
48
  Each tier stops with its own message on a file that does not resolve.
49
49
 
50
- | Tier | Stop |
51
- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
52
- | Task | `❌ No task at <path>. Path was supplied, not derived, so check it and re-run.` |
53
- | Plan | `❌ No plan at <path>. Path was supplied, not derived, so check it and re-run.` |
54
- | Derived | `❌ No approved plan at .canon/plans/feature-<slug>.md, where <slug> was derived from the current branch, <branch>. The invocation carried no argument, so pass a plan or a task path, or run /plan-feature first.` |
50
+ - Task: `❌ No task at <path>. Path was supplied, not derived, so check it and re-run.`
51
+ - Plan: `❌ No plan at <path>. Path was supplied, not derived, so check it and re-run.`
52
+ - Derived, which stops on the longer message below:
53
+ - `❌ No approved plan at .canon/plans/feature-<slug>.md, derived from branch <branch>. No argument was passed, so pass a plan or task path, or run /plan-feature first.`
55
54
 
56
55
  ### When a tier fails
57
56
 
@@ -5,20 +5,40 @@ description: The step an operator resumes from after each auto-ship stop point,
5
5
 
6
6
  # Failure recovery
7
7
 
8
- Every stop point leaves recoverable state. The user resumes manually from the appropriate step.
9
-
10
- | Stop point | Recovery |
11
- | ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------- |
12
- | No plan (derived) | Run `/plan-feature` to create one |
13
- | No plan (caller-supplied) | Check the path or slug passed to autoship, then re-run |
14
- | No task (caller-supplied) | Check the task path passed to autoship, then re-run |
15
- | Task carries no `Plan:` line | Write the plan, point the task's `Plan:` line at it, then re-run |
16
- | Task points at an archived plan | The work already shipped. Reopen the task against a live plan, or pass that plan's path directly. |
17
- | Task's `Plan:` pointer resolves to nothing | Repoint the task's `Plan:` line at the plan that exists, then re-run |
18
- | Resolved file carries no plan shape | Point autoship at a plan under `.canon/plans/feature-<slug>.md` or at a task under `.canon/tasks/`, then re-run |
19
- | Branch collision on worktree entry | `session-worktree` Step 5 found `<slug>` already as a local branch. Resolve manually (rename or delete the stale branch), then re-run autoship. |
20
- | Verify fails | Read logs, fix manually, run `/git-ship` |
21
- | git-ship fails | Inspect hook or remote error, run again |
22
- | Draft target is not this branch's | Nothing was marked. Read the number and `head=` from `git-pr`'s output, confirm they match, then run the mark by hand |
23
- | Review wait timed out | The record went to the controller. Once the reviewers are dispatched, re-run `canon pr review-state <number> --wait 1200 --json` in the background |
24
- | Review wait refused | Fix what the `reason` names, such as an unreadable remote, then re-run `canon pr review-state <number> --wait 1200 --json` in the background |
8
+ Every stop point leaves recoverable state. The user resumes manually from the appropriate step, named below for each stop point.
9
+
10
+ ## Worktree entry
11
+
12
+ The first stop comes at Step 0, ahead of any plan read:
13
+
14
+ - Branch collision on worktree entry. `session-worktree` Step 5 found `<slug>` already as a local branch, so resolve manually (rename or delete the stale branch), then re-run autoship.
15
+
16
+ ## Plan and task reads
17
+
18
+ The first plan read stops on a missing or misshapen file, and each repair differs:
19
+
20
+ - No plan (derived). Run `/plan-feature` to create one.
21
+ - No plan (caller-supplied). Check the path or slug passed to autoship, then re-run.
22
+ - No task (caller-supplied). Check the task path passed to autoship, then re-run.
23
+ - Task carries no `Plan:` line. Write the plan, point the task's `Plan:` line at it, then re-run.
24
+ - Resolved file carries no plan shape. Point autoship at a plan under `.canon/plans/feature-<slug>.md` or at a task under `.canon/tasks/`, then re-run.
25
+
26
+ A task's `Plan:` pointer can also fail after it is read:
27
+
28
+ - Task points at an archived plan. The work already shipped, so reopen the task against a live plan, or pass that plan's path directly.
29
+ - Task's `Plan:` pointer resolves to nothing. Repoint the task's `Plan:` line at the plan that exists, then re-run.
30
+
31
+ ## Build and ship
32
+
33
+ These stops come after the plan reads:
34
+
35
+ - Verify fails. Read logs, fix manually, run `/git-ship`.
36
+ - git-ship fails. Inspect the hook or remote error, then run again.
37
+
38
+ ## Draft mark and review wait
39
+
40
+ The last three stops sit after the pull request opens:
41
+
42
+ - Draft target is not this branch's. Nothing was marked, so read the number and `head=` from `git-pr`'s output, confirm they match, then run the mark by hand.
43
+ - Review wait timed out. The record went to the controller, so once the reviewers are dispatched, re-run `canon pr review-state <number> --wait 1200 --json` in the background.
44
+ - Review wait refused. Fix what the `reason` names, such as an unreadable remote, then re-run `canon pr review-state <number> --wait 1200 --json` in the background.
@@ -25,16 +25,18 @@ The archived task is what makes that base matter. The standard points its line a
25
25
 
26
26
  Tier 1 stops on four failures, each naming a different repair. Test them in the order below.
27
27
 
28
- | Failure | Stop |
29
- | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
30
- | The line links several plans | `❌ <path> links <N> plans on its Plan: line (<every target>), so the task does not say which one this run builds. Pass one plan path directly.` |
31
- | No `Plan:` line at all | `❌ <path> carries no Plan: line, so nothing there names a plan to run. Write the plan and point the task at it, or pass the plan path directly.` |
32
- | Pointer into a plans archive | `❌ <path> points at an archived plan, which describes work that already shipped. Reopen the task against a live plan, or pass that plan directly.` |
33
- | Pointer resolves to no file | `❌ <path> points at <target>, which does not exist. The citation is stale, so repoint the task or pass the plan path directly.` |
28
+ 1. The line links several plans. The stop reads:
29
+ - `❌ <path> links <N> plans on its Plan: line (<every target>), so the task does not say which one this run builds. Pass one plan path directly.`
30
+ 2. No `Plan:` line at all. The stop reads:
31
+ - `❌ <path> carries no Plan: line, so nothing there names a plan to run. Write the plan and point the task at it, or pass the plan path directly.`
32
+ 3. Pointer into a plans archive. The stop reads:
33
+ - `❌ <path> points at an archived plan, which describes work that already shipped. Reopen the task against a live plan, or pass that plan directly.`
34
+ 4. Pointer resolves to no file. The stop reads:
35
+ - `❌ <path> points at <target>, which does not exist. The citation is stale, so repoint the task or pass the plan path directly.`
34
36
 
35
37
  ### Why each test sits where it does
36
38
 
37
- Each row carries a reason a shortcut would get wrong.
39
+ Each test carries a reason a shortcut would get wrong.
38
40
 
39
41
  - Test several links first, since taking the first link runs whichever plan leads the line and reports success
40
42
  - A row still awaiting a plan is the ordinary case, so the no-line message names the missing pointer rather than the plan sections a reader would then hunt for
@@ -13,12 +13,15 @@ This step runs ahead of Step 1, so what it holds is the raw invocation argument
13
13
  canon tasks plan-branch <argument> --json
14
14
  ```
15
15
 
16
- | Record | Route |
17
- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
18
- | `conforms: true` | Pass the record's `branch`, already `<type>/<slug>`, and invoke nothing else to derive a name |
19
- | `conforms: false` | The plan's filename breaks a cap in `${CLAUDE_SKILL_DIR}/../git-branch/references/branch.md`. Pass `branch` anyway and say the cap it broke |
20
- | Anything else | Invoke `canon:session-worktree` bare and let its ladder derive the name, saying the verb did not answer |
16
+ | Record | Route |
17
+ | ----------------- | ------------------------------------------------------------------------------------------------------- |
18
+ | `conforms: true` | Pass the record's `branch`, already `<type>/<slug>`, and invoke nothing else to derive a name |
19
+ | `conforms: false` | The plan's filename breaks a branch-name cap. Pass `branch` anyway and say the cap it broke |
20
+ | Anything else | Invoke `canon:session-worktree` bare and let its ladder derive the name, saying the verb did not answer |
21
21
 
22
+ Four notes on those routes:
23
+
24
+ - The caps on a branch name sit in `${CLAUDE_SKILL_DIR}/../git-branch/references/branch.md`
22
25
  - On `conforms: false`, the alternative is a name this session shortened by hand, a second derivation and the thing this call exists to prevent. `git-branch` decides the rename at ship.
23
26
  - Anything else covers a refusal, a record carrying no `branch` key, and an installed binary carrying no `plan-branch` subcommand
24
27
  - Saying the verb did not answer lets a reader tell a derived name from a fallback one
@@ -73,10 +73,10 @@ For each row that reproduces, gather the evidence Step 5 reads, and record each
73
73
 
74
74
  Keep and promote carry the burden of proof, and a defect that still exists does not meet it. A row filed against the tree nearly always reproduces, so a reproduction test alone keeps almost every row. A row earns keep or promote only by naming a cost or a reach from Step 4's evidence.
75
75
 
76
- | Ground | What it takes |
77
- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
78
- | Cost | A wrong result this pass observed by running the verb or reading committed output, or an issue, run, person, or workaround commit it hit, not measured fixed |
79
- | Reach | The defect sits on a shipped path, so everyone reaching it receives a wrong instruction, a broken or unbounded behavior, or a wrong output, hit or not |
76
+ The two grounds, and what each takes:
77
+
78
+ - Cost. A wrong result this pass observed by running the verb or reading committed output, or an issue, run, person, or workaround commit it hit, not measured fixed.
79
+ - Reach. The defect sits on a shipped path, so everyone reaching it receives a wrong instruction, a broken or unbounded behavior, or a wrong output, hit or not.
80
80
 
81
81
  A plausible cost names nothing. A row arguing what could go wrong off a shipped path, with no observed output and no hit, is declined however likely it sounds.
82
82
 
@@ -96,12 +96,12 @@ A missing feature earns keep through the cost arm when a user asked for it in an
96
96
 
97
97
  Age is evidence in neither direction. An old hit reads the same as a new one, by whether the pass measures it as still able to recur.
98
98
 
99
- | Verdict | When |
100
- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
101
- | keep | The defect reproduces this pass, with a count or a path, and the row names a cost or a reach with its observation, hit, or shipped path |
102
- | promote | It qualifies for keep, and what it was waiting on has cleared, naming what cleared it |
103
- | archive | Every open outcome is measured shipped, naming the commit or pull request per outcome position |
104
- | decline | Anything else, including a reproduction naming no cost or reach, a defect the tree no longer holds, superseded work, or the filing alone |
99
+ Each verdict takes one condition:
100
+
101
+ - keep. The defect reproduces this pass, with a count or a path, and the row names a cost or a reach with its observation, hit, or shipped path.
102
+ - promote. It qualifies for keep, and what it was waiting on has cleared, naming what cleared it.
103
+ - archive. Every open outcome is measured shipped, naming the commit or pull request per outcome position.
104
+ - decline. Anything else, including a reproduction naming no cost or reach, a defect the tree no longer holds, superseded work, or the filing alone.
105
105
 
106
106
  ### Step 6: write the items
107
107
 
@@ -25,12 +25,12 @@ An item already applied, by the test in the skill's Step 1, is skipped silently,
25
25
 
26
26
  Take the task stem from the item heading. Every verdict leaves the folder slug on its task, which is how Step 1 and `task-board`'s unlinked-origin scan both see the folder as acted on.
27
27
 
28
- | Verdict | Command and board write |
29
- | ------- | -------------------------------------------------------------------------------------------------------------------------------- |
30
- | decline | `canon tasks decline <stem> --reason "<reason>, backlog triage <slug> item <cluster>#<n>" --json`, which clears the backlog line |
31
- | archive | `canon tasks outcome <stem> --close <p> ... --json` for the named positions, then `canon tasks archive <stem> --json` |
32
- | keep | Append the `## Findings` line naming what reproduced, leaving the backlog line |
33
- | promote | `canon tasks place <stem> --group needs-a-plan --bottom --waiting "<what cleared>. Last" --json`, which clears the backlog line |
28
+ The command and board write for each verdict:
29
+
30
+ - decline. `canon tasks decline <stem> --reason "<reason>, backlog triage <slug> item <cluster>#<n>" --json`, which clears the backlog line.
31
+ - archive. `canon tasks outcome <stem> --close <p> ... --json` for the named positions, then `canon tasks archive <stem> --json`.
32
+ - keep. Append the `## Findings` line naming what reproduced, leaving the backlog line.
33
+ - promote. `canon tasks place <stem> --group needs-a-plan --bottom --waiting "<what cleared>. Last" --json`, which clears the backlog line.
34
34
 
35
35
  ### Archive and missing files
36
36
 
@@ -38,14 +38,27 @@ The verb's own extraction already scopes to canonical doc types and reports noth
38
38
 
39
39
  Locate the finding by its `quote` in the file's current content. The quote is often a narrow fragment rather than the whole clause it sits in, since the regex layer's own match is a short pattern hit, so the edit below targets the sentence or bullet the quote sits in rather than the literal substring alone.
40
40
 
41
- - **Found exactly once.** Apply the verdict:
42
- - `REPLACE`: rewrite the sentence or bullet carrying the quote in place with the fact that now stands, per Step 3's rewrite-in-place rule. State what is current and drop what the quote restated, in the one edit.
43
- - `HISTORY`: rewrite the sentence or bullet carrying the quote to drop the narration and keep any current fact the same clause states. The quote narrates how the fact arrived rather than stating the fact, which a canonical doc excludes regardless of what wrote it, and a literal cut of the fragment alone would leave the rest of the clause grammatically stranded.
44
- - `MOVE`: do not cut. The verdict means correct content sitting on the wrong surface, such as a rule written into a context entry that belongs in a path-scoped rule, and only the model layer returns it. Cutting would delete content a fold with no model configured would never have flagged at all. Report the finding instead, naming the surface the `reason` names as where the content belongs, and leave the file unedited.
41
+ - **Found exactly once.** Apply the verdict under its heading below.
45
42
  - **Found more than once, or not found at all.** Report it as not located rather than guessing an occurrence or silently rewriting nothing. A model paraphrasing its quote is the usual cause.
46
43
 
47
44
  Run the classifier once per fold. Do not re-run it after applying a finding to check the edit, since a second pass over what this step wrote is the loop the verb's own reference already warns against.
48
45
 
46
+ ### REPLACE
47
+
48
+ Rewrite the sentence or bullet carrying the quote in place with the fact that now stands, per Step 3's rewrite-in-place rule. State what is current and drop what the quote restated, in the one edit.
49
+
50
+ ### HISTORY
51
+
52
+ Rewrite the sentence or bullet carrying the quote to drop the narration and keep any current fact the same clause states. The quote narrates how the fact arrived rather than stating the fact, which a canonical doc excludes regardless of what wrote it.
53
+
54
+ A literal cut of the fragment alone would leave the rest of the clause grammatically stranded.
55
+
56
+ ### MOVE
57
+
58
+ Do not cut. The verdict means correct content sits on the wrong surface, such as a rule written into a context entry that belongs in a path-scoped rule, and only the model layer returns it.
59
+
60
+ Report the finding instead, naming the surface the `reason` names as where the content belongs, and leave the file unedited. Cutting would delete content a fold with no model configured would never have flagged at all.
61
+
49
62
  ## Keeping a finding
50
63
 
51
64
  Keep a non-`KEEP` finding without applying it only when the quote is itself the alternative a nearby decision states and lost, or the file already carries the current fact elsewhere. State the reason in one line in the report. A keep costs one line, where an incorrect apply costs a rewrite of prose nobody asked to change.
@@ -58,6 +58,8 @@ Every limit is a count a reviewer can take, so a rule passes or fails it without
58
58
  | Bullet length | 200 characters |
59
59
  | Sentences per bullet | 2, one directive plus one exception, condition, or short reason |
60
60
 
61
+ Two habits keep a rule inside those limits.
62
+
61
63
  - Name one concern in `description`. A rule needing a seventh H2 holds two concerns and splits into two files.
62
64
  - Keep a reason to the why of its directive, never a paragraph of background
63
65
 
@@ -71,6 +73,8 @@ A bullet loads on every matching edit, so it earns its place only when removing
71
73
  | A convention that differs from the default | Anything the linter, formatter, or type checker enforces |
72
74
  | A footgun that ships silently | Information that changes often |
73
75
 
76
+ Three habits keep each bullet worth its load.
77
+
74
78
  - Write every bullet concrete enough to verify against a line of code. "Prefer `pathlib.Path` over `os.path`" passes, since it names both sides.
75
79
  - Pair a ban with the alternative to take, unless the ban or a neighbor bullet already names it
76
80
  - Use no emphasis words such as "CRITICAL" or "You MUST", which newer models overtrigger on
@@ -36,7 +36,7 @@ Read these in the same pass:
36
36
  1. Answer three questions in writing before drafting anything.
37
37
  - Does a rule, a verb, or an existing skill already reach this moment? A folder that only wraps something already reachable duplicates it rather than adding to the catalog.
38
38
  - Does the body carry a procedure a session would get wrong from first principles? A one-line wrapper around a single command needs no skill.
39
- - Will anything invoke it other than the author typing its name? Carry this one into the drafted `REQUIREMENT.md`'s `Must not` section as a review criterion instead, since nothing can answer it before the skill has run.
39
+ - Will anything invoke it other than the author typing its name? Carry this into the drafted `REQUIREMENT.md`'s `Must not` section as a review criterion, since nothing answers it before the run.
40
40
  - Tell the user and stop when the first answer is yes or the second is no. A non-interactive run stops on both too, reporting which question failed and writing no folder.
41
41
  2. Draft the full `SKILL.md` from the user's description, stating the current rule alone. An incident and the date it happened stay in git, never in the body, a reference, or the requirement.
42
42
  3. Draft the sibling `REQUIREMENT.md` from what the skill is for, in the shape the requirement standard states.
@@ -51,7 +51,7 @@ Steps 5 to 7 write the folder and check what landed before the run reports.
51
51
 
52
52
  5. Resolve the root, then write `<root>/<name>/SKILL.md` and `<root>/<name>/REQUIREMENT.md`.
53
53
  - The root is `.claude/skills/` in a target project.
54
- - Where the project root holds `claude/.claude-plugin/plugin.json`, which is the toolkit's own checkout, an `internal-*` skill goes under `.claude/skills/` and every other skill under `claude/skills/`, since the plugin ships that tree.
54
+ - In the toolkit's checkout, which holds `claude/.claude-plugin/plugin.json`, an `internal-*` skill goes under `.claude/skills/` and the rest under `claude/skills/`, since the plugin ships that tree.
55
55
  6. Run `canon claude skills audit --json` and read `findings.datedProvenance` for rows under `<root>/<name>/`.
56
56
  - Cut each row's date and incident from the file the row names, leaving them to git, and re-run until no row names the folder.
57
57
  - When `canon` does not resolve or its record carries no `datedProvenance` key, say the check did not run rather than reporting the skill clean.
@@ -28,6 +28,8 @@ Read the tree for these signals before running any command. A file that exists c
28
28
  | `vercel.json` at the root | vercel |
29
29
  | a `wrangler` or `vercel` entry in `package.json` scripts or dependencies | the host it names |
30
30
 
31
+ Read the signals together to settle the host.
32
+
31
33
  - Signals for one host only: take that host.
32
34
  - Signals for both, or for neither: ask the operator through the structured question surface, naming each signal found. Never default to a host, since a silent pick deploys to the wrong account.
33
35
  - A host the operator named in the request wins over the signals. Say which signals disagreed with it, if any.
@@ -81,15 +81,13 @@ Read `${CLAUDE_SKILL_DIR}/references/adopted.md` only when extending this guidan
81
81
 
82
82
  ## Excuses and rebuttals
83
83
 
84
- | Excuse | Rebuttal |
85
- | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
86
- | I searched for the name and nothing calls it | A search finds one spelling in the files it read. A handler looked up by string, a config key, or a published copy stays hidden. |
87
- | I'll keep the fallback until callers move | With no date and no owner, the fallback outlives every caller it was kept for. State when it goes and who removes it. |
88
- | The callers can migrate when they get to it | They will not. The owner of the surface moves its callers, or the deprecation never finishes. |
89
- | It's cleaner to add the new one and drop the old one now | One change that adds and removes leaves no state where a missed caller still works, and a revert undoes the migration too. |
90
- | This only matters on one machine | One caller is still a caller. Migrate it, or record it as the reason the old path stays. |
91
- | Nobody owns this, so I can just delete it | No owner is not no caller. Run the zero-consumers check before deciding, since unowned code is often the code nobody dares move. |
92
- | I'll mark it deprecated and move on | A notice with no shipped replacement and no removal date tells callers nothing they can act on. |
84
+ - **I searched for the name and nothing calls it.** A search finds one spelling in the files it read. A handler looked up by string, a config key, or a published copy stays hidden.
85
+ - **I'll keep the fallback until callers move.** With no date and no owner, the fallback outlives every caller it was kept for. State when it goes and who removes it.
86
+ - **The callers can migrate when they get to it.** They will not. The owner of the surface moves its callers, or the deprecation never finishes.
87
+ - **It's cleaner to add the new one and drop the old one now.** One change that adds and removes leaves no state where a missed caller still works, and a revert undoes the migration too.
88
+ - **This only matters on one machine.** One caller is still a caller. Migrate it, or record it as the reason the old path stays.
89
+ - **Nobody owns this, so I can just delete it.** No owner is not no caller. Run the zero-consumers check before deciding, since unowned code is often the code nobody dares move.
90
+ - **I'll mark it deprecated and move on.** A notice with no shipped replacement and no removal date tells callers nothing they can act on.
93
91
 
94
92
  ## Red flags
95
93
 
@@ -19,7 +19,7 @@ The skill body holds the order, the guards, and the report shape. This holds the
19
19
 
20
20
  The record carries the checkpoints it read against under `checkpoints` and the per-file weights under `entries`. Read both from the record rather than from a number stated here, since the CLI moves a checkpoint on its own cadence and a copy goes stale in place.
21
21
 
22
- - `heavyBullets`: a top-level bullet past the character checkpoint, which is a paragraph wearing a dash
22
+ - `heavyBullets`: a bullet past the character checkpoint, a nested one measured on its own, which is a paragraph wearing a dash
23
23
  - `heavyParagraphs`: a paragraph past the sentence or character checkpoint
24
24
  - `longestRun`, against `longestRunLine`: unbroken rendered lines between headings, which is the depth checkpoint
25
25
  - `cadence`, carrying `measured`, `flat`, and `repeating`: sentence-length spread and repeated openers
@@ -42,8 +42,8 @@ A missing tracking ref is not a guard, since an open pull request proves the bra
42
42
  2. Run `git add -A` to stage every change
43
43
  3. Invoke `canon:git-commit` to generate one conventional commit from the staged diff
44
44
  4. Push, in one of two cases.
45
- - When `git rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null` is empty, the branch has an open pull request and no tracking ref, so run `git push -u origin HEAD` to send the commit and set the ref in one step.
46
- - Otherwise push to the tracking branch with `git push`, and when `git merge-base --is-ancestor @{u} HEAD` exits non-zero, a caller rewrote the branch and a plain push is rejected, so push with `--force-with-lease` instead.
45
+ - When `git rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null` is empty, no ref tracks the branch, so run `git push -u origin HEAD` to push and set the ref in one step.
46
+ - Otherwise run `git push`, and when `git merge-base --is-ancestor @{u} HEAD` exits non-zero, a caller rewrote the branch, so use `--force-with-lease`, since a plain push is rejected.
47
47
 
48
48
  The lease is what stops the force from overwriting a commit this session never read. Run the ancestry test only where an upstream resolves, since it reads `@{u}`.
49
49
 
@@ -55,16 +55,27 @@ A missing tracking ref is not a guard, since an open pull request proves the bra
55
55
 
56
56
  6. Check for existing review comments: `gh api 'repos/{owner}/{repo}/pulls/<number>/comments' --jq 'length'`.
57
57
  7. Sync the body and title on every invocation, before the routing below decides on the reply.
58
- - Read the live title and body with `gh api repos/{owner}/{repo}/pulls/<number> --jq '{html_url, title, body}'`. Update the body with `gh api -X PATCH repos/{owner}/{repo}/pulls/<number> -F body=@<file> --silent` when the new commit changes scope, writing the new body to `<file>` first, and the title with `gh api -X PATCH repos/{owner}/{repo}/pulls/<number> -f title="<title>" --silent` when the scope shifted enough to make it inaccurate.
58
+ - Read the live title and body with `gh api repos/{owner}/{repo}/pulls/<number> --jq '{html_url, title, body}'`
59
+ - Update the body with `gh api -X PATCH repos/{owner}/{repo}/pulls/<number> -F body=@<file> --silent` when the new commit changes scope, writing the new body to `<file>` first
60
+ - Update the title with `gh api -X PATCH repos/{owner}/{repo}/pulls/<number> -f title="<title>" --silent` when the scope shifted enough to make it inaccurate
59
61
  - A fix commit answering a review changes what shipped exactly as much as an ordinary followup does, so the sync cannot wait on the invocation or the comment count below.
60
- - A body a person edited by hand between rounds gets no special handling: judge it against the tree the same way regardless of who wrote it last, since a hand-edit the fix commit has made stale is the exact drift this sync exists to close.
62
+ - Judge a body a person edited by hand against the tree like any other, since a hand-edit the fix commit made stale is the drift this sync closes.
61
63
  - Carry a `UI pass` line marked `<!-- pr-ui-pass -->` into any rewritten body unchanged, since `canon pr tick` owns it and a dropped marker defeats the reset on a new head.
62
64
  - Carry a `Fan-in members:` line into any rewritten body unchanged, since the post-merge hook reads it to delete the members and a dropped line deletes nothing and says nothing.
63
65
  8. Route on the invocation and the comment count for the reply alone.
64
66
  - When invoked with `reply-owned`, skip this step: the caller posts its own reply.
65
- - Otherwise, if the count is above zero, the followup addresses review feedback: write a one-line summary of the fix to `.canon/tmp/pr/followup/summary-<number>.md` in its own call, run `canon labels scan --body-file .canon/tmp/pr/followup/summary-<number>.md` against it, which applies the rule in `${CLAUDE_SKILL_DIR}/../git-pr/references/publish.md`, then post it with `gh api -X POST repos/{owner}/{repo}/issues/<number>/comments -F body=@.canon/tmp/pr/followup/summary-<number>.md --silent`. A hook on `Bash` runs the same scan on that body file and denies the post on a hit, and it refuses an inline `-f body=` value, so the body goes through a file. The `pull_request` check the git-pr surface carries triggers on a push or an open rather than a plain edit, so neither this comment nor the title and body step 7 synced reaches it.
67
+ - Otherwise, if the count is above zero, the followup addresses review feedback, so post the summary below.
66
68
  - If it is zero, nothing further runs. The sync in step 7 already did this branch's job.
67
69
 
70
+ ### Posting the step 8 reply
71
+
72
+ The body goes through a file, since a hook on `Bash` refuses an inline `-f body=` value and runs `canon labels scan` on the file before it allows the post.
73
+
74
+ - Write a one-line summary of the fix to `.canon/tmp/pr/followup/summary-<number>.md` in its own call
75
+ - Run `canon labels scan --body-file .canon/tmp/pr/followup/summary-<number>.md` against it, which applies the rule in `${CLAUDE_SKILL_DIR}/../git-pr/references/publish.md`
76
+ - Post it with `gh api -X POST repos/{owner}/{repo}/issues/<number>/comments -F body=@.canon/tmp/pr/followup/summary-<number>.md --silent`
77
+ - The `pull_request` check the git-pr surface carries triggers on a push or an open rather than a plain edit, so neither this comment nor the step 7 sync reaches it
78
+
68
79
  ## After completion
69
80
 
70
81
  Output one line:
@@ -59,17 +59,15 @@ A bug or task body carries up to four `##` sections, in this order.
59
59
 
60
60
  One `## Toolkit feedback` wrapper holds every field as a nested `###` heading. A flat `##` field is not recognized.
61
61
 
62
- | Field | Holds |
63
- | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
64
- | `### From project` (optional) | the reporting project's name or kind. |
65
- | `### Surface` (required) | the toolkit surface and its type, and the specific file or name when known. |
66
- | `### Observed` (required) | the observed behavior. |
67
- | `### Expected` (optional) | the expected behavior, or `unclear`. |
68
- | `### Repro` (optional) | repro details, or `none`. |
69
- | `### Proposed fix` (required) | a proposed fix, or `open` when the session settled on no direction. |
70
- | `### Suggested shape` (optional) | the template, output, or field list the change should take, inside a `<details>` block. Omit the heading when there is none. |
71
- | `### Why` (optional) | the reasons for the fix, one bullet each. Omit the heading when there are none. |
72
- | `### Links` (optional) | the issues, pull requests, or documents the report cites, one per line. Omit the heading when there are none. |
62
+ - **`### From project`** (optional): the reporting project's name or kind.
63
+ - **`### Surface`** (required): the toolkit surface and its type, and the specific file or name when known.
64
+ - **`### Observed`** (required): the observed behavior.
65
+ - **`### Expected`** (optional): the expected behavior, or `unclear`.
66
+ - **`### Repro`** (optional): repro details, or `none`.
67
+ - **`### Proposed fix`** (required): a proposed fix, or `open` when the session settled on no direction.
68
+ - **`### Suggested shape`** (optional): the template, output, or field list the change should take, inside a `<details>` block. Omit the heading when there is none.
69
+ - **`### Why`** (optional): the reasons for the fix, one bullet each. Omit the heading when there are none.
70
+ - **`### Links`** (optional): the issues, pull requests, or documents the report cites, one per line. Omit the heading when there are none.
73
71
 
74
72
  A required field is refused when present but empty. Write the fallback literal for From project, Expected, and Repro rather than leaving one blank.
75
73
 
@@ -71,11 +71,10 @@ A link to one Claude Code session names the session that wrote the text rather t
71
71
 
72
72
  A number referring to a pull request or an issue takes the form its destination renders. Write it bare where the destination auto-links it and in backticks where it does not, so a reference moved between destinations is rewritten rather than copied.
73
73
 
74
- | Destination | Form |
75
- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
76
- | Pull request or issue body, a comment on either, a commit | `#123`, or `owner/repo#123` across repositories <!-- canon-allow-reference: the two spellings this section defines, which a qualified form would destroy --> |
77
- | A markdown file browsed from the repository tree | the number in backticks, since neither spelling links there |
78
- | Content installed into another repository | qualified against a different, resolvable repository, or no number |
74
+ - Pull request or issue body, a comment on either, a commit: `#123` <!-- canon-allow-reference: the bare spelling this section defines, which a qualified form would destroy -->
75
+ - The same destinations when the reference crosses repositories: `owner/repo#123` <!-- canon-allow-reference: the two spellings this section defines, which a qualified form would destroy -->
76
+ - A markdown file browsed from the repository tree: the number in backticks, since neither spelling links there
77
+ - Content installed into another repository: qualified against a different, resolvable repository, or no number
79
78
 
80
79
  A commit message takes the bare form even though the log links nothing, since the remote is what the form is chosen for. Text quoted from another surface keeps the spelling it arrived with, since rewriting it reports the source as saying something it never said.
81
80
 
@@ -74,10 +74,17 @@ Classify the groups before generating commands, since the mode decides each bran
74
74
 
75
75
  Append the line for the run's mode to the preview.
76
76
 
77
- | Mode | Line |
78
- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
79
- | Stacked | `🔁 Sequential squash-merge loop. Squash-merge each PR bottom-up with --delete-branch. After each merge, signal me so I can restack the next branch onto main before you merge it.` |
80
- | Independent | `🔁 Squash-merge secondaries first, primary last. The primary keeps every cherry-picked commit, so merging it first lands every secondary's content on main and leaves the secondary PRs empty after rebase.` |
77
+ Stacked mode appends this line:
78
+
79
+ ```plaintext
80
+ 🔁 Sequential squash-merge loop. Squash-merge each PR bottom-up with --delete-branch. After each merge, signal me so I can restack the next branch onto main before you merge it.
81
+ ```
82
+
83
+ Independent mode appends this line:
84
+
85
+ ```plaintext
86
+ 🔁 Squash-merge secondaries first, primary last. The primary keeps every cherry-picked commit, so merging it first lands every secondary's content on main and leaves the secondary PRs empty after rebase.
87
+ ```
81
88
 
82
89
  After outputting the preview, execute the final commands immediately. Claude Code's tool permission dialog is the confirmation gate. Do not wait for user input.
83
90
 
@@ -14,7 +14,7 @@ Read the references below before a substantial markdown edit. Every rule in them
14
14
 
15
15
  Run `canon markdown audit <path>` after the edit, and fix every ban and density finding it reports before moving on. Rewrite the sentence carrying a ban hit rather than swapping the token for a near-synonym, and fix a density finding by its shape rather than trimming characters.
16
16
 
17
- A table finding reports without failing the run. Fix it the way `tables.md` gives for its limit.
17
+ A table cell past its cap and a list after a table gate where density does, and the row count and the wide token report without failing the run. Fix each the way `tables.md` gives for its limit.
18
18
 
19
19
  ## Routes
20
20
 
@@ -77,7 +77,7 @@ A bold section marker holding its line alone breaks a run the way a heading does
77
77
 
78
78
  ## Density
79
79
 
80
- Six limits keep a file scannable on sight, each a fact `canon markdown audit` measures that fails the push where the surface's gate is on. A table takes four more, in `tables.md` beside this file. Open an H3 with a lead sentence or a table, then a few bullets or a short paragraph, since monotone text is the defect whether all prose or all bullets.
80
+ Six limits keep a file scannable on sight, each a fact `canon markdown audit` measures that fails the push where the surface's gate is on. A table takes four more in `tables.md`, two of them gating. Open an H3 with a lead sentence or a table, then a few bullets or a short paragraph, since monotone text is the defect.
81
81
 
82
82
  | Rule | Limit |
83
83
  | ------------------- | ------------------------------------------------- |
@@ -95,7 +95,7 @@ Rules, a requirements or design record, a changelog, examples, and `.canon/` rec
95
95
  A paragraph is read straight through and a bullet is scanned, so the paragraph limit sits above the bullet one. Four sentences to a paragraph stays advice rather than a limit.
96
96
 
97
97
  - A paragraph's length folds in the lines that wrap it. A paragraph inside a list item counts toward its bullet, never as a paragraph or toward a run.
98
- - A bullet's length counts its continuation lines and excludes any bullet nested under it
98
+ - A bullet's length counts its continuation lines and excludes any bullet nested under it, and a nested bullet is measured on its own
99
99
  - A heading, list, table, fence, or bold section marker ends a run of paragraphs
100
100
  - A blank line, a continuation, and a nested item do not break a run of bullets, and a paragraph or a heading does
101
101
  - A run whose every item opens with a bold label or a link is exempt from the run limit, since each item names its own subject. A task box is not a link, so a checklist counts as plain.
@@ -5,18 +5,20 @@ description: The four limits a markdown table takes, being cell length, row coun
5
5
 
6
6
  # Table limits
7
7
 
8
- None of the density limits in `markdown.md` reads inside a table, so content too long for a bullet tends to move into a cell. Four table limits close that gap. `canon markdown audit` measures them on the paths density covers, outside fences, and reports each one without failing a push for now.
8
+ None of the density limits in `markdown.md` reads inside a table, so content too long for a bullet tends to move into a cell. Four table limits close that gap. `canon markdown audit` measures them on the paths density covers, outside fences.
9
+
10
+ Cell length and a list after a table fail a push where density gates. Rows and a wide token report without failing one.
9
11
 
10
12
  ## Limits
11
13
 
12
14
  Each limit is a count or a shape a scan settles, read the way a renderer draws the table. An escaped pipe stays inside its cell, and pipe lines with no delimiter row are not a table.
13
15
 
14
- | Rule | Limit |
15
- | ---------------- | ----------------------------------------------------- |
16
- | Cell length | 120 visible characters, counting a link by its label |
17
- | Rows | 8 body rows, not counting the header |
18
- | List after table | A list is never the next block after a table |
19
- | Wide token | No code span in a cell holds a run past 30 characters |
16
+ | Rule | Limit | Effect |
17
+ | ---------------- | ----------------------------------------------------- | ------------ |
18
+ | Cell length | 120 visible characters, counting a link by its label | Gates |
19
+ | Rows | 8 body rows, not counting the header | Reports only |
20
+ | List after table | A list is never the next block after a table | Gates |
21
+ | Wide token | No code span in a cell holds a run past 30 characters | Reports only |
20
22
 
21
23
  A paragraph or a heading between a table and a list clears the third limit, and a blank line alone does not.
22
24
 
@@ -163,6 +163,8 @@ Pick the `Next:` line from the roster check `${CLAUDE_SKILL_DIR}/../task-board/S
163
163
  | **Not found**, and this session's name starts with `worker-` or `planner-` | `Hand <task> to a worker`, naming no session, since none is live and the operator dispatches by hand |
164
164
  | **Not found otherwise**, or the read fails | `/session-worktree` |
165
165
 
166
+ Three rules apply to the `Next:` choice:
167
+
166
168
  - With a live orchestrator and no planner role held, send the plan path to it through `canon:session-relay`, resolved from its `sessionId`
167
169
  - A session holding `canon:role-planner` already owes that announcement through its own channel, so it sends none
168
170
  - Say which row fired in one clause, since a line with no stated reason is one the next reader re-derives.
@@ -81,6 +81,8 @@ The document opens with `# Feature: <short title>` and one paragraph stating wha
81
81
  | `## Questions` | numbered open decisions, each with a suggestion and an answer slot | Always |
82
82
  | `## Sources` | the outside sources a decision rests on, each with its quoted passage | When a decision rests on an outside source |
83
83
 
84
+ Four rules govern how a section is written:
85
+
84
86
  - Write each section as the H2 the table gives, so a reader scans the plan by its outline
85
87
  - A section written as a bold label, the spelling before this one, is still that section to every parser. Leave an existing plan's spelling alone.
86
88
  - Write `None identified.` under a required section with nothing to report rather than dropping the marker. A dropped section and an unconsidered one read identically.