@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,238 +0,0 @@
1
- import { readFile, readdir, stat } from 'node:fs/promises'
2
- import { basename, dirname, join, relative } from 'node:path'
3
- import type { AuditedFolder } from '@/context/folders'
4
- import { type BodyLine, bodyLines } from '@/markdown/scan'
5
-
6
- /** `standards/wireframes.md`'s `## States` heading, matched by text alone. */
7
- const STATES_HEADING = /^##\s+States\s*$/i
8
- const SECTION_HEADING = /^##\s+\S/
9
- const TABLE_ROW = /^\s*\|/
10
- const TABLE_SEPARATOR = /^\s*\|[\s:|-]+\|\s*$/
11
-
12
- /** The one exception the standard admits to the states/evidence bijection. */
13
- const NOT_CAPTURED = 'not captured'
14
-
15
- /** A fenced block's opening delimiter naming the `plaintext` info string. */
16
- const PLAINTEXT_FENCE = /^`{3,}\s*plaintext\s*$/i
17
-
18
- export interface StateRow {
19
- readonly line: number
20
- readonly state: string
21
- /** The evidence cell as written, backticks and all. */
22
- readonly evidence: string
23
- }
24
-
25
- export interface MissingFolderFinding {
26
- readonly line: number
27
- readonly state: string
28
- /** The cited path, so a report names what to go create or fix. */
29
- readonly path: string
30
- }
31
-
32
- export interface UnlistedFolderFinding {
33
- /** The evidence root the folder sits under, cited by at least one row. */
34
- readonly root: string
35
- readonly folder: string
36
- }
37
-
38
- export interface WireframeStatesReport {
39
- readonly rel: string
40
- readonly rows: readonly StateRow[]
41
- /** A row whose evidence cell names a path with no folder at it. */
42
- readonly missingFolders: readonly MissingFolderFinding[]
43
- /** A folder under a cited root that no row names. */
44
- readonly unlistedFolders: readonly UnlistedFolderFinding[]
45
- /** Line of the `plaintext` fence, absent from an entry carrying no sketch. */
46
- readonly sketchLine?: number
47
- /** Whether that sketch sits beside a state whose evidence already exists. */
48
- readonly sketchWithEvidence: boolean
49
- }
50
-
51
- function cells(row: string): string[] {
52
- const parts = row.split('|')
53
- if ((parts[0] ?? '').trim() === '') parts.shift()
54
- if ((parts[parts.length - 1] ?? '').trim() === '') parts.pop()
55
- return parts.map((cell) => cell.trim())
56
- }
57
-
58
- function columnIndex(headers: readonly string[], name: string): number {
59
- return headers.findIndex(
60
- (header) => header.toLowerCase() === name.toLowerCase(),
61
- )
62
- }
63
-
64
- /**
65
- * Reads the States table under the `## States` heading, matching its `State`
66
- * and `Evidence` columns by header text rather than position.
67
- *
68
- * Column order is the one part of the standard's own table the standard could
69
- * still move during its own review, per the plan this ships under. Matching
70
- * position would break silently on a reorder, where matching text breaks the
71
- * same way a renamed header would: visibly, by finding nothing.
72
- */
73
- export function parseStatesTable(lines: readonly BodyLine[]): StateRow[] {
74
- let index = 0
75
- while (index < lines.length && !STATES_HEADING.test(lines[index].text)) {
76
- index++
77
- }
78
- if (index >= lines.length) return []
79
- index++
80
-
81
- while (index < lines.length && !SECTION_HEADING.test(lines[index].text)) {
82
- const separator = lines[index + 1]
83
- if (
84
- TABLE_ROW.test(lines[index].text) &&
85
- separator !== undefined &&
86
- TABLE_SEPARATOR.test(separator.text)
87
- ) {
88
- break
89
- }
90
- index++
91
- }
92
- if (
93
- index >= lines.length ||
94
- SECTION_HEADING.test(lines[index].text) ||
95
- !TABLE_ROW.test(lines[index].text)
96
- ) {
97
- return []
98
- }
99
-
100
- const headers = cells(lines[index].text)
101
- const stateCol = columnIndex(headers, 'State')
102
- const evidenceCol = columnIndex(headers, 'Evidence')
103
- if (stateCol === -1 || evidenceCol === -1) return []
104
-
105
- index += 2
106
- const rows: StateRow[] = []
107
- while (index < lines.length && TABLE_ROW.test(lines[index].text)) {
108
- const row = cells(lines[index].text)
109
- rows.push({
110
- line: lines[index].number,
111
- state: row[stateCol] ?? '',
112
- evidence: row[evidenceCol] ?? '',
113
- })
114
- index++
115
- }
116
-
117
- return rows
118
- }
119
-
120
- /** Line of the first `plaintext`-fenced sketch, or nothing. */
121
- export function findSketchLine(lines: readonly BodyLine[]): number | undefined {
122
- for (const line of lines) {
123
- if (PLAINTEXT_FENCE.test(line.text.trim())) return line.number
124
- }
125
- return undefined
126
- }
127
-
128
- /**
129
- * Reads an evidence cell into the path it cites, or nothing for the one cell
130
- * value the standard exempts from having one.
131
- */
132
- function evidencePath(cell: string): string | undefined {
133
- const stripped = cell.replace(/^`+|`+$/g, '').trim()
134
- if (stripped.toLowerCase() === NOT_CAPTURED) return undefined
135
- return stripped.replace(/\/+$/, '')
136
- }
137
-
138
- async function isDirectory(path: string): Promise<boolean> {
139
- try {
140
- return (await stat(path)).isDirectory()
141
- } catch {
142
- return false
143
- }
144
- }
145
-
146
- async function subfolders(path: string): Promise<string[]> {
147
- try {
148
- const entries = await readdir(path, { withFileTypes: true })
149
- return entries
150
- .filter((entry) => entry.isDirectory())
151
- .map((entry) => entry.name)
152
- } catch {
153
- return []
154
- }
155
- }
156
-
157
- /**
158
- * Measures one wireframe entry against its own States table.
159
- *
160
- * The evidence cell resolves as the literal path it names against `root`,
161
- * per the plan's operator answer, since the standard puts the path there and a
162
- * root guessed from convention reads wrong in a monorepo carrying more than
163
- * one evidence tree.
164
- *
165
- * An unlisted folder is reported only under a root at least one row already
166
- * cites, per the same operator answer, so a project holding evidence with no
167
- * states table at all reports nothing. `roots` is built from the rows this
168
- * loop already resolved rather than scanned separately, which is what keeps
169
- * the two readings in the same pass.
170
- */
171
- export async function measureWireframeStates(
172
- root: string,
173
- rel: string,
174
- source: string,
175
- ): Promise<WireframeStatesReport> {
176
- const lines = bodyLines(source)
177
- const rows = parseStatesTable(lines)
178
- const sketchLine = findSketchLine(lines)
179
-
180
- const missingFolders: MissingFolderFinding[] = []
181
- const cited = new Map<string, Set<string>>()
182
- let anyCaptured = false
183
-
184
- for (const row of rows) {
185
- const path = evidencePath(row.evidence)
186
- if (path === undefined) continue
187
-
188
- if (!(await isDirectory(join(root, path)))) {
189
- missingFolders.push({ line: row.line, state: row.state, path })
190
- continue
191
- }
192
-
193
- anyCaptured = true
194
- const evidenceRoot = dirname(path)
195
- const folder = basename(path)
196
- const named = cited.get(evidenceRoot) ?? new Set<string>()
197
- named.add(folder)
198
- cited.set(evidenceRoot, named)
199
- }
200
-
201
- const unlistedFolders: UnlistedFolderFinding[] = []
202
- for (const [evidenceRoot, named] of cited) {
203
- for (const folder of await subfolders(join(root, evidenceRoot))) {
204
- if (!named.has(folder)) {
205
- unlistedFolders.push({ root: evidenceRoot, folder })
206
- }
207
- }
208
- }
209
-
210
- return {
211
- rel,
212
- rows,
213
- missingFolders,
214
- unlistedFolders,
215
- ...(sketchLine !== undefined && { sketchLine }),
216
- sketchWithEvidence: sketchLine !== undefined && anyCaptured,
217
- }
218
- }
219
-
220
- /** Measures every wireframe entry in a resolved `wireframes` folder. */
221
- export async function measureWireframeFolder(
222
- root: string,
223
- folder: AuditedFolder,
224
- ): Promise<WireframeStatesReport[]> {
225
- const reports: WireframeStatesReport[] = []
226
-
227
- for (const path of folder.entries) {
228
- reports.push(
229
- await measureWireframeStates(
230
- root,
231
- relative(root, path),
232
- await readFile(path, 'utf8'),
233
- ),
234
- )
235
- }
236
-
237
- return reports
238
- }
@@ -1,206 +0,0 @@
1
- import { readFile } from 'node:fs/promises'
2
- import { join } from 'node:path'
3
-
4
- /**
5
- * What this run can compute from the tree alone. A field stays absent rather
6
- * than empty when nothing local resolves it, so a caller never reads
7
- * "nothing to propose" as "propose removing what is already there."
8
- */
9
- export interface MetadataProposal {
10
- readonly description?: string
11
- readonly homepage?: string
12
- readonly topics?: readonly string[]
13
- }
14
-
15
- /** What the remote already carries, read by the caller rather than here. */
16
- export interface CurrentMetadata {
17
- readonly description: string
18
- readonly homepage: string
19
- readonly topics: readonly string[]
20
- }
21
-
22
- export interface MetadataDiff {
23
- readonly description?: { readonly current: string; readonly proposed: string }
24
- readonly homepage?: { readonly current: string; readonly proposed: string }
25
- readonly topics?: {
26
- readonly added: readonly string[]
27
- readonly removed: readonly string[]
28
- }
29
- }
30
-
31
- /** GitHub's own cap on the About field. */
32
- const MAX_DESCRIPTION_LENGTH = 350
33
-
34
- /** GitHub's own cap on topics per repository. */
35
- const MAX_TOPICS = 20
36
-
37
- /** GitHub's own shape for a topic: lowercase, alphanumeric, internal hyphens. */
38
- const TOPIC_PATTERN = /^[a-z0-9][a-z0-9-]*$/
39
-
40
- /**
41
- * Whether an already-trimmed, already-lowercased string is a shape GitHub
42
- * accepts as a topic. Exported so a caller validating an operator-supplied
43
- * topic list checks against the same rule this reader silently filters
44
- * `package.json`'s `keywords` through, rather than reimplementing it against
45
- * a looser test such as non-emptiness alone.
46
- */
47
- export function isValidTopic(topic: string): boolean {
48
- return TOPIC_PATTERN.test(topic)
49
- }
50
-
51
- /**
52
- * A bare image, or an image wrapped in a link, which is the shape a shields.io
53
- * badge takes. The wrapped alternative goes first, since the bare form would
54
- * otherwise match its inner image alone and leave the wrapping link behind.
55
- */
56
- const BADGE_TOKEN = /\[!\[[^\]]*\]\([^)]*\)\]\([^)]*\)|!\[[^\]]*\]\([^)]*\)/g
57
-
58
- /** True once every badge token is stripped and nothing remains. */
59
- function isBadgeLine(line: string): boolean {
60
- return line.replace(BADGE_TOKEN, '').trim() === ''
61
- }
62
-
63
- function stripInlineMarkdown(line: string): string {
64
- return line
65
- .replace(/\[([^\]]+)\]\([^)]*\)/g, '$1')
66
- .replace(/(\*\*|__)(.+?)\1/g, '$2')
67
- .replace(/(\*|_|`)(.+?)\1/g, '$2')
68
- .trim()
69
- }
70
-
71
- /**
72
- * The first prose line past the title and any badge row, stripped of inline
73
- * markdown and capped at GitHub's About field length.
74
- *
75
- * `undefined` only when the file carries nothing past its title and badges,
76
- * per the skill requirement's refusal boundary: a resolvable line that reads
77
- * as a title rather than a sentence is still proposed, since rejecting it is
78
- * a judgment for the person reading the proposal rather than for this reader.
79
- */
80
- export function extractOpeningLine(readme: string): string | undefined {
81
- for (const raw of readme.split('\n')) {
82
- const line = raw.trim()
83
- if (line === '' || line.startsWith('#') || isBadgeLine(line)) continue
84
-
85
- const stripped = stripInlineMarkdown(line)
86
- if (stripped === '') continue
87
-
88
- return stripped.length > MAX_DESCRIPTION_LENGTH
89
- ? `${stripped.slice(0, MAX_DESCRIPTION_LENGTH - 1)}…`
90
- : stripped
91
- }
92
- return undefined
93
- }
94
-
95
- /**
96
- * `package.json`'s `keywords` read as candidate topics, the field the wider
97
- * npm ecosystem already uses for this. Invalid entries are dropped silently
98
- * rather than refused, since a manifest mixing free-text keywords with
99
- * topic-shaped ones is ordinary and only the shaped half transfers.
100
- */
101
- function readTopics(keywords: unknown): readonly string[] | undefined {
102
- if (!Array.isArray(keywords)) return undefined
103
-
104
- const topics = new Set<string>()
105
- for (const entry of keywords) {
106
- if (typeof entry !== 'string') continue
107
- const topic = entry.trim().toLowerCase()
108
- if (isValidTopic(topic)) topics.add(topic)
109
- if (topics.size === MAX_TOPICS) break
110
- }
111
- return [...topics]
112
- }
113
-
114
- interface PackageFields {
115
- readonly homepage?: unknown
116
- readonly keywords?: unknown
117
- }
118
-
119
- async function readManifest(root: string): Promise<PackageFields | undefined> {
120
- try {
121
- const parsed: unknown = JSON.parse(
122
- await readFile(join(root, 'package.json'), 'utf8'),
123
- )
124
- return (parsed ?? undefined) as PackageFields | undefined
125
- } catch {
126
- return undefined
127
- }
128
- }
129
-
130
- /**
131
- * Computes what this tree can propose for its own repository metadata, with
132
- * no network call: an About text from the README's opening line, and a
133
- * homepage and a topic set from `package.json`, the one manifest every
134
- * target project this ships to already carries.
135
- *
136
- * A field a target project declares nowhere stays absent rather than empty,
137
- * so the caller comparing this against the remote never treats a field this
138
- * reader has no opinion on as a proposal to clear it.
139
- */
140
- export async function proposeMetadata(root: string): Promise<MetadataProposal> {
141
- const [readme, manifest] = await Promise.all([
142
- readFile(join(root, 'README.md'), 'utf8').catch(() => undefined),
143
- readManifest(root),
144
- ])
145
-
146
- const description =
147
- readme === undefined ? undefined : extractOpeningLine(readme)
148
-
149
- const homepage =
150
- typeof manifest?.homepage === 'string' && manifest.homepage.trim() !== ''
151
- ? manifest.homepage.trim()
152
- : undefined
153
-
154
- const topics = readTopics(manifest?.keywords)
155
-
156
- return {
157
- ...(description !== undefined && { description }),
158
- ...(homepage !== undefined && { homepage }),
159
- ...(topics !== undefined && topics.length > 0 && { topics }),
160
- }
161
- }
162
-
163
- /**
164
- * Compares a computed proposal against what the remote already carries.
165
- *
166
- * A field the proposal has no opinion on is never diffed, which is what
167
- * keeps a target project with no `keywords` field from seeing every existing
168
- * topic reported as a removal.
169
- */
170
- export function compareMetadata(
171
- current: CurrentMetadata,
172
- proposed: MetadataProposal,
173
- ): MetadataDiff {
174
- const diff: {
175
- description?: { current: string; proposed: string }
176
- homepage?: { current: string; proposed: string }
177
- topics?: { added: readonly string[]; removed: readonly string[] }
178
- } = {}
179
-
180
- if (
181
- proposed.description !== undefined &&
182
- proposed.description !== current.description
183
- ) {
184
- diff.description = {
185
- current: current.description,
186
- proposed: proposed.description,
187
- }
188
- }
189
-
190
- if (
191
- proposed.homepage !== undefined &&
192
- proposed.homepage !== current.homepage
193
- ) {
194
- diff.homepage = { current: current.homepage, proposed: proposed.homepage }
195
- }
196
-
197
- if (proposed.topics !== undefined) {
198
- const currentSet = new Set(current.topics)
199
- const proposedSet = new Set(proposed.topics)
200
- const added = proposed.topics.filter((topic) => !currentSet.has(topic))
201
- const removed = current.topics.filter((topic) => !proposedSet.has(topic))
202
- if (added.length > 0 || removed.length > 0) diff.topics = { added, removed }
203
- }
204
-
205
- return diff
206
- }
@@ -1,127 +0,0 @@
1
- ---
2
- title: Architecture reference
3
- description: Shape and content rules for canon/ARCHITECTURE.md
4
- paths:
5
- - 'canon/ARCHITECTURE.md'
6
- rule:
7
- - "Add a decision here only when it fills one of the standard's slots, and write any other decision into the context entry for the domain it constrains. At the record's stated cap, merge or retire an entry before adding one."
8
- ---
9
-
10
- # Architecture reference
11
-
12
- Applies to `canon/ARCHITECTURE.md`. Describes the system shape and the decisions behind it, not a tutorial, setup guide, or implementation walkthrough. Pair it with `CLAUDE.md`: session behavior lives there, and the system's shape lives here. Update when a decision is made or a risk is resolved.
13
-
14
- ## Scope
15
-
16
- Governs the system-shape document at `canon/ARCHITECTURE.md`: the overview, the decision entries, and the open risks.
17
-
18
- Does not govern:
19
-
20
- - Per-domain structure and narrative: `context.md`
21
- - Setup commands and install instructions: `readme.md`
22
- - Product scope, goals, and non-goals: `requirements.md`
23
-
24
- ## What goes in
25
-
26
- - A high-level overview of how the system is structured and why
27
- - Key technical decisions as named H3 entries, each filling one of the five slots below: what was chosen and why over the alternatives
28
- - Risks and open questions still unresolved
29
-
30
- A decision belongs here only when it fills a slot:
31
-
32
- - Stack and runtime: the language, the runtime, and the major libraries the system is built on
33
- - Delivery: how the system's content or code reaches the place it runs
34
- - Enforced boundaries: a boundary the system checks mechanically, and what enforces it
35
- - Layout: how the project's top-level roots and context tiers divide what they hold
36
- - Build principles: a rule for how a behavior is built that changes work in more than one domain, stated in one sentence
37
-
38
- The slots come from a scale test. Ask whether the decision would still be in this file if the project were ten times its size. At that size an always-loaded file holding one entry per decision fits no context window, and what survives is the shape: the stack, the delivery, the boundaries, the layout, and the principles. A decision that fits no slot is a domain decision, and its home is that domain's `canon/context/<domain>.md` entry under `## Decisions`, however many domains its reasoning touches. Reach is not the test, since nearly every decision reaches a second domain.
39
-
40
- ## What does not go in
41
-
42
- - How individual functions work line by line. The code carries its own behavior.
43
- - Full type definitions. They live in code. Reference the shape conceptually if needed.
44
- - A decision that fills no slot. It lives in the domain context entry it constrains, and this file carries at most one line pointing at it.
45
- - A measurement paragraph specific to one domain's own mechanism, even behind a slot decision. Route it to that domain's context entry and keep the choice, the alternative that lost, and one reason here.
46
- - The instances of a build principle. The principle takes one sentence here, and each instance lives in the domain entry where it applies.
47
- - The history of how a decision was reached or revised: rounds of candidates, a figure followed by its correction, a branch or change that moved a number. Cut it. Git and the change that introduced it keep the trail.
48
-
49
- ## Sections
50
-
51
- Use `## Overview`, `## Key technical decisions` with one named H3 per decision, and `## Risks / open questions`. Name each decision and give the reasoning, especially for non-obvious choices. Skip entries where the rationale is self-evident.
52
-
53
- ## Keeping it current
54
-
55
- - Rewrite a decision a later one changed rather than appending the change beside it. A reader should find the design that stands in one place, with the alternative that lost stated once.
56
- - Hold only what is open under `## Risks / open questions`. An entry leaves the section in the change that settles it, becoming a decision here when it fills a slot and moving to the domain context entry it constrains when it does not.
57
- - Carry an optional `reviewed: YYYY-MM-DD` frontmatter field naming the day someone last read the record whole for drift. Whoever finishes that review sets it as the review's last edit, and `canon records stale canonical` reads it to count the releases shipped since. A record with no field reads as never reviewed.
58
-
59
- ## Verification anchors
60
-
61
- A decision's reasoning stays correct while the numbers it cites move. The anchor records what a measured claim was read against, so a reader can tell a number that was checked and held from one nobody has looked at since.
62
-
63
- - Close a decision entry whose reasoning cites a measured number with a trailing sentence naming the short commit SHA and the ISO date that number was read: `Measured at <short-sha> on <YYYY-MM-DD>.`
64
- - Name a commit, never a pull request number or a branch. A branch is gone after merge, and a pull request number resolves only on the forge.
65
- - Carry one anchor per figure. When a number is re-measured, rewrite the number and its anchor in place, and leave the old value to the change that moved it.
66
- - Point at a generated file that already records a figure and its commit, rather than copying the figure and anchoring the copy.
67
- - Anchor on the number alone. A decision citing none takes no anchor whatever its reasoning rests on, because a marker over a claim nobody can re-measure is one no reader can falsify.
68
- - Anchor a decision when writing it or when amending its reasoning. Leave an entry written before the rule unanchored rather than dating it by blame, which is archaeology for a marker nothing reads back.
69
- - Read an absent anchor as unchecked rather than as current. On an entry citing no number there is nothing to check. On one citing a number the number is due a read.
70
- - Do not edit a claim in the pass that first anchors it. The anchor states what the claim was measured against, so changing both at once leaves nothing to check the anchor against.
71
- - Refresh the anchor whenever the number is re-read, whether or not it moved. A confirmed number and an unread one are the same text without the date.
72
-
73
- ## Revisit condition
74
-
75
- A decision says why it won and rarely says when that reason stops holding, so an entry can go stale while every word in it stays true. The revisit sentence names the finding that would show the harm the decision prevents has gone or moved.
76
-
77
- - Close each decision with one sentence of the form `Revisit when <finding>.` Name something a reader could notice, such as a platform release, a measured count crossing a line, or a dependency dropping a behavior.
78
- - Draft it from the harm the decision prevents, never from the mechanism it uses. A mechanism can keep working long after the reason for choosing it is gone, so re-checking it confirms the wrong fact.
79
- - Place the sentence after the reasoning and before any verification anchor, so the anchor stays the entry's last sentence.
80
- - Give a heading holding several principles one sentence, for the premise they share, rather than one per principle.
81
- - Adopt the requirement by stating the clause `Every decision closes with a revisit sentence.` in the record itself. The clause is the record's own, like the caps, so a checker reads it from the file and gates only a record that states it. A record stating none is reported and never gated.
82
- - A checker reads presence alone. A vacuous sentence passes it, so the sentence is only as useful as the finding it names.
83
-
84
- ## Caps
85
-
86
- Every session pays for this file before any work starts, so a heavy read is a real cost. Weight comes from two places, too many decisions and decisions written too long, so the record caps both, and caps the open risks beside them.
87
-
88
- - State each cap in the record itself. A checker reads only these spellings, so a clause worded another way is reported and never gated:
89
- - `This record holds at most <n> decisions.`
90
- - `at most <n> words a decision`, counting a decision's prose below its heading, fenced lines left out
91
- - `at most <n> risk bullets`, counting every list item under `## Risks / open questions`, nested ones included
92
- - The three can share one sentence, such as `This record holds at most 12 decisions, at most 150 words a decision, and at most 6 risk bullets.` Each cap is the record's own, so a record stating none is measured and never gated. The template and the seed a new project installs state all three, and a project loosens one by editing the number in its own record.
93
- - Set the decision cap near one decision per slot plus a small margin, the word cap near what one trade takes to state, about 150, and the risk cap at what a reader holds in mind at once, about 6.
94
- - At the decision cap, merge two decisions or retire one before adding another, and name which in the change that does it. Never pack two decisions under one heading, which the count cannot see.
95
- - At the word cap, cut a decision to the choice, the alternative that lost, the reason, and the revisit sentence. What it sheds is a mechanism or a measurement, which goes to the domain context entry it constrains, never under a second heading.
96
- - At the risk cap, drop what is no longer open before adding a risk.
97
- - Retire a decision by moving it to the domain context entry it constrains, not by deleting its reasoning.
98
- - The whole file's word count stays a report, read alongside the judgment, and never gates.
99
- - Yield to the paragraph weight checkpoint in `markdown.md`. A paragraph past the checkpoint is a defect no length guideline licenses.
100
-
101
- ## Template
102
-
103
- The revisit sentence closes the reasoning of every decision. The anchor sentence follows it on a decision whose reasoning cites a measured number and is absent from one that cites none. Each heading below names a slot, so a record starts with one decision per slot and renames each heading to the choice it records.
104
-
105
- ```markdown
106
- # Architecture
107
-
108
- ## Overview
109
-
110
- This record holds at most 12 decisions, at most 150 words a decision, and at most 6 risk bullets. Every decision closes with a revisit sentence.
111
-
112
- ## Key technical decisions
113
-
114
- ### Stack and runtime
115
-
116
- What was chosen, the alternative that lost, and why. Revisit when <finding>. Measured at <short-sha> on <YYYY-MM-DD>.
117
-
118
- ### Delivery
119
-
120
- ### Enforced boundaries
121
-
122
- ### Layout
123
-
124
- ### Build principles
125
-
126
- ## Risks / open questions
127
- ```