@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,814 @@
1
+ import { existsSync, readFileSync } from 'node:fs'
2
+ import { join } from 'node:path'
3
+
4
+ /** The always-loaded file whose bullets are the subjects this sweep matches. */
5
+ export const INSTRUCTIONS_REL = 'CLAUDE.md'
6
+
7
+ /** The seed a target receives, authored from the file above. */
8
+ export const SEED_REL = join('tooling', 'claude', 'seeds', 'CLAUDE.md')
9
+
10
+ /** The shipped plugin bodies, which is where a rule restated in prose lands. */
11
+ export const SHIPPED_SKILLS_REL = join('claude', 'skills')
12
+
13
+ /**
14
+ * Path-scoped rules, read from the authoring root rather than the consumed
15
+ * copy under `.claude/rules/`. A stack takes a whole rule folder, so a bullet
16
+ * landing anywhere under here ships to every target the stack reaches, the
17
+ * same way a bullet in the always-loaded file or the seed does. That is what
18
+ * makes a rule an instruction surface rather than a place a rule is merely
19
+ * quoted, and it is why a bullet moved here from the seed still belongs to
20
+ * the corpus this sweep reads rather than leaving it.
21
+ */
22
+ export const RULES_REL = join('governance', 'rules')
23
+
24
+ /**
25
+ * Path pairs whose duplication is deliberate and already recorded.
26
+ *
27
+ * The seed is authored from the always-loaded file and `claude-seed-sync`
28
+ * exists to reconcile the two, so a bullet appearing in both is the design
29
+ * rather than a defect. Excluding by pair rather than by content is what the
30
+ * plan settled on: the duplication is a location fact this repository already
31
+ * records, and a content test would have to rediscover it on every run.
32
+ *
33
+ * The exclusion reaches a repetition alone. A mirror that disagrees is the one
34
+ * shape the pairing cannot absorb, since the two files are meant to agree, so
35
+ * a polarity split on a declared pair stays a finding.
36
+ */
37
+ const MIRRORS: readonly (readonly [string, string])[] = [
38
+ [INSTRUCTIONS_REL, SEED_REL],
39
+ ]
40
+
41
+ /**
42
+ * A token appearing in more than this many statements carries no signal.
43
+ *
44
+ * Under one percent of the 2750 statements this repository offers. Tuned
45
+ * against that corpus rather than reasoned to, which is what the plan asked of
46
+ * the first run: `.claude/plans/` sits at 14 and is the anchor the motivating
47
+ * case turns on, while `file` sits at 371 and matches most of the tree.
48
+ */
49
+ export const COMMON_CEILING = 20
50
+
51
+ /** Weight two statements must share before they are read as one rule. */
52
+ export const ANCHOR_FLOOR = 3
53
+
54
+ /**
55
+ * What a backticked token is worth against a plain word.
56
+ *
57
+ * An author marking a span as code named an identifier rather than describing
58
+ * one, so `.claude/plans/archive/` says more about what a statement governs
59
+ * than any two prose words do. Weighting it is what lets the floor rise high
60
+ * enough to drop a coincidental word pair without losing a rule two surfaces
61
+ * spelled entirely differently around one shared path.
62
+ */
63
+ const SPAN_WEIGHT = 2
64
+
65
+ /**
66
+ * Weight a match needs before a polarity split is called a contradiction.
67
+ *
68
+ * Above the match floor on purpose. A thin match says two statements touch the
69
+ * same subject, which is not enough to claim one forbids what the other
70
+ * prescribes, so a weak pair reports as a repetition and the loudest class is
71
+ * reserved for a pair sharing real identity.
72
+ */
73
+ export const CONTRADICTION_FLOOR = 5
74
+
75
+ /**
76
+ * Words carrying no subject, dropped before anchors are counted.
77
+ *
78
+ * Short rather than exhaustive. The document-frequency ceiling above removes
79
+ * the rest on its own, and a hand-written list long enough to do that job
80
+ * would be a second corpus nobody maintains.
81
+ */
82
+ const STOPWORDS = new Set([
83
+ 'about',
84
+ 'after',
85
+ 'against',
86
+ 'already',
87
+ 'also',
88
+ 'and',
89
+ 'any',
90
+ 'are',
91
+ 'because',
92
+ 'been',
93
+ 'before',
94
+ 'being',
95
+ 'both',
96
+ 'but',
97
+ 'can',
98
+ 'each',
99
+ 'either',
100
+ 'else',
101
+ 'every',
102
+ 'for',
103
+ 'from',
104
+ 'has',
105
+ 'have',
106
+ 'her',
107
+ 'here',
108
+ 'his',
109
+ 'how',
110
+ 'into',
111
+ 'its',
112
+ 'itself',
113
+ 'more',
114
+ 'most',
115
+ 'much',
116
+ 'must',
117
+ 'once',
118
+ 'one',
119
+ 'only',
120
+ 'other',
121
+ 'our',
122
+ 'out',
123
+ 'over',
124
+ 'own',
125
+ 'per',
126
+ 'rather',
127
+ 'same',
128
+ 'she',
129
+ 'should',
130
+ 'since',
131
+ 'some',
132
+ 'such',
133
+ 'than',
134
+ 'that',
135
+ 'the',
136
+ 'their',
137
+ 'them',
138
+ 'then',
139
+ 'there',
140
+ 'these',
141
+ 'they',
142
+ 'this',
143
+ 'those',
144
+ 'through',
145
+ 'too',
146
+ 'under',
147
+ 'until',
148
+ 'upon',
149
+ 'very',
150
+ 'was',
151
+ 'were',
152
+ 'what',
153
+ 'when',
154
+ 'where',
155
+ 'which',
156
+ 'while',
157
+ 'who',
158
+ 'whose',
159
+ 'why',
160
+ 'will',
161
+ 'with',
162
+ 'would',
163
+ 'you',
164
+ 'your',
165
+ ])
166
+
167
+ /**
168
+ * Markers reading as a prohibition, which is the one polarity signal available
169
+ * without understanding the sentence.
170
+ *
171
+ * Deliberately narrow. `no` and `not` are excluded because both appear inside
172
+ * ordinary qualifying clauses, and widening the set turns most of the corpus
173
+ * into a suspected contradiction.
174
+ */
175
+ const PROHIBITIONS = ['never', 'do not', "don't", 'avoid', 'refuse']
176
+
177
+ export type RestatedRefusal = 'no-instructions' | 'no-surfaces'
178
+
179
+ export type Restatement = 'mirror' | 'repetition' | 'contradiction'
180
+
181
+ /** Which surface a restatement was found on, before any class is assigned. */
182
+ export type SurfaceKind = 'seed' | 'skill' | 'rule'
183
+
184
+ /**
185
+ * Which surface a later edit starts from.
186
+ *
187
+ * `unknown` is a first-class answer rather than a gap. The content-ownership
188
+ * table assigns a cross-domain rule and a domain-triggered one, and reaches
189
+ * nothing stated in a skill body the always-loaded file never names, so
190
+ * guessing there would put a reader on a surface nobody decided.
191
+ */
192
+ export type Authority = 'claude-md' | 'skill-body' | 'unknown'
193
+
194
+ export interface Statement {
195
+ /** Repository-relative, so a record reads the same from any working root. */
196
+ readonly file: string
197
+ /** One-based, matching the `file:line` form a reader clicks. */
198
+ readonly line: number
199
+ readonly text: string
200
+ }
201
+
202
+ export interface Surface extends Statement {
203
+ readonly kind: SurfaceKind
204
+ readonly restatement: Restatement
205
+ /** The distinctive tokens this match rested on, so a finding is auditable. */
206
+ readonly anchors: readonly string[]
207
+ /** Those anchors scored, with a backticked one counting double. */
208
+ readonly weight: number
209
+ readonly authority: Authority
210
+ /** Why the class and the authority read the way they do. */
211
+ readonly reason: string
212
+ }
213
+
214
+ export interface RestatedEntry {
215
+ readonly subject: Statement
216
+ readonly surfaces: readonly Surface[]
217
+ }
218
+
219
+ export interface RestatedCounts {
220
+ readonly contradictions: number
221
+ readonly repetitions: number
222
+ readonly mirrors: number
223
+ /** Duplicate clusters reaching three statements or more, the title's count. */
224
+ readonly threeSurface: number
225
+ }
226
+
227
+ export type RestatedReport =
228
+ | {
229
+ readonly kind: 'measured'
230
+ readonly corpus: {
231
+ readonly instructions: number
232
+ readonly seed: number
233
+ readonly bodies: number
234
+ /** Distinct rule files a bullet was read from, counted like `bodies`. */
235
+ readonly rules: number
236
+ /** Statements the three further surfaces offered, which bounds recall. */
237
+ readonly candidates: number
238
+ }
239
+ readonly matcher: {
240
+ readonly anchors: number
241
+ readonly common: number
242
+ readonly contradiction: number
243
+ }
244
+ readonly restatements: readonly RestatedEntry[]
245
+ readonly counts: RestatedCounts
246
+ }
247
+ | { readonly kind: 'unreadable'; readonly reason: RestatedRefusal }
248
+
249
+ interface Candidate extends Statement {
250
+ readonly kind: SurfaceKind
251
+ /** The skill folder this statement sits in, present on a body alone. */
252
+ readonly skill?: string
253
+ }
254
+
255
+ export interface Analysis {
256
+ readonly tokens: ReadonlySet<string>
257
+ /** The subset an author backticked, which weighs more than a plain word. */
258
+ readonly spans: ReadonlySet<string>
259
+ }
260
+
261
+ /** A statement paired with the tokens it carries. */
262
+ interface Indexed<T extends Statement> {
263
+ readonly statement: T
264
+ readonly analysis: Analysis
265
+ }
266
+
267
+ /**
268
+ * Splits text into the tokens an anchor can be drawn from.
269
+ *
270
+ * A code span keeps its inner text whole, since `.claude/plans/archive/` is the
271
+ * strongest anchor this corpus offers and splitting it on the punctuation would
272
+ * leave three words every second bullet also carries.
273
+ */
274
+ export function analyze(text: string): Analysis {
275
+ const spans: string[] = []
276
+ const withoutSpans = text.replace(/`([^`]+)`/g, (_match, inner: string) => {
277
+ spans.push(inner.toLowerCase())
278
+ return ' '
279
+ })
280
+
281
+ const words = withoutSpans
282
+ .toLowerCase()
283
+ .replace(/\[([^\]]*)\]\(([^)]*)\)/g, '$1 $2')
284
+ .split(/[^a-z0-9/._<>-]+/)
285
+ .map((word) => word.replace(/^[-._/]+|[-._/,;:]+$/g, ''))
286
+
287
+ const keep = (token: string): boolean =>
288
+ token.length >= 3 && !STOPWORDS.has(token)
289
+
290
+ return {
291
+ tokens: new Set([...spans, ...words].filter(keep)),
292
+ spans: new Set(spans.filter(keep)),
293
+ }
294
+ }
295
+
296
+ /**
297
+ * Whether a clause instructs against something, rather than merely describing
298
+ * something that does not happen.
299
+ *
300
+ * The marker has to open the clause. A prohibition is an instruction, and an
301
+ * instruction leads with its verb, so `Never delete a task file` prohibits
302
+ * where `a fallback never fires` reports. Reading the marker anywhere in the
303
+ * clause cannot separate those two, and this corpus writes both: the third
304
+ * false contradiction found here was `so a || fallback never fires` against a
305
+ * seed clause saying the same thing in other words.
306
+ *
307
+ * What it costs is a prohibition written mid-clause, as in `edit with the tool,
308
+ * never a stream editor`, which now reads as description. That miss lands the
309
+ * pair in the repetition class rather than dropping it, so both surfaces still
310
+ * reach the report and only the label is weaker.
311
+ */
312
+ function prohibits(text: string): boolean {
313
+ const opening = text
314
+ .toLowerCase()
315
+ .replace(/^[^a-z]*/, '')
316
+ .replace(/^(and|but|so|then|also|however)[\s,]+/, '')
317
+
318
+ return PROHIBITIONS.some((marker) => opening.startsWith(marker))
319
+ }
320
+
321
+ /**
322
+ * The clauses a statement's polarity is read against.
323
+ *
324
+ * The trailing span is kept, where `splitSentences` in `src/markdown/structure.ts`
325
+ * drops one no punctuation closes. A bullet routinely ends without a period and
326
+ * its last clause is routinely the one carrying the prohibition, so dropping it
327
+ * would lose exactly the half this reads. The two contracts differ, which is why
328
+ * this is a second splitter rather than a shared one.
329
+ */
330
+ function clauses(text: string): string[] {
331
+ return text
332
+ .split(/(?<=[.!?])\s+/)
333
+ .map((clause) => clause.trim())
334
+ .filter((clause) => clause !== '')
335
+ }
336
+
337
+ /**
338
+ * The clause a match landed in, which is where a prohibition has to sit before
339
+ * it says anything about the rule the two statements share.
340
+ *
341
+ * The densest clause rather than every clause carrying an anchor. A statement
342
+ * states one rule across several clauses, and a union answers true whenever any
343
+ * clause anywhere carries a marker, which is the whole statement again under
344
+ * another name.
345
+ *
346
+ * Both contradictions this repository reported were that defect. The
347
+ * always-loaded file splits the stream-editor rule across two bullets and the
348
+ * seed folds them into one, so the subject was the exception half alone while
349
+ * the seed's bullet carried the `never` from a clause the anchors never
350
+ * touched, and the two agreed completely.
351
+ */
352
+ function anchoredClause(text: string, anchors: ReadonlySet<string>): string {
353
+ const parts = clauses(text)
354
+ if (parts.length <= 1) return text
355
+
356
+ let best = text
357
+ let bestHits = 0
358
+
359
+ for (const part of parts) {
360
+ const tokens = analyze(part).tokens
361
+ let hits = 0
362
+ for (const anchor of anchors) if (tokens.has(anchor)) hits += 1
363
+
364
+ if (hits > bestHits) {
365
+ bestHits = hits
366
+ best = part
367
+ }
368
+ }
369
+
370
+ // No clause carries an anchor, which a boundary landing inside a code span
371
+ // can produce. Reading the whole statement is what this did before the clause
372
+ // scope, so it degrades to that rather than to no polarity at all.
373
+ return bestHits === 0 ? text : best
374
+ }
375
+
376
+ function isMirrorPair(subject: string, surface: string): boolean {
377
+ return MIRRORS.some(
378
+ ([left, right]) =>
379
+ (subject === left && surface === right) ||
380
+ (subject === right && surface === left),
381
+ )
382
+ }
383
+
384
+ function find(parent: Map<string, string>, node: string): string {
385
+ const above = parent.get(node)
386
+ if (above === undefined || above === node) return node
387
+ const root = find(parent, above)
388
+ parent.set(node, root)
389
+ return root
390
+ }
391
+
392
+ function union(parent: Map<string, string>, a: string, b: string): void {
393
+ if (!parent.has(a)) parent.set(a, a)
394
+ if (!parent.has(b)) parent.set(b, b)
395
+ const rootA = find(parent, a)
396
+ const rootB = find(parent, b)
397
+ if (rootA !== rootB) parent.set(rootA, rootB)
398
+ }
399
+
400
+ /**
401
+ * How many duplicate clusters reach three member statements or more.
402
+ *
403
+ * A rule sitting on both sides lets a cluster of mutually restating
404
+ * statements fragment across several `RestatedEntry` records, one per member
405
+ * that happens to lead the subject loop, so counting an entry whose own
406
+ * `surfaces.length >= 2` counts the same cluster more than once. Union over
407
+ * every reported pair instead: two statements in one component are one
408
+ * duplicated instruction wherever it was found from, and the entries a
409
+ * reader sees are one view into that same union, never a second source for
410
+ * its total.
411
+ */
412
+ function countThreeSurfaceClusters(pairs: ReadonlySet<string>): number {
413
+ const parent = new Map<string, string>()
414
+
415
+ for (const key of pairs) {
416
+ const [left, right] = key.split('|')
417
+ union(parent, left, right)
418
+ }
419
+
420
+ const sizes = new Map<string, number>()
421
+ for (const node of parent.keys()) {
422
+ const root = find(parent, node)
423
+ sizes.set(root, (sizes.get(root) ?? 0) + 1)
424
+ }
425
+
426
+ let clusters = 0
427
+ for (const size of sizes.values()) if (size >= 3) clusters += 1
428
+ return clusters
429
+ }
430
+
431
+ /**
432
+ * Bullets at the top level of a markdown file, which is the unit an instruction
433
+ * takes in the always-loaded file and in the seed.
434
+ */
435
+ function readBullets(root: string, relative: string): Statement[] {
436
+ const full = join(root, relative)
437
+ if (!existsSync(full)) return []
438
+
439
+ const statements: Statement[] = []
440
+ let fenced = false
441
+
442
+ readFileSync(full, 'utf8')
443
+ .split('\n')
444
+ .forEach((line, index) => {
445
+ if (line.trimStart().startsWith('```')) {
446
+ fenced = !fenced
447
+ return
448
+ }
449
+ if (fenced || !line.startsWith('- ')) return
450
+
451
+ statements.push({
452
+ file: relative.replaceAll('\\', '/'),
453
+ line: index + 1,
454
+ text: line.slice(2).trim(),
455
+ })
456
+ })
457
+
458
+ return statements
459
+ }
460
+
461
+ /**
462
+ * Every top-level bullet across every rule file, keyed to the file it came
463
+ * from rather than folded into one corpus. A rule reads as bullets under the
464
+ * same convention the always-loaded file and the seed use, so `readBullets`
465
+ * is reused per file rather than rebuilt for prose, and frontmatter is read
466
+ * past for the same reason it is: no line there starts with `- `.
467
+ */
468
+ function readRuleFiles(root: string, rulesRoot: string): Statement[] {
469
+ const files = [
470
+ ...new Bun.Glob('**/*.md').scanSync({ cwd: rulesRoot, onlyFiles: true }),
471
+ ].sort()
472
+
473
+ return files.flatMap((file) =>
474
+ readBullets(
475
+ root,
476
+ `${RULES_REL.replaceAll('\\', '/')}/${file.replaceAll('\\', '/')}`,
477
+ ),
478
+ )
479
+ }
480
+
481
+ /**
482
+ * Every prose line and bullet in a shipped body.
483
+ *
484
+ * Wider than the bullet rule above because the motivating case was stated in a
485
+ * body as a paragraph, so a bullet-only read would miss the one instance this
486
+ * sweep exists for. Headings, tables, and fenced blocks are read past: a
487
+ * heading names a section rather than stating a rule, and a fenced block is an
488
+ * example whose words are the surrounding prose's by construction.
489
+ */
490
+ function readBodyLines(root: string, skillsRoot: string): Candidate[] {
491
+ const candidates: Candidate[] = []
492
+
493
+ const files = [
494
+ ...new Bun.Glob('*/SKILL.md').scanSync({
495
+ cwd: skillsRoot,
496
+ onlyFiles: true,
497
+ }),
498
+ ].sort()
499
+
500
+ for (const file of files) {
501
+ const posix = file.replaceAll('\\', '/')
502
+ const skill = posix.split('/')[0]
503
+ const relative = `${SHIPPED_SKILLS_REL.replaceAll('\\', '/')}/${posix}`
504
+
505
+ const lines = readFileSync(join(root, relative), 'utf8').split('\n')
506
+ let fenced = false
507
+ let frontmatter = lines[0]?.trim() === '---'
508
+
509
+ for (const [index, line] of lines.entries()) {
510
+ const trimmed = line.trim()
511
+
512
+ // Frontmatter is metadata rather than instruction, and every body's
513
+ // `description` restates that skill's own purpose, so sweeping it makes
514
+ // each skill match any subject naming its domain.
515
+ if (frontmatter) {
516
+ if (index > 0 && trimmed === '---') frontmatter = false
517
+ continue
518
+ }
519
+
520
+ if (trimmed.startsWith('```')) {
521
+ fenced = !fenced
522
+ continue
523
+ }
524
+ if (fenced || trimmed === '') continue
525
+ if (trimmed.startsWith('#') || trimmed.startsWith('|')) continue
526
+ if (trimmed.startsWith('---')) continue
527
+
528
+ candidates.push({
529
+ file: relative,
530
+ line: index + 1,
531
+ text: trimmed.replace(/^[-*>]\s+/, ''),
532
+ kind: 'skill',
533
+ skill,
534
+ })
535
+ }
536
+ }
537
+
538
+ return candidates
539
+ }
540
+
541
+ /** How many statements each token appears in, which is what rarity is read off. */
542
+ function documentFrequency(
543
+ groups: readonly (readonly Indexed<Statement>[])[],
544
+ ): Map<string, number> {
545
+ const frequency = new Map<string, number>()
546
+
547
+ for (const group of groups) {
548
+ for (const entry of group) {
549
+ for (const token of entry.analysis.tokens) {
550
+ frequency.set(token, (frequency.get(token) ?? 0) + 1)
551
+ }
552
+ }
553
+ }
554
+
555
+ return frequency
556
+ }
557
+
558
+ function index<T extends Statement>(statement: T): Indexed<T> {
559
+ return { statement, analysis: analyze(statement.text) }
560
+ }
561
+
562
+ /**
563
+ * Which surface a later edit starts from, and why.
564
+ *
565
+ * A skill body earns authority only where the subject names that skill, which
566
+ * is the content-ownership table's rule that behavior triggered when editing
567
+ * domain X belongs to X's skill. Everything else the table does not reach is
568
+ * reported as unknown.
569
+ */
570
+ function authorityFor(
571
+ subject: Statement,
572
+ candidate: Candidate,
573
+ ): { authority: Authority; reason: string } {
574
+ if (candidate.kind === 'seed') {
575
+ return {
576
+ authority: 'claude-md',
577
+ reason: `${INSTRUCTIONS_REL} is authored first and the seed carries it to a target, so an edit starts there and reaches the seed through claude-seed-sync`,
578
+ }
579
+ }
580
+
581
+ if (
582
+ candidate.skill !== undefined &&
583
+ subject.text.toLowerCase().includes(candidate.skill.toLowerCase())
584
+ ) {
585
+ return {
586
+ authority: 'skill-body',
587
+ reason: `the subject names ${candidate.skill}, and behavior triggered only when editing one domain belongs to that domain's skill`,
588
+ }
589
+ }
590
+
591
+ return {
592
+ authority: 'unknown',
593
+ reason:
594
+ 'the content-ownership table assigns a cross-domain rule and a domain-triggered one, and reaches neither from here',
595
+ }
596
+ }
597
+
598
+ function classify(
599
+ subject: Statement,
600
+ candidate: Candidate,
601
+ weight: number,
602
+ anchors: ReadonlySet<string>,
603
+ ): { restatement: Restatement; reason: string } {
604
+ const split =
605
+ prohibits(anchoredClause(subject.text, anchors)) !==
606
+ prohibits(anchoredClause(candidate.text, anchors))
607
+
608
+ if (split && weight >= CONTRADICTION_FLOOR) {
609
+ return {
610
+ restatement: 'contradiction',
611
+ reason:
612
+ 'the clause each surface was matched on states this as a prohibition on one side alone, which is a polarity reading rather than a judgment about meaning',
613
+ }
614
+ }
615
+
616
+ if (isMirrorPair(subject.file, candidate.file)) {
617
+ return {
618
+ restatement: 'mirror',
619
+ reason:
620
+ 'both files sit on a declared mirror pair, where repeating the rule is the design',
621
+ }
622
+ }
623
+
624
+ return {
625
+ restatement: 'repetition',
626
+ reason: split
627
+ ? 'the matched clause carries a prohibition on one surface alone, on a match too thin to read that as a disagreement'
628
+ : 'two surfaces state one rule and neither is declared a copy of the other',
629
+ }
630
+ }
631
+
632
+ /**
633
+ * Every instruction the always-loaded file or a path-scoped rule states that a
634
+ * further surface also states.
635
+ *
636
+ * Matching is recall-first, keyed on distinctive tokens two statements share
637
+ * rather than on a phrase they spell the same way. The motivating case was one
638
+ * rule written three different ways, so a near-exact matcher would miss the
639
+ * defect the sweep exists for, and a recall-first reading can be narrowed from
640
+ * real output where the reverse cannot.
641
+ *
642
+ * The always-loaded file and every rule each open the search as a subject, and
643
+ * the seed, the shipped bodies, and every rule again close it as a candidate.
644
+ * A rule sits on both sides because a stack ships a whole rule folder, so a
645
+ * bullet duplicated between two rules reaches a target exactly as a bullet
646
+ * duplicated between the always-loaded file and a rule does, and neither shape
647
+ * is visible from one side alone.
648
+ *
649
+ * It reports and never gates. A restatement is legitimate more often than not,
650
+ * so a push failing on one would fail on the ordinary case.
651
+ */
652
+ export function readRestated(root: string): RestatedReport {
653
+ const instructions = readBullets(root, INSTRUCTIONS_REL)
654
+ if (instructions.length === 0) {
655
+ return { kind: 'unreadable', reason: 'no-instructions' }
656
+ }
657
+
658
+ const seed: Candidate[] = readBullets(root, SEED_REL).map((statement) => ({
659
+ ...statement,
660
+ kind: 'seed' as const,
661
+ }))
662
+
663
+ const skillsRoot = join(root, SHIPPED_SKILLS_REL)
664
+ const bodies = existsSync(skillsRoot) ? readBodyLines(root, skillsRoot) : []
665
+ const bodyFiles = new Set(bodies.map((candidate) => candidate.file)).size
666
+
667
+ const rulesRoot = join(root, RULES_REL)
668
+ const ruleStatements = existsSync(rulesRoot)
669
+ ? readRuleFiles(root, rulesRoot)
670
+ : []
671
+ const rules: Candidate[] = ruleStatements.map((statement) => ({
672
+ ...statement,
673
+ kind: 'rule' as const,
674
+ }))
675
+ const ruleFiles = new Set(ruleStatements.map((statement) => statement.file))
676
+ .size
677
+
678
+ if (seed.length === 0 && bodies.length === 0 && rules.length === 0) {
679
+ return { kind: 'unreadable', reason: 'no-surfaces' }
680
+ }
681
+
682
+ // A rule states a directive directly, the same as the always-loaded file, so
683
+ // it joins the subjects a match is searched from rather than sitting only on
684
+ // the candidate side. Without this, an instruction that moved out of the
685
+ // always-loaded file and into two rules has no subject left carrying it, and
686
+ // the pair the seed move produced would stay invisible to this sweep the
687
+ // same way it did before this corpus widened.
688
+ const subjects = [...instructions, ...rules].map(index)
689
+ const candidates = [...seed, ...bodies, ...rules].map(index)
690
+ const frequency = documentFrequency([subjects, candidates])
691
+
692
+ const distinctive = (analysis: Analysis): Set<string> =>
693
+ new Set(
694
+ [...analysis.tokens].filter(
695
+ (token) => (frequency.get(token) ?? 0) <= COMMON_CEILING,
696
+ ),
697
+ )
698
+
699
+ // Every candidate's rare set is invariant across the subject loop, so it is
700
+ // built once here rather than per pair. The corpora multiply out to hundreds
701
+ // of thousands of pairings, and rebuilding a set inside that is the shape
702
+ // `.claude/rules/core/040-performance.md` names.
703
+ const rareCandidates = candidates.map((candidate) => ({
704
+ ...candidate,
705
+ rare: distinctive(candidate.analysis),
706
+ }))
707
+
708
+ const restatements: RestatedEntry[] = []
709
+ let contradictions = 0
710
+ let repetitions = 0
711
+ let mirrors = 0
712
+
713
+ // A rule bullet sits on both sides now, so the pair it forms with another
714
+ // rule bullet would otherwise surface twice: once with each end read as the
715
+ // subject. Recording the pair the first time it is found and skipping it
716
+ // the second keeps one entry per duplicate regardless of which side a
717
+ // reader lands on, the way a seed-versus-body pair never could collide,
718
+ // since only a rule occupies both roles. The same skip is what fragments a
719
+ // cluster of mutually restating rules across several entries, which is why
720
+ // `countThreeSurfaceClusters` reads this set again below rather than the
721
+ // per-entry `surfaces.length` the loop produces.
722
+ const reportedPairs = new Set<string>()
723
+ const pairKey = (a: Statement, b: Statement): string => {
724
+ const left = `${a.file}:${a.line}`
725
+ const right = `${b.file}:${b.line}`
726
+ return left < right ? `${left}|${right}` : `${right}|${left}`
727
+ }
728
+
729
+ for (const subject of subjects) {
730
+ const rare = distinctive(subject.analysis)
731
+ const surfaces: Surface[] = []
732
+
733
+ for (const candidate of rareCandidates) {
734
+ // A second surface is a second file. Two bullets sharing anchors inside
735
+ // one rule are adjacent instructions on one topic rather than the same
736
+ // rule shipped twice, and only a rule can reach this branch at all,
737
+ // since it is the one kind read as both a subject and a candidate.
738
+ if (candidate.statement.file === subject.statement.file) continue
739
+
740
+ const key = pairKey(subject.statement, candidate.statement)
741
+ if (reportedPairs.has(key)) continue
742
+
743
+ const shared = [...candidate.rare]
744
+ .filter((token) => rare.has(token))
745
+ .sort()
746
+
747
+ const weight = shared.reduce(
748
+ (total, token) =>
749
+ total +
750
+ (subject.analysis.spans.has(token) &&
751
+ candidate.analysis.spans.has(token)
752
+ ? SPAN_WEIGHT
753
+ : 1),
754
+ 0,
755
+ )
756
+
757
+ if (weight < ANCHOR_FLOOR) continue
758
+ reportedPairs.add(key)
759
+
760
+ const { restatement, reason } = classify(
761
+ subject.statement,
762
+ candidate.statement,
763
+ weight,
764
+ new Set(shared),
765
+ )
766
+ const { authority, reason: why } = authorityFor(
767
+ subject.statement,
768
+ candidate.statement,
769
+ )
770
+
771
+ surfaces.push({
772
+ file: candidate.statement.file,
773
+ line: candidate.statement.line,
774
+ text: candidate.statement.text,
775
+ kind: candidate.statement.kind,
776
+ restatement,
777
+ anchors: shared,
778
+ weight,
779
+ authority,
780
+ reason: `${reason}; ${why}`,
781
+ })
782
+ }
783
+
784
+ if (surfaces.length === 0) continue
785
+
786
+ for (const surface of surfaces) {
787
+ if (surface.restatement === 'contradiction') contradictions += 1
788
+ else if (surface.restatement === 'mirror') mirrors += 1
789
+ else repetitions += 1
790
+ }
791
+
792
+ restatements.push({ subject: subject.statement, surfaces })
793
+ }
794
+
795
+ const threeSurface = countThreeSurfaceClusters(reportedPairs)
796
+
797
+ return {
798
+ kind: 'measured',
799
+ corpus: {
800
+ instructions: instructions.length,
801
+ seed: seed.length,
802
+ bodies: bodyFiles,
803
+ rules: ruleFiles,
804
+ candidates: candidates.length,
805
+ },
806
+ matcher: {
807
+ anchors: ANCHOR_FLOOR,
808
+ common: COMMON_CEILING,
809
+ contradiction: CONTRADICTION_FLOOR,
810
+ },
811
+ restatements,
812
+ counts: { contradictions, repetitions, mirrors, threeSurface },
813
+ }
814
+ }