@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,72 @@
1
+ ---
2
+ title: Orchestrator parked row runbook
3
+ description: Re-testing every parked blocker against the current tree, the two triggers that start the pass, and the two ways a re-test goes wrong
4
+ ---
5
+
6
+ Re-test every parked row as orchestrator. A blocker cell is a measurement taken the day the row was parked and nothing re-takes it, so a row can wait on a condition that stopped holding weeks earlier with no surface reporting the gap.
7
+
8
+ Two triggers start this pass. `orchestrator-sweep.md` ends by sending the rows `canon tasks validate` listed as untested here, because a merge changes the tree under every parked row at once rather than under the rows naming it. The other is an idle session: nothing merged, workers are building, no pull request is waiting on a first pass, and the board is not moving.
9
+
10
+ The two differ in scope rather than in procedure. A merge sends the untested rows, since the validator already re-took the two kinds it can settle. An idle session walks every parked row, because no event narrowed which of them to look at.
11
+
12
+ Neither trigger is a scheduler. `orchestrator-poll.md` owns the one recurring trigger this skill has, and a second loop firing into a static board is the always-on failure that file already warns about.
13
+
14
+ The sweep's own question stays distinct from this one. It asks which parked row to promote next, taking the blocker cell as read, and this pass asks whether that cell is still true. A session collapsing them runs whichever it remembers and re-tests nothing.
15
+
16
+ ## Scope
17
+
18
+ Every row under `## Up next` and `## Needs a plan` in `.claude/tasks/priority.md`. A `## Run now` row carries no blocker by definition, so the pass skips it. Resolve the board and each task file at the main worktree root, per Worktrees in `CLAUDE.md`.
19
+
20
+ Take the rows in board order and finish one before opening the next. Clearing a row changes what the next row collides with, so a pass that measures every row first and writes afterwards writes against a board it has already invalidated.
21
+
22
+ ## Re-testing a row
23
+
24
+ The blocker cell states what the row waits on, and each kind is tested differently. `canon tasks validate` already re-takes the first two and reports the rest as untested, so run it first and re-take by hand only what it names.
25
+
26
+ - Collision with a track in flight: the validator tests the file the cell cites against the Touches column of every `## Run now` row. A track that merged since the row was parked is no longer in flight, whatever the sets still share. A cell naming the file in prose rather than in backticks cites nothing, so write the collision the way the board format spells it and the check picks the row up on the next run.
27
+ - A dependency on another task: the validator opens the task a link in the cell names. One whose outcomes are all `[x]`, or one already archived, holds nothing. A cell naming the task in prose resolves to no file, so open it by hand and rewrite the cell as a link.
28
+ - A condition about the tree, such as a count of some shape or the presence of a construct: measure it again, per Two ways a re-test goes wrong below.
29
+ - Waiting on a plan: nothing external holds the row, so the pass writes the plan rather than testing anything. See The plan half below.
30
+ - Waiting on an operator action, such as a run that happens from a shell: record it as untestable this pass and name what the operator has to do. A session cannot clear it, and re-measuring it every pass is waste.
31
+
32
+ Write the result into the row. A re-test reported in chat is lost at the next compaction and the next pass measures the same thing again. Rewrite a blocker cell whose test no longer holds, move the row to the group its new state puts it in, and record the measurement in that task's `## Findings` with the date it was taken. `### Writing the board` in `claude-orchestrate` owns the method, and `canon tasks validate` runs once the board is rewritten and before the report.
33
+
34
+ ## Two ways a re-test goes wrong
35
+
36
+ Both return a confident wrong answer rather than an error, which is why each gets a step of its own.
37
+
38
+ The first is reading the condition looser than the code defines it. A row parked on a count of some shape names a shape its consumer defines precisely, and a count taken by eye or by a pattern that approximates it lands somewhere else. The row that motivated this runbook counted 19 instances of a shape whose real count under the consumer's definition was zero.
39
+
40
+ Find the code that consumes the shape, read the definition off it, and measure with that. A count taken any other way is not evidence about the blocker.
41
+
42
+ The second is measuring against the wrong tree. A command that ships to targets is at risk in corpora this repository never formats, so a condition measured here reads as unreachable while it stays live where the command runs. Ask which tree the condition is about before asking what the count is. A local formatter removing the shape on contact says nothing about a target running no such formatter.
43
+
44
+ ## The plan half
45
+
46
+ Plan what the pass clears, and plan any `## Needs a plan` row whose only blocker is the missing plan. The window is the argument: this pass runs while workers build and the session is otherwise idle, which is when planning costs the critical path nothing, and a row cleared with no plan is a row the next pass looks at again.
47
+
48
+ Run `claude-feature` for each, carrying the constraint per in-flight track that `## The loop` in `claude-orchestrate` requires. Verify each plan against the tree the way that step does, since a plan written now is written against a tree several branches are already changing.
49
+
50
+ Stop where `## Parallelism` stops rather than planning every row the pass cleared. A plan whose file set collides with every track in flight is one nobody can dispatch.
51
+
52
+ Do not restate the refill procedure. `claude-orchestrate` owns it under `## Refilling the ready queue` and `orchestrator-sweep.md` wraps it for a batch of merges, so this pass promotes through that method rather than a second one.
53
+
54
+ ## Two results that are not a re-test
55
+
56
+ Age is not evidence. A row untouched for weeks invites promotion, and the waiting is not an argument for it. `claude-orchestrate` refuses to promote a task to fill the queue and this pass inherits that refusal, so a row whose blocker still holds stays where it is. Reporting it as still parked, against a measurement taken this pass, is a result.
57
+
58
+ A scoping defect can wear a blocker. A task whose file set collides with every other task by construction is oversized rather than blocked, and planning it again produces the same plan nobody can dispatch. Split it into tasks with disjoint file sets, so the board stops carrying a row that re-measures the same way every pass.
59
+
60
+ ## Output
61
+
62
+ ```plaintext
63
+ Re-tested: <row>, <blocker> → <what the measurement showed>
64
+ Cleared: <row>, now <group>, plan at <path>
65
+ Still parked: <row>, <blocker> re-confirmed against <what was measured>
66
+ Untestable: <row>, waits on <operator action>
67
+ Split: <task> into <tasks>, file set collided with everything by construction
68
+ ```
69
+
70
+ Omit any row with nothing in it. Name what was measured rather than the verdict alone, since a re-test the reader cannot check is the same claim the row already carried.
71
+
72
+ Lead the reply with the three slots under `### Every later turn` in `claude-orchestrate`, so the human reads what they own before the evidence for it.
@@ -0,0 +1,87 @@
1
+ ---
2
+ title: Orchestrator poll runbook
3
+ description: The review trigger, the condition under which it runs, and how to read what it reports
4
+ ---
5
+
6
+ Run the orchestrator's review trigger. The poll reports pull request movement and the session acts on what it reports. The script reads and never writes, and the routing block below decides which report earns a review, which earns a dispatch, and which earns neither.
7
+
8
+ `${CLAUDE_SKILL_DIR}/scripts/poll.sh` is the script. It needs `gh` authenticated against the remote and `jq` on the path, and it reads the base branch from `origin/HEAD` rather than assuming a name.
9
+
10
+ ## When to run it
11
+
12
+ Start the poll on an open pull request and stop it when the last one merges with nothing else out. That is the whole condition and it resolves from `gh pr list` without asking the operator. A release pull request alone does not qualify, since its sweep carries no findings.
13
+
14
+ A dispatch no longer starts it. The script reads pull requests and a building worker has none, so every interval between the launch and the push is a fixed cost returning nothing, measured as five consecutive runs reporting no movement across roughly fifteen minutes while one worker built. What replaces it is the worker announcing its own pull request the moment it opens one, per the `claude-worker` skill that session loads, which covers exactly the transition the poll cannot observe.
15
+
16
+ The dispatched-worker condition survives as a fallback rather than as a trigger. Start the poll against any dispatch still out after thirty minutes with no announcement, which is past the upper end of the ten to thirty minutes a row here takes. The announcement is newer than the narrowing it justifies, so a silent failure would otherwise leave a finished worker unnoticed the way one already sat idle for eighteen minutes, and the fallback keeps the cover a straight narrowing would have removed. `${CLAUDE_SKILL_DIR}/scripts/watch.sh` covers the same window at a lower cost, since it reads the roster alongside the pull request list and reports a worker that vanished as well as one that finished.
17
+
18
+ A session holding a recurring-prompt scheduler starts the loop itself on that condition and cancels it on the same test, without waiting for the operator. Both halves belong to whoever holds the loop, since a session that can start one can stop one, and a runbook stating only the start leaves the always-on failure unaddressed on the side that causes it.
19
+
20
+ Nothing enforces this. No hook starts the poll and no check stops it, so the condition holds only while whoever holds the loop applies it. Left always-on it fires into an empty board through every gap between a dispatch and its push.
21
+
22
+ The poll is session-scoped and dies with the session that started it. Restart it after a compaction, and take the prompt from this file rather than from a transcript, since a running loop holds whatever wording it was started with and a correction here does not reach it.
23
+
24
+ The reverse direction is the one that goes unnoticed. A scheduled prompt cannot be edited, so every routing fix is a cancel and a re-create, and three made inside one session each reached the running loop and none reached this file. Edit the block here first and re-create the loop from what this file then says. Rewriting the block from memory drops what the shipped one covered, which is how a `SEEN` report once fell through to no rule at all.
25
+
26
+ ## The prompt
27
+
28
+ The requirement is a recurring prompt at roughly three minutes carrying the block below. `/loop 3m <the block>` is the mechanism this repository uses and one example among the schedulers a client may hold, so a client without that command reaches the same requirement through whatever recurring prompt it can schedule. Naming one vendor's command as the only path dates a file that ships to every target holding the plugin.
29
+
30
+ The interval belongs to the schedule rather than to the script. One run is a single snapshot returning in about six seconds, so a session that reads `3m` as the script's runtime and relaunches on completion fires every few seconds and never settles, which happened once for 35 minutes.
31
+
32
+ Resolve `${CLAUDE_SKILL_DIR}/scripts/poll.sh` to an absolute path and paste that in place of `<POLL_SCRIPT>` below. The variable expands while this runbook renders and not in a standalone loop turn, so a block carrying the variable reaches the session as a literal string and the run improvises a substitute.
33
+
34
+ ```plaintext
35
+ Poll GitHub for pull request movement by running <POLL_SCRIPT>, then act on what it reports.
36
+
37
+ - A release pull request, whatever state follows it: report it and stop. Its sweep carries no findings, so no pass is owed. Test this before any rule below, since a release pull request is reported OPENED like any other and would otherwise match that rule first.
38
+ - MOVED or RESPONSE on a pull request I have already reviewed: run the canon:claude-pr-review skill on it immediately, narrow pass. Re-reviews read prior..head and gain nothing from waiting.
39
+ - OPENED, or a pull request with no prior review pass: run the canon:claude-pr-review skill on it. A draft counts, since every pull request here opens as one and skipping drafts skips everything.
40
+ - SEEN: report it and stop. A pass already covers that head, whether it arrived out of band or before the poll first saw the pull request, so no review follows.
41
+ - STALLED: read the last pass and report what it carried. The pass has sat open for hours with nothing following it, so a worker mid-task is already ruled out and the dispatch either never went out or the session holding it is gone. Confirm and re-send it under the dispatch rule below, whatever grades the pass carried. Do not re-run a review to correct the heading, since a pass on an unchanged head with no response behind it stops by design.
42
+ - CONFLICT: report it and stop. The branch owner rebases, not this session.
43
+ - UNMATCHED: report it and stop. A comment posted under a heading outside the known set reaches nobody automatically, so a person decides whether to answer it by hand or the set needs a sixth heading.
44
+ - GONE: report it, then sweep the board by invoking the canon:claude-orchestrate skill and following its queue-refill sweep.
45
+ - A line starting `poll:`: report it verbatim and treat that pull request as unread this run. It is a failed query, not a state.
46
+ - Nothing changed: say exactly "No movement." and nothing else.
47
+
48
+ After any pass that posts a finding, at any severity, tell the session holding that branch to run the canon:claude-address-review skill. Resolve the target by running `canon sessions list --branch <branch> --json` at that moment, which scopes the match to this repository, then route on how many sessions it returned. Zero: report the invocation for me and dispatch nobody. Exactly one, with the confidence field reading "confirmed": address that name directly. Any other count, any other confidence, or a command that is missing or refuses: fall back to picking from a session listing, open by naming the worktree and branch you believe the reader holds, and ask to be corrected. Two sessions can hold one branch, so read the count rather than the first row. The threshold is stated once in the canon:claude-pr-review skill and governs the heading with it, so an open heading and an owed dispatch answer the same question and either one is enough to send.
49
+ ```
50
+
51
+ ## Reading the output
52
+
53
+ Every classification line names a pull request and a state. A line starting `poll:` is not a classification. It means a query failed and the script declined to guess, so that pull request keeps its last known state and is neither reported as moved nor swept as merged. Treat it as unread and let the next run classify it.
54
+
55
+ The script exits non-zero and classifies nothing when the open pull request list itself fails to load. That case would otherwise report every tracked pull request as merged, so the baseline is left untouched and the run says so.
56
+
57
+ The baseline lives at `.claude/.tmp/pr-poll/baseline.txt` under the main worktree root and is per-machine. A first run against a board already in flight reports each open pull request once before it settles.
58
+
59
+ The five review headings the script matches are written by `claude-pr-review` and `claude-address-review`, and the whole set is stated once in the first. A project that posts its reviews under different headings edits the jq filters in the script to match, or every pull request reads as never reviewed.
60
+
61
+ `UNMATCHED` is what a heading outside the five reaches, carried the same way `RESPONSE` is: a rising count against the baseline is what is new to this script, and the message names the heading so a person can tell whether to answer it by hand or add it to the set. It fires on a tracked pull request only, since a first sighting reports `SEEN` or `OPENED` and takes whatever count already sits on the thread as its starting baseline rather than flagging history the poll never watched.
62
+
63
+ `RESPONSE` is qualified by recency as well as by count, so it means a reply the last pass has not already answered rather than one this script has not seen before. A worker answers a finding and the reviewing session posts its close-out seconds later, which is the ordinary handback rather than a race, so a count on its own reported the answered thread on the next run and the re-review it routed to stopped at its own guard. The state now fires when the newest reply is stamped later than the last pass, and on a pull request carrying no pass at all, which is a worker talking to nobody and worth the turn. A reply landing inside the same second as the pass is dropped, matching the comparison `claude-pr-review` makes on the same two fields.
64
+
65
+ `STALLED` is the one state the script derives from a heading rather than from a commit or a count, since `claude-pr-review` posts the open heading exactly when a dispatch is owed, per the threshold that skill states. The heading alone cannot carry it, because an open pass means a dispatch was owed and made, so the ordinary healthy thread is a worker still working. The age of that pass is the third test: the state fires when the open pass covers the head, nothing has followed it, and it is older than the `STALE_AFTER` seconds set at the top of the script. It reports once per entry and fires again after any commit or reply resets the thread. A project whose workers run longer than the default two hours raises that number.
66
+
67
+ The state reaches every stalled dispatch, since one threshold governs the heading and the dispatch alike and a pass carrying anything posts the open heading. A minors-only pass therefore reports here on the same terms as a blocking one, which widens the state from what it caught while the two were split. It stays a heading test rather than a count test, so nothing here pins the summary line, which is a second string this script does not own.
68
+
69
+ ## The watch beside it
70
+
71
+ `${CLAUDE_SKILL_DIR}/scripts/watch.sh` is a long-running loop rather than a scheduled prompt. It reads the open pull request list and the session roster together every sixty seconds and prints one line per new pull request, per worker whose status changed, and per worker that dropped out of the roster. Start it in the background and read what it emits. It writes nothing.
72
+
73
+ Coverage is what it buys over the poll. A worker that finishes goes idle and a worker that crashes disappears, so a trigger reading pull requests alone stays silent through the second, and the roster read is the half `poll.sh` cannot make. It ran a full afternoon over four concurrent workers and caught every transition, while the three-minute poll it replaced reported no movement five times in a row.
74
+
75
+ It classifies nothing and routes nothing. A line it prints says a pull request opened or a worker moved, and the routing block above is still what decides whether a review follows, so the two compose rather than replace each other.
76
+
77
+ Every session in the repository holding a branch other than the base one counts as a worker, whoever launched it. The prototype matched the `orchestrator-` prefix instead, which reads a dispatched worker and misses every hand-launched one. A failed read of either source reports itself on a `watch:` line and leaves the baseline untouched, since reading an empty result as current state would report every worker gone on the pass after.
78
+
79
+ ## The stall alarm
80
+
81
+ `watch.sh` prints `WORKER-STOPPED <name> <branch> <dwell>s` once a `waiting` row crosses `STALL_THRESHOLD_S`, and `WORKER-UNMEASURABLE <name> <branch>` for one whose record carries neither timestamp the dwell falls back to. Both are prints, not alerts, so the operator learns of one only when a session already reading the loop's output relays it further. The toolkit ships no notification verb, since the surface a stall reaches the operator through is a session tool rather than a command a shell loop can call.
82
+
83
+ The two lines carry different confidence and the push has to say so rather than treat them as one signal. `WORKER-STOPPED` fires only once the dwell has already crossed the threshold, so it reports a wait already confirmed long. `WORKER-UNMEASURABLE` has no dwell to threshold on, so it fires on the first pass that meets a `waiting` row carrying neither stamp, whether that row has sat five seconds or fifty minutes.
84
+
85
+ On meeting either line, push a notification to the operator through whatever notification surface the client offers, `PushNotification` in this repository's client and one example among the surfaces a different client exposes. Name the worker, the branch, which of the two lines fired, and the dwell where `WORKER-STOPPED` carries one, and send it once per stall the same way `watch.sh` prints it once, rather than repeating it on every interval the row stays stopped.
86
+
87
+ The bound stays open. The alarm reaches the operator only through the controller's own read, so a controller mid-turn does not see the line for as long as the turn runs, and a controller that is itself stopped never does. Neither case closes here, since the watch loop and the notification surface both live inside the same session that has to be free to act on either.
@@ -0,0 +1,30 @@
1
+ ---
2
+ title: Orchestrator resume runbook
3
+ description: What to read back after a compaction, and how to treat a groundwork folder's three shelf lives
4
+ ---
5
+
6
+ Resume as orchestrator after a compaction. Read the board and the groundwork behind the live work before answering anything, because a compaction summary keeps conclusions and drops the reasoning that produced them.
7
+
8
+ 1. Resolve the main worktree root with `git worktree list --porcelain | grep -m 1 '^worktree ' | cut -d' ' -f2-`, falling back to `pwd`. Every path below resolves against it.
9
+ 2. Read `.claude/tasks/priority.md` for the ordering and what each task waits on. Read the newest `.claude/tasks/session-*.md` when one exists, per `${CLAUDE_SKILL_DIR}/../../standards/session.md`, and treat it as the previous session's scratch rather than a source.
10
+ 3. Read the task file for every entry under `## Run now`, plus any entry a live pull request names.
11
+ 4. Read the groundwork folder each of those tasks cites. Read `README.md` for the file map, then `06-decision.md` when the track is closed, or `07-next-session.md` and the numbered topic files when it is open. A task's Findings section is a lossy summary of its track, so planning against the summary re-derives what the folder already settled.
12
+ 5. Run `gh pr list --state open` and `git log --oneline -5` to see what shipped since the folder was written.
13
+ 6. Report in this shape and stop:
14
+
15
+ ```plaintext
16
+ Read: <task files>, <groundwork folders>
17
+ Shipped since: <commits or PRs>
18
+ Stale in the groundwork: <lean or number the shipped work overturned>
19
+ Next: <the one or two tasks ready to hand a worker>
20
+ ```
21
+
22
+ Treat a groundwork folder as three kinds of content with different shelf lives. Trust the reasoning and the method, which stay correct. Re-measure every count, size, and cost, because they were true when written. Check every `Leaning:` against what has shipped, because work spawned by a track routinely overturns the lean that spawned it and nothing writes back.
23
+
24
+ A plan a live task cites goes stale the same way, and `claude-orchestrate` states the check that catches it.
25
+
26
+ After each merge, place every finding the work produced before starting anything else. A finding that changes a standard goes to the standard, one that changes another task goes to that task's Findings, and one that overturns a groundwork lean gets marked answered in that folder. Findings recorded in a pull request thread and nowhere else are lost at merge.
27
+
28
+ Cite a file and line for any claim taken from a groundwork folder, so a later reader can tell a read from a recall.
29
+
30
+ Do not read every groundwork folder. Read the ones the live tasks cite. A speculative sweep of the whole folder is the cost this rule exists to bound.
@@ -0,0 +1,21 @@
1
+ ---
2
+ title: Orchestrator sweep runbook
3
+ description: The once-per-batch board sweep, plan re-verification, and where each merged finding goes
4
+ ---
5
+
6
+ Sweep the board as orchestrator after merging. Run this once per batch of merges, before answering what to do next, because the surfaces that record what shipped are the ones nothing updates on its own.
7
+
8
+ 1. Pull into the main worktree rather than fetching. A fetch leaves the local branch behind, so `git log` reports a state that has not arrived. A repository that adds a post-merge hook to name archive candidates gets it on a pull and never on a fetch.
9
+ 2. Follow `## Refilling the ready queue` in `claude-orchestrate`, every step in order. It owns the procedure. Do not restate it here and do not run it from memory.
10
+ 3. Re-verify every plan already written, not only the ones this sweep writes. A queued plan goes stale from whatever merged while it waited, and the loop's verify step fires at handoff rather than after a merge, so nothing else catches it. Grep each construct the plan names and count the sites against its claim, then open each file rather than trusting its account.
11
+ 4. Re-check any precondition a plan states about live state outside the repository. A remote branch, an open issue, or an installed version was true when the plan was written and is not a fact about the tree.
12
+
13
+ Run `canon worktrees list` after the pull, which is the first point at which the merges this sweep followed are readable. It owns the reclaim rule and it removes nothing, so read its rows and act on the ones you mean to. Never substitute a git ancestry check for it.
14
+
15
+ Run `canon tasks validate` once the board is rewritten and before reporting it. It resolves every plan pointer, accounts every task file against the board and the backlog both, tests the `## Run now` file sets for overlap, and re-takes the two blocker kinds a command can settle. It reports and never writes, so fix each row it names and run it again. A finding it reports is a board defect rather than a task finding, so it goes nowhere but the board.
16
+
17
+ Then run `orchestrator-parked.md` over the rows the validator listed as untested. Those carry the three blocker kinds no command settles, and the merge this sweep followed changed the tree under every one of them at once while the refill above re-read none. Take the untested rows alone rather than the whole board, since the validator already answered the rest.
18
+
19
+ Place every finding the merged work produced before promoting anything. A finding that changes a rule goes to the standard or the rule that states it, one that changes another task goes to that task's Findings, and one that overturns a groundwork lean gets marked answered in that folder. A pull request body counts as nowhere, since the thread stops being read the moment it merges.
20
+
21
+ Report what closed, what moved, and what is ready to hand a worker. Name the file set each ready task touches, so the reader can check the disjointness claim rather than take it.
@@ -0,0 +1,373 @@
1
+ #!/usr/bin/env bash
2
+ # Reports pull request movement since the last run. Reads only.
3
+ #
4
+ # The shebang is load-bearing. An earlier version ran under the operator's zsh,
5
+ # where an unquoted parameter expansion does not word-split, so `set -- $line`
6
+ # left every field but the first empty and every head compared unequal. The
7
+ # poll reported movement that had not happened, which is the one failure that
8
+ # makes a detection tool worth less than no tool.
9
+ set -e
10
+ set -o pipefail
11
+
12
+ # The baseline is per-machine mutable state, so it stays in gitignored scratch
13
+ # even though the script is tracked. Resolving the main worktree root rather
14
+ # than this file's own folder keeps a poll started from a linked worktree
15
+ # reading the baseline a poll started from main wrote.
16
+ MAIN_ROOT="$(git worktree list --porcelain 2>/dev/null | awk '/^worktree /{print $2; exit}')"
17
+ if [ -z "$MAIN_ROOT" ]; then
18
+ echo "poll: not a git repository, so nothing is classified" >&2
19
+ exit 1
20
+ fi
21
+ STATE_DIR="$MAIN_ROOT/.claude/.tmp/pr-poll"
22
+ mkdir -p "$STATE_DIR"
23
+ STATE="$STATE_DIR/baseline.txt"
24
+ touch "$STATE"
25
+
26
+ # The base branch is read rather than assumed, since this ships to projects that
27
+ # do not all call it `main`. A wrong base is not a visible failure: merge-tree
28
+ # reports every pull request as conflicted against a ref that does not resolve.
29
+ BASE_REF="$(git symbolic-ref --quiet --short refs/remotes/origin/HEAD 2>/dev/null || true)"
30
+ if [ -z "$BASE_REF" ]; then
31
+ BASE_REF="origin/$(gh repo view --json defaultBranchRef --jq .defaultBranchRef.name 2>/dev/null || echo main)"
32
+ fi
33
+ BASE_BRANCH="${BASE_REF#origin/}"
34
+
35
+ # These five strings are owned elsewhere and pinned here. `claude-pr-review`
36
+ # writes `## Review` and `## Review closed`, and states the full five-heading
37
+ # set once, beside the threshold it already states once. `claude-address-review`
38
+ # writes `## Review response`, `## Rebase`, and `## Post-review findings`, the
39
+ # last for a finding a worker produces after a close-out rather than in answer
40
+ # to one already on the thread. All three surfaces ship separately, so a
41
+ # heading added in either skill breaks a test here that no check reaches across.
42
+ #
43
+ # Both families match on the first line alone so the tests stay symmetric. The
44
+ # reply family carries `## Rebase` and `## Post-review findings` beside
45
+ # `## Review response` because neither answers a comment already on the thread,
46
+ # which is why both were kept outside the `## Review` family rather than folded
47
+ # into it.
48
+ JQ_LAST_REVIEWED_HEAD='
49
+ [ .reviews[]
50
+ | select((.body // "") | split("\n")[0] | rtrimstr("\r")
51
+ | . == "## Review" or . == "## Review closed")
52
+ ] | last | .commit.oid // empty
53
+ '
54
+ # The count alone answers whether a reply is new to this script, which is not
55
+ # the same question as whether it is newer than the pass it answers. The stamp
56
+ # of the newest reply comes out of the same selection so the recency test costs
57
+ # no second filter, and it is the one this file compares against `submittedAt`.
58
+ JQ_REPLY_STATE='
59
+ [ .comments[]
60
+ | select((.body // "") | split("\n")[0] | rtrimstr("\r")
61
+ | . == "## Review response" or . == "## Rebase"
62
+ or . == "## Post-review findings")
63
+ ] as $replies
64
+ | ($replies | length | tostring)
65
+ + " "
66
+ + ([ $replies[] | .createdAt // empty | fromdateiso8601 | floor ]
67
+ | max // 0 | tostring)
68
+ '
69
+ # A comment matching neither family above is the gap this filter exists to
70
+ # surface rather than absorb: a worker inventing a sixth heading used to reach
71
+ # this script as silence, indistinguishable from no comment at all. The count
72
+ # is read the same way the reply count is, a rising value against the baseline
73
+ # being new to this script, and the newest heading's text rides along for the
74
+ # report. It carries its spaces intact rather than encoded, since it is the
75
+ # last field on both sides of the pipe: `${unmatched_state#* }` below strips
76
+ # only the first space, and `read -r` further down hands its last named
77
+ # variable the rest of the line whole. Neither reads a heading's own spaces
78
+ # as a field separator, so a heading round-trips verbatim into the report a
79
+ # person reads when deciding whether to answer it by hand.
80
+ JQ_UNMATCHED_STATE='
81
+ [ .comments[]
82
+ | (.body // "") | split("\n")[0] | rtrimstr("\r")
83
+ | select(startswith("## "))
84
+ | select(. != "## Review" and . != "## Review closed"
85
+ and . != "## Review response" and . != "## Rebase"
86
+ and . != "## Post-review findings")
87
+ ] as $unclassified
88
+ | ($unclassified | length | tostring)
89
+ + " "
90
+ + (($unclassified | last) // "none")
91
+ '
92
+
93
+ # `claude-pr-review` states the threshold and posts `## Review` exactly when a
94
+ # pass carries a finding, so the heading of the last review is what says whether
95
+ # any work is owed on it. Taking it as well as the commit is what separates a
96
+ # thread waiting on a worker from one nothing is owed on. A project editing the
97
+ # two filters above for its own headings edits this one with them.
98
+ #
99
+ # The age of that review comes out of the same selection, because the heading
100
+ # alone cannot separate the two. Under the rule above an open heading means a
101
+ # dispatch was owed and made, so the ordinary healthy thread is a worker still
102
+ # working and every one of them would be reported minutes after the pass posted.
103
+ # jq computes the elapsed seconds itself, since `date -d` is GNU-only and this
104
+ # script runs wherever the plugin is installed. A review carrying no stamp reads
105
+ # as age zero and classifies nothing, which is the same answer the carry-forward
106
+ # path gives a pull request this run could not read.
107
+ #
108
+ # The stamp itself is emitted as a third field beside the age it was derived
109
+ # from, because the reply test below compares against the instant rather than
110
+ # against the elapsed seconds. A missing stamp reads as zero there too, where it
111
+ # sends a reply to be reported rather than suppressed. The two readers therefore
112
+ # fail in opposite directions on the same absent field, since zero silences the
113
+ # age test above and zero is the value the reply test reports on.
114
+ JQ_LAST_REVIEW_STATE='
115
+ [ .reviews[]
116
+ | select((.body // "") | split("\n")[0] | rtrimstr("\r")
117
+ | . == "## Review" or . == "## Review closed")
118
+ ] | last
119
+ | if . == null then "none 0 0"
120
+ else (if .submittedAt == null then 0
121
+ else (.submittedAt | fromdateiso8601 | floor)
122
+ end) as $at
123
+ | ((.body | split("\n")[0] | rtrimstr("\r")
124
+ | if . == "## Review" then "open" else "closed" end)
125
+ + " "
126
+ + (if $at == 0 then "0" else (((now | floor) - $at) | tostring) end)
127
+ + " "
128
+ + ($at | tostring))
129
+ end
130
+ '
131
+
132
+ # An open pass this old has nobody on it. The cycle it has to clear is a review
133
+ # landing and a worker pushing a follow-up, measured between ten and thirty
134
+ # minutes across a day of runs on 2026-08-14, so two hours sits about four times
135
+ # past the slowest observed and still well inside a working session. Shorter
136
+ # starts catching the slow healthy case this threshold exists to exclude, and
137
+ # longer lets a dead dispatch sit past the point a person would have noticed.
138
+ # A project whose workers run longer than that raises it here.
139
+ STALE_AFTER=7200
140
+
141
+ # A pull request this run could not read keeps the line it had, so the GONE
142
+ # sweep below does not read the gap as a merge and the baseline does not lose
143
+ # the head it already knew. Saying so on stderr is the point: a silent skip is
144
+ # how the failure this script was fixed for went unnoticed for a session.
145
+ carry_forward() {
146
+ local n=$1 reason=$2 old
147
+ old=$(grep "^$n " "$STATE" || true)
148
+ if [ -n "$old" ]; then
149
+ # Every other classification compares a field against itself on a carried
150
+ # line, so it fires nothing. That is also what keeps the two stamps unread
151
+ # here: the baseline never held them, so both arrive empty, and the count
152
+ # test in front of them fails before either reaches a numeric comparison.
153
+ # STALLED reads a heading that is carried too, so
154
+ # the field is blanked to a value no branch matches. Echoing it intact would
155
+ # classify a pull request this run never reached while stderr below says the
156
+ # opposite, and one successful read restores it a run later. An already
157
+ # reported thread keeps its marker, or an unreadable run would let the same
158
+ # thread be reported a second time.
159
+ echo "$old" | awk '{ if ($6 != "reported") $6 = "carried"; print }'
160
+ echo "poll: #$n $reason, so it keeps its last known state and goes unclassified" >&2
161
+ else
162
+ echo "poll: #$n $reason, and it has no last known state, so it goes unclassified" >&2
163
+ fi
164
+ }
165
+
166
+ snapshot() {
167
+ local numbers n payload head prior resp merges review_state
168
+ git fetch -q origin "$BASE_BRANCH" 2>/dev/null || true
169
+
170
+ # A failed list reaches the caller as no open pull requests, and that reports
171
+ # every tracked one as GONE. It is a louder wrong answer than the one this
172
+ # script was fixed for, so the run aborts rather than classify on it.
173
+ if ! numbers=$(gh pr list --state open --json number --jq '.[].number' 2>/dev/null); then
174
+ echo "poll: the open pull request list could not be read" >&2
175
+ return 1
176
+ fi
177
+
178
+ for n in $numbers; do
179
+ # A query that failed and a pull request with genuinely no reviews both
180
+ # arrive as an empty result. Reading the first as the second reported a
181
+ # reviewed pull request as never reviewed, which routes the re-review to
182
+ # the first-pass branch, where it reports and stops, so the movement sits
183
+ # unreviewed until a person notices. One query per pull request gives that
184
+ # failure a single place to surface.
185
+ if ! payload=$(gh pr view "$n" --json headRefOid,reviews,comments 2>/dev/null); then
186
+ carry_forward "$n" "could not be read"
187
+ continue
188
+ fi
189
+
190
+ head=$(jq -r '.headRefOid // empty' <<<"$payload")
191
+ if [ -z "$head" ]; then
192
+ carry_forward "$n" "returned no head"
193
+ continue
194
+ fi
195
+
196
+ prior=$(jq -r "$JQ_LAST_REVIEWED_HEAD" <<<"$payload")
197
+ # Split here rather than carried whole, because the count keeps the fourth
198
+ # column every baseline written so far already reads, and the stamp goes to
199
+ # the end of the line beside the pass stamp it is compared against.
200
+ reply_state=$(jq -r "$JQ_REPLY_STATE" <<<"$payload")
201
+ resp=${reply_state%% *}
202
+ reply_at=${reply_state##* }
203
+ # Three space-separated fields, so the line below carries them as its own
204
+ # sixth, seventh, and eighth rather than needing a split.
205
+ review_state=$(jq -r "$JQ_LAST_REVIEW_STATE" <<<"$payload")
206
+ # Split the same way as the reply state, carried as the line's tenth and
207
+ # eleventh fields.
208
+ unmatched_state=$(jq -r "$JQ_UNMATCHED_STATE" <<<"$payload")
209
+ unmatched_count=${unmatched_state%% *}
210
+ unmatched_heading=${unmatched_state#* }
211
+
212
+ # `gh pr view --json mergeable` reports UNKNOWN until GitHub finishes
213
+ # computing it, which is exactly when a poll asks. merge-tree answers
214
+ # locally against the base this machine has, so it never returns UNKNOWN.
215
+ git fetch -q origin "pull/$n/head" 2>/dev/null || true
216
+ if ! git cat-file -e "${head}^{commit}" 2>/dev/null ||
217
+ ! git rev-parse --verify -q "$BASE_REF" >/dev/null; then
218
+ # merge-tree exits non-zero on a ref it cannot resolve as well as on a
219
+ # real conflict, and the two are indistinguishable from its status. Both
220
+ # sides are checked, because an unresolvable base reports every pull
221
+ # request as conflicted rather than one, which is the louder half.
222
+ merges=unknown
223
+ elif git merge-tree --write-tree "$BASE_REF" "$head" >/dev/null 2>&1; then
224
+ merges=clean
225
+ else
226
+ merges=conflict
227
+ fi
228
+
229
+ echo "$n $head ${prior:-none} $resp $merges $review_state $reply_at $unmatched_count $unmatched_heading"
230
+ done
231
+ }
232
+
233
+ # The reason is printed by whichever branch failed, since this one cannot tell
234
+ # a list query from a parse and naming either would misattribute the other.
235
+ if ! NEW=$(snapshot); then
236
+ echo "poll: nothing is classified this run and the baseline is unchanged" >&2
237
+ exit 1
238
+ fi
239
+ CHANGED=0
240
+ # The baseline is written from here rather than from the snapshot, because the
241
+ # heading field is the one column a classification rewrites. STALLED reports a
242
+ # standing condition rather than a transition, so without a written marker it
243
+ # would fire on every later run and the board would never read "No movement."
244
+ FINAL=""
245
+
246
+ while read -r n head prior resp merges heading age pass_at reply_at unmatched_count unmatched_heading; do
247
+ [ -z "$n" ] && continue
248
+ state=$heading
249
+ old=$(grep "^$n " "$STATE" || true)
250
+ if [ -z "$old" ]; then
251
+ # A pull request first seen here may already carry a pass, when it opened
252
+ # and was reviewed between two runs. Reporting it as new invites a first
253
+ # pass the thread already has.
254
+ if [ "$prior" = "$head" ]; then
255
+ echo "SEEN #$n at ${head:0:7}, first sighting, already covered by a pass"
256
+ elif [ "$merges" = unknown ]; then
257
+ # `unknown` means no merge was attempted, so it is left off the line
258
+ # rather than printed where a verdict belongs.
259
+ echo "OPENED #$n at ${head:0:7}"
260
+ else
261
+ echo "OPENED #$n at ${head:0:7}, $merges against $BASE_BRANCH"
262
+ fi
263
+ CHANGED=1
264
+ FINAL+="$n $head $prior $resp $merges $state $unmatched_count"$'\n'
265
+ continue
266
+ fi
267
+ old_head=$(echo "$old" | cut -d' ' -f2)
268
+ old_resp=$(echo "$old" | cut -d' ' -f4)
269
+ old_merges=$(echo "$old" | cut -d' ' -f5)
270
+ # Only the marker is read off this field, so a baseline written before it
271
+ # existed yields empty and reads as not yet reported, which is what it is. The
272
+ # age decides the rest, so the first run after an upgrade needs no history and
273
+ # classifies a thread already past the threshold rather than waiting a run.
274
+ old_heading=$(echo "$old" | cut -d' ' -f6)
275
+ old_unmatched=$(echo "$old" | cut -d' ' -f7)
276
+
277
+ # A conflict arrives from the base moving, not from the branch, so it is
278
+ # reported on the transition rather than only when the head changes.
279
+ if [ "$merges" = conflict ] && [ "$old_merges" != conflict ]; then
280
+ echo "CONFLICT #$n no longer merges into $BASE_BRANCH"
281
+ CHANGED=1
282
+ fi
283
+
284
+ # A rising count is what is new to this script, the same test RESPONSE
285
+ # below runs against the reply family. It fires on a tracked pull request
286
+ # only: a first sighting reports SEEN or OPENED and takes whatever count
287
+ # already sits on the thread as its starting baseline rather than flagging
288
+ # history retroactively. A carried line supplies neither, since
289
+ # carry_forward re-echoes the shorter baseline shape rather than a full
290
+ # snapshot line, so both sides default to zero the way old_unmatched
291
+ # already does.
292
+ if [ "${unmatched_count:-0}" -gt "${old_unmatched:-0}" ]; then
293
+ echo "UNMATCHED #$n posted under '$unmatched_heading'"
294
+ CHANGED=1
295
+ fi
296
+
297
+ if [ "$head" != "$old_head" ]; then
298
+ if [ "$prior" = "none" ]; then
299
+ echo "MOVED #$n -> ${head:0:7}, never reviewed"
300
+ elif [ "$prior" = "$head" ]; then
301
+ # An out-of-band pass reviewed this head before the poll saw it move, so
302
+ # the range is empty because it is covered rather than because it broke.
303
+ # Without this the force-push branch below claims a rewrite that never
304
+ # happened, which is the class of false report the shebang note names.
305
+ echo "SEEN #$n -> ${head:0:7}, already covered by the last pass"
306
+ else
307
+ # The range needs both commits local, and a force-push leaves the prior
308
+ # one unreachable. Count what resolves and say nothing when it does not,
309
+ # rather than reporting a zero that reads as no new work.
310
+ git fetch -q origin "pull/$n/head" 2>/dev/null || true
311
+ since=$(git log --oneline "${prior}..${head}" 2>/dev/null | wc -l | tr -d ' ' || true)
312
+ if [ -n "$since" ] && [ "$since" != "0" ]; then
313
+ echo "MOVED #$n -> ${head:0:7}, $since commit(s) since your last pass"
314
+ else
315
+ echo "MOVED #$n -> ${head:0:7}, range unresolved, likely force-pushed"
316
+ fi
317
+ fi
318
+ CHANGED=1
319
+ elif [ "$resp" -gt "$old_resp" ] &&
320
+ { [ "$pass_at" = 0 ] || [ "$reply_at" -gt "$pass_at" ]; }; then
321
+ # The count says the reply is new to this script and the stamp says it is
322
+ # newer than the pass, and both are needed. A worker answers a finding and
323
+ # the reviewing session closes out seconds later, so the count alone reports
324
+ # an answered thread on the next run and the session spends a turn learning
325
+ # `claude-pr-review` will refuse it. The gaps observed were 96, 24, and 35
326
+ # seconds, which is a reviewing session reading a reply and posting, so this
327
+ # is the ordinary handback rather than a race.
328
+ #
329
+ # A pull request carrying no pass at all reads as stamp zero and reports,
330
+ # which is the case the count was the right test for: a reply with nothing
331
+ # behind it is a worker talking to nobody and worth the turn. The test is
332
+ # strictly greater to match the guard `claude-pr-review` states on the same
333
+ # two fields, which drops a reply landing inside the same second as the
334
+ # pass, a narrower failure than the one it removes.
335
+ #
336
+ # The gate sits in the condition rather than in the body so a suppressed
337
+ # reply falls through to STALLED below, which is a thread this run should
338
+ # still be able to reach.
339
+ echo "RESPONSE #$n answered with no new commit"
340
+ CHANGED=1
341
+ elif [ "$heading" = open ] && [ "$old_heading" != reported ] &&
342
+ [ "$prior" = "$head" ] && [ "$age" -ge "$STALE_AFTER" ]; then
343
+ # Three things at once: the last pass is open, it covers the head so no
344
+ # commit followed it, and the two branches above found no reply either. The
345
+ # open heading is posted exactly when a dispatch is owed, per the threshold
346
+ # `claude-pr-review` states, so a pass owing one left it here at any grade
347
+ # it carried. Any two of these describe an ordinary review waiting on a
348
+ # worker, which is why the age carries the third: without it every
349
+ # dispatched worker is reported minutes into the work it was sent to do,
350
+ # and a signal firing on the healthy path is one an operator learns to
351
+ # skip. The report still asks for a confirmation rather than asserting.
352
+ echo "STALLED #$n open at ${head:0:7}, no commit or reply in $((age / 3600))h"
353
+ state=reported
354
+ CHANGED=1
355
+ elif [ "$heading" = open ] && [ "$old_heading" = reported ]; then
356
+ # Nothing above fired, so the thread has not moved since it was reported.
357
+ # Carry the marker rather than the freshly derived heading, or the state
358
+ # re-enters next run and STALLED oscillates instead of reporting once.
359
+ state=reported
360
+ fi
361
+ FINAL+="$n $head $prior $resp $merges $state $unmatched_count"$'\n'
362
+ done <<<"$NEW"
363
+
364
+ while read -r n _rest; do
365
+ [ -z "$n" ] && continue
366
+ echo "$NEW" | grep -q "^$n " || {
367
+ echo "GONE #$n merged or closed"
368
+ CHANGED=1
369
+ }
370
+ done <"$STATE"
371
+
372
+ [ "$CHANGED" -eq 0 ] && echo "No movement."
373
+ printf '%s' "$FINAL" >"$STATE"