@erclx/canon 5.32.0 → 5.33.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 (210) 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/auto-ship/references/worktree-name.md +1 -1
  4. package/claude/skills/backlog-triage/REQUIREMENT.md +2 -2
  5. package/claude/skills/backlog-triage/SKILL.md +1 -1
  6. package/claude/skills/canon-cli/REQUIREMENT.md +16 -13
  7. package/claude/skills/canon-cli/SKILL.md +75 -32
  8. package/claude/skills/canon-feedback/SKILL.md +1 -1
  9. package/claude/skills/{canvas → canvas-board}/REQUIREMENT.md +2 -2
  10. package/claude/skills/{canvas → canvas-board}/SKILL.md +2 -2
  11. package/{standards → claude/skills/canvas-board/references}/design.md +11 -2
  12. package/claude/skills/context-fold/REQUIREMENT.md +25 -33
  13. package/claude/skills/context-fold/SKILL.md +25 -70
  14. package/claude/skills/context-fold/references/classify.md +3 -3
  15. package/claude/skills/context-fold/references/completion-report.md +4 -4
  16. package/claude/skills/context-fold/references/context-refresh.md +5 -5
  17. package/{standards → claude/skills/context-fold/references}/context.md +16 -5
  18. package/claude/skills/context-fold/references/unusable-baseline.md +3 -5
  19. package/claude/skills/create-rule/SKILL.md +2 -2
  20. package/{standards → claude/skills/create-rule/references}/rule.md +12 -5
  21. package/claude/skills/create-skill/SKILL.md +2 -2
  22. package/{standards → claude/skills/create-skill/references}/skill.md +17 -14
  23. package/claude/skills/design-taste/REQUIREMENT.md +3 -3
  24. package/claude/skills/design-taste/SKILL.md +1 -1
  25. package/claude/skills/docs-sync/REQUIREMENT.md +1 -1
  26. package/claude/skills/docs-sync/SKILL.md +3 -3
  27. package/{standards → claude/skills/docs-sync/references}/docs.md +11 -0
  28. package/{standards → claude/skills/docs-sync/references}/readme.md +9 -2
  29. package/claude/skills/draft-and-pick/SKILL.md +5 -5
  30. package/claude/skills/draft-doc/SKILL.md +6 -6
  31. package/claude/skills/draft-figure/REQUIREMENT.md +2 -2
  32. package/claude/skills/draft-figure/SKILL.md +2 -2
  33. package/claude/skills/draft-figure/references/hand-drawn.md +1 -1
  34. package/claude/skills/draft-figure/references/mermaid-path.md +1 -1
  35. package/claude/skills/draft-identity/REQUIREMENT.md +2 -2
  36. package/claude/skills/draft-identity/SKILL.md +2 -2
  37. package/claude/skills/draft-ready/SKILL.md +1 -1
  38. package/claude/skills/git-branch/SKILL.md +1 -1
  39. package/{standards → claude/skills/git-branch/references}/branch.md +1 -1
  40. package/claude/skills/git-commit/SKILL.md +2 -2
  41. package/claude/skills/git-followup/REQUIREMENT.md +1 -1
  42. package/claude/skills/git-followup/SKILL.md +1 -1
  43. package/claude/skills/git-issue/SKILL.md +1 -1
  44. package/claude/skills/git-pr/SKILL.md +13 -13
  45. package/claude/skills/git-pr/references/evidence.md +2 -2
  46. package/claude/skills/git-pr/references/final-command.md +38 -14
  47. package/claude/skills/git-pr/references/labels.md +5 -1
  48. package/{standards → claude/skills/git-pr/references}/pr.md +19 -9
  49. package/{standards → claude/skills/git-pr/references}/publish.md +4 -2
  50. package/claude/skills/git-pr/references/task-number.md +1 -1
  51. package/claude/skills/git-ship/SKILL.md +15 -14
  52. package/claude/skills/git-split/SKILL.md +3 -3
  53. package/claude/skills/git-stage/SKILL.md +1 -1
  54. package/claude/skills/plan-feature/REQUIREMENT.md +1 -1
  55. package/claude/skills/review-address/SKILL.md +1 -1
  56. package/claude/skills/review-address/references/post-review-findings.md +1 -1
  57. package/claude/skills/review-pr/REQUIREMENT.md +1 -1
  58. package/claude/skills/review-pr/SKILL.md +3 -3
  59. package/claude/skills/review-pr/references/body-variants.md +2 -2
  60. package/claude/skills/review-pr/references/key-changes.md +3 -3
  61. package/claude/skills/review-pr/references/post.md +4 -2
  62. package/claude/skills/review-ui/REQUIREMENT.md +1 -1
  63. package/claude/skills/review-ui/SKILL.md +1 -1
  64. package/claude/skills/review-ui/references/post.md +1 -1
  65. package/claude/skills/role-orchestrator/REQUIREMENT.md +3 -0
  66. package/claude/skills/role-orchestrator/SKILL.md +2 -2
  67. package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +2 -2
  68. package/claude/skills/role-orchestrator/references/orchestrator-handoff.md +8 -5
  69. package/claude/skills/role-orchestrator/references/orchestrator-launch.md +23 -1
  70. package/claude/skills/role-orchestrator/references/orchestrator-refill.md +2 -2
  71. package/claude/skills/role-orchestrator/references/orchestrator-resume.md +3 -0
  72. package/claude/skills/role-planner/REQUIREMENT.md +2 -1
  73. package/claude/skills/role-planner/SKILL.md +13 -2
  74. package/claude/skills/role-reviewer/SKILL.md +1 -1
  75. package/claude/skills/role-worker/REQUIREMENT.md +1 -0
  76. package/claude/skills/role-worker/SKILL.md +9 -1
  77. package/claude/skills/session-map/SKILL.md +1 -1
  78. package/claude/skills/session-resume/REQUIREMENT.md +2 -2
  79. package/claude/skills/session-worktree/SKILL.md +5 -5
  80. package/claude/skills/target-setup/references/detect.md +1 -1
  81. package/claude/skills/task-board/REQUIREMENT.md +7 -12
  82. package/claude/skills/task-board/SKILL.md +5 -17
  83. package/claude/skills/task-board/references/tasks.md +2 -2
  84. package/claude/skills/task-complete/REQUIREMENT.md +47 -0
  85. package/claude/skills/task-complete/SKILL.md +86 -0
  86. package/claude/skills/{task-board → task-complete}/references/archive.md +4 -4
  87. package/claude/skills/{context-fold → task-complete}/references/receipt-sweep.md +5 -5
  88. package/claude/skills/task-complete/references/unusable-baseline.md +19 -0
  89. package/claude/skills/teach-workspace/REQUIREMENT.md +6 -2
  90. package/claude/skills/teach-workspace/SKILL.md +8 -4
  91. package/claude/skills/teach-workspace/references/lesson-craft.md +1 -1
  92. package/claude/skills/{context-fold/references/promoted-pages.md → teach-workspace/references/promotion-fold.md} +3 -3
  93. package/claude/skills/teach-workspace/references/promotion.md +3 -3
  94. package/claude/skills/ui-checklist/SKILL.md +1 -1
  95. package/claude/skills/ux-audit/SKILL.md +1 -1
  96. package/claude/skills/ux-measure/SKILL.md +1 -1
  97. package/docs/agents/canvas.md +1 -1
  98. package/docs/agents/commands.md +3 -2
  99. package/docs/agents/context-audit-checks.md +4 -4
  100. package/docs/agents/index.md +3 -3
  101. package/docs/agents/indexes.md +1 -1
  102. package/docs/agents/install-and-sync.md +6 -11
  103. package/docs/agents/key-changes.md +5 -5
  104. package/docs/agents/label-coverage.md +11 -0
  105. package/docs/agents/migrate.md +1 -1
  106. package/docs/agents/records-push.md +2 -2
  107. package/docs/agents/records-stale.md +12 -47
  108. package/docs/agents/records-validate.md +3 -7
  109. package/docs/agents/records.md +5 -27
  110. package/docs/agents/rule-citations.md +1 -1
  111. package/docs/agents/sandbox.md +4 -4
  112. package/docs/agents/skills-audit.md +1 -1
  113. package/docs/agents/tasks-plans.md +2 -2
  114. package/docs/agents/tasks.md +2 -2
  115. package/docs/target/migrations.md +10 -2
  116. package/docs/target/projects.md +1 -1
  117. package/docs/target/sync.md +1 -1
  118. package/docs/workflow/operating-model.md +2 -2
  119. package/docs/workflow/parallel-features.md +7 -7
  120. package/docs/workflow/skill-map-on-request.md +9 -9
  121. package/docs/workflow/skill-map.md +12 -11
  122. package/docs/workflow/visual-design-workflow.md +2 -2
  123. package/governance/renames.toml +0 -11
  124. package/governance/rules/canon/context.md +13 -0
  125. package/governance/rules/claude/skill.md +17 -0
  126. package/governance/stacks/base.toml +2 -4
  127. package/package.json +4 -4
  128. package/scripts/core/install-check.sh +3 -3
  129. package/scripts/lib/ui.sh +0 -159
  130. package/src/audits/catalog.ts +0 -1
  131. package/src/claude/cases/misc.ts +1 -1
  132. package/src/claude/cases/setup.ts +4 -0
  133. package/src/claude/cases/workflow.ts +6 -1
  134. package/src/claude/seeds.ts +1 -1
  135. package/src/claude/skills-audit.ts +2 -2
  136. package/src/cli/help.ts +3 -1
  137. package/src/cli/ui.ts +2 -3
  138. package/src/commands/claude.ts +1 -1
  139. package/src/commands/gov.ts +2 -15
  140. package/src/commands/labels.ts +326 -7
  141. package/src/commands/migrate.ts +6 -1
  142. package/src/commands/pr.ts +1 -1
  143. package/src/commands/records.ts +13 -399
  144. package/src/commands/sandbox.ts +3 -6
  145. package/src/commands/tasks.ts +3 -3
  146. package/src/context/audit.ts +5 -5
  147. package/src/context/folders.ts +1 -1
  148. package/src/design/budget.ts +1 -1
  149. package/src/design/components.ts +1 -1
  150. package/src/design/document.ts +1 -1
  151. package/src/design/regen.ts +1 -1
  152. package/src/design/render.ts +1 -1
  153. package/src/gate/measures.ts +1 -1
  154. package/src/gate/sequencer.ts +56 -1
  155. package/src/gate/stages.ts +1 -7
  156. package/src/gov/install.ts +2 -3
  157. package/src/gov/stacks.ts +1 -1
  158. package/src/hooks/post-merge.ts +1 -1
  159. package/src/labels/apply.ts +39 -0
  160. package/src/labels/format.ts +3 -3
  161. package/src/labels/phase.ts +3 -3
  162. package/src/labels/sections.ts +43 -0
  163. package/src/migrate/skill-names.ts +20 -1
  164. package/src/pr/paths.ts +4 -4
  165. package/src/records/backup.ts +16 -6
  166. package/src/records/stale-canonical.ts +31 -1
  167. package/src/records/validate.ts +2 -188
  168. package/src/sandbox/census.ts +1 -1
  169. package/src/sandbox/coverage.ts +2 -6
  170. package/src/sandbox/equivalence.ts +5 -44
  171. package/src/sandbox/expect.ts +4 -4
  172. package/src/sandbox/provision.ts +13 -36
  173. package/src/sandbox/scenario.ts +26 -48
  174. package/src/sandbox/tree.ts +2 -6
  175. package/src/standards/read.ts +14 -1
  176. package/src/sync/git.ts +1 -1
  177. package/src/tasks/archive.ts +5 -5
  178. package/src/tasks/branch.ts +3 -3
  179. package/src/tasks/label.ts +1 -1
  180. package/src/tasks/record.ts +2 -2
  181. package/src/tasks/validate.ts +1 -1
  182. package/standards/index.md +0 -13
  183. package/standards/standard.md +1 -7
  184. package/tooling/base/reference.md +1 -1
  185. package/tooling/base/seeds/.github/pull_request_template.md +2 -2
  186. package/tooling/claude/reference.md +1 -1
  187. package/tooling/claude/seeds/.claude/hooks/pr-create-log.sh +9 -1
  188. package/tooling/claude/seeds/.claude/hooks/review-post-scan.sh +171 -0
  189. package/tooling/claude/seeds/.claude/settings.json +9 -0
  190. package/governance/rules/standards/context.md +0 -14
  191. package/governance/rules/standards/design.md +0 -12
  192. package/governance/rules/standards/readme.md +0 -12
  193. package/governance/rules/standards/rule.md +0 -13
  194. package/governance/rules/standards/skill.md +0 -21
  195. package/governance/rules/standards/standard.md +0 -13
  196. package/scripts/config.sh +0 -11
  197. package/scripts/lib/gov.sh +0 -14
  198. package/scripts/lib/sandbox-fixtures.sh +0 -191
  199. package/scripts/lib/sandbox-git.sh +0 -125
  200. package/scripts/lib/sandbox-path.sh +0 -50
  201. package/scripts/sandbox-hook.sh +0 -99
  202. package/src/gov/standard-rules.ts +0 -179
  203. package/src/records/migrate.ts +0 -78
  204. package/src/records/stale.ts +0 -243
  205. package/src/sandbox/hooks.ts +0 -128
  206. /package/claude/skills/{canvas → canvas-board}/references/canvas.md +0 -0
  207. /package/{standards → claude/skills/draft-figure/references}/figures.md +0 -0
  208. /package/{standards → claude/skills/git-branch/references}/slug.md +0 -0
  209. /package/{standards → claude/skills/git-commit/references}/commit.md +0 -0
  210. /package/{standards → claude/skills/git-commit/references}/versioning.md +0 -0
