@erclx/canon 5.30.0 → 5.32.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 (343) hide show
  1. package/README.md +1 -1
  2. package/claude/.claude-plugin/plugin.json +1 -1
  3. package/claude/skills/api-design/references/adopted.md +1 -1
  4. package/claude/skills/auto-ship/REQUIREMENT.md +4 -16
  5. package/claude/skills/auto-ship/SKILL.md +13 -58
  6. package/claude/skills/auto-ship/references/draft-mark.md +2 -2
  7. package/claude/skills/auto-ship/references/failure-recovery.md +1 -5
  8. package/claude/skills/auto-ship/references/tier-failures.md +1 -1
  9. package/claude/skills/auto-ship/references/ui-checklist.md +2 -2
  10. package/claude/skills/auto-ship/references/verb-absent.md +3 -27
  11. package/claude/skills/backlog-triage/SKILL.md +1 -1
  12. package/claude/skills/build-in-slices/REQUIREMENT.md +1 -1
  13. package/claude/skills/build-in-slices/references/adopted.md +1 -1
  14. package/claude/skills/canon-cli/REQUIREMENT.md +4 -6
  15. package/claude/skills/canon-cli/SKILL.md +2 -2
  16. package/claude/skills/canon-feedback/REQUIREMENT.md +1 -1
  17. package/claude/skills/canon-feedback/SKILL.md +10 -13
  18. package/claude/skills/canon-feedback-triage/REQUIREMENT.md +13 -14
  19. package/claude/skills/canon-feedback-triage/SKILL.md +50 -29
  20. package/claude/skills/canvas/REQUIREMENT.md +3 -4
  21. package/claude/skills/canvas/SKILL.md +5 -5
  22. package/{standards → claude/skills/canvas/references}/canvas.md +12 -1
  23. package/claude/skills/ci-workflow/REQUIREMENT.md +0 -1
  24. package/claude/skills/context-fold/REQUIREMENT.md +8 -20
  25. package/claude/skills/context-fold/SKILL.md +21 -45
  26. package/claude/skills/context-fold/references/classify.md +7 -8
  27. package/claude/skills/context-fold/references/completion-report.md +2 -6
  28. package/claude/skills/context-fold/references/context-refresh.md +5 -15
  29. package/claude/skills/context-fold/references/promoted-pages.md +3 -3
  30. package/claude/skills/context-fold/references/receipt-sweep.md +6 -17
  31. package/claude/skills/context-fold/references/unusable-baseline.md +4 -6
  32. package/claude/skills/create-rule/REQUIREMENT.md +0 -1
  33. package/claude/skills/create-skill/REQUIREMENT.md +6 -4
  34. package/claude/skills/create-skill/SKILL.md +9 -8
  35. package/{standards → claude/skills/create-skill/references}/skill-paths.md +1 -0
  36. package/claude/skills/decision-escalate/REQUIREMENT.md +1 -1
  37. package/claude/skills/decision-escalate/SKILL.md +1 -1
  38. package/claude/skills/design-taste/REQUIREMENT.md +0 -2
  39. package/claude/skills/design-taste/SKILL.md +2 -4
  40. package/claude/skills/design-taste/references/vocabulary.md +1 -1
  41. package/claude/skills/docs-sync/REQUIREMENT.md +3 -3
  42. package/claude/skills/docs-sync/SKILL.md +1 -1
  43. package/claude/skills/document-health/REQUIREMENT.md +2 -2
  44. package/claude/skills/document-health/SKILL.md +4 -5
  45. package/claude/skills/document-health/references/axes.md +4 -6
  46. package/claude/skills/draft-and-pick/REQUIREMENT.md +16 -14
  47. package/claude/skills/draft-and-pick/SKILL.md +40 -32
  48. package/claude/skills/draft-and-pick/references/live-arms.md +9 -9
  49. package/claude/skills/draft-doc/REQUIREMENT.md +5 -5
  50. package/claude/skills/draft-doc/SKILL.md +10 -11
  51. package/claude/skills/draft-doc/references/requirements.md +61 -0
  52. package/claude/skills/draft-doc/references/wiki-kind.md +53 -0
  53. package/claude/skills/draft-doc/references/wiki.md +50 -30
  54. package/claude/skills/draft-figure/REQUIREMENT.md +2 -2
  55. package/claude/skills/draft-figure/SKILL.md +5 -5
  56. package/claude/skills/draft-figure/references/architecture-set.md +4 -4
  57. package/claude/skills/draft-figure/references/hand-drawn.md +1 -1
  58. package/claude/skills/draft-figure/references/mermaid-path.md +55 -0
  59. package/claude/skills/draft-figure/references/mermaid.md +43 -34
  60. package/claude/skills/draft-identity/REQUIREMENT.md +1 -1
  61. package/claude/skills/draft-identity/SKILL.md +9 -7
  62. package/claude/skills/draft-ready/REQUIREMENT.md +1 -1
  63. package/claude/skills/draft-ready/SKILL.md +6 -6
  64. package/claude/skills/draft-ready/references/assembly.md +1 -1
  65. package/{standards → claude/skills/draft-ready/references}/ready.md +13 -5
  66. package/claude/skills/git-issue/SKILL.md +1 -1
  67. package/claude/skills/git-issue/references/issue.md +3 -3
  68. package/claude/skills/git-pr/SKILL.md +1 -1
  69. package/claude/skills/git-ship/REQUIREMENT.md +0 -2
  70. package/claude/skills/git-ship/SKILL.md +16 -24
  71. package/claude/skills/git-stage/SKILL.md +1 -1
  72. package/claude/skills/markdown-craft/REQUIREMENT.md +23 -0
  73. package/claude/skills/markdown-craft/SKILL.md +17 -0
  74. package/{standards → claude/skills/markdown-craft/references}/markdown.md +14 -7
  75. package/claude/skills/plan-feature/REQUIREMENT.md +2 -1
  76. package/claude/skills/plan-feature/SKILL.md +14 -8
  77. package/{standards → claude/skills/plan-feature/references}/plan.md +33 -21
  78. package/claude/skills/plan-groundwork/SKILL.md +5 -6
  79. package/{standards → claude/skills/plan-groundwork/references}/groundwork.md +17 -4
  80. package/claude/skills/plan-intake/SKILL.md +2 -3
  81. package/{standards → claude/skills/plan-intake/references}/intake.md +17 -2
  82. package/claude/skills/plan-intake-answer/REQUIREMENT.md +1 -1
  83. package/claude/skills/plan-intake-answer/SKILL.md +1 -1
  84. package/claude/skills/review-address/SKILL.md +2 -2
  85. package/claude/skills/review-address/references/post-review-findings.md +1 -1
  86. package/claude/skills/review-craft/REQUIREMENT.md +3 -3
  87. package/claude/skills/review-craft/SKILL.md +2 -2
  88. package/claude/skills/review-craft/references/ui.md +4 -4
  89. package/claude/skills/review-pr/REQUIREMENT.md +1 -1
  90. package/claude/skills/review-pr/SKILL.md +5 -7
  91. package/claude/skills/review-pr/references/post.md +2 -2
  92. package/claude/skills/review-ui/REQUIREMENT.md +0 -1
  93. package/claude/skills/review-ui/SKILL.md +1 -1
  94. package/claude/skills/review-ui/references/post.md +1 -1
  95. package/claude/skills/role-orchestrator/REQUIREMENT.md +0 -3
  96. package/claude/skills/role-orchestrator/SKILL.md +8 -8
  97. package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +4 -3
  98. package/claude/skills/role-orchestrator/references/orchestrator-handoff.md +7 -10
  99. package/claude/skills/role-orchestrator/references/orchestrator-parked.md +2 -2
  100. package/claude/skills/role-orchestrator/references/orchestrator-refill.md +0 -3
  101. package/claude/skills/role-orchestrator/references/orchestrator-resume.md +1 -1
  102. package/claude/skills/role-planner/SKILL.md +2 -2
  103. package/claude/skills/role-reviewer/SKILL.md +1 -1
  104. package/claude/skills/role-worker/SKILL.md +2 -2
  105. package/claude/skills/session-compact/REQUIREMENT.md +1 -4
  106. package/claude/skills/session-compact/SKILL.md +3 -13
  107. package/claude/skills/session-compact/references/handoff-note.md +2 -2
  108. package/claude/skills/session-map/REQUIREMENT.md +1 -9
  109. package/claude/skills/session-map/SKILL.md +8 -16
  110. package/{standards → claude/skills/session-map/references}/session.md +17 -11
  111. package/claude/skills/session-relay/REQUIREMENT.md +1 -1
  112. package/claude/skills/session-resume/REQUIREMENT.md +4 -6
  113. package/claude/skills/session-resume/SKILL.md +3 -9
  114. package/claude/skills/session-worktree/SKILL.md +1 -1
  115. package/claude/skills/session-worktree/references/main-root-writes.md +2 -2
  116. package/claude/skills/systematic-debugging/REQUIREMENT.md +0 -1
  117. package/claude/skills/target-check/REQUIREMENT.md +3 -3
  118. package/claude/skills/target-check/SKILL.md +1 -1
  119. package/claude/skills/target-check/references/domains.md +1 -1
  120. package/claude/skills/target-setup/REQUIREMENT.md +4 -4
  121. package/claude/skills/target-setup/SKILL.md +3 -3
  122. package/claude/skills/target-setup/references/detect.md +1 -1
  123. package/claude/skills/target-setup/references/indexes.md +1 -1
  124. package/claude/skills/task-board/SKILL.md +4 -4
  125. package/claude/skills/task-board/references/archive.md +1 -1
  126. package/{standards → claude/skills/task-board/references}/board.md +9 -0
  127. package/claude/skills/task-board/references/decline.md +1 -1
  128. package/{standards → claude/skills/task-board/references}/tasks.md +13 -8
  129. package/claude/skills/teach-workspace/SKILL.md +1 -1
  130. package/claude/skills/teach-workspace/references/revisit.md +1 -1
  131. package/{standards → claude/skills/teach-workspace/references}/teach.md +17 -5
  132. package/claude/skills/test-first/REQUIREMENT.md +1 -1
  133. package/claude/skills/ux-audit/REQUIREMENT.md +2 -2
  134. package/claude/skills/ux-audit/SKILL.md +2 -3
  135. package/claude/skills/write-human/REQUIREMENT.md +0 -1
  136. package/claude/skills/write-human/SKILL.md +2 -2
  137. package/docs/agents/audits.md +1 -1
  138. package/docs/agents/canvas.md +1 -1
  139. package/docs/agents/commands.md +6 -10
  140. package/docs/agents/context-audit-checks.md +10 -24
  141. package/docs/agents/context-audit.md +7 -15
  142. package/docs/agents/context-classify.md +5 -5
  143. package/docs/agents/counts.md +5 -5
  144. package/docs/agents/drift-surfaces.md +2 -2
  145. package/docs/agents/index.md +3 -4
  146. package/docs/agents/install-and-sync.md +6 -6
  147. package/docs/agents/intake.md +1 -1
  148. package/docs/agents/markdown-audit.md +1 -1
  149. package/docs/agents/records-stale.md +5 -5
  150. package/docs/agents/records-validate.md +1 -1
  151. package/docs/agents/records.md +2 -2
  152. package/docs/agents/restated.md +1 -1
  153. package/docs/agents/routing.md +1 -1
  154. package/docs/agents/rule-citations.md +1 -1
  155. package/docs/agents/skills-audit.md +2 -2
  156. package/docs/agents/state-scoped-risk.md +2 -2
  157. package/docs/agents/superseded.md +2 -2
  158. package/docs/agents/tasks-plans.md +2 -2
  159. package/docs/agents/tasks-validate.md +1 -1
  160. package/docs/agents/tasks.md +1 -1
  161. package/docs/agents/teach.md +1 -1
  162. package/docs/agents/test-order.md +1 -1
  163. package/docs/agents/upstream.md +94 -0
  164. package/docs/target/migrations.md +24 -0
  165. package/docs/target/projects.md +5 -7
  166. package/docs/target/scenarios.md +2 -2
  167. package/docs/target/sync.md +3 -3
  168. package/docs/workflow/ai-workflow.md +7 -38
  169. package/docs/workflow/index.md +1 -1
  170. package/docs/workflow/operating-model.md +8 -8
  171. package/docs/workflow/parallel-features.md +7 -7
  172. package/docs/workflow/skill-map-on-request.md +13 -16
  173. package/docs/workflow/skill-map.md +20 -32
  174. package/docs/workflow/visual-design-workflow.md +11 -14
  175. package/governance/renames.toml +0 -23
  176. package/governance/rules/canon/605-worktrees.md +1 -1
  177. package/governance/rules/{standards → canon}/plan.md +5 -5
  178. package/governance/rules/canon/tasks.md +18 -0
  179. package/governance/rules/writing/markdown.md +23 -0
  180. package/governance/stacks/base.toml +1 -1
  181. package/package.json +1 -1
  182. package/scripts/core/install-check.sh +2 -2
  183. package/scripts/core/regen-web-previews.ts +1 -1
  184. package/src/audits/catalog.ts +5 -97
  185. package/src/claude/cases/authoring.ts +5 -18
  186. package/src/claude/cases/misc.ts +0 -17
  187. package/src/claude/cases/setup.ts +3 -20
  188. package/src/claude/cases/workflow.ts +0 -33
  189. package/src/claude/seeds.ts +1 -7
  190. package/src/claude/skills-audit.ts +1 -1
  191. package/src/claude/skills-reach.ts +1 -1
  192. package/src/cli/help.ts +3 -4
  193. package/src/cli.ts +2 -4
  194. package/src/commands/claude.ts +9 -15
  195. package/src/commands/context.ts +18 -280
  196. package/src/commands/feedback.ts +22 -91
  197. package/src/commands/gov.ts +2 -2
  198. package/src/commands/records.ts +24 -6
  199. package/src/commands/sync.ts +5 -3
  200. package/src/commands/upstream.ts +430 -0
  201. package/src/context/audit.ts +6 -15
  202. package/src/context/classify/extract.ts +6 -13
  203. package/src/context/classify/patterns.ts +2 -41
  204. package/src/context/classify/prompts.ts +2 -6
  205. package/src/context/classify/run.ts +3 -3
  206. package/src/context/folders.ts +7 -12
  207. package/src/context/gate.ts +11 -91
  208. package/src/context/requirements.ts +13 -4
  209. package/src/counts/scan.ts +3 -3
  210. package/src/design/components.ts +2 -2
  211. package/src/feedback/body.ts +0 -12
  212. package/src/gate/measures.ts +6 -57
  213. package/src/gate/stages.ts +0 -1
  214. package/src/gov/citations.ts +9 -3
  215. package/src/gov/restated.ts +3 -3
  216. package/src/markdown/structure.ts +3 -3
  217. package/src/migrate/record-layout.ts +3 -3
  218. package/src/records/backup.ts +1 -0
  219. package/src/records/ordinal.ts +1 -1
  220. package/src/records/prune.ts +2 -1
  221. package/src/records/stale-canonical.ts +2 -2
  222. package/src/records/validate.ts +55 -0
  223. package/src/roots/record.ts +2 -1
  224. package/src/sandbox/scenario.ts +115 -11
  225. package/src/shipped/references.ts +3 -3
  226. package/src/standards/read.ts +52 -1
  227. package/src/sync/seeds-report.ts +1 -1
  228. package/src/tasks/answers.ts +14 -1
  229. package/src/tasks/reach.ts +2 -2
  230. package/src/tasks/stack.ts +176 -0
  231. package/src/tasks/validate.ts +1 -1
  232. package/src/teach/workspace.ts +1 -1
  233. package/src/upstream/catalog.ts +112 -0
  234. package/src/upstream/cursor.ts +120 -0
  235. package/src/upstream/digest.ts +75 -0
  236. package/src/upstream/due.ts +128 -0
  237. package/src/upstream/releases.ts +170 -0
  238. package/standards/context.md +3 -5
  239. package/standards/design.md +2 -3
  240. package/standards/index.md +0 -23
  241. package/standards/publish.md +1 -1
  242. package/standards/rule.md +1 -1
  243. package/standards/skill.md +1 -1
  244. package/tooling/base/reference.md +1 -1
  245. package/tooling/claude/reference.md +6 -20
  246. package/tooling/claude/seeds/.claude/hooks/index-reminder.sh +1 -1
  247. package/tooling/claude/seeds/.claude/hooks/records-index.sh +8 -20
  248. package/tooling/claude/seeds/.claude/hooks/standards-audit.sh +3 -3
  249. package/tooling/claude/seeds/CLAUDE.md +1 -3
  250. package/tooling/claude/seeds/canon/REQUIREMENTS.md +2 -9
  251. package/tooling/web/configs/e2e/screenshot.ts +0 -1
  252. package/claude/skills/auto-ship/references/review-findings.md +0 -20
  253. package/claude/skills/bash-cli-script/REQUIREMENT.md +0 -43
  254. package/claude/skills/bash-cli-script/SKILL.md +0 -53
  255. package/claude/skills/bash-cli-script/references/template.md +0 -19
  256. package/claude/skills/canon-operator/REQUIREMENT.md +0 -62
  257. package/claude/skills/canon-operator/SKILL.md +0 -113
  258. package/claude/skills/canon-rollout/REQUIREMENT.md +0 -59
  259. package/claude/skills/canon-rollout/SKILL.md +0 -140
  260. package/claude/skills/canon-rollout/references/failure-recovery.md +0 -20
  261. package/claude/skills/context-fold/references/anchor-sweep.md +0 -58
  262. package/claude/skills/context-fold/references/wireframe-sweep.md +0 -45
  263. package/claude/skills/create-standard/REQUIREMENT.md +0 -33
  264. package/claude/skills/create-standard/SKILL.md +0 -31
  265. package/claude/skills/design-extract/REQUIREMENT.md +0 -42
  266. package/claude/skills/design-extract/SKILL.md +0 -108
  267. package/claude/skills/draft-and-pick/references/frame.html +0 -118
  268. package/claude/skills/draft-doc/references/wireframes.md +0 -48
  269. package/claude/skills/index-lookup/REQUIREMENT.md +0 -35
  270. package/claude/skills/index-lookup/SKILL.md +0 -44
  271. package/claude/skills/markdown-propose/REQUIREMENT.md +0 -47
  272. package/claude/skills/markdown-propose/SKILL.md +0 -122
  273. package/claude/skills/markdown-propose/references/format.md +0 -118
  274. package/claude/skills/memory-capture/REQUIREMENT.md +0 -50
  275. package/claude/skills/memory-capture/SKILL.md +0 -117
  276. package/claude/skills/memory-review/REQUIREMENT.md +0 -52
  277. package/claude/skills/memory-review/SKILL.md +0 -129
  278. package/claude/skills/memory-review/references/later-phases.md +0 -118
  279. package/claude/skills/memory-review/references/receipt-format.md +0 -64
  280. package/claude/skills/project-commands/REQUIREMENT.md +0 -42
  281. package/claude/skills/project-commands/SKILL.md +0 -87
  282. package/claude/skills/repo-metadata/REQUIREMENT.md +0 -37
  283. package/claude/skills/repo-metadata/SKILL.md +0 -54
  284. package/claude/skills/restate-plainly/REQUIREMENT.md +0 -41
  285. package/claude/skills/restate-plainly/SKILL.md +0 -41
  286. package/claude/skills/review-branch/REQUIREMENT.md +0 -40
  287. package/claude/skills/review-branch/SKILL.md +0 -137
  288. package/claude/skills/seed-sync/REQUIREMENT.md +0 -45
  289. package/claude/skills/seed-sync/SKILL.md +0 -158
  290. package/claude/skills/sketch-design/REQUIREMENT.md +0 -38
  291. package/claude/skills/sketch-design/SKILL.md +0 -94
  292. package/claude/skills/ux-walkthrough/REQUIREMENT.md +0 -56
  293. package/claude/skills/ux-walkthrough/SKILL.md +0 -53
  294. package/claude/skills/ux-walkthrough/references/builds.md +0 -15
  295. package/claude/skills/ux-walkthrough/references/candidate-pages.md +0 -11
  296. package/claude/skills/ux-walkthrough/references/measuring.md +0 -22
  297. package/claude/skills/ux-walkthrough/references/record.md +0 -36
  298. package/claude/skills/ux-walkthrough/references/relay.md +0 -25
  299. package/docs/agents/architecture-record.md +0 -36
  300. package/docs/agents/review-classification.md +0 -76
  301. package/governance/rules/canon/601-indexes.md +0 -9
  302. package/governance/rules/canon/603-memory.md +0 -11
  303. package/governance/rules/claude/565-behavior.md +0 -27
  304. package/governance/rules/claude/567-planning.md +0 -16
  305. package/governance/rules/claude/575-hooks.md +0 -16
  306. package/governance/rules/claude/576-settings.md +0 -13
  307. package/governance/rules/claude/592-claude-md.md +0 -19
  308. package/governance/rules/lang/120-bash.md +0 -18
  309. package/governance/rules/standards/architecture.md +0 -13
  310. package/governance/rules/standards/groundwork.md +0 -13
  311. package/governance/rules/standards/intake.md +0 -12
  312. package/governance/rules/standards/markdown.md +0 -16
  313. package/governance/rules/standards/memory.md +0 -12
  314. package/governance/rules/standards/ready.md +0 -13
  315. package/governance/rules/standards/requirements.md +0 -12
  316. package/governance/rules/standards/session.md +0 -13
  317. package/governance/rules/standards/tasks.md +0 -15
  318. package/governance/rules/standards/teach.md +0 -14
  319. package/governance/rules/standards/wiki.md +0 -13
  320. package/governance/rules/standards/wireframes.md +0 -13
  321. package/governance/rules/writing/800-prose.md +0 -15
  322. package/src/autoship/classify.ts +0 -75
  323. package/src/autoship/paths.ts +0 -50
  324. package/src/commands/autoship.ts +0 -129
  325. package/src/commands/repo.ts +0 -393
  326. package/src/context/architecture.ts +0 -654
  327. package/src/context/wireframe-states.ts +0 -238
  328. package/src/repo/metadata.ts +0 -206
  329. package/standards/architecture.md +0 -127
  330. package/standards/memory.md +0 -158
  331. package/standards/mermaid.md +0 -64
  332. package/standards/requirements.md +0 -79
  333. package/standards/wiki.md +0 -77
  334. package/standards/wireframes.md +0 -143
  335. package/tooling/claude/seeds/.claude/memory/index.md +0 -8
  336. package/tooling/claude/seeds/canon/ARCHITECTURE.md +0 -23
  337. package/tooling/claude/seeds/canon/wireframes/index.md +0 -8
  338. /package/{standards → claude/skills/create-skill/references}/skill-practice.md +0 -0
  339. /package/{standards → claude/skills/create-skill/references}/skill-requirement.md +0 -0
  340. /package/{standards → claude/skills/plan-feature/references}/plan-lifecycle.md +0 -0
  341. /package/{standards → claude/skills/plan-groundwork/references}/groundwork-spikes.md +0 -0
  342. /package/{standards → claude/skills/task-board/references}/tasks-decline.md +0 -0
  343. /package/{standards → claude/skills/teach-workspace/references}/glossary.md +0 -0
