@erclx/canon 5.32.0 → 5.34.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 (250) hide show
  1. package/claude/.claude-plugin/plugin.json +1 -1
  2. package/claude/skills/api-design/REQUIREMENT.md +1 -2
  3. package/claude/skills/api-design/SKILL.md +2 -3
  4. package/claude/skills/api-design/references/adopted.md +1 -1
  5. package/claude/skills/auto-ship/SKILL.md +1 -1
  6. package/claude/skills/auto-ship/references/worktree-name.md +1 -1
  7. package/claude/skills/backlog-triage/REQUIREMENT.md +2 -2
  8. package/claude/skills/backlog-triage/SKILL.md +1 -1
  9. package/claude/skills/canon-cli/REQUIREMENT.md +16 -13
  10. package/claude/skills/canon-cli/SKILL.md +75 -32
  11. package/claude/skills/canon-feedback/REQUIREMENT.md +1 -0
  12. package/claude/skills/canon-feedback/SKILL.md +11 -29
  13. package/claude/skills/canon-feedback/references/template.md +53 -0
  14. package/claude/skills/{canvas → canvas-board}/REQUIREMENT.md +2 -2
  15. package/claude/skills/{canvas → canvas-board}/SKILL.md +6 -3
  16. package/claude/skills/{canvas → canvas-board}/references/canvas.md +26 -3
  17. package/{standards → claude/skills/canvas-board/references}/design.md +11 -2
  18. package/claude/skills/code-craft/REQUIREMENT.md +1 -1
  19. package/claude/skills/code-craft/SKILL.md +0 -1
  20. package/claude/skills/context-fold/REQUIREMENT.md +25 -33
  21. package/claude/skills/context-fold/SKILL.md +25 -70
  22. package/claude/skills/context-fold/references/classify.md +3 -3
  23. package/claude/skills/context-fold/references/completion-report.md +4 -4
  24. package/claude/skills/context-fold/references/context-refresh.md +5 -5
  25. package/{standards → claude/skills/context-fold/references}/context.md +16 -5
  26. package/claude/skills/context-fold/references/unusable-baseline.md +3 -5
  27. package/claude/skills/create-rule/SKILL.md +3 -3
  28. package/{standards → claude/skills/create-rule/references}/rule.md +15 -6
  29. package/claude/skills/create-skill/SKILL.md +2 -2
  30. package/{standards → claude/skills/create-skill/references}/skill.md +18 -15
  31. package/claude/skills/design-taste/REQUIREMENT.md +3 -3
  32. package/claude/skills/design-taste/SKILL.md +1 -1
  33. package/claude/skills/docs-sync/REQUIREMENT.md +1 -1
  34. package/claude/skills/docs-sync/SKILL.md +3 -3
  35. package/{standards → claude/skills/docs-sync/references}/docs.md +11 -0
  36. package/{standards → claude/skills/docs-sync/references}/readme.md +9 -2
  37. package/claude/skills/draft-and-pick/SKILL.md +5 -5
  38. package/claude/skills/draft-doc/SKILL.md +6 -6
  39. package/claude/skills/draft-figure/REQUIREMENT.md +2 -2
  40. package/claude/skills/draft-figure/SKILL.md +2 -2
  41. package/claude/skills/draft-figure/references/hand-drawn.md +1 -1
  42. package/claude/skills/draft-figure/references/mermaid-path.md +1 -1
  43. package/claude/skills/draft-identity/REQUIREMENT.md +2 -2
  44. package/claude/skills/draft-identity/SKILL.md +2 -2
  45. package/claude/skills/draft-ready/SKILL.md +1 -1
  46. package/claude/skills/git-branch/SKILL.md +1 -1
  47. package/{standards → claude/skills/git-branch/references}/branch.md +1 -1
  48. package/claude/skills/git-commit/SKILL.md +2 -2
  49. package/claude/skills/git-followup/REQUIREMENT.md +1 -1
  50. package/claude/skills/git-followup/SKILL.md +1 -1
  51. package/claude/skills/git-issue/SKILL.md +1 -1
  52. package/claude/skills/git-issue/references/issue.md +4 -1
  53. package/claude/skills/git-pr/SKILL.md +13 -13
  54. package/claude/skills/git-pr/references/evidence.md +2 -2
  55. package/claude/skills/git-pr/references/final-command.md +38 -14
  56. package/claude/skills/git-pr/references/labels.md +5 -1
  57. package/{standards → claude/skills/git-pr/references}/pr.md +19 -9
  58. package/{standards → claude/skills/git-pr/references}/publish.md +4 -2
  59. package/claude/skills/git-pr/references/task-number.md +1 -1
  60. package/claude/skills/git-ship/SKILL.md +15 -14
  61. package/claude/skills/git-split/SKILL.md +3 -3
  62. package/claude/skills/git-stage/SKILL.md +1 -1
  63. package/claude/skills/plan-feature/REQUIREMENT.md +1 -1
  64. package/claude/skills/review-address/SKILL.md +1 -1
  65. package/claude/skills/review-address/references/post-review-findings.md +1 -1
  66. package/claude/skills/review-craft/references/ui.md +1 -1
  67. package/claude/skills/review-pr/REQUIREMENT.md +1 -1
  68. package/claude/skills/review-pr/SKILL.md +3 -3
  69. package/claude/skills/review-pr/references/body-variants.md +2 -2
  70. package/claude/skills/review-pr/references/key-changes.md +3 -3
  71. package/claude/skills/review-pr/references/post.md +4 -2
  72. package/claude/skills/review-ui/REQUIREMENT.md +1 -1
  73. package/claude/skills/review-ui/SKILL.md +8 -6
  74. package/claude/skills/review-ui/references/driving.md +8 -8
  75. package/claude/skills/review-ui/references/post.md +53 -17
  76. package/claude/skills/role-orchestrator/REQUIREMENT.md +3 -0
  77. package/claude/skills/role-orchestrator/SKILL.md +2 -2
  78. package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +2 -2
  79. package/claude/skills/role-orchestrator/references/orchestrator-handoff.md +8 -5
  80. package/claude/skills/role-orchestrator/references/orchestrator-launch.md +32 -1
  81. package/claude/skills/role-orchestrator/references/orchestrator-refill.md +2 -2
  82. package/claude/skills/role-orchestrator/references/orchestrator-resume.md +3 -0
  83. package/claude/skills/role-planner/REQUIREMENT.md +2 -1
  84. package/claude/skills/role-planner/SKILL.md +13 -2
  85. package/claude/skills/role-reviewer/SKILL.md +1 -1
  86. package/claude/skills/role-worker/REQUIREMENT.md +1 -0
  87. package/claude/skills/role-worker/SKILL.md +9 -1
  88. package/claude/skills/session-map/SKILL.md +1 -1
  89. package/claude/skills/session-resume/REQUIREMENT.md +2 -2
  90. package/claude/skills/session-worktree/SKILL.md +5 -5
  91. package/claude/skills/target-setup/SKILL.md +10 -4
  92. package/claude/skills/target-setup/references/detect.md +20 -3
  93. package/claude/skills/task-board/REQUIREMENT.md +7 -12
  94. package/claude/skills/task-board/SKILL.md +5 -17
  95. package/claude/skills/task-board/references/tasks.md +2 -2
  96. package/claude/skills/task-complete/REQUIREMENT.md +47 -0
  97. package/claude/skills/task-complete/SKILL.md +86 -0
  98. package/claude/skills/{task-board → task-complete}/references/archive.md +4 -4
  99. package/claude/skills/{context-fold → task-complete}/references/receipt-sweep.md +5 -5
  100. package/claude/skills/task-complete/references/unusable-baseline.md +19 -0
  101. package/claude/skills/teach-workspace/REQUIREMENT.md +6 -2
  102. package/claude/skills/teach-workspace/SKILL.md +8 -4
  103. package/claude/skills/teach-workspace/references/lesson-craft.md +1 -1
  104. package/claude/skills/{context-fold/references/promoted-pages.md → teach-workspace/references/promotion-fold.md} +3 -3
  105. package/claude/skills/teach-workspace/references/promotion.md +3 -3
  106. package/claude/skills/test-craft/REQUIREMENT.md +2 -3
  107. package/claude/skills/test-craft/SKILL.md +15 -3
  108. package/claude/skills/ui-checklist/SKILL.md +1 -1
  109. package/claude/skills/ux-audit/SKILL.md +1 -1
  110. package/claude/skills/ux-measure/SKILL.md +1 -1
  111. package/docs/agents/canvas.md +6 -2
  112. package/docs/agents/commands.md +4 -3
  113. package/docs/agents/context-audit-checks.md +4 -4
  114. package/docs/agents/gov-detect.md +47 -0
  115. package/docs/agents/index.md +7 -4
  116. package/docs/agents/indexes.md +1 -1
  117. package/docs/agents/install-and-sync.md +10 -11
  118. package/docs/agents/key-changes.md +5 -5
  119. package/docs/agents/label-coverage.md +11 -0
  120. package/docs/agents/migrate.md +1 -1
  121. package/docs/agents/pr-evidence-layout.md +70 -0
  122. package/docs/agents/pr-evidence.md +8 -9
  123. package/docs/agents/pr-frames.md +7 -2
  124. package/docs/agents/pr-lift.md +132 -0
  125. package/docs/agents/records-push.md +2 -2
  126. package/docs/agents/records-stale.md +12 -47
  127. package/docs/agents/records-validate.md +3 -7
  128. package/docs/agents/records.md +5 -27
  129. package/docs/agents/rule-citations.md +1 -1
  130. package/docs/agents/sandbox.md +4 -4
  131. package/docs/agents/scripting.md +2 -1
  132. package/docs/agents/skills-audit.md +1 -1
  133. package/docs/agents/tasks-plans.md +2 -2
  134. package/docs/agents/tasks.md +2 -2
  135. package/docs/target/migrations.md +10 -2
  136. package/docs/target/projects.md +4 -4
  137. package/docs/target/sync.md +1 -1
  138. package/docs/workflow/operating-model.md +2 -2
  139. package/docs/workflow/parallel-features.md +7 -7
  140. package/docs/workflow/skill-map-on-request.md +9 -9
  141. package/docs/workflow/skill-map.md +12 -11
  142. package/docs/workflow/visual-design-workflow.md +2 -2
  143. package/governance/detect.toml +201 -0
  144. package/governance/renames.toml +0 -11
  145. package/governance/rules/canon/context.md +13 -0
  146. package/governance/rules/claude/skill.md +17 -0
  147. package/governance/rules/code/000-code.md +1 -13
  148. package/governance/rules/code/010-testing.md +2 -30
  149. package/governance/rules/ui/400-ui.md +1 -1
  150. package/governance/rules/ui/480-canvas.md +13 -0
  151. package/governance/stacks/base.toml +2 -4
  152. package/package.json +4 -4
  153. package/scripts/core/install-check.sh +3 -3
  154. package/scripts/core/regen-hero.ts +0 -6
  155. package/scripts/lib/ui.sh +0 -168
  156. package/src/audits/catalog.ts +0 -1
  157. package/src/canvas/arrange.ts +66 -0
  158. package/src/canvas/capture.ts +20 -6
  159. package/src/canvas/content.ts +39 -0
  160. package/src/canvas/fit.ts +238 -0
  161. package/src/canvas/tokens.ts +28 -10
  162. package/src/capture/frame-values.ts +2 -32
  163. package/src/claude/cases/misc.ts +1 -1
  164. package/src/claude/cases/setup.ts +4 -0
  165. package/src/claude/cases/workflow.ts +6 -1
  166. package/src/claude/seeds.ts +1 -1
  167. package/src/claude/skills-audit.ts +2 -2
  168. package/src/cli/help.ts +3 -1
  169. package/src/cli/ui.ts +2 -3
  170. package/src/commands/canvas.ts +179 -1
  171. package/src/commands/claude.ts +1 -1
  172. package/src/commands/gov.ts +146 -16
  173. package/src/commands/labels.ts +326 -7
  174. package/src/commands/migrate.ts +6 -1
  175. package/src/commands/pr.ts +377 -2
  176. package/src/commands/records.ts +13 -399
  177. package/src/commands/sandbox.ts +3 -6
  178. package/src/commands/tasks.ts +3 -3
  179. package/src/context/audit.ts +5 -5
  180. package/src/context/folders.ts +1 -1
  181. package/src/design/budget.ts +1 -1
  182. package/src/design/components.ts +1 -1
  183. package/src/design/document.ts +1 -1
  184. package/src/design/regen.ts +1 -1
  185. package/src/design/render.ts +1 -1
  186. package/src/feedback/body.ts +26 -5
  187. package/src/gate/measures.ts +59 -45
  188. package/src/gate/sequencer.ts +56 -1
  189. package/src/gate/stages.ts +4 -11
  190. package/src/gov/detect.ts +301 -0
  191. package/src/gov/install.ts +2 -3
  192. package/src/gov/list.ts +23 -2
  193. package/src/gov/stacks.ts +10 -1
  194. package/src/hooks/post-merge.ts +1 -1
  195. package/src/labels/apply.ts +39 -0
  196. package/src/labels/format.ts +3 -3
  197. package/src/labels/phase.ts +3 -3
  198. package/src/labels/sections.ts +43 -0
  199. package/src/migrate/skill-names.ts +20 -1
  200. package/src/pr/evidence.ts +218 -45
  201. package/src/pr/frames.ts +23 -3
  202. package/src/pr/lift.ts +418 -0
  203. package/src/pr/paths.ts +4 -4
  204. package/src/pr/rest.ts +3 -0
  205. package/src/pr/review-scope.ts +76 -0
  206. package/src/records/backup.ts +16 -6
  207. package/src/records/stale-canonical.ts +31 -1
  208. package/src/records/validate.ts +2 -188
  209. package/src/sandbox/census.ts +1 -1
  210. package/src/sandbox/coverage.ts +2 -6
  211. package/src/sandbox/equivalence.ts +19 -51
  212. package/src/sandbox/expect.ts +4 -4
  213. package/src/sandbox/provision.ts +13 -36
  214. package/src/sandbox/scenario.ts +26 -48
  215. package/src/sandbox/tree.ts +2 -6
  216. package/src/standards/read.ts +14 -1
  217. package/src/sync/git.ts +1 -1
  218. package/src/tasks/archive.ts +5 -5
  219. package/src/tasks/branch.ts +3 -3
  220. package/src/tasks/label.ts +1 -1
  221. package/src/tasks/record.ts +2 -2
  222. package/src/tasks/validate.ts +1 -1
  223. package/standards/index.md +0 -13
  224. package/standards/standard.md +1 -7
  225. package/tooling/base/reference.md +1 -1
  226. package/tooling/base/seeds/.github/pull_request_template.md +2 -2
  227. package/tooling/claude/reference.md +1 -1
  228. package/tooling/claude/seeds/.claude/hooks/pr-create-log.sh +9 -1
  229. package/tooling/claude/seeds/.claude/hooks/review-post-scan.sh +171 -0
  230. package/tooling/claude/seeds/.claude/settings.json +9 -0
  231. package/governance/rules/standards/context.md +0 -14
  232. package/governance/rules/standards/design.md +0 -12
  233. package/governance/rules/standards/readme.md +0 -12
  234. package/governance/rules/standards/rule.md +0 -13
  235. package/governance/rules/standards/skill.md +0 -21
  236. package/governance/rules/standards/standard.md +0 -13
  237. package/scripts/config.sh +0 -11
  238. package/scripts/lib/gov.sh +0 -14
  239. package/scripts/lib/sandbox-fixtures.sh +0 -191
  240. package/scripts/lib/sandbox-git.sh +0 -125
  241. package/scripts/lib/sandbox-path.sh +0 -50
  242. package/scripts/sandbox-hook.sh +0 -99
  243. package/src/gov/standard-rules.ts +0 -179
  244. package/src/records/migrate.ts +0 -78
  245. package/src/records/stale.ts +0 -243
  246. package/src/sandbox/hooks.ts +0 -128
  247. /package/{standards → claude/skills/draft-figure/references}/figures.md +0 -0
  248. /package/{standards → claude/skills/git-branch/references}/slug.md +0 -0
  249. /package/{standards → claude/skills/git-commit/references}/commit.md +0 -0
  250. /package/{standards → claude/skills/git-commit/references}/versioning.md +0 -0
