@erclx/canon 4.0.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 (643) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +129 -0
  3. package/claude/.claude-plugin/plugin.json +19 -0
  4. package/claude/skills/bash-cli-script/REQUIREMENT.md +42 -0
  5. package/claude/skills/bash-cli-script/SKILL.md +48 -0
  6. package/claude/skills/bash-cli-script/references/template.md +43 -0
  7. package/claude/skills/bash-script/REQUIREMENT.md +36 -0
  8. package/claude/skills/bash-script/SKILL.md +100 -0
  9. package/claude/skills/bash-script/references/patterns.md +349 -0
  10. package/claude/skills/canon-cli/REQUIREMENT.md +41 -0
  11. package/claude/skills/canon-cli/SKILL.md +103 -0
  12. package/claude/skills/canon-feedback-file/REQUIREMENT.md +40 -0
  13. package/claude/skills/canon-feedback-file/SKILL.md +80 -0
  14. package/claude/skills/canon-feedback-triage/REQUIREMENT.md +40 -0
  15. package/claude/skills/canon-feedback-triage/SKILL.md +63 -0
  16. package/claude/skills/canon-operator/REQUIREMENT.md +61 -0
  17. package/claude/skills/canon-operator/SKILL.md +108 -0
  18. package/claude/skills/canon-rollout/REQUIREMENT.md +59 -0
  19. package/claude/skills/canon-rollout/SKILL.md +147 -0
  20. package/claude/skills/canon-screencast/REQUIREMENT.md +39 -0
  21. package/claude/skills/canon-screencast/SKILL.md +167 -0
  22. package/claude/skills/canon-slides-draft/REQUIREMENT.md +39 -0
  23. package/claude/skills/canon-slides-draft/SKILL.md +62 -0
  24. package/claude/skills/ci-workflow/REQUIREMENT.md +40 -0
  25. package/claude/skills/ci-workflow/SKILL.md +65 -0
  26. package/claude/skills/ci-workflow/references/workflows.md +98 -0
  27. package/claude/skills/claude-address-review/REQUIREMENT.md +57 -0
  28. package/claude/skills/claude-address-review/SKILL.md +212 -0
  29. package/claude/skills/claude-address-review/references/rebase-conflicts.md +39 -0
  30. package/claude/skills/claude-autoship/REQUIREMENT.md +50 -0
  31. package/claude/skills/claude-autoship/SKILL.md +207 -0
  32. package/claude/skills/claude-design-extract/REQUIREMENT.md +42 -0
  33. package/claude/skills/claude-design-extract/SKILL.md +102 -0
  34. package/claude/skills/claude-diagram/REQUIREMENT.md +45 -0
  35. package/claude/skills/claude-diagram/SKILL.md +177 -0
  36. package/claude/skills/claude-docs/REQUIREMENT.md +60 -0
  37. package/claude/skills/claude-docs/SKILL.md +287 -0
  38. package/claude/skills/claude-docs/references/anchor-sweep.md +58 -0
  39. package/claude/skills/claude-docs/references/wireframe-sweep.md +45 -0
  40. package/claude/skills/claude-feature/REQUIREMENT.md +36 -0
  41. package/claude/skills/claude-feature/SKILL.md +115 -0
  42. package/claude/skills/claude-groundwork/REQUIREMENT.md +48 -0
  43. package/claude/skills/claude-groundwork/SKILL.md +142 -0
  44. package/claude/skills/claude-intake/REQUIREMENT.md +49 -0
  45. package/claude/skills/claude-intake/SKILL.md +114 -0
  46. package/claude/skills/claude-intake-answer/REQUIREMENT.md +48 -0
  47. package/claude/skills/claude-intake-answer/SKILL.md +90 -0
  48. package/claude/skills/claude-markdown-propose/REQUIREMENT.md +48 -0
  49. package/claude/skills/claude-markdown-propose/SKILL.md +118 -0
  50. package/claude/skills/claude-markdown-propose/references/format.md +107 -0
  51. package/claude/skills/claude-memory-capture/REQUIREMENT.md +50 -0
  52. package/claude/skills/claude-memory-capture/SKILL.md +101 -0
  53. package/claude/skills/claude-memory-review/REQUIREMENT.md +50 -0
  54. package/claude/skills/claude-memory-review/SKILL.md +210 -0
  55. package/claude/skills/claude-memory-review/references/receipt-format.md +48 -0
  56. package/claude/skills/claude-orchestrate/REQUIREMENT.md +121 -0
  57. package/claude/skills/claude-orchestrate/SKILL.md +241 -0
  58. package/claude/skills/claude-orchestrate/references/orchestrator-dispatch.md +132 -0
  59. package/claude/skills/claude-orchestrate/references/orchestrator-handoff.md +32 -0
  60. package/claude/skills/claude-orchestrate/references/orchestrator-parked.md +72 -0
  61. package/claude/skills/claude-orchestrate/references/orchestrator-poll.md +87 -0
  62. package/claude/skills/claude-orchestrate/references/orchestrator-resume.md +30 -0
  63. package/claude/skills/claude-orchestrate/references/orchestrator-sweep.md +21 -0
  64. package/claude/skills/claude-orchestrate/scripts/poll.sh +373 -0
  65. package/claude/skills/claude-orchestrate/scripts/watch.sh +181 -0
  66. package/claude/skills/claude-pr-review/REQUIREMENT.md +47 -0
  67. package/claude/skills/claude-pr-review/SKILL.md +295 -0
  68. package/claude/skills/claude-review/REQUIREMENT.md +39 -0
  69. package/claude/skills/claude-review/SKILL.md +142 -0
  70. package/claude/skills/claude-seed-sync/REQUIREMENT.md +45 -0
  71. package/claude/skills/claude-seed-sync/SKILL.md +156 -0
  72. package/claude/skills/claude-standards-audit/REQUIREMENT.md +33 -0
  73. package/claude/skills/claude-standards-audit/SKILL.md +99 -0
  74. package/claude/skills/claude-tasks/REQUIREMENT.md +43 -0
  75. package/claude/skills/claude-tasks/SKILL.md +161 -0
  76. package/claude/skills/claude-teach/REQUIREMENT.md +56 -0
  77. package/claude/skills/claude-teach/SKILL.md +196 -0
  78. package/claude/skills/claude-teach/references/lesson-craft.md +59 -0
  79. package/claude/skills/claude-teach/references/pedagogy.md +67 -0
  80. package/claude/skills/claude-teach/references/promotion.md +54 -0
  81. package/claude/skills/claude-ui-test/REQUIREMENT.md +40 -0
  82. package/claude/skills/claude-ui-test/SKILL.md +77 -0
  83. package/claude/skills/claude-ux-audit/REQUIREMENT.md +40 -0
  84. package/claude/skills/claude-ux-audit/SKILL.md +79 -0
  85. package/claude/skills/claude-ux-measure/REQUIREMENT.md +48 -0
  86. package/claude/skills/claude-ux-measure/SKILL.md +122 -0
  87. package/claude/skills/claude-worker/REQUIREMENT.md +54 -0
  88. package/claude/skills/claude-worker/SKILL.md +96 -0
  89. package/claude/skills/claude-worktree/REQUIREMENT.md +58 -0
  90. package/claude/skills/claude-worktree/SKILL.md +136 -0
  91. package/claude/skills/create-rule/REQUIREMENT.md +45 -0
  92. package/claude/skills/create-rule/SKILL.md +68 -0
  93. package/claude/skills/create-skill/REQUIREMENT.md +38 -0
  94. package/claude/skills/create-skill/SKILL.md +32 -0
  95. package/claude/skills/create-snippet/REQUIREMENT.md +39 -0
  96. package/claude/skills/create-snippet/SKILL.md +30 -0
  97. package/claude/skills/create-standard/REQUIREMENT.md +35 -0
  98. package/claude/skills/create-standard/SKILL.md +29 -0
  99. package/claude/skills/decision-escalate/REQUIREMENT.md +45 -0
  100. package/claude/skills/decision-escalate/SKILL.md +79 -0
  101. package/claude/skills/docs-sync/REQUIREMENT.md +41 -0
  102. package/claude/skills/docs-sync/SKILL.md +95 -0
  103. package/claude/skills/git-branch/REQUIREMENT.md +38 -0
  104. package/claude/skills/git-branch/SKILL.md +60 -0
  105. package/claude/skills/git-commit/REQUIREMENT.md +36 -0
  106. package/claude/skills/git-commit/SKILL.md +49 -0
  107. package/claude/skills/git-followup/REQUIREMENT.md +43 -0
  108. package/claude/skills/git-followup/SKILL.md +48 -0
  109. package/claude/skills/git-issue/REQUIREMENT.md +38 -0
  110. package/claude/skills/git-issue/SKILL.md +65 -0
  111. package/claude/skills/git-pr/REQUIREMENT.md +50 -0
  112. package/claude/skills/git-pr/SKILL.md +164 -0
  113. package/claude/skills/git-pr/references/labels.md +95 -0
  114. package/claude/skills/git-ship/REQUIREMENT.md +42 -0
  115. package/claude/skills/git-ship/SKILL.md +55 -0
  116. package/claude/skills/git-split/REQUIREMENT.md +39 -0
  117. package/claude/skills/git-split/SKILL.md +162 -0
  118. package/claude/skills/git-stage/REQUIREMENT.md +39 -0
  119. package/claude/skills/git-stage/SKILL.md +73 -0
  120. package/claude/skills/git-worktree/REQUIREMENT.md +38 -0
  121. package/claude/skills/git-worktree/SKILL.md +130 -0
  122. package/claude/skills/migration-claude-md/REQUIREMENT.md +40 -0
  123. package/claude/skills/migration-claude-md/SKILL.md +76 -0
  124. package/claude/skills/migration-context/REQUIREMENT.md +36 -0
  125. package/claude/skills/migration-context/SKILL.md +95 -0
  126. package/claude/skills/migration-standards-drop/REQUIREMENT.md +55 -0
  127. package/claude/skills/migration-standards-drop/SKILL.md +113 -0
  128. package/claude/skills/migration-superseded/REQUIREMENT.md +44 -0
  129. package/claude/skills/migration-superseded/SKILL.md +115 -0
  130. package/claude/skills/project-commands/REQUIREMENT.md +42 -0
  131. package/claude/skills/project-commands/SKILL.md +85 -0
  132. package/claude/skills/restate-plainly/REQUIREMENT.md +41 -0
  133. package/claude/skills/restate-plainly/SKILL.md +39 -0
  134. package/claude/skills/session-map/REQUIREMENT.md +57 -0
  135. package/claude/skills/session-map/SKILL.md +70 -0
  136. package/claude/skills/session-resume/REQUIREMENT.md +49 -0
  137. package/claude/skills/session-resume/SKILL.md +51 -0
  138. package/claude/skills/setup-gov/REQUIREMENT.md +37 -0
  139. package/claude/skills/setup-gov/SKILL.md +77 -0
  140. package/claude/skills/setup-indexes/REQUIREMENT.md +45 -0
  141. package/claude/skills/setup-indexes/SKILL.md +153 -0
  142. package/claude/skills/setup-init/REQUIREMENT.md +45 -0
  143. package/claude/skills/setup-init/SKILL.md +127 -0
  144. package/claude/skills/setup-plugins/REQUIREMENT.md +42 -0
  145. package/claude/skills/setup-plugins/SKILL.md +81 -0
  146. package/claude/skills/setup-plugins/references/plugin-catalog.md +53 -0
  147. package/claude/skills/setup-verify/REQUIREMENT.md +39 -0
  148. package/claude/skills/setup-verify/SKILL.md +51 -0
  149. package/claude/skills/systematic-debugging/REQUIREMENT.md +41 -0
  150. package/claude/skills/systematic-debugging/SKILL.md +70 -0
  151. package/claude/skills/write-human/REQUIREMENT.md +46 -0
  152. package/claude/skills/write-human/SKILL.md +68 -0
  153. package/claude/skills/write-human/references/density.md +38 -0
  154. package/claude/skills/write-human/references/machine-tells.md +107 -0
  155. package/claude/skills/write-human/references/source-material.md +37 -0
  156. package/claude/skills/youtube-transcripts/REQUIREMENT.md +38 -0
  157. package/claude/skills/youtube-transcripts/SKILL.md +34 -0
  158. package/docs/agents/audits.md +98 -0
  159. package/docs/agents/capture.md +37 -0
  160. package/docs/agents/census.md +23 -0
  161. package/docs/agents/commands.md +146 -0
  162. package/docs/agents/comments.md +34 -0
  163. package/docs/agents/context-audit-checks.md +120 -0
  164. package/docs/agents/context-audit.md +83 -0
  165. package/docs/agents/counts.md +76 -0
  166. package/docs/agents/demo.md +86 -0
  167. package/docs/agents/docs.md +17 -0
  168. package/docs/agents/gate.md +84 -0
  169. package/docs/agents/index.md +47 -0
  170. package/docs/agents/indexes.md +35 -0
  171. package/docs/agents/install-and-sync.md +385 -0
  172. package/docs/agents/intake.md +81 -0
  173. package/docs/agents/key-changes.md +103 -0
  174. package/docs/agents/label-coverage.md +73 -0
  175. package/docs/agents/markdown-audit.md +197 -0
  176. package/docs/agents/output-shape.md +70 -0
  177. package/docs/agents/overview.md +26 -0
  178. package/docs/agents/records.md +170 -0
  179. package/docs/agents/restated.md +81 -0
  180. package/docs/agents/review-classification.md +77 -0
  181. package/docs/agents/routing.md +61 -0
  182. package/docs/agents/rule-citations.md +98 -0
  183. package/docs/agents/sandbox.md +71 -0
  184. package/docs/agents/scripting.md +149 -0
  185. package/docs/agents/sessions.md +120 -0
  186. package/docs/agents/skills-audit.md +94 -0
  187. package/docs/agents/skills-reach.md +64 -0
  188. package/docs/agents/standards-audit.md +38 -0
  189. package/docs/agents/state-scoped-risk.md +105 -0
  190. package/docs/agents/superseded.md +85 -0
  191. package/docs/agents/targets.md +83 -0
  192. package/docs/agents/tasks.md +200 -0
  193. package/docs/agents/teach.md +158 -0
  194. package/docs/agents/test-order.md +56 -0
  195. package/docs/agents/worktrees.md +62 -0
  196. package/docs/ai-workflow.md +317 -0
  197. package/docs/index.md +23 -0
  198. package/docs/operating-model.md +223 -0
  199. package/docs/target-projects.md +258 -0
  200. package/docs/visual-design-workflow.md +151 -0
  201. package/docs/zshrc-aliases.md +65 -0
  202. package/governance/rules/ci/700-ci-workflow.md +44 -0
  203. package/governance/rules/claude/500-prose.md +15 -0
  204. package/governance/rules/claude/501-markdown.md +14 -0
  205. package/governance/rules/claude/510-context.md +28 -0
  206. package/governance/rules/claude/511-indexes.md +14 -0
  207. package/governance/rules/claude/520-wireframes.md +12 -0
  208. package/governance/rules/claude/530-requirements.md +11 -0
  209. package/governance/rules/claude/540-architecture.md +11 -0
  210. package/governance/rules/claude/550-design.md +11 -0
  211. package/governance/rules/claude/555-tasks.md +12 -0
  212. package/governance/rules/claude/556-groundwork.md +11 -0
  213. package/governance/rules/claude/557-intake.md +11 -0
  214. package/governance/rules/claude/558-plan.md +22 -0
  215. package/governance/rules/claude/559-memory.md +11 -0
  216. package/governance/rules/claude/560-diagrams.md +18 -0
  217. package/governance/rules/claude/561-teach.md +13 -0
  218. package/governance/rules/claude/562-session.md +15 -0
  219. package/governance/rules/claude/570-skill.md +24 -0
  220. package/governance/rules/claude/575-hooks.md +17 -0
  221. package/governance/rules/claude/576-settings.md +14 -0
  222. package/governance/rules/claude/580-readme.md +11 -0
  223. package/governance/rules/claude/590-rule-authoring.md +12 -0
  224. package/governance/rules/claude/591-standard-authoring.md +12 -0
  225. package/governance/rules/claude/592-claude-md.md +19 -0
  226. package/governance/rules/core/000-constitution.md +30 -0
  227. package/governance/rules/core/005-behavior.md +27 -0
  228. package/governance/rules/core/010-testing.md +35 -0
  229. package/governance/rules/core/015-output.md +20 -0
  230. package/governance/rules/core/020-concurrency.md +22 -0
  231. package/governance/rules/core/025-indexes.md +9 -0
  232. package/governance/rules/core/030-error-handling.md +31 -0
  233. package/governance/rules/core/035-tasks.md +13 -0
  234. package/governance/rules/core/040-performance.md +20 -0
  235. package/governance/rules/core/045-memory.md +12 -0
  236. package/governance/rules/core/050-logging.md +20 -0
  237. package/governance/rules/core/055-scratch.md +9 -0
  238. package/governance/rules/core/060-naming.md +19 -0
  239. package/governance/rules/core/065-spelling.md +19 -0
  240. package/governance/rules/core/070-planning.md +18 -0
  241. package/governance/rules/core/075-dependencies.md +25 -0
  242. package/governance/rules/core/080-config-comments.md +22 -0
  243. package/governance/rules/core/085-worktrees.md +17 -0
  244. package/governance/rules/core/087-git.md +11 -0
  245. package/governance/rules/core/090-code-comments.md +39 -0
  246. package/governance/rules/framework/200-react.md +51 -0
  247. package/governance/rules/framework/210-astro.md +41 -0
  248. package/governance/rules/framework/220-fastapi.md +43 -0
  249. package/governance/rules/framework/230-nextjs.md +48 -0
  250. package/governance/rules/framework/250-tailwind.md +32 -0
  251. package/governance/rules/framework/260-shadcn.md +34 -0
  252. package/governance/rules/lang/100-typescript.md +40 -0
  253. package/governance/rules/lang/110-python.md +42 -0
  254. package/governance/rules/lang/120-bash.md +19 -0
  255. package/governance/rules/lib/300-testing-ts.md +39 -0
  256. package/governance/rules/lib/305-e2e-reliability.md +34 -0
  257. package/governance/rules/lib/306-test-scope.md +25 -0
  258. package/governance/rules/lib/310-zod.md +25 -0
  259. package/governance/rules/lib/320-tanstack-query.md +32 -0
  260. package/governance/rules/lib/330-testing-py.md +44 -0
  261. package/governance/rules/lib/340-pydantic.md +38 -0
  262. package/governance/rules/lib/350-security-web.md +32 -0
  263. package/governance/rules/lib/360-security-server.md +39 -0
  264. package/governance/rules/lib/370-database.md +35 -0
  265. package/governance/rules/snippets/505-at-references.md +9 -0
  266. package/governance/rules/ui/400-ui.md +36 -0
  267. package/governance/rules/ui/410-a11y.md +48 -0
  268. package/governance/rules/ui/420-forms.md +36 -0
  269. package/governance/rules/ui/430-ux-completeness.md +65 -0
  270. package/governance/rules/ui/440-surface-capture.md +34 -0
  271. package/governance/rules/ui/450-link-behavior.md +19 -0
  272. package/governance/stacks/astro.toml +2 -0
  273. package/governance/stacks/base.toml +8 -0
  274. package/governance/stacks/node-server.toml +2 -0
  275. package/governance/stacks/node.toml +2 -0
  276. package/governance/stacks/python-fastapi.toml +2 -0
  277. package/governance/stacks/python.toml +2 -0
  278. package/governance/stacks/react.toml +2 -0
  279. package/package.json +69 -0
  280. package/scripts/config.sh +11 -0
  281. package/scripts/core/bootstrap.sh +81 -0
  282. package/scripts/core/check-color-source.sh +41 -0
  283. package/scripts/core/check-ignore-parity.sh +162 -0
  284. package/scripts/core/check-plugin-boundary.sh +45 -0
  285. package/scripts/core/check-seed-independence.sh +59 -0
  286. package/scripts/core/check-skill-paths.sh +24 -0
  287. package/scripts/core/clean.sh +36 -0
  288. package/scripts/core/install-check.sh +101 -0
  289. package/scripts/core/list-seed-roots.sh +18 -0
  290. package/scripts/core/regen-claude-copies.sh +10 -0
  291. package/scripts/core/regen-hero.sh +217 -0
  292. package/scripts/core/regen-indexes.sh +10 -0
  293. package/scripts/core/regen-tooling-paths.sh +61 -0
  294. package/scripts/core/repair-bare-flag.sh +19 -0
  295. package/scripts/core/snapshot.sh +134 -0
  296. package/scripts/core/update.sh +35 -0
  297. package/scripts/docs/list.sh +165 -0
  298. package/scripts/lib/frontmatter.sh +30 -0
  299. package/scripts/lib/gov.sh +14 -0
  300. package/scripts/lib/sandbox-fixtures.sh +191 -0
  301. package/scripts/lib/sandbox-git.sh +125 -0
  302. package/scripts/lib/sandbox-path.sh +206 -0
  303. package/scripts/lib/tooling.sh +35 -0
  304. package/scripts/lib/ui.sh +266 -0
  305. package/scripts/lib/worktree.sh +20 -0
  306. package/scripts/manage-sandbox.sh +466 -0
  307. package/scripts/snippets/create.sh +156 -0
  308. package/scripts/standards/list.sh +115 -0
  309. package/scripts/tooling/create.sh +109 -0
  310. package/scripts/tooling/verify.sh +179 -0
  311. package/snippets/align.md +12 -0
  312. package/snippets/claude/decision-memo.md +39 -0
  313. package/snippets/claude/feature-recap.md +19 -0
  314. package/snippets/claude/figma-steps.md +24 -0
  315. package/snippets/compact-summary.md +5 -0
  316. package/snippets/decision-help.md +6 -0
  317. package/snippets/meta-prompt.md +14 -0
  318. package/snippets/research-prompt.md +7 -0
  319. package/snippets/session-notes.md +11 -0
  320. package/snippets/snippets.toml +5 -0
  321. package/snippets/step-by-step.md +10 -0
  322. package/snippets/web-research.md +21 -0
  323. package/src/audits/baseline.ts +201 -0
  324. package/src/audits/catalog.ts +876 -0
  325. package/src/audits/run.ts +204 -0
  326. package/src/autoship/classify.ts +75 -0
  327. package/src/autoship/paths.ts +51 -0
  328. package/src/binary.ts +16 -0
  329. package/src/browser/engine.ts +40 -0
  330. package/src/census/count.ts +113 -0
  331. package/src/claude/cases/all.ts +24 -0
  332. package/src/claude/cases/authoring.ts +53 -0
  333. package/src/claude/cases/claude-workflow.ts +158 -0
  334. package/src/claude/cases/git.ts +44 -0
  335. package/src/claude/cases/misc.ts +27 -0
  336. package/src/claude/cases/setup.ts +94 -0
  337. package/src/claude/gitignore.ts +51 -0
  338. package/src/claude/routing.ts +283 -0
  339. package/src/claude/seeds-list.ts +47 -0
  340. package/src/claude/seeds.ts +150 -0
  341. package/src/claude/settings.ts +151 -0
  342. package/src/claude/skills-audit.ts +228 -0
  343. package/src/claude/skills-drift.ts +156 -0
  344. package/src/claude/skills-list.ts +99 -0
  345. package/src/claude/skills-rank.ts +320 -0
  346. package/src/claude/skills-reach.ts +227 -0
  347. package/src/cli-run.ts +43 -0
  348. package/src/cli.ts +200 -0
  349. package/src/commands/audits.ts +350 -0
  350. package/src/commands/autoship.ts +129 -0
  351. package/src/commands/capture.ts +133 -0
  352. package/src/commands/census.ts +105 -0
  353. package/src/commands/claude.ts +1286 -0
  354. package/src/commands/comments.ts +240 -0
  355. package/src/commands/context.ts +857 -0
  356. package/src/commands/demo.ts +389 -0
  357. package/src/commands/deps.ts +173 -0
  358. package/src/commands/design.ts +36 -0
  359. package/src/commands/docs.ts +60 -0
  360. package/src/commands/feedback-format.ts +23 -0
  361. package/src/commands/feedback.ts +112 -0
  362. package/src/commands/gate.ts +189 -0
  363. package/src/commands/gov.ts +1265 -0
  364. package/src/commands/indexes.ts +184 -0
  365. package/src/commands/init.ts +113 -0
  366. package/src/commands/intake.ts +406 -0
  367. package/src/commands/inventory.ts +256 -0
  368. package/src/commands/labels.ts +361 -0
  369. package/src/commands/markdown.ts +544 -0
  370. package/src/commands/migrate.ts +175 -0
  371. package/src/commands/pass-through.ts +39 -0
  372. package/src/commands/pr.ts +411 -0
  373. package/src/commands/records.ts +728 -0
  374. package/src/commands/sandbox.ts +468 -0
  375. package/src/commands/secrets.ts +132 -0
  376. package/src/commands/serve.ts +159 -0
  377. package/src/commands/sessions.ts +408 -0
  378. package/src/commands/slides.ts +126 -0
  379. package/src/commands/snippets.ts +84 -0
  380. package/src/commands/standards.ts +247 -0
  381. package/src/commands/sync.ts +428 -0
  382. package/src/commands/targets.ts +319 -0
  383. package/src/commands/tasks.ts +743 -0
  384. package/src/commands/teach.ts +786 -0
  385. package/src/commands/tooling.ts +573 -0
  386. package/src/commands/transcripts.ts +44 -0
  387. package/src/commands/upgrade.ts +231 -0
  388. package/src/commands/wiki.ts +100 -0
  389. package/src/commands/worktrees.ts +191 -0
  390. package/src/comments/scan.ts +338 -0
  391. package/src/comments/trend.ts +207 -0
  392. package/src/comments/vocabulary.ts +85 -0
  393. package/src/context/architecture.ts +364 -0
  394. package/src/context/audit.ts +790 -0
  395. package/src/context/citations.ts +196 -0
  396. package/src/context/folders.ts +186 -0
  397. package/src/context/gate.ts +57 -0
  398. package/src/context/index-drift.ts +64 -0
  399. package/src/context/narration.ts +99 -0
  400. package/src/copy.ts +30 -0
  401. package/src/counts/catalogs.ts +96 -0
  402. package/src/counts/numbers.ts +79 -0
  403. package/src/counts/scan.ts +314 -0
  404. package/src/demo/beats.ts +135 -0
  405. package/src/demo/compile.ts +326 -0
  406. package/src/demo/container.ts +63 -0
  407. package/src/demo/cursors.ts +55 -0
  408. package/src/demo/drive.ts +357 -0
  409. package/src/demo/pointer.ts +178 -0
  410. package/src/demo/theme.ts +112 -0
  411. package/src/deps/audit.ts +153 -0
  412. package/src/design/parse.ts +116 -0
  413. package/src/design/render.ts +249 -0
  414. package/src/docs/read.ts +77 -0
  415. package/src/exec.ts +16 -0
  416. package/src/exempt-marker.ts +43 -0
  417. package/src/frontmatter.ts +13 -0
  418. package/src/gate/measures.ts +682 -0
  419. package/src/gate/sequencer.ts +386 -0
  420. package/src/gate/stages.ts +412 -0
  421. package/src/git-env.ts +36 -0
  422. package/src/git-files.ts +105 -0
  423. package/src/git-ignore.ts +46 -0
  424. package/src/github-format.ts +13 -0
  425. package/src/github.ts +24 -0
  426. package/src/gov/adapter.ts +103 -0
  427. package/src/gov/citations.ts +514 -0
  428. package/src/gov/consumed.ts +129 -0
  429. package/src/gov/install.ts +132 -0
  430. package/src/gov/list.ts +106 -0
  431. package/src/gov/payload.ts +39 -0
  432. package/src/gov/restated.ts +814 -0
  433. package/src/gov/stacks.ts +205 -0
  434. package/src/gov/superseded.ts +415 -0
  435. package/src/gov/test-order.ts +407 -0
  436. package/src/indexes/frontmatter.ts +46 -0
  437. package/src/indexes/regen.ts +84 -0
  438. package/src/indexes/render.ts +201 -0
  439. package/src/indexes/walk.ts +83 -0
  440. package/src/init/flags.ts +60 -0
  441. package/src/init/plan.ts +114 -0
  442. package/src/init/run.ts +46 -0
  443. package/src/init/steps.ts +77 -0
  444. package/src/intake/folder.ts +320 -0
  445. package/src/intake/items.ts +174 -0
  446. package/src/inventory/config.ts +117 -0
  447. package/src/inventory/group.ts +76 -0
  448. package/src/inventory/subjects.ts +114 -0
  449. package/src/inventory/walk.ts +129 -0
  450. package/src/labels/audit.ts +82 -0
  451. package/src/labels/coverage.ts +79 -0
  452. package/src/labels/map.ts +101 -0
  453. package/src/labels/phase.ts +95 -0
  454. package/src/markdown/bans.ts +94 -0
  455. package/src/markdown/files.ts +102 -0
  456. package/src/markdown/gate.ts +28 -0
  457. package/src/markdown/scan.ts +295 -0
  458. package/src/markdown/structure.ts +730 -0
  459. package/src/migrate/apply.ts +115 -0
  460. package/src/migrate/plan.ts +103 -0
  461. package/src/migrate/rename.ts +183 -0
  462. package/src/pr/bijection.ts +145 -0
  463. package/src/pr/paths.ts +335 -0
  464. package/src/process/harness.ts +167 -0
  465. package/src/project-root.ts +19 -0
  466. package/src/records/backup.ts +455 -0
  467. package/src/records/migrate.ts +78 -0
  468. package/src/records/size.ts +260 -0
  469. package/src/records/validate.ts +1162 -0
  470. package/src/sandbox/census.ts +228 -0
  471. package/src/sandbox/coverage.ts +115 -0
  472. package/src/sandbox/expect.ts +629 -0
  473. package/src/sandbox/tree.ts +47 -0
  474. package/src/secrets/marker.ts +30 -0
  475. package/src/secrets/patterns.ts +142 -0
  476. package/src/secrets/scan.ts +123 -0
  477. package/src/secrets/shipped.ts +111 -0
  478. package/src/seed-marker.ts +74 -0
  479. package/src/serve/static.ts +322 -0
  480. package/src/sessions/claim.ts +85 -0
  481. package/src/sessions/live.ts +79 -0
  482. package/src/sessions/registry.ts +137 -0
  483. package/src/sessions/resolve.ts +333 -0
  484. package/src/slides/layouts.ts +391 -0
  485. package/src/slides/open.ts +18 -0
  486. package/src/slides/parse.ts +84 -0
  487. package/src/slides/render.ts +88 -0
  488. package/src/slides/styles.ts +44 -0
  489. package/src/snippets/categories.ts +66 -0
  490. package/src/snippets/list.ts +32 -0
  491. package/src/snippets/presets.ts +50 -0
  492. package/src/standards/audit.ts +132 -0
  493. package/src/standards/read.ts +100 -0
  494. package/src/sync/check.ts +692 -0
  495. package/src/sync/engine.ts +576 -0
  496. package/src/sync/git.ts +225 -0
  497. package/src/sync/history.ts +123 -0
  498. package/src/sync/layout.ts +140 -0
  499. package/src/sync/reverse.ts +268 -0
  500. package/src/sync/seeds-report.ts +126 -0
  501. package/src/sync/stamp.ts +358 -0
  502. package/src/sync/target.ts +70 -0
  503. package/src/sync/workflow.ts +200 -0
  504. package/src/target.ts +43 -0
  505. package/src/targets/pulls.ts +250 -0
  506. package/src/targets/registry.ts +203 -0
  507. package/src/targets/resolve.ts +145 -0
  508. package/src/targets/sweep.ts +246 -0
  509. package/src/tasks/archive.ts +506 -0
  510. package/src/tasks/record.ts +311 -0
  511. package/src/tasks/trunk.ts +89 -0
  512. package/src/tasks/validate.ts +1001 -0
  513. package/src/teach/lesson.ts +180 -0
  514. package/src/teach/workspace.ts +842 -0
  515. package/src/tooling/gitignore.ts +122 -0
  516. package/src/tooling/inject.ts +193 -0
  517. package/src/tooling/list.ts +39 -0
  518. package/src/tooling/manifest.ts +176 -0
  519. package/src/tooling/package.ts +166 -0
  520. package/src/tooling/read.ts +65 -0
  521. package/src/tooling/scan.ts +147 -0
  522. package/src/tooling/stamp.ts +44 -0
  523. package/src/transcripts/fetch.ts +156 -0
  524. package/src/transcripts/metadata.ts +54 -0
  525. package/src/transcripts/vtt.ts +114 -0
  526. package/src/ui.ts +266 -0
  527. package/src/version/compare.ts +53 -0
  528. package/src/version/installed.ts +40 -0
  529. package/src/version/manager.ts +67 -0
  530. package/src/version/skew.ts +192 -0
  531. package/src/wiki/init.ts +85 -0
  532. package/src/worktree.ts +143 -0
  533. package/src/worktrees/reclaim.ts +306 -0
  534. package/standards/architecture.md +72 -0
  535. package/standards/branch.md +59 -0
  536. package/standards/commit.md +72 -0
  537. package/standards/context.md +151 -0
  538. package/standards/design.md +93 -0
  539. package/standards/diagrams.md +152 -0
  540. package/standards/glossary.md +75 -0
  541. package/standards/groundwork.md +211 -0
  542. package/standards/index.md +36 -0
  543. package/standards/intake.md +192 -0
  544. package/standards/issue.md +94 -0
  545. package/standards/markdown.md +137 -0
  546. package/standards/memory.md +144 -0
  547. package/standards/plan.md +172 -0
  548. package/standards/pr.md +139 -0
  549. package/standards/publish.md +51 -0
  550. package/standards/readme.md +208 -0
  551. package/standards/requirements.md +70 -0
  552. package/standards/rule.md +118 -0
  553. package/standards/session.md +109 -0
  554. package/standards/skill.md +300 -0
  555. package/standards/slug.md +39 -0
  556. package/standards/snippets.md +76 -0
  557. package/standards/standard.md +170 -0
  558. package/standards/tasks.md +254 -0
  559. package/standards/teach.md +153 -0
  560. package/standards/versioning.md +71 -0
  561. package/standards/wireframes.md +113 -0
  562. package/tooling/astro/configs/astro.config.mjs +31 -0
  563. package/tooling/astro/configs/eslint.config.js +79 -0
  564. package/tooling/astro/configs/playwright.config.ts +26 -0
  565. package/tooling/astro/configs/tsconfig.json +12 -0
  566. package/tooling/astro/configs/vitest.config.ts +22 -0
  567. package/tooling/astro/manifest.toml +34 -0
  568. package/tooling/astro/reference.md +60 -0
  569. package/tooling/base/configs/.editorconfig +5 -0
  570. package/tooling/base/configs/.github/pull_request_template.md +18 -0
  571. package/tooling/base/configs/.github/workflows/verify.yml +35 -0
  572. package/tooling/base/configs/.husky/commit-msg +1 -0
  573. package/tooling/base/configs/.husky/post-merge +61 -0
  574. package/tooling/base/configs/.husky/post-rewrite +21 -0
  575. package/tooling/base/configs/.husky/pre-commit +1 -0
  576. package/tooling/base/configs/.husky/pre-push +1 -0
  577. package/tooling/base/configs/.prettierrc +12 -0
  578. package/tooling/base/configs/.shellcheckrc +1 -0
  579. package/tooling/base/configs/.vscode/extensions.json +9 -0
  580. package/tooling/base/configs/.vscode/settings.json +3 -0
  581. package/tooling/base/configs/commitlint.config.js +11 -0
  582. package/tooling/base/configs/scripts/verify.sh +64 -0
  583. package/tooling/base/manifest.toml +30 -0
  584. package/tooling/base/reference.md +98 -0
  585. package/tooling/base/seeds/.claude/context/ci.md +33 -0
  586. package/tooling/base/seeds/.claude/context/development.md +37 -0
  587. package/tooling/base/seeds/.claude/context/index.md +11 -0
  588. package/tooling/base/seeds/.cspell/project-terms.txt +0 -0
  589. package/tooling/base/seeds/.cspell/tech-stack.txt +19 -0
  590. package/tooling/base/seeds/.lintstagedrc +8 -0
  591. package/tooling/base/seeds/.prettierignore +0 -0
  592. package/tooling/base/seeds/cspell.json +20 -0
  593. package/tooling/claude/manifest.toml +14 -0
  594. package/tooling/claude/reference.md +79 -0
  595. package/tooling/claude/seeds/.claude/ARCHITECTURE.md +13 -0
  596. package/tooling/claude/seeds/.claude/DESIGN.md +62 -0
  597. package/tooling/claude/seeds/.claude/REQUIREMENTS.md +18 -0
  598. package/tooling/claude/seeds/.claude/diagrams/index.md +8 -0
  599. package/tooling/claude/seeds/.claude/hooks/index-reminder.sh +50 -0
  600. package/tooling/claude/seeds/.claude/hooks/memory-index.sh +68 -0
  601. package/tooling/claude/seeds/.claude/hooks/path-form.sh +57 -0
  602. package/tooling/claude/seeds/.claude/hooks/scratch-guard.sh +52 -0
  603. package/tooling/claude/seeds/.claude/hooks/standards-audit.sh +86 -0
  604. package/tooling/claude/seeds/.claude/hooks/tasks-index.sh +71 -0
  605. package/tooling/claude/seeds/.claude/memory/index.md +8 -0
  606. package/tooling/claude/seeds/.claude/settings.json +47 -0
  607. package/tooling/claude/seeds/.claude/tasks/index.md +8 -0
  608. package/tooling/claude/seeds/.claude/wireframes/index.md +8 -0
  609. package/tooling/claude/seeds/CLAUDE.md +29 -0
  610. package/tooling/claude/user/settings.template.json +10 -0
  611. package/tooling/claude/user/statusline-command.sh +53 -0
  612. package/tooling/python/configs/.coveragerc +14 -0
  613. package/tooling/python/configs/.python-version +1 -0
  614. package/tooling/python/configs/mypy.ini +6 -0
  615. package/tooling/python/configs/pytest.ini +4 -0
  616. package/tooling/python/configs/ruff.toml +15 -0
  617. package/tooling/python/configs/scripts/verify.sh +77 -0
  618. package/tooling/python/manifest.toml +16 -0
  619. package/tooling/python/reference.md +66 -0
  620. package/tooling/python/seeds/.cspell/tech-stack.txt +19 -0
  621. package/tooling/python/seeds/tests/test_smoke.py +2 -0
  622. package/tooling/vite-react/configs/playwright.config.ts +26 -0
  623. package/tooling/vite-react/configs/tsconfig.json +35 -0
  624. package/tooling/vite-react/configs/vite.config.ts +24 -0
  625. package/tooling/vite-react/configs/vitest.config.ts +26 -0
  626. package/tooling/vite-react/manifest.toml +23 -0
  627. package/tooling/vite-react/reference.md +55 -0
  628. package/tooling/vite-react/seeds/.cspell/project-terms.txt +1 -0
  629. package/tooling/vite-react/seeds/.cspell/tech-stack.txt +1 -0
  630. package/tooling/web/configs/.github/workflows/verify.yml +134 -0
  631. package/tooling/web/configs/.vscode/extensions.json +13 -0
  632. package/tooling/web/configs/.vscode/settings.json +10 -0
  633. package/tooling/web/configs/e2e/home.spec.ts +6 -0
  634. package/tooling/web/configs/e2e/screenshot.ts +53 -0
  635. package/tooling/web/configs/eslint.config.js +82 -0
  636. package/tooling/web/configs/scripts/screenshot.sh +28 -0
  637. package/tooling/web/configs/scripts/verify.sh +80 -0
  638. package/tooling/web/configs/scripts/worktree-port.sh +74 -0
  639. package/tooling/web/configs/src/test/setup.ts +8 -0
  640. package/tooling/web/manifest.toml +58 -0
  641. package/tooling/web/reference.md +115 -0
  642. package/tooling/web/seeds/.cspell/tech-stack.txt +18 -0
  643. package/tsconfig.json +14 -0