@@ -1,129 +0,0 @@
1
- import type { Command } from 'commander'
2
- import {
3
- type ClassifyRefusal,
4
- type FailedTest,
5
- classifyChanges,
6
- } from '@/autoship/classify'
7
- import { intro, logInfo, logStep, logWarn, outro, plural } from '@/cli/ui'
8
-
9
- interface ClassifyOptions {
10
- readonly json?: boolean
11
- }
12
-
13
- /** What a reader does about the one way the classification produced no reading. */
14
- const REFUSALS: Record<ClassifyRefusal, string> = {
15
- 'no-changes':
16
- 'No changed files were supplied, so there is no set to classify. Pass the set the ship chain already computed.',
17
- }
18
-
19
- /** Why each failed test sends the branch to review, in the reader's terms. */
20
- const TESTS: Record<FailedTest, string> = {
21
- extension: 'is not prose, so this branch carries a change review reads',
22
- 'behavior-path':
23
- 'sits under a behavior path, so its prose states what an agent does',
24
- }
25
-
26
- export function register(program: Command): void {
27
- const autoship = program
28
- .command('autoship')
29
- .description('Answer the decisions the ship chain used to make in prose')
30
- .helpOption('-h, --help', 'Show this help message')
31
-
32
- autoship
33
- .command('classify')
34
- .description('Decide whether a changed set needs the review pass')
35
- .argument('[paths...]', 'Changed set to classify, as names')
36
- .helpOption('-h, --help', 'Show this help message')
37
- .option('--json', 'Add a machine-readable record on stdout')
38
- .addHelpText(
39
- 'after',
40
- [
41
- '',
42
- 'Reads names only and never touches git, so the caller hands over the',
43
- 'set it already computed rather than paying for a second baseline that',
44
- 'can disagree with the first.',
45
- '',
46
- 'The skip needs both tests to pass. Every name reads as prose, and no',
47
- 'name sits under a behavior path, which is where markdown states what',
48
- 'an agent does. One behavior file sends the whole branch to review,',
49
- 'since documentation shipped beside a behavior change does not cancel',
50
- 'it.',
51
- '',
52
- 'An empty set refuses rather than skipping. Both tests are universally',
53
- 'quantified, so an empty set satisfies them vacuously, and reading that',
54
- 'as prose-only would route a branch past review for producing no output.',
55
- '',
56
- 'Exit codes:',
57
- ' 0 prose-only, so the review pass can be skipped',
58
- ' 1 refused, with the reason on stderr or in the JSON record',
59
- ' 2 review is needed, with the file and the test it failed named',
60
- '',
61
- 'Examples:',
62
- ' canon autoship classify docs/index.md README.md',
63
- ' canon autoship classify --json .claude/skills/deploy-check/SKILL.md',
64
- '',
65
- ].join('\n'),
66
- )
67
- .action((paths: string[], opts: ClassifyOptions) => {
68
- process.exitCode = runClassify(paths, opts)
69
- })
70
- }
71
-
72
- function runClassify(paths: string[], opts: ClassifyOptions): number {
73
- const emitJson = opts.json ?? false
74
- const result = classifyChanges(paths)
75
-
76
- intro('canon autoship classify')
77
-
78
- // The frame renders on stderr in both modes and the record goes to stdout
79
- // alone, so an operator reading the terminal sees the refusal rather than a
80
- // command that appeared to do nothing.
81
- if (result.kind === 'refused') {
82
- logStep('Refused')
83
- logWarn(REFUSALS[result.reason])
84
- outro()
85
-
86
- if (emitJson) {
87
- process.stdout.write(
88
- `${JSON.stringify({
89
- decision: 'refused',
90
- reason: result.reason,
91
- message: REFUSALS[result.reason],
92
- })}\n`,
93
- )
94
- }
95
- return 1
96
- }
97
-
98
- logStep('Scope')
99
- logInfo(`${plural(paths.length, 'path')} supplied by the caller`)
100
-
101
- if (result.kind === 'skip') {
102
- logStep('Prose only')
103
- logInfo('every path reads as prose and none states agent behavior')
104
- outro()
105
-
106
- if (emitJson) {
107
- process.stdout.write(
108
- `${JSON.stringify({ decision: 'skip', changed: paths })}\n`,
109
- )
110
- }
111
- return 0
112
- }
113
-
114
- logStep('Review')
115
- logWarn(`${result.file} ${TESTS[result.test]}.`)
116
- outro()
117
-
118
- if (emitJson) {
119
- process.stdout.write(
120
- `${JSON.stringify({
121
- decision: 'review',
122
- test: result.test,
123
- file: result.file,
124
- changed: paths,
125
- })}\n`,
126
- )
127
- }
128
- return 2
129
- }
@@ -1,393 +0,0 @@
1
- import { resolve } from 'node:path'
2
- import type { Command } from 'commander'
3
- import { execa } from 'execa'
4
- import { gitEnv } from '@/git/env'
5
- import {
6
- compareMetadata,
7
- type CurrentMetadata,
8
- isValidTopic,
9
- type MetadataDiff,
10
- proposeMetadata,
11
- } from '@/repo/metadata'
12
- import {
13
- intro,
14
- logAdd,
15
- logInfo,
16
- logRemove,
17
- logStep,
18
- logWarn,
19
- outro,
20
- } from '@/cli/ui'
21
-
22
- const GH_TIMEOUT_MS = 30_000
23
-
24
- type SourceRefusal = 'gh-missing' | 'gh-failed'
25
- type ApplyRefusal =
26
- | SourceRefusal
27
- | 'no-changes'
28
- | 'empty-topics'
29
- | 'invalid-topics'
30
- | 'wrong-repo'
31
-
32
- const REFUSALS: Record<ApplyRefusal, string> = {
33
- 'gh-missing': 'gh is not on the path, so no remote could be read.',
34
- 'gh-failed':
35
- 'gh could not read this repository. Check the remote and gh auth status.',
36
- 'no-changes':
37
- 'Nothing to apply. Pass --description, --homepage, or --topics with the answered value.',
38
- 'empty-topics':
39
- '--topics carried no usable topic, and this command never reads that as a request to remove every topic the remote carries. Pass the full desired set, or omit the flag to leave topics untouched.',
40
- 'invalid-topics':
41
- '--topics carried an entry GitHub does not accept as a topic (lowercase letters, digits, and internal hyphens only). This command never narrows the desired set silently, since dropping it changes what the remaining diff removes.',
42
- 'wrong-repo':
43
- 'The remote --root resolved to is not the one --repo named. This command never writes to a repository the invocation did not explicitly confirm.',
44
- }
45
-
46
- interface ProposeOptions {
47
- readonly root?: string
48
- readonly json?: boolean
49
- }
50
-
51
- interface ApplyOptions {
52
- readonly description?: string
53
- readonly homepage?: string
54
- readonly topics?: string
55
- readonly repo: string
56
- readonly root?: string
57
- readonly json?: boolean
58
- }
59
-
60
- type CurrentRead =
61
- | {
62
- readonly kind: 'read'
63
- readonly current: CurrentMetadata
64
- readonly nameWithOwner: string
65
- }
66
- | { readonly kind: 'refused'; readonly reason: SourceRefusal }
67
-
68
- /** Reads what the remote already carries. The one network call this domain makes to read. */
69
- async function readCurrent(cwd: string): Promise<CurrentRead> {
70
- if (Bun.which('gh') === null) return { kind: 'refused', reason: 'gh-missing' }
71
-
72
- try {
73
- const result = await execa(
74
- 'gh',
75
- [
76
- 'repo',
77
- 'view',
78
- '--json',
79
- 'description,homepageUrl,repositoryTopics,nameWithOwner',
80
- ],
81
- { cwd, timeout: GH_TIMEOUT_MS, env: gitEnv(), extendEnv: false },
82
- )
83
- const row = JSON.parse(result.stdout) as {
84
- description?: string
85
- homepageUrl?: string
86
- repositoryTopics?: readonly { name: string }[]
87
- nameWithOwner?: string
88
- }
89
- return {
90
- kind: 'read',
91
- current: {
92
- description: row.description ?? '',
93
- homepage: row.homepageUrl ?? '',
94
- topics: (row.repositoryTopics ?? []).map((topic) => topic.name),
95
- },
96
- nameWithOwner: row.nameWithOwner ?? '',
97
- }
98
- } catch {
99
- return { kind: 'refused', reason: 'gh-failed' }
100
- }
101
- }
102
-
103
- export function register(program: Command): void {
104
- const repo = program
105
- .command('repo')
106
- .description('Read and write this repository’s own remote metadata')
107
- .helpOption('-h, --help', 'Show this help message')
108
-
109
- const metadata = repo
110
- .command('metadata')
111
- .description('The About description, homepage, and topics GitHub shows')
112
- .helpOption('-h, --help', 'Show this help message')
113
-
114
- metadata
115
- .command('propose')
116
- .description(
117
- 'Compare a locally computed description, homepage, and topic set against the remote',
118
- )
119
- .helpOption('-h, --help', 'Show this help message')
120
- .option('--root <path>', 'Repository to read, defaulting to the cwd')
121
- .option('--json', 'Add a machine-readable record on stdout')
122
- .addHelpText(
123
- 'after',
124
- [
125
- '',
126
- "Computes from the tree alone: a description from the README's opening",
127
- 'line past its title and badges, and a homepage and topics from',
128
- "package.json's own homepage and keywords fields. A field neither file",
129
- 'declares is never diffed, so an undeclared topic set never reads as a',
130
- 'proposal to clear what the remote already carries.',
131
- '',
132
- 'This is the read half. It never writes. Run `canon repo metadata apply`',
133
- 'with the answered fields once a proposal is worth taking.',
134
- '',
135
- 'Exit codes:',
136
- ' 0 the remote already carries what this run computed',
137
- ' 1 refused, with the reason on stderr or in the JSON record',
138
- ' 2 a difference was found',
139
- '',
140
- 'Examples:',
141
- ' canon repo metadata propose',
142
- ' canon repo metadata propose --json',
143
- '',
144
- ].join('\n'),
145
- )
146
- .action(async (opts: ProposeOptions) => {
147
- process.exitCode = await runPropose(opts)
148
- })
149
-
150
- metadata
151
- .command('apply')
152
- .description(
153
- 'Write an explicitly supplied description, homepage, or topic set to the remote',
154
- )
155
- .helpOption('-h, --help', 'Show this help message')
156
- .requiredOption(
157
- '--repo <owner/name>',
158
- 'Repository this run is allowed to write to, checked against what gh resolves',
159
- )
160
- .option('--description <text>', 'About text to write')
161
- .option('--homepage <url>', 'Homepage URL to write')
162
- .option(
163
- '--topics <list>',
164
- 'Comma-separated topic set to write, replacing what the remote carries',
165
- )
166
- .option('--root <path>', 'Repository to read, defaulting to the cwd')
167
- .option('--json', 'Add a machine-readable record on stdout')
168
- .addHelpText(
169
- 'after',
170
- [
171
- '',
172
- 'Takes the answered value for each field directly rather than',
173
- 're-running propose, so a change this writes is always one a person or',
174
- 'a second invocation already read and confirmed. --topics is the full',
175
- 'desired set. gh only takes an add and a remove list, so this reads the',
176
- 'current set once to compute both. An entry shaped like something other',
177
- 'than a GitHub topic refuses the whole run rather than being dropped.',
178
- '',
179
- '--repo is required and takes no default, since --root silently',
180
- 'resolving to the caller’s own cwd is what turned a verification run',
181
- 'against this exact command into a live write against a public remote.',
182
- 'The value is checked against what gh resolves for --root, so the',
183
- 'write refuses on any repository the invocation did not name aloud.',
184
- '',
185
- 'Exit codes:',
186
- ' 0 the write succeeded, or the remote already matched',
187
- ' 1 refused, with the reason on stderr or in the JSON record',
188
- '',
189
- 'Examples:',
190
- ' canon repo metadata apply --repo erclx/canon --description "One source for conventions."',
191
- ' canon repo metadata apply --repo erclx/canon --topics cli-tool,governance,standards',
192
- '',
193
- ].join('\n'),
194
- )
195
- .action(async (opts: ApplyOptions) => {
196
- process.exitCode = await runApply(opts)
197
- })
198
- }
199
-
200
- function refuse(
201
- reason: ApplyRefusal,
202
- emitJson: boolean,
203
- root: string,
204
- detail?: string,
205
- ): number {
206
- const message =
207
- detail === undefined ? REFUSALS[reason] : `${REFUSALS[reason]} ${detail}`
208
-
209
- logStep('Refused')
210
- logWarn(message)
211
- outro()
212
-
213
- if (emitJson) {
214
- process.stdout.write(`${JSON.stringify({ root, reason, message })}\n`)
215
- }
216
- return 1
217
- }
218
-
219
- async function runPropose(opts: ProposeOptions): Promise<number> {
220
- const root = resolve(opts.root ?? process.cwd())
221
- const emitJson = opts.json ?? false
222
-
223
- intro('canon repo metadata propose')
224
-
225
- const [proposal, currentRead] = await Promise.all([
226
- proposeMetadata(root),
227
- readCurrent(root),
228
- ])
229
-
230
- if (currentRead.kind === 'refused') {
231
- return refuse(currentRead.reason, emitJson, root)
232
- }
233
-
234
- const diff: MetadataDiff = compareMetadata(currentRead.current, proposal)
235
- const changed = Object.keys(diff).length > 0
236
-
237
- logStep('Repository')
238
- logInfo(currentRead.nameWithOwner)
239
-
240
- logStep('Computed')
241
- if (
242
- proposal.description === undefined &&
243
- proposal.homepage === undefined &&
244
- proposal.topics === undefined
245
- ) {
246
- logInfo('nothing local resolved a description, a homepage, or topics')
247
- } else {
248
- if (proposal.description !== undefined) {
249
- logInfo(`description: ${proposal.description}`)
250
- }
251
- if (proposal.homepage !== undefined)
252
- logInfo(`homepage: ${proposal.homepage}`)
253
- if (proposal.topics !== undefined) {
254
- logInfo(`topics: ${proposal.topics.join(', ')}`)
255
- }
256
- }
257
-
258
- logStep(changed ? 'Difference' : 'No difference')
259
- if (!changed) {
260
- logInfo('the remote already carries what this run computed')
261
- } else {
262
- if (diff.description !== undefined) {
263
- logWarn(
264
- `description: "${diff.description.current}" → "${diff.description.proposed}"`,
265
- )
266
- }
267
- if (diff.homepage !== undefined) {
268
- logWarn(
269
- `homepage: "${diff.homepage.current}" → "${diff.homepage.proposed}"`,
270
- )
271
- }
272
- if (diff.topics !== undefined) {
273
- for (const topic of diff.topics.added) logAdd(`topic: ${topic}`)
274
- for (const topic of diff.topics.removed) logRemove(`topic: ${topic}`)
275
- }
276
- logInfo(
277
- 'Run `canon repo metadata apply` with the answered fields. This run writes nothing.',
278
- )
279
- }
280
-
281
- outro()
282
-
283
- if (emitJson) {
284
- process.stdout.write(
285
- `${JSON.stringify({
286
- root,
287
- repo: currentRead.nameWithOwner,
288
- current: currentRead.current,
289
- proposal,
290
- diff,
291
- })}\n`,
292
- )
293
- }
294
-
295
- return changed ? 2 : 0
296
- }
297
-
298
- async function runApply(opts: ApplyOptions): Promise<number> {
299
- const root = resolve(opts.root ?? process.cwd())
300
- const emitJson = opts.json ?? false
301
-
302
- intro('canon repo metadata apply')
303
-
304
- if (
305
- opts.description === undefined &&
306
- opts.homepage === undefined &&
307
- opts.topics === undefined
308
- ) {
309
- return refuse('no-changes', emitJson, root)
310
- }
311
-
312
- let desired: ReadonlySet<string> | undefined
313
- if (opts.topics !== undefined) {
314
- const entries = opts.topics
315
- .split(',')
316
- .map((topic) => topic.trim().toLowerCase())
317
- .filter((topic) => topic !== '')
318
- if (entries.length === 0) return refuse('empty-topics', emitJson, root)
319
-
320
- const invalid = entries.filter((topic) => !isValidTopic(topic))
321
- if (invalid.length > 0) {
322
- return refuse(
323
- 'invalid-topics',
324
- emitJson,
325
- root,
326
- `Invalid: ${invalid.join(', ')}`,
327
- )
328
- }
329
-
330
- desired = new Set(entries)
331
- }
332
-
333
- // Read ahead of every write, never only for --topics, since this is the
334
- // one call that confirms --repo names the remote --root actually resolved
335
- // to. A write skipping it on a description-only or homepage-only run would
336
- // reopen the gap --repo exists to close.
337
- const currentRead = await readCurrent(root)
338
- if (currentRead.kind === 'refused')
339
- return refuse(currentRead.reason, emitJson, root)
340
- if (currentRead.nameWithOwner !== opts.repo) {
341
- return refuse('wrong-repo', emitJson, root)
342
- }
343
-
344
- const args = ['repo', 'edit']
345
- if (opts.description !== undefined)
346
- args.push('--description', opts.description)
347
- if (opts.homepage !== undefined) args.push('--homepage', opts.homepage)
348
-
349
- if (desired !== undefined) {
350
- const currentSet = new Set(currentRead.current.topics)
351
- const toAdd = [...desired].filter((topic) => !currentSet.has(topic))
352
- const toRemove = currentRead.current.topics.filter(
353
- (topic) => !desired.has(topic),
354
- )
355
-
356
- if (toAdd.length > 0) args.push('--add-topic', toAdd.join(','))
357
- if (toRemove.length > 0) args.push('--remove-topic', toRemove.join(','))
358
- }
359
-
360
- if (args.length === 2) {
361
- logStep('Applied')
362
- logInfo('the remote already matches every field supplied. Nothing written.')
363
- outro()
364
-
365
- if (emitJson) {
366
- process.stdout.write(`${JSON.stringify({ root, written: false })}\n`)
367
- }
368
- return 0
369
- }
370
-
371
- if (Bun.which('gh') === null) return refuse('gh-missing', emitJson, root)
372
-
373
- try {
374
- await execa('gh', args, {
375
- cwd: root,
376
- timeout: GH_TIMEOUT_MS,
377
- env: gitEnv(),
378
- extendEnv: false,
379
- })
380
- } catch {
381
- return refuse('gh-failed', emitJson, root)
382
- }
383
-
384
- logStep('Applied')
385
- logInfo('gh repo edit wrote the supplied fields to the remote.')
386
- outro()
387
-
388
- if (emitJson) {
389
- process.stdout.write(`${JSON.stringify({ root, written: true })}\n`)
390
- }
391
-
392
- return 0
393
- }