@@ -9,12 +9,15 @@ import {
9
9
  DEFAULT_FRAME,
10
10
  listPages,
11
11
  moveFrame,
12
+ pruneLayout,
12
13
  readPage,
13
14
  readSelection,
14
15
  renamePage,
15
16
  resizeFrame,
16
17
  } from '@/canvas/content'
18
+ import { planArrange } from '@/canvas/arrange'
17
19
  import { captureCanvas, captureComposite } from '@/canvas/capture'
20
+ import { fitCanvas, isOwed } from '@/canvas/fit'
18
21
  import {
19
22
  type EditRefused,
20
23
  editFrameAtIndex,
@@ -148,7 +151,7 @@ export function register(program: Command): void {
148
151
  .command('canvas')
149
152
  .helpOption('-h, --help', 'Show this help message')
150
153
  .description(
151
- 'Local canvas of pages and HTML frames (serve, list, page, frame, selection, edit, editing, capture)',
154
+ 'Local canvas of pages and HTML frames (serve, list, page, frame, selection, edit, editing, capture, fit, arrange)',
152
155
  )
153
156
  .addHelpText(
154
157
  'after',
@@ -167,6 +170,8 @@ export function register(program: Command): void {
167
170
  " canon canvas edit drafts/hero --element 4 --set 'color=var(--color-accent)'",
168
171
  ' canon canvas editing drafts/hero',
169
172
  ' canon canvas capture drafts/hero',
173
+ ' canon canvas fit drafts/hero --json',
174
+ ' canon canvas arrange drafts --check',
170
175
  ' canon canvas list --json',
171
176
  '',
172
177
  ].join('\n'),
@@ -728,6 +733,179 @@ export function register(program: Command): void {
728
733
  outro()
729
734
  if (failed) process.exitCode = 1
730
735
  })
736
+
737
+ withRoot(
738
+ canvas
739
+ .command('fit')
740
+ .description("Set each frame's height to its content")
741
+ .argument('<target>', '<page>/<frame> for one frame, <page> for all')
742
+ .option('--check', 'Report what is owed and write nothing'),
743
+ )
744
+ .addHelpText(
745
+ 'after',
746
+ [
747
+ '',
748
+ 'Measures each frame in the capture browser at its box width, through',
749
+ 'the served address, so the faces the canvas serves are the ones read.',
750
+ 'The height is the content height rounded up, with no margin. A',
751
+ 'content wider than the box is reported and the box is never widened.',
752
+ 'A frame whose body text names a family the canvas does not serve is',
753
+ 'reported as font, since its height may not hold in another browser.',
754
+ '',
755
+ 'Run canvas arrange after a fit that grew a frame, since rows below',
756
+ 'do not move on their own.',
757
+ '',
758
+ 'Exit codes:',
759
+ ' 0 written, or nothing owed under --check',
760
+ ' 1 refused or the browser failed',
761
+ ' 2 --check found a height off, a wide frame, or a font',
762
+ '',
763
+ ].join('\n'),
764
+ )
765
+ .action(async (target: string, opts: FitOptions) => {
766
+ process.exitCode = await runFit(target, opts)
767
+ })
768
+
769
+ withRoot(
770
+ canvas
771
+ .command('arrange')
772
+ .description('Re-lay a page in rows on the frame gap')
773
+ .argument('<page>', 'Page to arrange')
774
+ .option('--check', 'Report what is owed and write nothing'),
775
+ )
776
+ .addHelpText(
777
+ 'after',
778
+ [
779
+ '',
780
+ 'Keeps the rows the frames already sit in, orders each left to right on',
781
+ 'the frame gap, stacks the rows on their tallest frame, and drops',
782
+ 'layout entries whose frame file is gone.',
783
+ '',
784
+ 'Exit codes:',
785
+ ' 0 written, or nothing owed under --check',
786
+ ' 1 refused',
787
+ ' 2 --check found frames to move or entries to drop',
788
+ '',
789
+ ].join('\n'),
790
+ )
791
+ .action(async (pageName: string, opts: FitOptions) => {
792
+ process.exitCode = await runArrange(pageName, opts)
793
+ })
794
+ }
795
+
796
+ interface FitOptions extends RootOptions {
797
+ readonly check?: boolean
798
+ }
799
+
800
+ async function runFit(target: string, opts: FitOptions): Promise<number> {
801
+ const emitJson = opts.json ?? false
802
+ const resolved = await resolveRoot(opts)
803
+ if (!resolved.ok) return refuse(resolved, emitJson)
804
+
805
+ const check = opts.check ?? false
806
+ let outcome: Awaited<ReturnType<typeof fitCanvas>>
807
+ try {
808
+ outcome = await fitCanvas(resolved.root, target, !check)
809
+ } catch (error) {
810
+ const detail = error instanceof Error ? error.message : String(error)
811
+ return refuse({ ok: false, reason: 'capture-failed', detail }, emitJson)
812
+ }
813
+ if (!outcome.ok) return refuse(outcome, emitJson)
814
+
815
+ const frames = outcome.rows.map((row) => ({ ...row, owed: isOwed(row) }))
816
+ const owed = frames.some((row) => row.owed)
817
+ if (emitJson) writeJson({ ok: true, written: outcome.written, frames })
818
+ intro(BANNER)
819
+ for (const row of frames) {
820
+ const name = `${row.page}/${row.frame}`
821
+ if (row.fitted === row.height) logInfo(`${name} fits at ${row.height}`)
822
+ else if (outcome.written) logAdd(`${name} ${row.height} to ${row.fitted}`)
823
+ else logStep(`${name} owes ${row.height} to ${row.fitted}`)
824
+ if (row.wide) logWarn(`${name} is wider than its box, so widen the box`)
825
+ if (row.viewport) {
826
+ logWarn(
827
+ `${name} sets a height in viewport units, so its height was not written and a fixed height fits it`,
828
+ )
829
+ }
830
+ if (row.font) {
831
+ logWarn(
832
+ `${name} sets ${row.family}, which the canvas does not serve, so its height may not hold in another browser`,
833
+ )
834
+ }
835
+ }
836
+ outro()
837
+ return check && owed ? 2 : 0
838
+ }
839
+
840
+ async function runArrange(pageName: string, opts: FitOptions): Promise<number> {
841
+ const emitJson = opts.json ?? false
842
+ const resolved = await resolveRoot(opts)
843
+ if (!resolved.ok) return refuse(resolved, emitJson)
844
+ const { root } = resolved
845
+
846
+ const page = readPage(root, pageName)
847
+ if (!page) {
848
+ return refuse(
849
+ {
850
+ ok: false,
851
+ reason: 'no-page',
852
+ detail: `page ${pageName} does not exist`,
853
+ },
854
+ emitJson,
855
+ )
856
+ }
857
+ if (page.layoutIssue) {
858
+ return refuse(
859
+ {
860
+ ok: false,
861
+ reason: 'malformed-layout',
862
+ detail: `${pageName}/layout.json does not parse`,
863
+ },
864
+ emitJson,
865
+ )
866
+ }
867
+
868
+ const check = opts.check ?? false
869
+ const pruned = pruneLayout(root, pageName, !check)
870
+ if (!pruned.ok) return refuse(pruned, emitJson)
871
+
872
+ const moves = planArrange(page.frames)
873
+ if (!check) {
874
+ for (const move of moves) {
875
+ const outcome = resizeFrame(root, pageName, move.name, move.to)
876
+ if (!outcome.ok) return refuse(outcome, emitJson)
877
+ }
878
+ }
879
+
880
+ if (emitJson) {
881
+ writeJson({
882
+ ok: true,
883
+ page: pageName,
884
+ written: !check,
885
+ moved: moves.map((move) => ({
886
+ frame: move.name,
887
+ from: { x: move.from.x, y: move.from.y },
888
+ to: { x: move.to.x, y: move.to.y },
889
+ })),
890
+ dropped: pruned.dropped,
891
+ })
892
+ }
893
+ intro(BANNER)
894
+ for (const move of moves) {
895
+ const text = `${pageName}/${move.name} ${move.from.x}, ${move.from.y} to ${move.to.x}, ${move.to.y}`
896
+ if (check) logStep(`${text} is owed`)
897
+ else logAdd(text)
898
+ }
899
+ for (const name of pruned.dropped) {
900
+ logWarn(
901
+ `${pageName}/${name} has no frame file, so its entry is ${check ? 'owed a drop' : 'dropped'}`,
902
+ )
903
+ }
904
+ if (moves.length === 0 && pruned.dropped.length === 0) {
905
+ logInfo(`${pageName} is already laid out`)
906
+ }
907
+ outro()
908
+ return check && (moves.length > 0 || pruned.dropped.length > 0) ? 2 : 0
731
909
  }
732
910
 
733
911
  interface CaptureOptions extends RootOptions {
@@ -386,7 +386,7 @@ export function register(program: Command): void {
386
386
  skills
387
387
  .command('audit')
388
388
  .description(
389
- 'Report both skill corpora against the mechanical rules in standards/skill.md',
389
+ 'Report both skill corpora against the mechanical rules in claude/skills/create-skill/references/skill.md',
390
390
  )
391
391
  .argument('[path]', 'Project root, defaulting to the current directory')
392
392
  .helpOption('-h, --help', 'Show this help message')
@@ -12,7 +12,6 @@ import { checkoutMismatchWarning, PROJECT_ROOT } from '@/roots/project'
12
12
  import { creationRel, SCRATCH } from '@/roots/record'
13
13
  import { createGovAdapter } from '@/gov/adapter'
14
14
  import { regenConsumedRules } from '@/gov/consumed'
15
- import { regenStandardRules } from '@/gov/standard-rules'
16
15
  import { installRules, lookupRules } from '@/gov/install'
17
16
  import { buildGovCatalog, describeRule, describeStack } from '@/gov/list'
18
17
  import { buildRulesPayload, listRuleFiles } from '@/gov/payload'
@@ -20,6 +19,7 @@ import {
20
19
  govStackExists,
21
20
  listGovStacks,
22
21
  mergeExtraRules,
22
+ resolveExtraRules,
23
23
  resolveRules,
24
24
  } from '@/gov/stacks'
25
25
  import {
@@ -52,6 +52,7 @@ import {
52
52
  type TestOrderReport,
53
53
  } from '@/gov/test-order'
54
54
  import { recordStamp, runDomainSync } from '@/sync/engine'
55
+ import { detectRules } from '@/gov/detect'
55
56
  import { writeChainStamp } from '@/sync/stamp'
56
57
  import { resolveTarget } from '@/targets/validate'
57
58
  import {
@@ -154,6 +155,11 @@ export function register(program: Command): void {
154
155
  ' canon gov install react',
155
156
  ' canon gov install node ../my-app',
156
157
  ' canon gov install astro --add 200-react,260-shadcn ../my-app',
158
+ ' canon gov install --add tasks,plan ../my-app',
159
+ '',
160
+ 'With --add and no stack, the named rules install alone. A first',
161
+ 'positional naming a stack is read as the stack, so pass . or a path',
162
+ 'carrying a slash to force a target that shares a stack name.',
157
163
  '',
158
164
  ].join('\n'),
159
165
  )
@@ -191,18 +197,14 @@ export function register(program: Command): void {
191
197
 
192
198
  gov
193
199
  .command('regen')
194
- .description(
195
- "Rebuild the standard-pointer rules and a repository's own .claude/rules/",
196
- )
200
+ .description("Rebuild a repository's own .claude/rules/")
197
201
  .helpOption('-h, --help', 'Show this help message')
198
202
  .option('--root <path>', 'Repository root to regenerate', PROJECT_ROOT)
199
203
  .addHelpText(
200
204
  'after',
201
205
  [
202
206
  '',
203
- 'First writes governance/rules/standards/ from the paths: and rule:',
204
- 'fields in each standard under standards/. Then reads',
205
- 'internal/governance.toml and installs the stack it names, plus any',
207
+ 'Reads internal/governance.toml and installs the stack it names, plus any',
206
208
  'rules under internal/rules/. Unlike install and sync, this runs',
207
209
  'against the toolkit root, whose .claude/rules/ is produced output.',
208
210
  '',
@@ -226,6 +228,31 @@ export function register(program: Command): void {
226
228
  process.exitCode = runList(opts)
227
229
  })
228
230
 
231
+ gov
232
+ .command('detect')
233
+ .description('Propose opt-in rules from the evidence a target holds')
234
+ .argument('[target]', 'Target directory', '.')
235
+ .helpOption('-h, --help', 'Show this help message')
236
+ .option('--stack <stack>', 'Leave out every rule this stack resolves')
237
+ .option('--json', 'Emit machine-readable JSON')
238
+ .addHelpText(
239
+ 'after',
240
+ [
241
+ '',
242
+ 'Reads governance/detect.toml and matches each rule against the',
243
+ "target's manifests and git-listed paths to depth two. Pass the",
244
+ 'proposals to canon gov install --add once confirmed.',
245
+ '',
246
+ 'Examples:',
247
+ ' canon gov detect --json',
248
+ ' canon gov detect ../my-app --stack node --json',
249
+ '',
250
+ ].join('\n'),
251
+ )
252
+ .action(async (target: string, opts: DetectOptions) => {
253
+ process.exitCode = await runDetect(target, opts)
254
+ })
255
+
229
256
  gov
230
257
  .command('test-order')
231
258
  .description(
@@ -1075,6 +1102,7 @@ function runList(opts: ListOptions): number {
1075
1102
  ...(sections.stacks ? { stacks: catalog.stacks } : {}),
1076
1103
  ...(sections.rules ? { rules: catalog.rules } : {}),
1077
1104
  unreferenced: catalog.unreferenced,
1105
+ staleDetect: catalog.staleDetect,
1078
1106
  })}\n`,
1079
1107
  )
1080
1108
  return 0
@@ -1102,6 +1130,51 @@ function runList(opts: ListOptions): number {
1102
1130
  return 0
1103
1131
  }
1104
1132
 
1133
+ interface DetectOptions {
1134
+ readonly stack?: string
1135
+ readonly json?: boolean
1136
+ }
1137
+
1138
+ async function runDetect(target: string, opts: DetectOptions): Promise<number> {
1139
+ const resolved = resolve(target)
1140
+ if (!existsSync(resolved)) {
1141
+ logError(`Target not found: ${target}`)
1142
+ return 1
1143
+ }
1144
+
1145
+ const result = await detectRules(PROJECT_ROOT, resolved, opts.stack)
1146
+ if (!result.ok) {
1147
+ logError(`Stack not found: ${result.missingStack}`)
1148
+ return 1
1149
+ }
1150
+
1151
+ if (opts.json) {
1152
+ process.stdout.write(
1153
+ `${JSON.stringify({
1154
+ target: resolved,
1155
+ stack: opts.stack ?? null,
1156
+ proposals: result.proposals,
1157
+ })}\n`,
1158
+ )
1159
+ return 0
1160
+ }
1161
+
1162
+ intro('canon gov detect')
1163
+ if (result.proposals.length === 0) {
1164
+ logInfo('No opt-in rule matched this target')
1165
+ } else {
1166
+ logStep('Proposed rules')
1167
+ for (const proposal of result.proposals) {
1168
+ const reasons = proposal.evidence
1169
+ .map((evidence) => `${evidence.match} in ${evidence.path}`)
1170
+ .join(', ')
1171
+ logInfo(`${proposal.rule}: ${reasons}`)
1172
+ }
1173
+ }
1174
+ outro()
1175
+ return 0
1176
+ }
1177
+
1105
1178
  /**
1106
1179
  * Silent on success so the consumed-copy stage that calls it stays quiet. It is
1107
1180
  * the only work that stage does now. The installed set is readable on disk, so
@@ -1121,14 +1194,6 @@ async function runRegen(
1121
1194
 
1122
1195
  const root = resolve(opts.root ?? PROJECT_ROOT)
1123
1196
 
1124
- // The consumed copy installs from `governance/rules/`, so the band this
1125
- // writes has to be fresh before that copy is built from it.
1126
- const generated = await regenStandardRules(root)
1127
- if (!generated.ok) {
1128
- process.stderr.write(`Standard-rules regen failed: ${generated.reason}\n`)
1129
- return 1
1130
- }
1131
-
1132
1197
  const result = await regenConsumedRules(root)
1133
1198
 
1134
1199
  if (!result.ok) {
@@ -1184,6 +1249,11 @@ async function runInstall(
1184
1249
  ): Promise<number> {
1185
1250
  intro('canon gov install')
1186
1251
 
1252
+ const add = opts.add ?? ''
1253
+ if (add !== '' && isAddWithoutStack(stack, target)) {
1254
+ return installExtras(add, stack ?? target)
1255
+ }
1256
+
1187
1257
  const resolved = resolveTarget(target, PROJECT_ROOT)
1188
1258
  if (typeof resolved === 'number') return resolved
1189
1259
 
@@ -1207,7 +1277,6 @@ async function runInstall(
1207
1277
  return 1
1208
1278
  }
1209
1279
 
1210
- const add = opts.add ?? ''
1211
1280
  const rules =
1212
1281
  add === '' ? resolution.rules : mergeExtraRules(resolution.rules, add)
1213
1282
 
@@ -1260,6 +1329,67 @@ async function runInstall(
1260
1329
  return 0
1261
1330
  }
1262
1331
 
1332
+ /**
1333
+ * Whether `--add` runs with no stack under it. Commander fills the first
1334
+ * positional before the second, so `--add a,b ../my-app` arrives with the
1335
+ * target in `stack` and the default `.` in `target`. A first positional that
1336
+ * names a stack keeps today's meaning, so a folder named `node` reads as the
1337
+ * stack, and `.` or a path carrying a slash is how a caller forces the target.
1338
+ */
1339
+ function isAddWithoutStack(stack: string | undefined, target: string): boolean {
1340
+ if (stack === undefined) return true
1341
+ if (target !== '.') return false
1342
+ if (stack === '.' || stack.includes('/')) return true
1343
+ return !govStackExists(PROJECT_ROOT, stack)
1344
+ }
1345
+
1346
+ /**
1347
+ * Installs `--add` names alone. The chain stamp is left as it was, since the
1348
+ * extras are not a stack, and overwriting the chain would cost a target the
1349
+ * missing-rule report its stack install recorded.
1350
+ */
1351
+ async function installExtras(add: string, target: string): Promise<number> {
1352
+ const resolved = resolveTarget(target, PROJECT_ROOT)
1353
+ if (typeof resolved === 'number') return resolved
1354
+
1355
+ logStep('Resolving extras with no stack')
1356
+
1357
+ const { found, missing } = lookupRules(PROJECT_ROOT, resolveExtraRules(add))
1358
+ for (const rule of missing) logWarn(`${rule} (source not found, skipping)`)
1359
+ for (const source of found) logInfo(source.rule)
1360
+
1361
+ if (found.length === 0) {
1362
+ logError('No named rule resolved, so nothing was installed.')
1363
+ outro()
1364
+ return 1
1365
+ }
1366
+
1367
+ const shouldInstall = await select({
1368
+ message: `Install ${found.length} rules to ${displayPath(target, CANON_RULES_REL)}?`,
1369
+ options: [
1370
+ { value: true, label: 'Yes' },
1371
+ { value: false, label: 'No' },
1372
+ ],
1373
+ nonInteractiveDefault: true,
1374
+ })
1375
+
1376
+ if (!shouldInstall) {
1377
+ logWarn('Cancelled')
1378
+ outro()
1379
+ return 0
1380
+ }
1381
+
1382
+ logStep('Installing rules')
1383
+ for (const rel of await installRules(found, resolved)) logAdd(rel)
1384
+
1385
+ await recordStamp(createGovAdapter(PROJECT_ROOT), resolved, new Date())
1386
+
1387
+ const { GREEN, NC } = palette(process.stderr)
1388
+ outro()
1389
+ process.stderr.write(`${GREEN}✓ Rules installed${NC}\n`)
1390
+ return 0
1391
+ }
1392
+
1263
1393
  /**
1264
1394
  * Emits the rules payload the `gov-*` skills read. The output lands in the
1265
1395
  * target's scratch directory rather than stdout because the file is the