@@ -0,0 +1,730 @@
1
+ import { type BodyLine, visibleText } from '@/markdown/scan'
2
+
3
+ const HEADING = /^#{1,6}\s/
4
+
5
+ /**
6
+ * A section marker holding its line alone, which is the signpost a document
7
+ * uses where its own template asks for bold rather than a heading.
8
+ *
9
+ * `standards/plan.md` gives `## Summary` a heading and marks the four sections
10
+ * below it this way, so a conforming plan read as one run from its first line
11
+ * to its last and all seven live plans reported past the depth checkpoint, at
12
+ * 106 to 166 rendered lines. A measure firing on a whole corpus says nothing
13
+ * about it, and it costs more than silence, since a reader who learns to skip
14
+ * the depth section skips it on the file where a deep run genuinely sits.
15
+ *
16
+ * Breaking on the marker clears two of those seven and moves the other five
17
+ * onto the seams inside them, at 42 to 77 lines, which is the measure telling
18
+ * a long section from a long file for the first time. Moving the template to
19
+ * headings was the alternative and it clears no plan already written, leaving
20
+ * each flagged until someone rewrites it.
21
+ *
22
+ * The marker starts at column zero and the whole line is the marker or none of
23
+ * it is. A bold phrase opening a sentence is emphasis rather than a seam, and
24
+ * an indented one is a label inside a list item, so both stay prose. This ships
25
+ * as package data every project reads, where a missed break costs one unbroken
26
+ * run and a false one shortens every run around it until the measure stops
27
+ * reporting, which is the dearer of the two.
28
+ *
29
+ * A colon ends most markers and not all of them. `claude-pr-review` writes
30
+ * three colon-less ones into every body it posts and a bold path heading for
31
+ * each file it reviews, so requiring the colon held a real seam out. Two
32
+ * signals stand in where the colon is absent, because the shape a marker has to
33
+ * be told from is a sentence set in bold and no one test separates both kinds
34
+ * of marker from it.
35
+ *
36
+ * A label that is one whole code span breaks at any width. A path runs long and
37
+ * a sentence set in bold is never a single span, so shape settles this half
38
+ * where width cannot. Across 81 colon-less markers in the review bodies posted
39
+ * on this repository, the 30 path headings run from 20 to 70 characters, and no
40
+ * colon-less line in the records tree is a whole span at all, so the rule adds
41
+ * reach without adding a false break.
42
+ *
43
+ * `MARKER_WIDTH` covers the rest and is read off the corpus rather than picked.
44
+ * Of the remaining 51 markers in those review bodies, 50 sit at or under 20
45
+ * visible characters, and so do 48 of the 94 colon-less lines in the records
46
+ * tree at `9960a4d7`, every one of them a label. The 21 to 30 band above it
47
+ * holds 8 lines nobody can classify on sight, where `One change across four
48
+ * files.` reads as a sentence and `Rule plus a mechanical half` reads as a
49
+ * seam, so the ceiling sits under that band rather than over it. That is where
50
+ * the asymmetry above points, a false break being the dearer error. Terminal
51
+ * punctuation separates nothing, since 45 of the 48 shortest colon-less lines
52
+ * end in one.
53
+ *
54
+ * Both signals govern the colon-less shape alone. A colon is its own evidence
55
+ * of a label, and capping the colon form as well takes the break back from four
56
+ * markers between 31 and 50 characters that already have it, every one of them
57
+ * a genuine section marker.
58
+ *
59
+ * One marker in that review corpus is reached by neither signal, a 48-character
60
+ * heading a session wrote by hand rather than from the template. Widening to
61
+ * catch it means raising the ceiling back through the band, so it is left as
62
+ * the cheap error the asymmetry above already names.
63
+ */
64
+ const BOLD_LINE = /^\*\*([^*]+)\*\*\s*$/
65
+ const SPAN_LABEL = /^`[^`]+`$/
66
+ const MARKER_WIDTH = 20
67
+
68
+ const LIST_ITEM = /^(\s*)([-*+]|\d+\.)\s+/
69
+ const TABLE_ROW = /^\s*\|/
70
+ const TABLE_SEPARATOR = /^\s*\|[\s:|-]+\|\s*$/
71
+ const BLOCKQUOTE = /^\s*>/
72
+
73
+ /**
74
+ * A sentence ends on terminal punctuation followed by the start of another
75
+ * sentence or by the end of the paragraph.
76
+ *
77
+ * Requiring an opening capital after the space is what keeps a version pin and
78
+ * a decimal from each reading as two sentences, since neither is followed by
79
+ * one. An abbreviation ahead of a capitalized word still counts, which
80
+ * over-reports by one on the sentence that carries it and is why this measure
81
+ * reports rather than gates.
82
+ *
83
+ * A code span opens a sentence that carries no capital at all, since a command
84
+ * name is lowercase and the requirement above would catch nothing there. The
85
+ * backtick therefore stands alone rather than joining the delimiters a capital
86
+ * follows, and the price is that a span opening a fragment mid-paragraph reads
87
+ * as a sentence start.
88
+ */
89
+ const SENTENCE_END = /[.!?]["'’”)\]]*(?=\s+(?:["'“(\[]*[A-Z]|`)|\s*$)/g
90
+
91
+ /**
92
+ * Checkpoints the structural measures run against.
93
+ *
94
+ * These are the definition rather than a fallback. Reading each number out of
95
+ * the sentence `standards/markdown.md` states it in was the alternative, and it
96
+ * put a parser contract on a document authored for people, where a rewording
97
+ * degraded a number and the report had to carry a legend saying which one. The
98
+ * standard still states every number for a reader, and moving one is an edit to
99
+ * both.
100
+ *
101
+ * The last three come from the `## Rhythm` section of the `write-human` skill
102
+ * rather than from a standard. That skill states what good rhythm is and this
103
+ * measures against the statement, so the numbers are read off the sentences
104
+ * that already carry them: a longest and shortest sentence within roughly five
105
+ * words of each other is one cadence, an opening word repeating twice is
106
+ * coincidence and three times is a pattern, and both rules are written about a
107
+ * paragraph, which needs a third sentence before either says anything.
108
+ */
109
+ export const CHECKPOINTS = {
110
+ run: 40,
111
+ peerBullet: 130,
112
+ bullet: 400,
113
+ paragraph: 700,
114
+ sentences: 4,
115
+ renderWidth: 80,
116
+ cadence: 3,
117
+ spread: 5,
118
+ opener: 2,
119
+ } as const
120
+
121
+ /**
122
+ * The reading a cadence rate is compared against, rather than a checkpoint.
123
+ *
124
+ * A count with nothing beside it reads as a finding, and the two counts the
125
+ * cadence step reports have no range a reader can place them in: twelve flat
126
+ * paragraphs out of forty says the same as two without one. Naming that a
127
+ * healthy range differs by surface states that a range exists rather than what
128
+ * it looks like, so the reading travels with the command.
129
+ *
130
+ * This is an observation rather than a rule, which is why it sits apart from
131
+ * `CHECKPOINTS`. Nothing compares a run against it and no exit code reads it. A
132
+ * project whose corpus is entirely terse reference prose is expected to sit
133
+ * above the high end, and that is the measure working.
134
+ *
135
+ * Read across 483 markdown files at `6c273324` on 2026-08-20. A number here
136
+ * goes stale against the corpus it describes with nothing comparing the two, so
137
+ * re-measure before moving one.
138
+ */
139
+ export const BASELINE = {
140
+ /** Share of every measured paragraph sitting at or under the spread checkpoint. */
141
+ flatShare: 8,
142
+ /** Measured paragraphs a file needs before its own rate means anything. */
143
+ floor: 10,
144
+ low: 0,
145
+ median: 6,
146
+ high: 21,
147
+ } as const
148
+
149
+ /**
150
+ * Columns a source line wraps at when rendered.
151
+ *
152
+ * Nothing in this repository sets a line width and entries are authored one
153
+ * line per bullet, so the rendered width is the viewer's rather than the file's.
154
+ */
155
+ export const RENDER_WIDTH: number = CHECKPOINTS.renderWidth
156
+
157
+ export interface Checkpoints {
158
+ readonly run: number
159
+ readonly peerBullet: number
160
+ readonly bullet: number
161
+ readonly paragraph: number
162
+ readonly sentences: number
163
+ readonly renderWidth: number
164
+ /** Sentences a paragraph needs before its cadence is measured at all. */
165
+ readonly cadence: number
166
+ /** Words between the longest and shortest sentence, at or under which the paragraph reads as one cadence. */
167
+ readonly spread: number
168
+ /** Times one opening word may open a sentence in a paragraph before it is a pattern. */
169
+ readonly opener: number
170
+ }
171
+
172
+ export interface BulletFinding {
173
+ readonly line: number
174
+ /** Weight as folded, so a report says how far past the checkpoint it sits. */
175
+ readonly characters: number
176
+ }
177
+
178
+ export interface ParagraphFinding {
179
+ readonly line: number
180
+ readonly sentences: number
181
+ readonly characters: number
182
+ }
183
+
184
+ /**
185
+ * One measured paragraph, carrying both cadence numbers rather than one.
186
+ *
187
+ * A paragraph reported for a narrow spread is usually worth reading for its
188
+ * openers too, and splitting the two into separate finding types would name the
189
+ * same line twice with half the picture on each.
190
+ */
191
+ export interface CadenceFinding {
192
+ readonly line: number
193
+ readonly sentences: number
194
+ /** Words between the longest and shortest sentence. */
195
+ readonly spread: number
196
+ /** Times the most repeated opening word opens a sentence here. */
197
+ readonly repeats: number
198
+ /** The opening word behind `repeats`, lowercased and stripped of punctuation. */
199
+ readonly opener: string
200
+ }
201
+
202
+ /**
203
+ * A file's cadence distribution, reported rather than listed finding by finding.
204
+ *
205
+ * Both numbers are advisory in a way even the weight checkpoints are not. A
206
+ * healthy range differs by surface, so terse reference prose and a page written
207
+ * for a reader sit at different spreads and one range applied across the corpus
208
+ * would report the surfaces that are correct. The counts are what a reader
209
+ * compares against the stated range, and the two worst paragraphs are where
210
+ * they open the file.
211
+ */
212
+ export interface CadenceReport {
213
+ /** Paragraphs carrying at least the floor sentence count. */
214
+ readonly measured: number
215
+ /** Measured paragraphs at or under the spread checkpoint. */
216
+ readonly flat: number
217
+ /** Measured paragraphs past the opener checkpoint. */
218
+ readonly repeating: number
219
+ /** Narrowest paragraph among the flat ones, or undefined when none is flat. */
220
+ readonly flattest: CadenceFinding | undefined
221
+ /** Most repetitive paragraph among the repeating ones, or undefined when none repeats. */
222
+ readonly mostRepeated: CadenceFinding | undefined
223
+ }
224
+
225
+ export interface StructureReport {
226
+ readonly rel: string
227
+ /** Rendered lines at the render width, not source lines. */
228
+ readonly longestRun: number
229
+ /** First line of the longest run, or 0 when the file has no run at all. */
230
+ readonly longestRunLine: number
231
+ readonly heavyBullets: readonly BulletFinding[]
232
+ readonly heavyParagraphs: readonly ParagraphFinding[]
233
+ readonly cadence: CadenceReport
234
+ }
235
+
236
+ /**
237
+ * Height a source line occupies once wrapped.
238
+ *
239
+ * A blank line renders as the gap it is rather than as nothing, which keeps it
240
+ * the distance the source measure already counted it as.
241
+ *
242
+ * The width is measured against what renders, since a rendered line shows a
243
+ * link's anchor text rather than its destination and a source measure
244
+ * over-counts exactly where this rule cares how far a reader travels.
245
+ */
246
+ export function renderedHeight(text: string, width = RENDER_WIDTH): number {
247
+ return Math.max(1, Math.ceil(visibleText(text).length / width))
248
+ }
249
+
250
+ /**
251
+ * Reports whether a run is the peer list the standard exempts.
252
+ *
253
+ * Every non-blank line has to be a list item at one indent. Prose mixed into
254
+ * the run or a nested level inside it ends the exemption, because either one
255
+ * means the block is no longer a flat set a reader can skim. Bullet count says
256
+ * nothing on its own, since a catalog of one-liners and a wall of paragraphs
257
+ * reach the same count and read nothing alike, so the average bullet decides.
258
+ */
259
+ function isScannablePeerList(
260
+ run: readonly BodyLine[],
261
+ checkpoint: number,
262
+ ): boolean {
263
+ const indents = new Set<number>()
264
+ let items = 0
265
+ let characters = 0
266
+
267
+ for (const line of run) {
268
+ const text = line.text.trim()
269
+ if (text === '') continue
270
+
271
+ const match = line.text.match(LIST_ITEM)
272
+ if (!match) return false
273
+ indents.add(match[1].length)
274
+ items++
275
+ characters += visibleText(text).length
276
+ }
277
+
278
+ if (indents.size !== 1) return false
279
+
280
+ return characters / items < checkpoint
281
+ }
282
+
283
+ /**
284
+ * Reports whether a run is a table, the second shape the checkpoint cannot fix.
285
+ *
286
+ * The peer list above is exempt because it is already navigable. A table is
287
+ * exempt for the other reason: the remedy does not exist. A heading dropped
288
+ * inside one splits the table into two tables rather than breaking the run.
289
+ *
290
+ * Every non-blank line has to be a row, and a delimiter is required rather than
291
+ * assumed. A run holding a table between paragraphs is genuinely mixed and a
292
+ * heading breaks it at a seam either side, and a stack of lines opening with a
293
+ * pipe and no delimiter renders as paragraph text.
294
+ */
295
+ function isTableRun(run: readonly BodyLine[]): boolean {
296
+ let separators = 0
297
+
298
+ for (const line of run) {
299
+ if (line.text.trim() === '') continue
300
+ if (!TABLE_ROW.test(line.text)) return false
301
+ if (TABLE_SEPARATOR.test(line.text)) separators++
302
+ }
303
+
304
+ return separators > 0
305
+ }
306
+
307
+ /**
308
+ * Reports whether a line is a section marker rather than emphasis.
309
+ *
310
+ * `BOLD_LINE` fixes the shape, and a colon-less label then answers to
311
+ * `SPAN_LABEL` or to `MARKER_WIDTH`, per the record above. Both read visible
312
+ * text, which reduces a link to its anchor text and leaves a backticked path
313
+ * counted whole, the same reading `isScannablePeerList` takes. The reduction is
314
+ * what lets a linked path reach the span test at all, since the markup around
315
+ * it would fail the pattern the destination is still attached.
316
+ */
317
+ function isSectionMarker(text: string): boolean {
318
+ const marker = text.match(BOLD_LINE)
319
+ if (!marker) return false
320
+
321
+ const label = marker[1]
322
+ if (label.endsWith(':')) return true
323
+
324
+ const visible = visibleText(label)
325
+
326
+ return SPAN_LABEL.test(visible) || visible.length <= MARKER_WIDTH
327
+ }
328
+
329
+ /**
330
+ * Measures the longest run of lines no signpost breaks, in rendered lines.
331
+ *
332
+ * A heading breaks a run and so does a section marker, which is the same
333
+ * signpost written the way a template asked for it. `isSectionMarker` above
334
+ * fixes which lines qualify.
335
+ *
336
+ * Fenced lines are skipped rather than treated as breaks, per the standard:
337
+ * they leave the count without ending the run, so prose either side of an
338
+ * example still measures as the one stretch a reader scrolls through. Blank
339
+ * lines do count, since the checkpoint is about how far a reader travels
340
+ * between signposts and a blank line is distance like any other.
341
+ *
342
+ * Height is what a reader travels, and source lines only stand in for it while
343
+ * lines stay short. A file authored one line per bullet puts a paragraph on
344
+ * each, so a block of fifteen bullets measures as fifteen and renders past
345
+ * sixty. Wrapping every line at a stated width is what closes that gap.
346
+ */
347
+ export function longestRun(
348
+ lines: readonly BodyLine[],
349
+ checkpoints: Checkpoints,
350
+ ): { length: number; line: number } {
351
+ let longest = 0
352
+ let longestLine = 0
353
+ let run: BodyLine[] = []
354
+
355
+ const close = (): void => {
356
+ // The reported line is the run's first non-blank one, since that is what an
357
+ // editor should open. A run of nothing but blank lines is the gap between
358
+ // two headings rather than a stretch a reader travels, so it never counts.
359
+ const first = run.find((line) => line.text.trim() !== '')
360
+
361
+ if (
362
+ first &&
363
+ !isScannablePeerList(run, checkpoints.peerBullet) &&
364
+ !isTableRun(run)
365
+ ) {
366
+ const height = run.reduce(
367
+ (sum, line) => sum + renderedHeight(line.text, checkpoints.renderWidth),
368
+ 0,
369
+ )
370
+
371
+ if (height > longest) {
372
+ longest = height
373
+ longestLine = first.number
374
+ }
375
+ }
376
+ run = []
377
+ }
378
+
379
+ for (const line of lines) {
380
+ if (line.fenced) continue
381
+
382
+ if (HEADING.test(line.text) || isSectionMarker(line.text)) {
383
+ close()
384
+ continue
385
+ }
386
+
387
+ run.push(line)
388
+ }
389
+
390
+ close()
391
+
392
+ return { length: longest, line: longestLine }
393
+ }
394
+
395
+ /**
396
+ * Finds the top-level bullets carrying more than a decision.
397
+ *
398
+ * A nested item is left out rather than folded into its parent, since the
399
+ * checkpoint asks what one bullet carries and a child carries its own. Lines
400
+ * continuing a bullet do fold in, so a heavy bullet cannot fall under the
401
+ * checkpoint by being wrapped across two source lines. A fenced line closes the
402
+ * open bullet, which keeps a bullet from absorbing the example below it.
403
+ */
404
+ export function heavyBullets(
405
+ lines: readonly BodyLine[],
406
+ checkpoints: Checkpoints,
407
+ ): BulletFinding[] {
408
+ const findings: BulletFinding[] = []
409
+ let open: BulletFinding | null = null
410
+
411
+ const close = (): void => {
412
+ if (open && open.characters > checkpoints.bullet) findings.push(open)
413
+ open = null
414
+ }
415
+
416
+ for (const line of lines) {
417
+ if (line.fenced) {
418
+ close()
419
+ continue
420
+ }
421
+
422
+ const item = line.text.match(LIST_ITEM)
423
+ const text = line.text.trim()
424
+
425
+ // Structure is read off the raw line and only the weight is masked. A line
426
+ // carrying nothing but an autolink has no visible text at all, and reading
427
+ // its masked form as blank would close the bullet it continues.
428
+ if (item) {
429
+ close()
430
+ if (item[1].length === 0) {
431
+ open = { line: line.number, characters: visibleText(text).length }
432
+ }
433
+ continue
434
+ }
435
+
436
+ if (text === '' || HEADING.test(line.text) || TABLE_ROW.test(line.text)) {
437
+ close()
438
+ continue
439
+ }
440
+
441
+ // The joining space a wrapped line would have carried, so folding two
442
+ // source lines measures what one unwrapped line would have.
443
+ if (open)
444
+ open = {
445
+ ...open,
446
+ characters: open.characters + visibleText(text).length + 1,
447
+ }
448
+ }
449
+
450
+ close()
451
+
452
+ return findings
453
+ }
454
+
455
+ /**
456
+ * Splits a paragraph into the sentences terminal punctuation closes.
457
+ *
458
+ * A trailing span no punctuation closes is dropped rather than returned, which
459
+ * keeps this and the count below one definition. The pattern already matches a
460
+ * paragraph's final period through its end-of-text alternative, so the only
461
+ * span this drops is a paragraph genuinely ending without terminal punctuation,
462
+ * which is a fragment rather than a sentence to either measure.
463
+ */
464
+ function splitSentences(text: string): string[] {
465
+ const sentences: string[] = []
466
+ let start = 0
467
+
468
+ for (const match of text.matchAll(SENTENCE_END)) {
469
+ const end = match.index + match[0].length
470
+ sentences.push(text.slice(start, end).trim())
471
+ start = end
472
+ }
473
+
474
+ return sentences
475
+ }
476
+
477
+ function countSentences(text: string): number {
478
+ return splitSentences(text).length
479
+ }
480
+
481
+ /**
482
+ * Splits the body into its prose paragraphs, each a run of consecutive lines.
483
+ *
484
+ * A heading, a list item, a table row, a blockquote, a blank line, and a fence
485
+ * each end one, so a bullet is measured by `heavyBullets` alone and never
486
+ * twice. Both paragraph measures walk this rather than one each, since two
487
+ * walks deciding what a paragraph is would drift apart.
488
+ */
489
+ function paragraphBlocks(lines: readonly BodyLine[]): BodyLine[][] {
490
+ const blocks: BodyLine[][] = []
491
+ let block: BodyLine[] = []
492
+
493
+ const close = (): void => {
494
+ if (block.length > 0) blocks.push(block)
495
+ block = []
496
+ }
497
+
498
+ for (const line of lines) {
499
+ const text = line.text.trim()
500
+ const breaks =
501
+ line.fenced ||
502
+ text === '' ||
503
+ HEADING.test(line.text) ||
504
+ LIST_ITEM.test(line.text) ||
505
+ TABLE_ROW.test(line.text) ||
506
+ BLOCKQUOTE.test(line.text)
507
+
508
+ if (breaks) {
509
+ close()
510
+ continue
511
+ }
512
+
513
+ block.push(line)
514
+ }
515
+
516
+ close()
517
+
518
+ return blocks
519
+ }
520
+
521
+ /** Folds a paragraph's source lines into the one line they would have wrapped from. */
522
+ function paragraphText(block: readonly BodyLine[]): string {
523
+ return block.map((line) => line.text.trim()).join(' ')
524
+ }
525
+
526
+ /**
527
+ * Finds the prose paragraphs past either half of the standard's checkpoint.
528
+ *
529
+ * Both halves are stated in the standard and both are read from it. The weight
530
+ * half was added there rather than borrowed from the bullet checkpoint, which
531
+ * governs a different construct: one number feeding both would move the
532
+ * paragraph rule whenever the bullet rule was changed, and an author reading
533
+ * the sentence cap would find no weight rule to read at all.
534
+ *
535
+ * The two numbers coincide today because paragraph and bullet weight measure
536
+ * one population, sharing a median near 170 characters with no gap behind
537
+ * either candidate. They are separate checkpoints regardless, so either moves
538
+ * without dragging the other.
539
+ */
540
+ export function heavyParagraphs(
541
+ lines: readonly BodyLine[],
542
+ checkpoints: Checkpoints,
543
+ ): ParagraphFinding[] {
544
+ const findings: ParagraphFinding[] = []
545
+
546
+ for (const block of paragraphBlocks(lines)) {
547
+ const text = paragraphText(block)
548
+ const sentences = countSentences(text)
549
+ const characters = visibleText(text).length
550
+
551
+ if (
552
+ sentences > checkpoints.sentences ||
553
+ characters > checkpoints.paragraph
554
+ ) {
555
+ findings.push({ line: block[0].number, sentences, characters })
556
+ }
557
+ }
558
+
559
+ return findings
560
+ }
561
+
562
+ /** Punctuation either side of a word, so an opener is compared on its letters. */
563
+ const WORD_EDGE = /^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu
564
+
565
+ /**
566
+ * The word a sentence opens on, lowercased and stripped of punctuation.
567
+ *
568
+ * A sentence opening on a code span yields the command inside it rather than a
569
+ * backtick, which is the word a reader hears. Casing is dropped because an
570
+ * opener repeating is a repetition whether or not one of the two sits mid-list
571
+ * and lost its capital.
572
+ */
573
+ function openingWord(sentence: string): string {
574
+ const [first = ''] = sentence.split(/\s+/)
575
+ return first.replace(WORD_EDGE, '').toLowerCase()
576
+ }
577
+
578
+ /**
579
+ * Measures one paragraph's spread and its most repeated opener.
580
+ *
581
+ * Words are counted off the text a reader is shown, so a link contributes its
582
+ * anchor text and a destination contributes nothing. The sentence boundaries do
583
+ * not move with that masking, since the pattern requires whitespace after the
584
+ * terminal punctuation and no destination or autolink carries any.
585
+ */
586
+ function measureParagraph(block: readonly BodyLine[]): CadenceFinding {
587
+ const sentences = splitSentences(visibleText(paragraphText(block)))
588
+ const lengths = sentences.map(
589
+ (sentence) => sentence.split(/\s+/).filter(Boolean).length,
590
+ )
591
+
592
+ const counts = new Map<string, number>()
593
+ for (const sentence of sentences) {
594
+ const word = openingWord(sentence)
595
+ if (word !== '') counts.set(word, (counts.get(word) ?? 0) + 1)
596
+ }
597
+
598
+ let opener = ''
599
+ let repeats = 0
600
+ for (const [word, count] of counts) {
601
+ if (count > repeats) {
602
+ opener = word
603
+ repeats = count
604
+ }
605
+ }
606
+
607
+ // A paragraph closing no sentence at all has no spread rather than a
608
+ // negative one, and the floor drops it before either number is read.
609
+ const spread =
610
+ lengths.length === 0 ? 0 : Math.max(...lengths) - Math.min(...lengths)
611
+
612
+ return {
613
+ line: block[0].number,
614
+ sentences: sentences.length,
615
+ spread,
616
+ repeats,
617
+ opener,
618
+ }
619
+ }
620
+
621
+ /**
622
+ * Reports how a file's prose is distributed across sentence length and opening
623
+ * word, which is the layer the ban sets cannot reach.
624
+ *
625
+ * A banned-word list expresses negatives and every symptom this answers is the
626
+ * absence of something, so no addition to that set catches a paragraph whose
627
+ * sentences are all one length. The measure stops at what is countable. A
628
+ * sentence's grammatical shape and whether it carries a finite verb are the two
629
+ * rules `write-human` states that this does not implement. Reporting them wrong
630
+ * is worse than not reporting them, since they name the exact failure this
631
+ * exists to measure.
632
+ *
633
+ * That was a prediction and it has now been measured, against two parsers
634
+ * rather than against the idea of one. Both ran over 11,389 paragraph sentences
635
+ * across 503 markdown files at `c7e92612` on 2026-08-20, and they disagree by
636
+ * a factor of four on a number one of them has to be wrong about.
637
+ *
638
+ * `compromise` reported 2 percent. It tags a fronted past participle as a
639
+ * finite past-tense verb, so all twelve of those sentences in
640
+ * `.claude/ARCHITECTURE.md` read as carrying one, eleven opening `Measured at`
641
+ * and one `Overturned by`, which is the exact shape the measure exists to
642
+ * catch. It is not even consistent with itself there: `Measured at` tags a verb
643
+ * and `Measured against` tags an adjective.
644
+ *
645
+ * `wink-pos-tagger` reported 9 percent and fixes that class, tagging the
646
+ * participle `VBN`. Roughly three quarters of what it flags is still wrong:
647
+ * hand-classifying every 21st of the 1,071 flagged sentences put 12 of 51
648
+ * genuinely verbless. Two classes split the other 39 almost evenly and neither
649
+ * is a tuning problem. Nineteen are imperatives, which this instructional
650
+ * corpus is full of and whose verb tags as a proper noun as often as a verb.
651
+ * Twenty are ordinary declaratives whose predicate is noun-ambiguous and tags
652
+ * as a noun outright, so `Each maps to a skill.` and `Nothing checks either
653
+ * one.` both report verbless. This corpus is built from exactly those words:
654
+ * reports, answers, checks, maps, holds, names, carries, records, measures.
655
+ *
656
+ * Separating them needs to know which token is the predicate, which is syntax
657
+ * rather than a tag. Neither tagger carries one and nothing was found worth
658
+ * taking for a number nothing gates on, so the rule the deferral set is met and
659
+ * still returns no: a parse was tried, twice, and neither produced a number
660
+ * worth printing beside two that hold.
661
+ *
662
+ * Where a shape sits is a different question and is already answered. A heading
663
+ * fragment, a list item, and a table cell are all legitimately verbless and
664
+ * none of them reaches this measure, because `paragraphBlocks` ends a paragraph
665
+ * on each. That exclusion is structural and needs no grammar. The imperative is
666
+ * the one that sits in paragraph prose, and it is what neither parser could
667
+ * separate out.
668
+ *
669
+ * A paragraph under the floor is skipped rather than scored. A two-sentence
670
+ * configuration note has no spread worth reading, and the opener rule is
671
+ * written about a third sentence turning a coincidence into a pattern, so
672
+ * neither measure says anything before the floor is reached.
673
+ */
674
+ export function measureCadence(
675
+ lines: readonly BodyLine[],
676
+ checkpoints: Checkpoints,
677
+ ): CadenceReport {
678
+ const measured = paragraphBlocks(lines)
679
+ .map(measureParagraph)
680
+ .filter((finding) => finding.sentences >= checkpoints.cadence)
681
+
682
+ const flat = measured.filter(
683
+ (finding) => finding.spread <= checkpoints.spread,
684
+ )
685
+ const repeating = measured.filter(
686
+ (finding) => finding.repeats > checkpoints.opener,
687
+ )
688
+
689
+ return {
690
+ measured: measured.length,
691
+ flat: flat.length,
692
+ repeating: repeating.length,
693
+ flattest: worst(flat, (finding) => -finding.spread),
694
+ mostRepeated: worst(repeating, (finding) => finding.repeats),
695
+ }
696
+ }
697
+
698
+ /**
699
+ * The file's worst paragraph on one measure, earliest line breaking a tie.
700
+ *
701
+ * Only paragraphs already past their checkpoint are passed in, so a file
702
+ * reading healthy names nothing rather than naming its least healthy paragraph,
703
+ * which a reader would take for a finding.
704
+ */
705
+ function worst(
706
+ findings: readonly CadenceFinding[],
707
+ rank: (finding: CadenceFinding) => number,
708
+ ): CadenceFinding | undefined {
709
+ return findings.reduce<CadenceFinding | undefined>(
710
+ (held, finding) => (!held || rank(finding) > rank(held) ? finding : held),
711
+ undefined,
712
+ )
713
+ }
714
+
715
+ export function measureStructure(
716
+ rel: string,
717
+ lines: readonly BodyLine[],
718
+ checkpoints: Checkpoints,
719
+ ): StructureReport {
720
+ const run = longestRun(lines, checkpoints)
721
+
722
+ return {
723
+ rel,
724
+ longestRun: run.length,
725
+ longestRunLine: run.line,
726
+ heavyBullets: heavyBullets(lines, checkpoints),
727
+ heavyParagraphs: heavyParagraphs(lines, checkpoints),
728
+ cadence: measureCadence(lines, checkpoints),
729
+ }
730
+ }