@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,18 +1,13 @@
1
- import { existsSync, mkdirSync, writeFileSync } from 'node:fs'
2
- import { join } from 'node:path'
3
- import type { Command } from 'commander'
1
+ import { Option, type Command } from 'commander'
4
2
  import {
5
3
  deriveDomainLabels,
6
- deriveSlug,
7
4
  deriveTitle,
8
5
  missingField,
9
6
  missingFieldMessage,
10
7
  } from '@/feedback/body'
11
- import { checkoutMismatchWarning, PROJECT_ROOT } from '@/roots/project'
12
- import { creationRel } from '@/roots/record'
13
8
  import { createGithubIssue } from '@/feedback/github'
14
9
  import { issueFailureMessage } from '@/feedback/github-format'
15
- import { frameError, frameSuccess, logWarn, palette } from '@/cli/ui'
10
+ import { frameError, frameSuccess } from '@/cli/ui'
16
11
 
17
12
  function readStdin(): Promise<string> {
18
13
  return new Promise((resolveStream, rejectStream) => {
@@ -25,62 +20,17 @@ function readStdin(): Promise<string> {
25
20
  })
26
21
  }
27
22
 
28
- function timestamp(): string {
29
- const d = new Date()
30
- const pad = (n: number): string => n.toString().padStart(2, '0')
31
- return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`
32
- }
33
-
34
- /**
35
- * A registry install ships no `.claude/`, so a local write would land inside
36
- * `node_modules/` and report a project-relative path the operator cannot find.
37
- * The toolkit source is the only root where local scratch reaches a maintainer.
38
- */
39
- function isToolkitSource(): boolean {
40
- return existsSync(join(PROJECT_ROOT, '.claude'))
41
- }
42
-
43
- /**
44
- * One producer, one record folder. Feedback is not a review, so it writes
45
- * under a root folder of its own rather than inside `review/`, and `canon
46
- * records push` backs it with no list edit, since it carries every `.canon/`
47
- * entry it does not exclude.
48
- */
49
- function writeLocal(body: string, mismatch: string | undefined): string {
50
- // Resolved against the same root the write joins onto. `creationRel` reads
51
- // whichever root already carries a `feedback/` folder and falls back to the
52
- // creation root when neither does.
53
- const relativeDir = creationRel(PROJECT_ROOT, 'feedback')
54
- const feedbackDir = join(PROJECT_ROOT, relativeDir)
55
- mkdirSync(feedbackDir, { recursive: true })
56
- const filename = `feedback-${deriveSlug(body)}-${timestamp()}.md`
57
- const filePath = join(feedbackDir, filename)
58
- writeFileSync(filePath, `${body}\n`, 'utf8')
59
-
60
- // Manual frame rather than `frameSuccess`, since the mismatch warning is a
61
- // frame-interior line landing between the opener and the command title,
62
- // matching the convention `#1587` set at its other six sites.
63
- const { GREEN, GREY, NC, WHITE } = palette(process.stderr)
64
- process.stderr.write(`${GREY}┌${NC}\n`)
65
- if (mismatch !== undefined) logWarn(mismatch)
66
- process.stderr.write(
67
- `${GREY}│${NC} ${WHITE}canon feedback${NC}\n${GREY}│${NC}\n${GREY}│${NC} ${GREEN}✓${NC} ${join(relativeDir, filename)}\n${GREY}└${NC}\n`,
68
- )
69
- return filePath
70
- }
71
-
72
23
  export function register(program: Command): void {
73
24
  program
74
25
  .command('feedback')
75
26
  .helpOption('-h, --help', 'Show this help message')
76
27
  .description(
77
- 'Write toolkit feedback from stdin to .canon/feedback/, or open a GitHub issue with --github',
78
- )
79
- .option(
80
- '--github',
81
- 'Open a GitHub issue on the toolkit repo instead of writing local scratch',
28
+ 'Open a GitHub issue on the toolkit repo from a feedback block on stdin',
82
29
  )
83
- .action(async (opts: { github?: boolean }) => {
30
+ // A cached plugin still pipes `--github`, and commander rejects an unknown
31
+ // option, so the flag stays accepted and hidden for one release.
32
+ .addOption(new Option('--github').hideHelp())
33
+ .action(async () => {
84
34
  if (process.stdin.isTTY) {
85
35
  frameError(
86
36
  'No feedback on stdin. Pipe a markdown block: pbpaste | canon feedback',
@@ -95,8 +45,8 @@ export function register(program: Command): void {
95
45
  return
96
46
  }
97
47
 
98
- // Both write paths, not `--github` alone. A report missing its surface is
99
- // no more useful sitting in the triage queue on disk than in the tracker.
48
+ // A report missing its surface is no more useful in the tracker than
49
+ // anywhere else, so validation runs before any `gh` call.
100
50
  const absent = missingField(body)
101
51
  if (absent) {
102
52
  frameError(missingFieldMessage(absent))
@@ -104,39 +54,20 @@ export function register(program: Command): void {
104
54
  return
105
55
  }
106
56
 
107
- if (opts.github) {
108
- const result = await createGithubIssue({
109
- title: deriveTitle(body),
110
- body,
111
- labels: ['feedback', ...deriveDomainLabels(body)],
112
- })
113
- if (result.ok) {
114
- frameSuccess('canon feedback', result.url)
115
- process.stdout.write(`${result.url}\n`)
116
- return
117
- }
118
- const reason = issueFailureMessage(result)
119
- if (!isToolkitSource()) {
120
- frameError(reason)
121
- process.exitCode = 1
122
- return
123
- }
124
- const { NC, YELLOW } = palette(process.stderr)
125
- process.stderr.write(
126
- `${YELLOW}! ${reason} Wrote local scratch instead.${NC}\n`,
127
- )
128
- }
129
-
130
- if (!isToolkitSource()) {
131
- frameError(
132
- 'Local scratch needs the toolkit source. Re-run with --github to open an issue instead.',
133
- )
134
- process.exitCode = 1
57
+ const result = await createGithubIssue({
58
+ title: deriveTitle(body),
59
+ body,
60
+ labels: ['feedback', ...deriveDomainLabels(body)],
61
+ })
62
+ if (result.ok) {
63
+ frameSuccess('canon feedback', result.url)
64
+ process.stdout.write(`${result.url}\n`)
135
65
  return
136
66
  }
137
-
138
- const mismatch = checkoutMismatchWarning(process.cwd())
139
- const filePath = writeLocal(body, mismatch)
140
- process.stdout.write(`${filePath}\n`)
67
+ // The block goes to stdout so the session can paste it where the issue
68
+ // would have gone, and the reason frames on stderr.
69
+ frameError(issueFailureMessage(result))
70
+ process.stdout.write(`${body}\n`)
71
+ process.exitCode = 1
141
72
  })
142
73
  }
@@ -239,8 +239,8 @@ export function register(program: Command): void {
239
239
  'after',
240
240
  [
241
241
  '',
242
- 'Measures the rule in .claude/rules/canon/claude/567-planning.md that asks for',
243
- 'the test before the code. It reports and never gates, because pairing',
242
+ 'Measures the test-first order the test-first skill and auto-ship hold: the',
243
+ 'test before the code. It reports and never gates, because pairing',
244
244
  'a test to an implementation is a judgment.',
245
245
  '',
246
246
  'Coverage:',
@@ -57,6 +57,7 @@ import {
57
57
  plural,
58
58
  } from '@/cli/ui'
59
59
  import { currentWorktreeRoot, mainWorktreeRoot } from '@/git/worktree'
60
+ import { readLivePlans, stackFindings } from '@/tasks/stack'
60
61
 
61
62
  /** Returned when a record carries a finding, which is the gating result. */
62
63
  const EXIT_FINDINGS = 2
@@ -299,10 +300,10 @@ export function register(program: Command): void {
299
300
  'Entries are ordered due first, then those citing a missing path, then the',
300
301
  'longest unreviewed, then by name, so a review takes the first N as a batch.',
301
302
  '',
302
- 'canonical: reads REQUIREMENTS.md and ARCHITECTURE.md at whichever surface',
303
- 'root carries each, in the current worktree since both are tracked. Each',
304
- 'reports its reviewed date and the release tags merged into HEAD after that',
305
- 'day, or never when it carries no date. --days does not apply.',
303
+ 'canonical: reads REQUIREMENTS.md at whichever surface root carries it,',
304
+ 'in the current worktree since it is tracked. It reports its reviewed date',
305
+ 'and the release tags merged into HEAD after that day, or never when it',
306
+ 'carries no date. --days does not apply.',
306
307
  '',
307
308
  'Exit codes:',
308
309
  ' 0 the reading completed, whatever it found',
@@ -644,7 +645,7 @@ async function runStaleCanonical(
644
645
  logStep('Canonical')
645
646
 
646
647
  if (outcome.docs.length === 0) {
647
- logInfo('neither REQUIREMENTS.md nor ARCHITECTURE.md exists')
648
+ logInfo('REQUIREMENTS.md does not exist')
648
649
  } else {
649
650
  for (const doc of outcome.docs) logInfo(describeCanonical(doc, outcome))
650
651
  }
@@ -911,7 +912,24 @@ async function runValidate(
911
912
 
912
913
  const root = opts.root ?? (await defaultRoot(kind))
913
914
 
914
- return report(await validateRecords(root, kind), emitJson, root)
915
+ return report(await validateWithStacks(root, kind), emitJson, root)
916
+ }
917
+
918
+ /**
919
+ * Appends the one plans finding that reads across files. It sits in the command
920
+ * layer because `src/records/` sits below `src/tasks/`, and `migrate` iterates
921
+ * `validateRecords` alone, so it never meets a finding with no safe repair.
922
+ */
923
+ async function validateWithStacks(
924
+ root: string,
925
+ kind: RecordKind,
926
+ ): Promise<ValidateOutcome> {
927
+ const outcome = await validateRecords(root, kind)
928
+ if (!outcome.ok || kind !== 'plans') return outcome
929
+
930
+ const stacked = stackFindings(await readLivePlans(root))
931
+
932
+ return { ...outcome, findings: [...outcome.findings, ...stacked] }
915
933
  }
916
934
 
917
935
  function report(
@@ -282,7 +282,7 @@ function renderTooling(report: CheckReport): void {
282
282
  /**
283
283
  * Seeds print their own section because no sync command applies them. A `stale`
284
284
  * seed is safe to take whole and a `drifted` one holds edits, which is the split
285
- * `seed-sync` reads to decide what needs a section-level merge.
285
+ * that decides what needs a hand merge.
286
286
  */
287
287
  function renderSeeds(report: CheckReport): void {
288
288
  const notable = report.seeds.entries.filter(
@@ -300,7 +300,9 @@ function renderSeeds(report: CheckReport): void {
300
300
  logWarn(`${entry.rel} (${entry.state})`)
301
301
  }
302
302
 
303
- logInfo('Run /canon:seed-sync to reconcile these section by section.')
303
+ logInfo(
304
+ 'A stale seed is safe to take whole. Merge a drifted one by hand against the toolkit copy.',
305
+ )
304
306
  }
305
307
 
306
308
  /**
@@ -401,7 +403,7 @@ async function runSync(target: string): Promise<number> {
401
403
 
402
404
  if (existsSync(join(resolved, '.claude'))) {
403
405
  process.stderr.write(
404
- `${GREY}Tip: run \`/seed-sync\` to audit seed drift per section, preserving local customizations.${NC}\n`,
406
+ `${GREY}Tip: merge a drifted seed by hand against the toolkit copy, and take a stale one whole.${NC}\n`,
405
407
  )
406
408
  }
407
409
 
@@ -0,0 +1,430 @@
1
+ import { resolve } from 'node:path'
2
+ import { $ } from 'bun'
3
+ import type { Command } from 'commander'
4
+ import { intro, logInfo, logStep, logWarn, outro, plural } from '@/cli/ui'
5
+ import { currentWorktreeRoot, mainWorktreeRoot } from '@/git/worktree'
6
+ import { buildCatalog, renderCatalog } from '@/upstream/catalog'
7
+ import {
8
+ advanceCursor,
9
+ compareVersions,
10
+ llmsDifference,
11
+ readCursor,
12
+ readLastLlms,
13
+ } from '@/upstream/cursor'
14
+ import { buildDigest } from '@/upstream/digest'
15
+ import {
16
+ decideDue,
17
+ passesVocabulary,
18
+ readDueCache,
19
+ writeDueCache,
20
+ type DueReason,
21
+ type Gap,
22
+ } from '@/upstream/due'
23
+ import {
24
+ githubPageFetcher,
25
+ grepHint,
26
+ keepLines,
27
+ walkReleases,
28
+ type FetchFailure,
29
+ } from '@/upstream/releases'
30
+
31
+ const LLMS_URL = 'https://code.claude.com/docs/llms.txt'
32
+ const VERSION = /^\d+(\.\d+)*$/
33
+ const READ_BUDGET_MS = 5000
34
+
35
+ interface RootOption {
36
+ readonly root?: string
37
+ readonly json?: boolean
38
+ }
39
+
40
+ interface FetchOptions extends RootOption {
41
+ readonly since?: string
42
+ }
43
+
44
+ interface AdvanceOptions extends RootOption {
45
+ readonly intake: string
46
+ readonly llms: boolean
47
+ }
48
+
49
+ type Refusal =
50
+ | 'no-cursor'
51
+ | 'bad-since'
52
+ | 'cursor-missing'
53
+ | FetchFailure
54
+ | 'older-than-cursor'
55
+ | 'invalid-version'
56
+
57
+ const REFUSALS: Record<Refusal, string> = {
58
+ 'no-cursor':
59
+ 'No cursor is stored, so there is no range to read. Pass --since <version> with the last version a digest covered.',
60
+ 'bad-since': 'The --since value is not a dotted version such as 2.1.256.',
61
+ 'cursor-missing':
62
+ 'The releases feed ran out before reaching a release at or below the cursor, so the range cannot be bounded. Pass --since with a more recent version.',
63
+ 'rate-limited':
64
+ 'GitHub refused the request. Set GH_TOKEN or GITHUB_TOKEN to lift the hourly limit.',
65
+ network: 'The releases feed could not be reached.',
66
+ http: 'GitHub answered with an error for the releases feed.',
67
+ 'older-than-cursor':
68
+ 'The version is older than the stored cursor, and the cursor never moves back.',
69
+ 'invalid-version': 'The version is not a dotted number such as 2.1.289.',
70
+ }
71
+
72
+ export function register(program: Command): void {
73
+ const upstream = program
74
+ .command('upstream')
75
+ .description(
76
+ 'Read Claude Code releases against the toolkit (fetch, catalog, advance)',
77
+ )
78
+ .helpOption('-h, --help', 'Show this help message')
79
+ .addHelpText(
80
+ 'after',
81
+ [
82
+ '',
83
+ 'Ships in the published CLI as `canon gate` and `canon sandbox` do, but',
84
+ 'only the toolkit repository has the skill that drives it, so a target',
85
+ 'has no use for these verbs.',
86
+ '',
87
+ ].join('\n'),
88
+ )
89
+
90
+ upstream
91
+ .command('fetch')
92
+ .description(
93
+ 'List the release lines since the cursor, with the mechanical noise dropped',
94
+ )
95
+ .helpOption('-h, --help', 'Show this help message')
96
+ .option('--since <version>', 'Read from this version instead of the cursor')
97
+ .option('--root <path>', 'Repository to read, defaulting to the cwd')
98
+ .option('--json', 'Add a machine-readable record on stdout')
99
+ .addHelpText(
100
+ 'after',
101
+ [
102
+ '',
103
+ 'Writes nothing. The cursor moves only through `upstream advance`, so a',
104
+ 'run that dies midway reads the same range next time.',
105
+ '',
106
+ 'Exit codes:',
107
+ ' 0 the range was read, possibly empty',
108
+ ' 1 refused, with the reason on stderr or in the JSON record',
109
+ '',
110
+ 'Examples:',
111
+ ' canon upstream fetch --json',
112
+ ' canon upstream fetch --since 2.1.256 --json',
113
+ '',
114
+ ].join('\n'),
115
+ )
116
+ .action(async (opts: FetchOptions) => {
117
+ process.exitCode = await runFetch(opts)
118
+ })
119
+
120
+ upstream
121
+ .command('catalog')
122
+ .description("List the toolkit's own mechanisms, one line each")
123
+ .helpOption('-h, --help', 'Show this help message')
124
+ .option('--root <path>', 'Repository to read, defaulting to the cwd')
125
+ .option('--json', 'Emit the sections as a JSON record instead of text')
126
+ .addHelpText(
127
+ 'after',
128
+ [
129
+ '',
130
+ 'Writes nothing. Skills, verbs, hooks, and gate stages are read from the',
131
+ 'tree, so the list cannot drift from it. A row that exists but cannot be',
132
+ 'described is named under Gaps.',
133
+ '',
134
+ 'Examples:',
135
+ ' canon upstream catalog',
136
+ ' canon upstream catalog --json',
137
+ '',
138
+ ].join('\n'),
139
+ )
140
+ .action(async (opts: RootOption) => {
141
+ process.exitCode = await runCatalog(opts)
142
+ })
143
+
144
+ upstream
145
+ .command('due')
146
+ .description('Say whether a digest is due, from the cursor and the install')
147
+ .helpOption('-h, --help', 'Show this help message')
148
+ .option('--root <path>', 'Repository to read, defaulting to the main root')
149
+ .option('--json', 'Add a machine-readable record on stdout')
150
+ .addHelpText(
151
+ 'after',
152
+ [
153
+ '',
154
+ 'Due when the cursor is a week old and the installed version is ahead of',
155
+ 'it, or sooner when an Added line in the gap names a canon surface. The',
156
+ 'gap is read from GitHub at most once a day and cached under the scratch',
157
+ 'folder, and a failed read is held for the day as well.',
158
+ '',
159
+ 'Exit codes:',
160
+ ' 0 answered, whether or not a digest is due',
161
+ '',
162
+ 'Examples:',
163
+ ' canon upstream due --json',
164
+ '',
165
+ ].join('\n'),
166
+ )
167
+ .action(async (opts: RootOption) => {
168
+ process.exitCode = await runDue(opts)
169
+ })
170
+
171
+ upstream
172
+ .command('advance')
173
+ .description('Move the cursor to a version once its digest is filed')
174
+ .helpOption('-h, --help', 'Show this help message')
175
+ .argument('<version>', 'Last version the digest covered')
176
+ .requiredOption('--intake <slug>', 'Intake folder the digest was filed in')
177
+ .option('--no-llms', 'Keep the stored llms.txt rather than fetching it')
178
+ .option('--root <path>', 'Repository to write, defaulting to the cwd')
179
+ .option('--json', 'Add a machine-readable record on stdout')
180
+ .addHelpText(
181
+ 'after',
182
+ [
183
+ '',
184
+ 'Writes the cursor and the current llms.txt under the upstream record',
185
+ 'folder through a temp file and a rename. It refuses a version older than',
186
+ 'the stored cursor.',
187
+ '',
188
+ 'Exit codes:',
189
+ ' 0 the cursor moved',
190
+ ' 1 refused, with the reason on stderr or in the JSON record',
191
+ '',
192
+ 'Examples:',
193
+ ' canon upstream advance 2.1.289 --intake claude-code-2-1-257-to-2-1-289',
194
+ '',
195
+ ].join('\n'),
196
+ )
197
+ .action(async (version: string, opts: AdvanceOptions) => {
198
+ process.exitCode = await runAdvance(version, opts)
199
+ })
200
+ }
201
+
202
+ function refuse(
203
+ reason: Refusal,
204
+ extra: Record<string, unknown>,
205
+ emitJson: boolean,
206
+ ): number {
207
+ logStep('Refused')
208
+ logWarn(REFUSALS[reason])
209
+ outro()
210
+
211
+ if (emitJson) {
212
+ process.stdout.write(
213
+ `${JSON.stringify({ ...extra, reason, message: REFUSALS[reason] })}\n`,
214
+ )
215
+ }
216
+
217
+ return 1
218
+ }
219
+
220
+ async function fetchLlms(): Promise<string | null> {
221
+ try {
222
+ const response = await fetch(LLMS_URL)
223
+
224
+ return response.ok ? await response.text() : null
225
+ } catch {
226
+ return null
227
+ }
228
+ }
229
+
230
+ async function runFetch(opts: FetchOptions): Promise<number> {
231
+ const emitJson = opts.json ?? false
232
+ const cursorRoot = opts.root ? resolve(opts.root) : await mainWorktreeRoot()
233
+ const readRoot = opts.root ? resolve(opts.root) : await currentWorktreeRoot()
234
+
235
+ intro('canon upstream fetch')
236
+
237
+ if (opts.since !== undefined && !VERSION.test(opts.since)) {
238
+ return refuse('bad-since', { since: opts.since }, emitJson)
239
+ }
240
+ const cursor = opts.since ?? readCursor(cursorRoot)?.version
241
+ if (!cursor) return refuse('no-cursor', {}, emitJson)
242
+
243
+ const digest = await buildDigest(cursor, githubPageFetcher(), (name) =>
244
+ grepHint(readRoot, name),
245
+ )
246
+ if (digest.kind === 'failed') {
247
+ return refuse(digest.reason, {}, emitJson)
248
+ }
249
+ if (digest.kind === 'cursor-missing') {
250
+ return refuse('cursor-missing', { cursor: digest.cursor }, emitJson)
251
+ }
252
+
253
+ const llms = await fetchLlms()
254
+ const difference =
255
+ llms === null ? null : llmsDifference(readLastLlms(cursorRoot), llms)
256
+
257
+ logStep('Range')
258
+ logInfo(
259
+ `${digest.from} to ${digest.to}, ${plural(digest.releases, 'release')}, ${plural(digest.lines.length, 'line')} kept`,
260
+ )
261
+ if (difference === null) logWarn('llms.txt could not be fetched')
262
+ outro()
263
+
264
+ if (!emitJson) {
265
+ for (const line of digest.lines) {
266
+ process.stdout.write(`${line.version}\t${line.text}\n`)
267
+ }
268
+ } else {
269
+ process.stdout.write(
270
+ `${JSON.stringify({
271
+ from: digest.from,
272
+ to: digest.to,
273
+ releases: digest.releases,
274
+ lines: digest.lines,
275
+ llms: difference,
276
+ })}\n`,
277
+ )
278
+ }
279
+
280
+ return 0
281
+ }
282
+
283
+ const DUE_TEXT: Record<DueReason, string> = {
284
+ 'no-cursor': 'No digest has been run, so there is no cursor.',
285
+ week: 'A week has passed since the last digest.',
286
+ vocabulary: 'A release since the last digest added something canon may use.',
287
+ current: 'The installed version is at or below the cursor.',
288
+ recent:
289
+ 'The last digest is under a week old and nothing in the gap stands out.',
290
+ 'unknown-version': 'The installed version could not be read.',
291
+ }
292
+
293
+ async function installedVersion(): Promise<string | null> {
294
+ try {
295
+ const result = await $`claude --version`.quiet().nothrow()
296
+ const match = /^\d+(\.\d+)*/.exec(result.stdout.toString().trim())
297
+
298
+ return match ? match[0] : null
299
+ } catch {
300
+ return null
301
+ }
302
+ }
303
+
304
+ // A failed read is stored as a null gap, so a rate limit hit by one session of
305
+ // a wave is not retried by the nineteen after it.
306
+ async function readGap(
307
+ root: string,
308
+ installed: string,
309
+ cursor: string,
310
+ now: Date,
311
+ ): Promise<Gap | null> {
312
+ const cached = readDueCache(root, installed, cursor, now)
313
+ if (cached) return cached.gap
314
+
315
+ // The hook that calls this has a 10 second timeout, and a network that drops
316
+ // packets would otherwise hold every session start to it with nothing cached.
317
+ const walk = await walkReleases(
318
+ cursor,
319
+ githubPageFetcher(process.env, AbortSignal.timeout(READ_BUDGET_MS)),
320
+ )
321
+ const gap: Gap | null =
322
+ walk.kind === 'reached'
323
+ ? {
324
+ releases: walk.releases.length,
325
+ lines: walk.releases
326
+ .flatMap(keepLines)
327
+ .map((line) => line.text)
328
+ .filter(passesVocabulary),
329
+ }
330
+ : null
331
+
332
+ try {
333
+ await writeDueCache(root, {
334
+ checkedAt: now.toISOString(),
335
+ installed,
336
+ cursor,
337
+ gap,
338
+ })
339
+ } catch {
340
+ // A cache that cannot be written costs a request, not a wrong answer.
341
+ }
342
+
343
+ return gap
344
+ }
345
+
346
+ async function runDue(opts: RootOption): Promise<number> {
347
+ const root = opts.root ? resolve(opts.root) : await mainWorktreeRoot()
348
+ const now = new Date()
349
+ const cursor = readCursor(root)
350
+ const installed = await installedVersion()
351
+ const isAhead =
352
+ cursor !== null &&
353
+ installed !== null &&
354
+ compareVersions(installed, cursor.version) > 0
355
+ const gap = isAhead
356
+ ? await readGap(root, installed, cursor.version, now)
357
+ : null
358
+ const result = decideDue({ installed, cursor, gap, now })
359
+
360
+ intro('canon upstream due')
361
+ logStep(result.due ? 'Due' : 'Not due')
362
+ logInfo(DUE_TEXT[result.reason])
363
+ outro()
364
+
365
+ if (opts.json) {
366
+ process.stdout.write(
367
+ `${JSON.stringify({
368
+ ...result,
369
+ installed,
370
+ cursor: cursor?.version ?? null,
371
+ })}\n`,
372
+ )
373
+ }
374
+
375
+ return 0
376
+ }
377
+
378
+ async function runCatalog(opts: RootOption): Promise<number> {
379
+ const root = opts.root ? resolve(opts.root) : await currentWorktreeRoot()
380
+ const catalog = buildCatalog(root)
381
+
382
+ if (opts.json) {
383
+ process.stdout.write(`${JSON.stringify(catalog)}\n`)
384
+ } else {
385
+ process.stdout.write(`${renderCatalog(catalog)}\n`)
386
+ }
387
+
388
+ return 0
389
+ }
390
+
391
+ async function runAdvance(
392
+ version: string,
393
+ opts: AdvanceOptions,
394
+ ): Promise<number> {
395
+ const emitJson = opts.json ?? false
396
+ const root = opts.root ? resolve(opts.root) : await mainWorktreeRoot()
397
+
398
+ intro('canon upstream advance')
399
+
400
+ const llms = opts.llms ? await fetchLlms() : null
401
+ const outcome = await advanceCursor(root, {
402
+ version,
403
+ intake: opts.intake,
404
+ today: new Date().toISOString().slice(0, 10),
405
+ llms,
406
+ })
407
+ if (outcome.kind === 'refused') {
408
+ return refuse(outcome.reason, { version }, emitJson)
409
+ }
410
+
411
+ logStep('Cursor')
412
+ logInfo(`Moved to ${version}, filed as ${opts.intake}`)
413
+ if (opts.llms && llms === null) {
414
+ logWarn('llms.txt could not be fetched, so the stored copy is unchanged')
415
+ }
416
+ outro()
417
+
418
+ if (emitJson) {
419
+ process.stdout.write(
420
+ `${JSON.stringify({
421
+ version,
422
+ intake: opts.intake,
423
+ advanced: true,
424
+ llms: llms === null ? 'kept' : 'stored',
425
+ })}\n`,
426
+ )
427
+ }
428
+
429
+ return 0
430
+ }