@@ -60,8 +60,21 @@ export function standardRoots(root: string): StandardRoot[] {
60
60
  */
61
61
  const MOVED_STANDARDS: Readonly<Record<string, string>> = {
62
62
  board: 'claude/skills/task-board/references/board.md',
63
+ context: 'claude/skills/context-fold/references/context.md',
64
+ design: 'claude/skills/canvas-board/references/design.md',
65
+ docs: 'claude/skills/docs-sync/references/docs.md',
66
+ figures: 'claude/skills/draft-figure/references/figures.md',
67
+ readme: 'claude/skills/docs-sync/references/readme.md',
68
+ rule: 'claude/skills/create-rule/references/rule.md',
69
+ skill: 'claude/skills/create-skill/references/skill.md',
70
+ branch: 'claude/skills/git-branch/references/branch.md',
71
+ commit: 'claude/skills/git-commit/references/commit.md',
72
+ pr: 'claude/skills/git-pr/references/pr.md',
73
+ publish: 'claude/skills/git-pr/references/publish.md',
74
+ slug: 'claude/skills/git-branch/references/slug.md',
75
+ versioning: 'claude/skills/git-commit/references/versioning.md',
63
76
  markdown: 'claude/skills/markdown-craft/references/markdown.md',
64
- canvas: 'claude/skills/canvas/references/canvas.md',
77
+ canvas: 'claude/skills/canvas-board/references/canvas.md',
65
78
  glossary: 'claude/skills/teach-workspace/references/glossary.md',
66
79
  groundwork: 'claude/skills/plan-groundwork/references/groundwork.md',
67
80
  'groundwork-spikes':
package/src/sync/git.ts CHANGED
@@ -96,7 +96,7 @@ export function pullRequestBody(changes: readonly DomainChange[]): string {
96
96
  '',
97
97
  `Sync ${domains} from toolkit.`,
98
98
  '',
99
- '## Key Changes',
99
+ '## Key changes',
100
100
  '',
101
101
  ]
102
102
 
@@ -578,7 +578,7 @@ function isBulletFor(line: string, target: string): boolean {
578
578
 
579
579
  /**
580
580
  * Resolves the `Plan:` target against the board and against the project root
581
- * both, which is how `context-fold` reads the same line. It accepts `../plans/x.md`
581
+ * both, which is how `task-complete` reads the same line. It accepts `../plans/x.md`
582
582
  * and `.canon/plans/x.md` as one file, so a gate reading only the first form
583
583
  * would pass the second and strand the plan this exists to protect.
584
584
  *
@@ -633,7 +633,7 @@ function atOneRoot(path: string, plans: string[], root: string): string {
633
633
  /**
634
634
  * Names the other live tasks whose `Plan:` line lands on the same file through
635
635
  * any of its links. This is
636
- * the rule `context-fold` applies before it archives a plan, held here so one
636
+ * the rule the `post-merge` hook applies before it archives a plan, held here so one
637
637
  * question has one implementation: a plan another live task still cites is a
638
638
  * plan the sweep is correct to leave, and a guard that read the folder instead
639
639
  * refused every task sharing one plan and deadlocked the board against the
@@ -697,7 +697,7 @@ export type CitationOutcome = PlanCitations | ArchiveRefused
697
697
 
698
698
  /**
699
699
  * Answers where one task's plan sits and who else holds it, which is the whole
700
- * of the last-live-citation rule. `context-fold` reads this rather than scanning
700
+ * of the last-live-citation rule. The archive reads this rather than scanning
701
701
  * the board itself, so the sweep that moves a plan and the gate that refuses a
702
702
  * task archive cannot drift into disagreeing about which plan is free.
703
703
  *
@@ -897,7 +897,7 @@ async function resolveStem(
897
897
 
898
898
  /**
899
899
  * Archives one task as a single unit: the move, the ordering-row removal, and
900
- * the index regen. The hook and `task-board` both call this, so every gate
900
+ * the index regen. The hook and `task-complete` both call this, so every gate
901
901
  * refuses rather than reports. A caller with nobody watching cannot act on a
902
902
  * warning, and two callers gating differently is the drift this exists to stop.
903
903
  */
@@ -918,7 +918,7 @@ export async function archiveTask(
918
918
  const from = join(dir, `${stem}.md`)
919
919
  const text = await readFile(from, 'utf8')
920
920
 
921
- // `context-fold` ticks outcomes at ship time, before the merge, so an earlier
921
+ // `task-complete` ticks outcomes at ship time, before the merge, so an earlier
922
922
  // slice merging after the last slice shipped would find every box ticked
923
923
  // while the last slice is still open. Only the last number closes the task.
924
924
  const last = readPullRequest(text).at(-1)
@@ -14,10 +14,10 @@ const MARKDOWN = '.md'
14
14
  */
15
15
  export const PLAN_BRANCH_TYPE = 'feat'
16
16
 
17
- /** The description cap in `standards/branch.md`, in kebab-separated words. */
17
+ /** The description cap in `claude/skills/git-branch/references/branch.md`, in kebab-separated words. */
18
18
  export const DESCRIPTION_WORD_CAP = 4
19
19
 
20
- /** The branch length cap in `standards/branch.md`, in characters. */
20
+ /** The branch length cap in `claude/skills/git-branch/references/branch.md`, in characters. */
21
21
  export const BRANCH_LENGTH_CAP = 50
22
22
 
23
23
  export interface PlanBranch {
@@ -48,7 +48,7 @@ function slugOf(path: string): string {
48
48
  * of them name. It is the one derivation, so the collision check and the
49
49
  * worktree entry it gates cannot hold two answers for one plan.
50
50
  *
51
- * Conformance covers both caps `standards/branch.md` states, being the word
51
+ * Conformance covers both caps `claude/skills/git-branch/references/branch.md` states, being the word
52
52
  * count of the description and the length of the whole branch. A slug is a
53
53
  * plan's own filename rather than a name anyone chose for a branch, so a plan
54
54
  * can name a branch this refuses to grade as conforming, and reporting that is
@@ -185,7 +185,7 @@ async function reserveLabel(root: string, label: string): Promise<boolean> {
185
185
  * the task file after this returns, so a reservation with no file behind it is
186
186
  * the ordinary state for a few seconds, and a scan blind to it would offer the
187
187
  * same label to every retry. Reservations are never expired: a claim that died
188
- * leaves a gap, and `standards/versioning.md` permits gaps.
188
+ * leaves a gap, and `claude/skills/git-commit/references/versioning.md` permits gaps.
189
189
  */
190
190
  export async function claimLabel(
191
191
  root: string,
@@ -420,7 +420,7 @@ async function openTask(
420
420
  * resolve the board root in-process.
421
421
  *
422
422
  * `branches` names the recording branch and every name it carried before a
423
- * rename, since `git-ship` renames a branch after `context-fold` has already
423
+ * rename, since `git-ship` renames a branch after `task-complete` has already
424
424
  * written the marker under the old name. Each comes off the `Pending branch:`
425
425
  * line even when the number was already listed, so a rerun repairs a stale
426
426
  * marker.
@@ -504,7 +504,7 @@ export async function recordPlan(
504
504
  }
505
505
 
506
506
  /**
507
- * Marks the named outcomes `[x]` in place. `context-fold` runs this against a
507
+ * Marks the named outcomes `[x]` in place. `task-complete` runs this against a
508
508
  * board it can read and cannot edit from a linked worktree, and the positions
509
509
  * come from the read it already made.
510
510
  *
@@ -597,7 +597,7 @@ async function listTaskStems(dir: string): Promise<string[]> {
597
597
 
598
598
  /**
599
599
  * Resolves a pointer against the board and against the project root both, the
600
- * way `context-fold` reads the same line. A fragment is dropped first, since an
600
+ * way `task-complete` reads the same line. A fragment is dropped first, since an
601
601
  * anchor is part of the link and never part of the path.
602
602
  */
603
603
  function resolves(target: string, dir: string, root: string): boolean {
@@ -7,17 +7,4 @@ subtitle: Reference docs for consistent authoring across the toolkit and target
7
7
 
8
8
  Reference docs for consistent authoring across the toolkit and target projects.
9
9
 
10
- - [Branch reference](branch.md): Branch naming format and type conventions
11
- - [Commit reference](commit.md): Commit message format and type conventions
12
- - [Context entry reference](context.md): Shape and content rules for canon/context/<domain>.md entries
13
- - [Design reference](design.md): Shape and content rules for canon/DESIGN.md
14
- - [Docs reference](docs.md): Reader and jurisdiction, frontmatter, page structure, what a page links out to, the diagram permission, and when a category earns a subfolder
15
- - [Figure reference](figures.md): When a figure earns its place, the render-first policy for a graph-shaped subject, freehand SVG as the escape hatch, and its wrapping, color, and accessibility rules
16
- - [Pull request reference](pr.md): Pull request title and body conventions
17
- - [Publish reference](publish.md): Scan an author runs against finished text, the cross-reference form each destination takes, and the response to an unreadable source
18
- - [Readme reference](readme.md): Readme voice, structure, and content conventions
19
- - [Governance rule reference](rule.md): Rule frontmatter, body shape, and voice for .claude/rules files
20
- - [Claude skill reference](skill.md): Skill structure and authoring rules for Claude Code
21
- - [Slug reference](slug.md): Transform from a git branch name to a slug, and the three responses to an empty result
22
10
  - [Standard reference](standard.md): Shape and content rules for authoring a standard
23
- - [Versioning reference](versioning.md): Phase label vs semver discipline across tasks, PRs, reviews, issues, commits, and tags
@@ -1,10 +1,6 @@
1
1
  ---
2
2
  title: Standard reference
3
3
  description: Shape and content rules for authoring a standard
4
- paths:
5
- - 'standards/**/*.md'
6
- rule:
7
- - 'Read it before writing or editing a standard. Do not work the shape from memory.'
8
4
  ---
9
5
 
10
6
  # Standard reference
@@ -68,9 +64,7 @@ A standard failing these questions is non-conforming even when it satisfies ever
68
64
 
69
65
  - Start the file with a frontmatter block carrying at least `title` and `description`. A consuming surface may require more.
70
66
  - `title`: names the doc type in sentence case, suffixed `reference` (`Commit reference`, `Branch reference`)
71
- - `description`: one line naming what the standard covers. It becomes the index link label on install, and the generated rule's description and pointer bullet.
72
- - `paths` (optional): the globs where the standard applies, one per entry, copied verbatim into the rule `canon gov regen` generates for it. Omit it when no path should route to the standard, and no rule is generated.
73
- - `rule` (optional): operative bullets the generated rule carries after its pointer bullet, one per entry, each a full sentence. Quote an entry holding a backtick, a colon, or a leading `-`.
67
+ - `description`: one line naming what the standard covers. It becomes the index link label on install.
74
68
 
75
69
  ## Structure
76
70
 
@@ -29,7 +29,7 @@ Seeds live in `tooling/base/seeds/`. Sync drops each once on first install and n
29
29
  - `.lintstagedrc`: the glob map below.
30
30
  - `.prettierignore`: created empty. Projects add their own entries.
31
31
  - `.github/workflows/verify.yml`: runs on pull requests targeting `main` and on `workflow_dispatch`. A project with more than one language root adds its own jobs here.
32
- - `.github/pull_request_template.md`: `## Summary`, `## Key Changes`, `## Technical Context`, `## Testing`. A project adds the checklist lines its own review needs.
32
+ - `.github/pull_request_template.md`: `## Summary`, `## Key changes`, `## Technical context`, `## Testing`. A project adds the checklist lines its own review needs.
33
33
  - `canon/context/`: extend the `ci` and `development` entries with project-specific commands, workflows, or deploy steps. Canonical rationale stays in this reference.
34
34
 
35
35
  ## Tool pairing
@@ -2,12 +2,12 @@
2
2
 
3
3
  <Action Verb> <Direct Object> to <Result>.
4
4
 
5
- ## Key Changes
5
+ ## Key changes
6
6
 
7
7
  - <Verb> <specific component/file/function> (<reason if non-obvious>)
8
8
  - <Verb> <specific component/file/function>
9
9
 
10
- <!-- ## Technical Context (optional, omit for docs/config/trivial changes) -->
10
+ <!-- ## Technical context (optional, omit for docs/config/trivial changes) -->
11
11
  <!-- - <Why this approach was taken, not what changed> -->
12
12
 
13
13
  ## Testing
@@ -21,7 +21,7 @@ canon/
21
21
 
22
22
  .canon/
23
23
  ├── tasks/ ← seeded. One file per task plus a generated index.md, local scratch only. `archive/` holds the tasks that shipped.
24
- ├── plans/ ← execution detail for multi-step tasks. `feature-*.md` entries swept by context-fold into `archive/`.
24
+ ├── plans/ ← execution detail for multi-step tasks. `feature-*.md` entries moved into `archive/` by `canon tasks archive` when the `post-merge` hook archives their task.
25
25
  ├── review/ ← one subfolder per producer. `branch/` no longer written, `design/` for canon design render.
26
26
  └── tmp/ ← ephemeral scratch space, safe to delete without loss
27
27
  ```
@@ -24,8 +24,9 @@ IFS= read -r -d '' -t 2 input
24
24
  # its edit path prints a URL as well and is not an opening.
25
25
  command=$(printf '%s' "$input" | jq -r '.tool_input.command // empty')
26
26
  stdout=$(printf '%s' "$input" | jq -r '.tool_response.stdout // empty')
27
+ is_hand_opened=false
27
28
  case "$command" in
28
- *"gh pr create"*) ;;
29
+ *"gh pr create"*) is_hand_opened=true ;;
29
30
  *)
30
31
  printf '%s\n' "$stdout" | grep -qx 'opened=true' || exit 0
31
32
  ;;
@@ -57,4 +58,11 @@ session=$(printf '%s' "$input" | jq -r '.session_id // "unknown"')
57
58
  printf -- '- %s session=%s pr=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$session" "$url" >>"$log_dir/log.md"
58
59
 
59
60
  msg="Pull request $url just opened. If this session holds a worker's channel obligation, announce it to the controller now, per role-worker."
61
+ # A pull request opened by hand skipped git-pr, so nothing has labelled it.
62
+ # The verb is named only where the installed canon carries it, since a skill
63
+ # can reach a target ahead of the release that ships the verb.
64
+ if [ "$is_hand_opened" = true ] && canon labels apply --help >/dev/null 2>&1; then
65
+ msg="$msg Run canon labels apply ${url##*/} to label it."
66
+ fi
67
+
60
68
  jq -nc --arg msg "$msg" '{hookSpecificOutput:{hookEventName:"PostToolUse",additionalContext:$msg}}'
@@ -0,0 +1,171 @@
1
+ #!/usr/bin/env bash
2
+
3
+ # Claude Code sends a payload and closes stdin. A bare read with nothing feeding
4
+ # it blocks forever and holds the session open, so the read is bounded. `read`
5
+ # rather than `timeout cat`, which macOS does not ship.
6
+ IFS= read -r -d '' -t 2 input
7
+ [ -n "$input" ] || {
8
+ printf '%s reads a Claude Code hook payload on stdin and cannot be run by hand.\n' "${0##*/}" >&2
9
+ exit 1
10
+ }
11
+
12
+ # Every Bash call reaches this line, so the command is read in one jq pass and
13
+ # matched with a shell pattern before anything else costs a process.
14
+ fields=$(printf '%s' "$input" | jq -r 'if .tool_name == "Bash" then [(.cwd // ""), (.tool_input.command // "")] | @json else empty end')
15
+ [ -n "$fields" ] || exit 0
16
+
17
+ cmd=$(printf '%s' "$fields" | jq -r '.[1]')
18
+ case "$cmd" in
19
+ *'gh api'*) ;;
20
+ *) exit 0 ;;
21
+ esac
22
+
23
+ # A command that only mentions a post is not one: a heredoc writing a review
24
+ # body that quotes the command, or a search whose pattern holds it. The string
25
+ # is cut into the commands it runs, with heredoc bodies dropped and quoted text
26
+ # left whole, and only a command that starts with `gh api` is read further.
27
+ # Splitting on the separators outside quotes is what keeps `echo 'a; gh api'`
28
+ # one command rather than two.
29
+ IFS= read -r -d '' splitter <<'AWK'
30
+ { s = s (NR > 1 ? "\n" : "") $0 }
31
+ function emit() {
32
+ gsub(/\n/, " ", seg)
33
+ sub(/^[ \t]+/, "", seg)
34
+ if (seg != "") print seg
35
+ seg = ""
36
+ }
37
+ END {
38
+ n = length(s); q = ""; seg = ""; nt = 0; i = 1
39
+ while (i <= n) {
40
+ c = substr(s, i, 1)
41
+ if (q == "'") { seg = seg c; if (c == "'") q = ""; i++; continue }
42
+ if (q == "\"") {
43
+ if (c == "\\") { seg = seg c substr(s, i + 1, 1); i += 2; continue }
44
+ if (c == "\"") q = ""
45
+ seg = seg c; i++; continue
46
+ }
47
+ if (c == "\\") { seg = seg c substr(s, i + 1, 1); i += 2; continue }
48
+ if (c == "'" || c == "\"") { q = c; seg = seg c; i++; continue }
49
+ if (c == "#" && (seg == "" || substr(seg, length(seg), 1) ~ /[ \t]/)) {
50
+ while (i <= n && substr(s, i, 1) != "\n") i++
51
+ continue
52
+ }
53
+ if (c == "<" && substr(s, i, 2) == "<<" && substr(s, i, 3) != "<<<") {
54
+ j = i + 2; dash = 0
55
+ if (substr(s, j, 1) == "-") { dash = 1; j++ }
56
+ while (substr(s, j, 1) ~ /[ \t]/) j++
57
+ oq = substr(s, j, 1)
58
+ if (oq == "'" || oq == "\"") j++
59
+ tag = ""
60
+ while (substr(s, j, 1) ~ /[A-Za-z0-9_]/) { tag = tag substr(s, j, 1); j++ }
61
+ if (oq == "'" || oq == "\"") j++
62
+ if (tag != "") { nt++; tags[nt] = tag; dashes[nt] = dash; i = j; continue }
63
+ }
64
+ if (c == "\n") {
65
+ emit(); i++
66
+ for (t = 1; t <= nt; t++) {
67
+ while (i <= n) {
68
+ e = index(substr(s, i), "\n")
69
+ if (e == 0) { line = substr(s, i); i = n + 1 } else { line = substr(s, i, e - 1); i += e }
70
+ if (dashes[t]) sub(/^\t+/, "", line)
71
+ if (line == tags[t]) break
72
+ }
73
+ }
74
+ nt = 0; continue
75
+ }
76
+ if (c == ";" || c == "|" || c == "(" || c == ")" || c == "`" || c == "&") {
77
+ if (c == "&" && (substr(seg, length(seg), 1) ~ /[<>]/ || substr(s, i + 1, 1) == ">")) { seg = seg c; i++; continue }
78
+ emit(); i++; continue
79
+ }
80
+ seg = seg c; i++
81
+ }
82
+ emit()
83
+ }
84
+ AWK
85
+
86
+ # A block reaches the session through stderr, and stdout stays empty so nothing
87
+ # unframed lands in the hook protocol.
88
+ deny() {
89
+ printf '%s\n' "$1" >&2
90
+ exit 2
91
+ }
92
+
93
+ # The thread a review or a comment lands on: a review post or edit, and an issue
94
+ # or pull request comment post or edit. A pull request's inline comments are left
95
+ # out, since no shipped skill posts one.
96
+ seg="[^/[:space:]'\"]+"
97
+ end="(['\" ?]|\$)"
98
+ endpoint="(pulls/$seg/reviews(/$seg)?|issues/$seg/comments|issues/comments/$seg)$end"
99
+ starts_gh_api="^([A-Za-z_][A-Za-z0-9_]*=[^[:space:]]*[[:space:]]+)*((command|exec)[[:space:]]+)?gh[[:space:]]+api([[:space:]]|\$)"
100
+ body_inline="[[:space:]](-f|-F|--field|--raw-field)[[:space:]=]*['\"]?body="
101
+ body_file="[[:space:]](-F|--field)[[:space:]=]*['\"]?body=@([^'\"[:space:]]+)"
102
+
103
+ # Reads one command already known to start with `gh api` and returns when it
104
+ # has nothing to scan or the scan came back clean.
105
+ check() {
106
+ local call=$1 method='' file='' cwd out code warnings
107
+
108
+ [[ $call =~ $endpoint ]] || return 0
109
+
110
+ # gh sends POST when a field flag is present and no method is named, and the
111
+ # explicit method wins when one is. A call naming neither is a read.
112
+ if [[ $call =~ (-X|--method)[[:space:]=]*([A-Za-z]+) ]]; then
113
+ method=$(printf '%s' "${BASH_REMATCH[2]}" | tr '[:lower:]' '[:upper:]')
114
+ elif [[ $call =~ [[:space:]](-f|-F|--field|--raw-field|--input)([[:space:]=]|$) ]]; then
115
+ method=POST
116
+ fi
117
+ case "$method" in
118
+ POST | PATCH | PUT) ;;
119
+ *) return 0 ;;
120
+ esac
121
+
122
+ if [[ $call =~ $body_file ]]; then
123
+ file=${BASH_REMATCH[2]}
124
+ elif [[ $call =~ $body_inline ]]; then
125
+ deny 'This review or comment post passes its body inline, so the label scan cannot read it. Write the body to a file in an earlier call and pass it as -F body=@<file>.'
126
+ else
127
+ return 0
128
+ fi
129
+
130
+ [ "$file" != '-' ] || deny 'This post reads its body from stdin, so the label scan cannot read it. Write the body to a file in an earlier call and pass it as -F body=@<file>.'
131
+
132
+ if ! command -v canon >/dev/null 2>&1; then
133
+ jq -nc '{hookSpecificOutput:{hookEventName:"PreToolUse",additionalContext:"The label scan did not run on this review or comment post, since canon is not on PATH. Run canon labels scan --body-file on the body once it is."}}'
134
+ exit 0
135
+ fi
136
+
137
+ # The payload's own cwd is the directory a relative path was written against.
138
+ # The project dir and this process's directory can each be a different worktree.
139
+ cwd=$(printf '%s' "$fields" | jq -r '.[0]')
140
+ [ -n "$cwd" ] || cwd=${CLAUDE_PROJECT_DIR:-.}
141
+ case "$file" in
142
+ /*) ;;
143
+ *) file="$cwd/$file" ;;
144
+ esac
145
+
146
+ # A body written by the same call does not exist when this hook runs, so the
147
+ # post would go out unread by the scan. Blocking is the only verdict that holds.
148
+ [ -f "$file" ] || deny "The body file $file does not exist yet, so the label scan cannot read it. Write the body in an earlier call, then post it."
149
+
150
+ # No --title: a review body carries none, and a title would grade it as an
151
+ # ordinary pull request body.
152
+ out=$(cd "$cwd" && canon labels scan --body-file "$file" 2>&1)
153
+ code=$?
154
+ [ "$code" -ne 0 ] || return 0
155
+
156
+ warnings=$(printf '%s\n' "$out" | grep -F '!')
157
+ [ -n "$warnings" ] || warnings=$out
158
+ if [ "$code" -eq 2 ]; then
159
+ deny "The label scan found text this post would publish. Fix the body file and post again.
160
+ $warnings"
161
+ fi
162
+ deny "The label scan could not grade $file (exit $code). Fix what it reports and post again.
163
+ $warnings"
164
+ }
165
+
166
+ commands=$(printf '%s' "$cmd" | awk "$splitter")
167
+ while IFS= read -r line; do
168
+ [[ $line =~ $starts_gh_api ]] || continue
169
+ check "$line"
170
+ done <<<"$commands"
171
+ exit 0
@@ -27,6 +27,15 @@
27
27
  "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/scratch-guard.sh"
28
28
  }
29
29
  ]
30
+ },
31
+ {
32
+ "matcher": "Bash",
33
+ "hooks": [
34
+ {
35
+ "type": "command",
36
+ "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/review-post-scan.sh"
37
+ }
38
+ ]
30
39
  }
31
40
  ],
32
41
  "PostToolUse": [
@@ -1,14 +0,0 @@
1
- ---
2
- # Generated by `canon gov regen` from standards/context.md. Edit that frontmatter, not this file.
3
- description: Shape and content rules for canon/context/<domain>.md entries
4
- paths:
5
- - 'canon/context/**'
6
- ---
7
-
8
- # Context entry standards
9
-
10
- ## Authority
11
-
12
- - Follow the context standard for shape and content rules for canon/context/<domain>.md entries. It is the single source. Read it with `canon standards context`.
13
- - Before non-trivial work in a domain, read its `canon/context/<domain>.md` entry. Use `canon/context/index.md` to find it.
14
- - Leave the entry conforming when work in its domain changes what it describes.
@@ -1,12 +0,0 @@
1
- ---
2
- # Generated by `canon gov regen` from standards/design.md. Edit that frontmatter, not this file.
3
- description: Shape and content rules for canon/DESIGN.md
4
- paths:
5
- - 'canon/DESIGN.md'
6
- ---
7
-
8
- # Design standards
9
-
10
- ## Authority
11
-
12
- - Follow the design standard for shape and content rules for canon/DESIGN.md. It is the single source. Read it with `canon standards design`.
@@ -1,12 +0,0 @@
1
- ---
2
- # Generated by `canon gov regen` from standards/readme.md. Edit that frontmatter, not this file.
3
- description: Readme voice, structure, and content conventions
4
- paths:
5
- - '**/README.md'
6
- ---
7
-
8
- # Readme standards
9
-
10
- ## Authority
11
-
12
- - Follow the readme standard for readme voice, structure, and content conventions. It is the single source. Read it with `canon standards readme`.
@@ -1,13 +0,0 @@
1
- ---
2
- # Generated by `canon gov regen` from standards/rule.md. Edit that frontmatter, not this file.
3
- description: Rule frontmatter, body shape, and voice for .claude/rules files
4
- paths:
5
- - '.claude/rules/**/*.md'
6
- ---
7
-
8
- # Governance rule standards
9
-
10
- ## Authority
11
-
12
- - Follow the rule standard for rule frontmatter, body shape, and voice for .claude/rules files. It is the single source. Read it with `canon standards rule`.
13
- - Read it before writing or editing a rule. Do not work the shape from memory.
@@ -1,21 +0,0 @@
1
- ---
2
- # Generated by `canon gov regen` from standards/skill.md. Edit that frontmatter, not this file.
3
- description: Skill structure and authoring rules for Claude Code
4
- paths:
5
- - '.claude/skills/**/SKILL.md'
6
- - 'claude/skills/**/SKILL.md'
7
- - '.claude/skills/**/REQUIREMENT.md'
8
- - 'claude/skills/**/REQUIREMENT.md'
9
- ---
10
-
11
- # Claude skill standards
12
-
13
- ## Authority
14
-
15
- - Follow the skill standard for skill structure and authoring rules for Claude Code. It is the single source. Read it with `canon standards skill`.
16
- - Confirm `canon:create-skill`'s two creation-time questions are answered before a new `SKILL.md` lands, whether drafted by hand, by another skill, or by `canon:create-skill` itself. Carry the third question into the sibling `REQUIREMENT.md`'s `Must not` section as a review criterion rather than a gate.
17
- - Report it rather than proceeding silently when `canon:create-skill` does not resolve. It ships with the plugin and this rule ships with the CLI, so a project that installed governance alone does not have it.
18
- - Re-read a skill body this session edited before invoking that skill again in the same session
19
- - Do not read a resolved file path in a held body as evidence the body is current
20
- - Follow the skill requirement standard for the shape a `REQUIREMENT.md` states. Read it with `canon standards skill-requirement`.
21
- - Follow the skill path standard for which root a path in a body resolves against and how a body cites a standard. Read it with `canon standards skill-paths`.
@@ -1,13 +0,0 @@
1
- ---
2
- # Generated by `canon gov regen` from standards/standard.md. Edit that frontmatter, not this file.
3
- description: Shape and content rules for authoring a standard
4
- paths:
5
- - 'standards/**/*.md'
6
- ---
7
-
8
- # Standard standards
9
-
10
- ## Authority
11
-
12
- - Follow the standard standard for shape and content rules for authoring a standard. It is the single source. Read it with `canon standards standard`.
13
- - Read it before writing or editing a standard. Do not work the shape from memory.
package/scripts/config.sh DELETED
@@ -1,11 +0,0 @@
1
- #!/usr/bin/env bash
2
-
3
- if [ -z "${GITHUB_ORG:-}" ]; then
4
- _origin_url="$(git config --get remote.origin.url 2>/dev/null || true)"
5
- if [ -n "$_origin_url" ]; then
6
- GITHUB_ORG="$(echo "$_origin_url" | sed -nE 's#^(git@github\.com:|https://github\.com/)([^/]+)/.+#\2#p')"
7
- fi
8
- unset _origin_url
9
- fi
10
-
11
- export GITHUB_ORG="${GITHUB_ORG:-}"
@@ -1,14 +0,0 @@
1
- #!/usr/bin/env bash
2
-
3
- # Sole survivor of the gov lib. Called once per rule file inside install and
4
- # sandbox loops, where routing through the CLI would cost a process per file.
5
-
6
- rule_subdir() {
7
- local src="$1"
8
- local rules_root="${2:-$PROJECT_ROOT/governance/rules}"
9
- local rel="${src#"$rules_root/"}"
10
- local subdir
11
- subdir=$(dirname "$rel")
12
- [ "$subdir" = "." ] && subdir=""
13
- echo "$subdir"
14
- }