@opengsd/gsd-core 1.13.0 → 1.15.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 (441) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.ja-JP.md +3 -3
  4. package/README.ko-KR.md +3 -3
  5. package/README.pt-BR.md +3 -3
  6. package/README.zh-CN.md +3 -3
  7. package/agents/gsd-advisor-researcher.compact.md +85 -0
  8. package/agents/gsd-ai-researcher.compact.md +96 -0
  9. package/agents/gsd-assumptions-analyzer.compact.md +81 -0
  10. package/agents/gsd-code-fixer.compact.md +459 -0
  11. package/agents/gsd-code-fixer.md +9 -8
  12. package/agents/gsd-code-reviewer.compact.md +269 -0
  13. package/agents/gsd-code-reviewer.md +15 -3
  14. package/agents/gsd-codebase-mapper.compact.md +760 -0
  15. package/agents/gsd-debug-session-manager.compact.md +360 -0
  16. package/agents/gsd-debug-session-manager.md +17 -2
  17. package/agents/gsd-debugger.md +2 -2
  18. package/agents/gsd-doc-classifier.compact.md +192 -0
  19. package/agents/gsd-doc-synthesizer.compact.md +200 -0
  20. package/agents/gsd-doc-verifier.compact.md +143 -0
  21. package/agents/gsd-doc-writer.compact.md +440 -0
  22. package/agents/gsd-dom-verifier.compact.md +138 -0
  23. package/agents/gsd-domain-researcher.compact.md +141 -0
  24. package/agents/gsd-eval-auditor.compact.md +160 -0
  25. package/agents/gsd-eval-auditor.md +1 -1
  26. package/agents/gsd-eval-planner.compact.md +137 -0
  27. package/agents/gsd-executor.md +13 -8
  28. package/agents/gsd-framework-selector.compact.md +82 -0
  29. package/agents/gsd-integration-checker.compact.md +245 -0
  30. package/agents/gsd-intel-updater.compact.md +226 -0
  31. package/agents/gsd-intel-updater.md +1 -1
  32. package/agents/gsd-mempalace-curator.compact.md +45 -0
  33. package/agents/gsd-nyquist-auditor.compact.md +179 -0
  34. package/agents/gsd-pattern-mapper.compact.md +275 -0
  35. package/agents/gsd-phase-researcher.md +19 -11
  36. package/agents/gsd-plan-checker.md +8 -7
  37. package/agents/gsd-planner.md +12 -8
  38. package/agents/gsd-project-researcher.compact.md +587 -0
  39. package/agents/gsd-project-researcher.md +1 -1
  40. package/agents/gsd-research-synthesizer.compact.md +212 -0
  41. package/agents/gsd-research-synthesizer.md +1 -1
  42. package/agents/gsd-roadmapper.compact.md +454 -0
  43. package/agents/gsd-roadmapper.md +13 -0
  44. package/agents/gsd-security-auditor.compact.md +162 -0
  45. package/agents/gsd-ui-auditor.compact.md +404 -0
  46. package/agents/gsd-ui-auditor.md +155 -17
  47. package/agents/gsd-ui-checker.compact.md +277 -0
  48. package/agents/gsd-ui-researcher.compact.md +282 -0
  49. package/agents/gsd-ui-researcher.md +1 -1
  50. package/agents/gsd-user-profiler.compact.md +108 -0
  51. package/agents/gsd-verifier.md +10 -9
  52. package/bin/install.js +848 -163
  53. package/commands/gsd/autonomous.md +2 -2
  54. package/commands/gsd/capture.md +1 -1
  55. package/commands/gsd/cleanup.md +1 -0
  56. package/commands/gsd/code-review.md +2 -1
  57. package/commands/gsd/complete-milestone.md +1 -0
  58. package/commands/gsd/config.md +1 -0
  59. package/commands/gsd/debug.md +1 -0
  60. package/commands/gsd/graphify.md +1 -0
  61. package/commands/gsd/health.md +1 -0
  62. package/commands/gsd/mempalace-capture.md +8 -3
  63. package/commands/gsd/mempalace-recall.md +1 -0
  64. package/commands/gsd/new-milestone.md +1 -0
  65. package/commands/gsd/new-project.md +1 -0
  66. package/commands/gsd/next.md +1 -0
  67. package/commands/gsd/pause-work.md +1 -0
  68. package/commands/gsd/phase.md +1 -0
  69. package/commands/gsd/plan-review-convergence.md +6 -6
  70. package/commands/gsd/pr-branch.md +1 -0
  71. package/commands/gsd/progress.md +1 -1
  72. package/commands/gsd/quick-batch.md +1 -1
  73. package/commands/gsd/resume-work.md +1 -0
  74. package/commands/gsd/review-backlog.md +1 -0
  75. package/commands/gsd/review.md +2 -3
  76. package/commands/gsd/settings.md +2 -1
  77. package/commands/gsd/stats.md +1 -0
  78. package/commands/gsd/thread.md +1 -0
  79. package/commands/gsd/workspace.md +1 -0
  80. package/commands/gsd/workstreams.md +1 -0
  81. package/gsd-core/bin/check-latest-version.cjs +8 -3
  82. package/gsd-core/bin/gsd-tools.cjs +672 -146
  83. package/gsd-core/bin/lib/adr-parser.cjs +4 -2
  84. package/gsd-core/bin/lib/artifacts.cjs +2 -1
  85. package/gsd-core/bin/lib/audit.cjs +119 -34
  86. package/gsd-core/bin/lib/broken-windows.cjs +168 -49
  87. package/gsd-core/bin/lib/capability-lifecycle.cjs +10 -6
  88. package/gsd-core/bin/lib/capability-loader.cjs +135 -1
  89. package/gsd-core/bin/lib/capability-registry.cjs +96 -189
  90. package/gsd-core/bin/lib/capability-source.cjs +19 -2
  91. package/gsd-core/bin/lib/capability-validator.cjs +14 -2
  92. package/gsd-core/bin/lib/check-command-router.cjs +213 -49
  93. package/gsd-core/bin/lib/code-review-depth.cjs +2 -2
  94. package/gsd-core/bin/lib/codex-agent-toml.cjs +21 -25
  95. package/gsd-core/bin/lib/commands.cjs +823 -112
  96. package/gsd-core/bin/lib/config-loader.cjs +66 -4
  97. package/gsd-core/bin/lib/config.cjs +186 -45
  98. package/gsd-core/bin/lib/coverage.cjs +1 -1
  99. package/gsd-core/bin/lib/decisions.cjs +164 -45
  100. package/gsd-core/bin/lib/external-descriptor-trust.cjs +29 -14
  101. package/gsd-core/bin/lib/frontmatter.cjs +13 -0
  102. package/gsd-core/bin/lib/graphify.cjs +10 -2
  103. package/gsd-core/bin/lib/gsd2-import.cjs +1 -2
  104. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +12 -1
  105. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +1 -1
  106. package/gsd-core/bin/lib/host-runtime-detection.cjs +9 -0
  107. package/gsd-core/bin/lib/init.cjs +614 -86
  108. package/gsd-core/bin/lib/install-engine.cjs +29 -3
  109. package/gsd-core/bin/lib/install-profiles.cjs +14 -0
  110. package/gsd-core/bin/lib/installer-migrations.cjs +41 -5
  111. package/gsd-core/bin/lib/loop-resolver.cjs +50 -31
  112. package/gsd-core/bin/lib/mcp-catalog.cjs +2 -2
  113. package/gsd-core/bin/lib/milestone.cjs +37 -13
  114. package/gsd-core/bin/lib/model-resolver.cjs +253 -53
  115. package/gsd-core/bin/lib/phase-command-router.cjs +16 -2
  116. package/gsd-core/bin/lib/phase-id-card.cjs +32 -0
  117. package/gsd-core/bin/lib/phase-id-display.cjs +78 -0
  118. package/gsd-core/bin/lib/phase-id.cjs +268 -27
  119. package/gsd-core/bin/lib/phase-lifecycle.cjs +61 -0
  120. package/gsd-core/bin/lib/phase-locator.cjs +29 -10
  121. package/gsd-core/bin/lib/phase.cjs +393 -88
  122. package/gsd-core/bin/lib/plan-document.cjs +49 -1
  123. package/gsd-core/bin/lib/planning-document.cjs +459 -0
  124. package/gsd-core/bin/lib/planning-inspect.cjs +52 -19
  125. package/gsd-core/bin/lib/planning-snapshot.cjs +61 -12
  126. package/gsd-core/bin/lib/planning-workspace.cjs +57 -3
  127. package/gsd-core/bin/lib/pr-branch-patterns.cjs +57 -0
  128. package/gsd-core/bin/lib/pristine-baseline.cjs +182 -0
  129. package/gsd-core/bin/lib/probe-core.cjs +7 -1
  130. package/gsd-core/bin/lib/prohibition-enforcement.cjs +91 -4
  131. package/gsd-core/bin/lib/project-root.cjs +41 -2
  132. package/gsd-core/bin/lib/quick-batch.cjs +1 -1
  133. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +61 -2
  134. package/gsd-core/bin/lib/research-store.cjs +11 -12
  135. package/gsd-core/bin/lib/review-lane-descriptor.cjs +10 -30
  136. package/gsd-core/bin/lib/review-lane-invocation.cjs +23 -0
  137. package/gsd-core/bin/lib/review-reviewer-selection.cjs +2 -2
  138. package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +337 -0
  139. package/gsd-core/bin/lib/roadmap-command-router.cjs +12 -4
  140. package/gsd-core/bin/lib/roadmap-parser.cjs +219 -18
  141. package/gsd-core/bin/lib/roadmap-upgrade.cjs +1539 -13
  142. package/gsd-core/bin/lib/roadmap.cjs +356 -42
  143. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +310 -41
  144. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +15 -4
  145. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +13 -5
  146. package/gsd-core/bin/lib/runtime-homes.cjs +4 -0
  147. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +408 -37
  148. package/gsd-core/bin/lib/runtime-name-policy.cjs +111 -1
  149. package/gsd-core/bin/lib/security.cjs +126 -7
  150. package/gsd-core/bin/lib/shell-command-projection.cjs +10 -6
  151. package/gsd-core/bin/lib/state-document.cjs +130 -28
  152. package/gsd-core/bin/lib/state-md-schema.cjs +21 -14
  153. package/gsd-core/bin/lib/state-transition.cjs +181 -30
  154. package/gsd-core/bin/lib/state.cjs +265 -27
  155. package/gsd-core/bin/lib/surface.cjs +77 -3
  156. package/gsd-core/bin/lib/task-command-router.cjs +12 -6
  157. package/gsd-core/bin/lib/tdd-red-evidence.cjs +78 -5
  158. package/gsd-core/bin/lib/uat-predicate.cjs +47 -4
  159. package/gsd-core/bin/lib/uat.cjs +9 -1
  160. package/gsd-core/bin/lib/ui-consideration-probe.cjs +15 -2
  161. package/gsd-core/bin/lib/ui-frontend-evidence.cjs +100 -9
  162. package/gsd-core/bin/lib/undo-commit-selection.cjs +131 -0
  163. package/gsd-core/bin/lib/update-context.cjs +30 -24
  164. package/gsd-core/bin/lib/vendor/js-yaml.cjs +11 -3
  165. package/gsd-core/bin/lib/verification.cjs +315 -30
  166. package/gsd-core/bin/lib/verify-command-grounding.cjs +47 -3
  167. package/gsd-core/bin/lib/verify.cjs +320 -48
  168. package/gsd-core/bin/lib/workstream-inventory.cjs +1 -0
  169. package/gsd-core/bin/lib/worktree-base-ref.cjs +482 -73
  170. package/gsd-core/bin/lib/worktree-safety.cjs +797 -58
  171. package/gsd-core/bin/shared/config-defaults.manifest.json +4 -0
  172. package/gsd-core/bin/shared/config-schema.manifest.json +6 -0
  173. package/gsd-core/bin/verify-reapply-patches.cjs +439 -80
  174. package/gsd-core/references/checkpoints.md +5 -3
  175. package/gsd-core/references/compact-content-gate.md +66 -0
  176. package/gsd-core/references/edge-probe-fixtures/01-round-half-even/expected-coverage.json +28 -3
  177. package/gsd-core/references/edge-probe-fixtures/02-merge-intervals/expected-coverage.json +37 -4
  178. package/gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/expected-coverage.json +28 -3
  179. package/gsd-core/references/edge-probe-fixtures/04-money-rounding/expected-coverage.json +28 -3
  180. package/gsd-core/references/edge-probe-fixtures/05-list-dedupe/expected-coverage.json +37 -4
  181. package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/expected-coverage.json +37 -4
  182. package/gsd-core/references/edge-probe.md +195 -21
  183. package/gsd-core/references/execute-phase-between-wave-reset.md +7 -6
  184. package/gsd-core/references/execute-phase-wave-guard.md +22 -11
  185. package/gsd-core/references/gsd-run-resolver.md +1 -1
  186. package/gsd-core/references/loop-hook-dispatch.md +18 -0
  187. package/gsd-core/references/model-profiles.md +13 -4
  188. package/gsd-core/references/phase-argument-parsing.md +9 -7
  189. package/gsd-core/references/phase-id-convention.md +28 -0
  190. package/gsd-core/references/planner-gap-closure.md +2 -0
  191. package/gsd-core/references/planner-load-graph-context.md +24 -13
  192. package/gsd-core/references/planner-verify-command-grounding.md +14 -0
  193. package/gsd-core/references/planning-config.md +14 -2
  194. package/gsd-core/references/tdd.md +30 -4
  195. package/gsd-core/references/thinking-models-planning.md +18 -2
  196. package/gsd-core/references/ui-consideration-probe.md +10 -5
  197. package/gsd-core/references/verification-patterns.md +17 -4
  198. package/gsd-core/references/verify-command-path-resolvability.md +10 -2
  199. package/gsd-core/references/worktree-path-safety.md +433 -2
  200. package/gsd-core/templates/README.md +7 -1
  201. package/gsd-core/templates/state.md +6 -3
  202. package/gsd-core/templates/summary.compact.md +212 -0
  203. package/gsd-core/templates/user-setup.compact.md +199 -0
  204. package/gsd-core/templates/user-setup.md +0 -9
  205. package/gsd-core/templates/verification-report.md +1 -1
  206. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  207. package/gsd-core/workflows/add-backlog.md +1 -1
  208. package/gsd-core/workflows/add-phase.md +1 -1
  209. package/gsd-core/workflows/add-tests.md +2 -2
  210. package/gsd-core/workflows/add-todo.md +6 -5
  211. package/gsd-core/workflows/ai-integration-phase.md +11 -3
  212. package/gsd-core/workflows/audit-fix.md +1 -1
  213. package/gsd-core/workflows/audit-milestone.md +1 -1
  214. package/gsd-core/workflows/audit-uat.md +1 -1
  215. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +9 -18
  216. package/gsd-core/workflows/autonomous.md +29 -16
  217. package/gsd-core/workflows/check-todos.md +6 -4
  218. package/gsd-core/workflows/cleanup.md +5 -3
  219. package/gsd-core/workflows/code-review/steps/dispatch-fix.md +4 -3
  220. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +8 -1
  221. package/gsd-core/workflows/code-review-fix.md +108 -22
  222. package/gsd-core/workflows/code-review.md +216 -73
  223. package/gsd-core/workflows/complete-milestone/detail/elaboration.md +274 -0
  224. package/gsd-core/workflows/complete-milestone.md +41 -264
  225. package/gsd-core/workflows/debug.md +3 -3
  226. package/gsd-core/workflows/diagnose-issues.md +1 -1
  227. package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
  228. package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
  229. package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
  230. package/gsd-core/workflows/discuss-phase.md +1 -1
  231. package/gsd-core/workflows/do.md +2 -2
  232. package/gsd-core/workflows/docs-update/detail/elaboration.md +179 -0
  233. package/gsd-core/workflows/docs-update.md +17 -158
  234. package/gsd-core/workflows/edit-phase.md +1 -1
  235. package/gsd-core/workflows/eval-review.md +10 -3
  236. package/gsd-core/workflows/execute-phase/detail/elaboration.md +124 -0
  237. package/gsd-core/workflows/execute-phase/steps/code-review-disposition.md +1017 -0
  238. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +19 -4
  239. package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +56 -0
  240. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +43 -4
  241. package/gsd-core/workflows/execute-phase/steps/executor-progress-policy.md +43 -0
  242. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
  243. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
  244. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
  245. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +1 -1
  246. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +46 -9
  247. package/gsd-core/workflows/execute-phase/steps/protected-branch.md +1 -1
  248. package/gsd-core/workflows/execute-phase/steps/ready-wave-gate.md +37 -0
  249. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +1 -1
  250. package/gsd-core/workflows/execute-phase/steps/sequential-root-pin.md +35 -0
  251. package/gsd-core/workflows/execute-phase/steps/stale-reverification.md +24 -0
  252. package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +1 -1
  253. package/gsd-core/workflows/execute-phase/steps/threat-id-gate.md +28 -0
  254. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +1 -1
  255. package/gsd-core/workflows/execute-phase/steps/worktree-base-check.md +25 -0
  256. package/gsd-core/workflows/execute-phase.md +83 -172
  257. package/gsd-core/workflows/execute-plan.md +24 -10
  258. package/gsd-core/workflows/explore.md +3 -3
  259. package/gsd-core/workflows/extract-learnings.md +2 -1
  260. package/gsd-core/workflows/fast.md +1 -1
  261. package/gsd-core/workflows/forensics.md +1 -1
  262. package/gsd-core/workflows/graduation.md +1 -1
  263. package/gsd-core/workflows/health.md +2 -2
  264. package/gsd-core/workflows/help/modes/full.compact.md +398 -0
  265. package/gsd-core/workflows/help/modes/full.md +5 -5
  266. package/gsd-core/workflows/help/modes/topic.md +15 -5
  267. package/gsd-core/workflows/help.md +1 -1
  268. package/gsd-core/workflows/import.md +2 -2
  269. package/gsd-core/workflows/inbox.md +2 -2
  270. package/gsd-core/workflows/ingest-docs.md +3 -3
  271. package/gsd-core/workflows/insert-phase.md +1 -1
  272. package/gsd-core/workflows/list-seeds.md +1 -1
  273. package/gsd-core/workflows/list-workspaces.md +1 -1
  274. package/gsd-core/workflows/manager.md +2 -2
  275. package/gsd-core/workflows/map-codebase.md +52 -5
  276. package/gsd-core/workflows/milestone-summary.md +1 -1
  277. package/gsd-core/workflows/mvp-phase.md +1 -1
  278. package/gsd-core/workflows/new-milestone.md +56 -14
  279. package/gsd-core/workflows/new-project/detail/elaboration.md +216 -0
  280. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +3 -3
  281. package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +1 -1
  282. package/gsd-core/workflows/new-project.md +39 -209
  283. package/gsd-core/workflows/new-workspace.md +2 -2
  284. package/gsd-core/workflows/next.md +1 -1
  285. package/gsd-core/workflows/note.md +1 -1
  286. package/gsd-core/workflows/onboard.md +1 -1
  287. package/gsd-core/workflows/pause-work.md +1 -1
  288. package/gsd-core/workflows/plan-phase/detail/elaboration.md +209 -0
  289. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +17 -5
  290. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +1 -1
  291. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +23 -5
  292. package/gsd-core/workflows/plan-phase.md +45 -187
  293. package/gsd-core/workflows/plan-review-convergence.md +21 -5
  294. package/gsd-core/workflows/plant-seed.md +62 -20
  295. package/gsd-core/workflows/pr-branch.md +132 -20
  296. package/gsd-core/workflows/profile-user.md +2 -2
  297. package/gsd-core/workflows/progress.md +1 -1
  298. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +25 -0
  299. package/gsd-core/workflows/quick/steps/quick-verification.md +1 -1
  300. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +1 -1
  301. package/gsd-core/workflows/quick-batch/steps/batch-init.md +1 -1
  302. package/gsd-core/workflows/quick-batch/steps/completion.md +1 -1
  303. package/gsd-core/workflows/quick-batch/steps/merge-wave.md +1 -1
  304. package/gsd-core/workflows/quick-batch/steps/planner-wave.md +1 -1
  305. package/gsd-core/workflows/quick-batch/steps/research-phase.md +1 -1
  306. package/gsd-core/workflows/quick-batch/steps/resume-mode.md +1 -1
  307. package/gsd-core/workflows/quick-batch/steps/verification-wave.md +1 -1
  308. package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +1 -1
  309. package/gsd-core/workflows/quick-batch.md +1 -1
  310. package/gsd-core/workflows/quick.md +29 -10
  311. package/gsd-core/workflows/reapply-patches.md +86 -6
  312. package/gsd-core/workflows/remove-phase.md +1 -1
  313. package/gsd-core/workflows/remove-workspace.md +2 -2
  314. package/gsd-core/workflows/resume-project.md +1 -1
  315. package/gsd-core/workflows/review.md +31 -16
  316. package/gsd-core/workflows/scan.md +1 -1
  317. package/gsd-core/workflows/secure-phase.md +3 -2
  318. package/gsd-core/workflows/settings-advanced.md +30 -10
  319. package/gsd-core/workflows/settings-integrations.md +2 -3
  320. package/gsd-core/workflows/settings.md +22 -9
  321. package/gsd-core/workflows/ship.md +3 -2
  322. package/gsd-core/workflows/sketch-wrap-up.md +1 -1
  323. package/gsd-core/workflows/sketch.md +1 -1
  324. package/gsd-core/workflows/smart-entry.md +2 -2
  325. package/gsd-core/workflows/spec-phase.md +15 -5
  326. package/gsd-core/workflows/spike-wrap-up.md +1 -1
  327. package/gsd-core/workflows/spike.md +1 -1
  328. package/gsd-core/workflows/stats.md +1 -1
  329. package/gsd-core/workflows/sync-skills.md +5 -5
  330. package/gsd-core/workflows/thread.md +1 -1
  331. package/gsd-core/workflows/transition.md +1 -1
  332. package/gsd-core/workflows/ui-phase.md +44 -8
  333. package/gsd-core/workflows/ui-review.md +18 -4
  334. package/gsd-core/workflows/ultraplan-phase.md +1 -1
  335. package/gsd-core/workflows/undo.md +339 -20
  336. package/gsd-core/workflows/update.md +14 -12
  337. package/gsd-core/workflows/validate-phase.md +3 -2
  338. package/gsd-core/workflows/verify-work/detail/elaboration.md +230 -0
  339. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +1 -1
  340. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
  341. package/gsd-core/workflows/verify-work.md +101 -196
  342. package/hooks/dist/gsd-agent-isolation-guard.js +66 -16
  343. package/hooks/dist/gsd-context-monitor.js +88 -15
  344. package/hooks/dist/gsd-cursor-subagent-start.js +34 -14
  345. package/hooks/dist/gsd-secret-read-guard.js +71 -19
  346. package/hooks/dist/gsd-statusline.js +81 -20
  347. package/hooks/dist/gsd-validate-commit.sh +97 -8
  348. package/hooks/dist/gsd-worktree-path-guard.js +25 -14
  349. package/hooks/dist/gsd-write-guard.js +46 -1
  350. package/hooks/dist/lib/dispatch-identity.js +187 -0
  351. package/hooks/dist/lib/filename-classification.js +64 -0
  352. package/hooks/dist/lib/isolation-deny-reason.js +53 -1
  353. package/hooks/dist/lib/isolation-sentinel.js +58 -19
  354. package/hooks/gsd-agent-isolation-guard.js +66 -16
  355. package/hooks/gsd-context-monitor.js +88 -15
  356. package/hooks/gsd-cursor-subagent-start.js +34 -14
  357. package/hooks/gsd-secret-read-guard.js +71 -19
  358. package/hooks/gsd-statusline.js +81 -20
  359. package/hooks/gsd-validate-commit.sh +97 -8
  360. package/hooks/gsd-worktree-path-guard.js +25 -14
  361. package/hooks/gsd-write-guard.js +46 -1
  362. package/hooks/lib/dispatch-identity.js +187 -0
  363. package/hooks/lib/filename-classification.js +64 -0
  364. package/hooks/lib/isolation-deny-reason.js +53 -1
  365. package/hooks/lib/isolation-sentinel.js +58 -19
  366. package/package.json +11 -6
  367. package/scripts/benchmark-compact-content-variants.cjs +298 -0
  368. package/scripts/benchmark-compact-content.cjs +368 -0
  369. package/scripts/build-hooks.js +15 -6
  370. package/scripts/check-contract-drift.cjs +131 -12
  371. package/scripts/check-env.cjs +36 -8
  372. package/scripts/check-glossary-refs.cjs +25 -21
  373. package/scripts/ci-next-health.cjs +271 -0
  374. package/scripts/ci-prepare-test-scope.cjs +7 -7
  375. package/scripts/ci-test-scope.cjs +126 -20
  376. package/scripts/ci-timeout-report.cjs +1 -1
  377. package/scripts/command-contract-helpers.cjs +3 -0
  378. package/scripts/diff-touches-shipped-paths.cjs +1 -1
  379. package/scripts/docs-guard-registry.cjs +35 -2
  380. package/scripts/gen-adr-index.cjs +8 -2
  381. package/scripts/gen-inventory-manifest.cjs +12 -0
  382. package/scripts/gen-loop-host-contract.cjs +69 -0
  383. package/scripts/gen-platform-conformance-tier.cjs +557 -0
  384. package/scripts/lib/drift-scan.cjs +1 -1
  385. package/scripts/lib/macos-conformance-tier.generated.cjs +224 -0
  386. package/scripts/lib/ndjson-reporter.cjs +3 -2
  387. package/scripts/lib/npm-version-check-diagnosis.cjs +59 -0
  388. package/scripts/lib/platform-conformance-tier.generated.cjs +287 -0
  389. package/scripts/lib/suite-detection.cjs +32 -0
  390. package/scripts/lint-allowed-tools-parity.cjs +221 -0
  391. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +47 -3
  392. package/scripts/lint-phase-arg-assignment.cjs +257 -0
  393. package/scripts/lint-phase-id-drift.cjs +623 -13
  394. package/scripts/lint-pr-branch-pattern-drift.cjs +148 -0
  395. package/scripts/lint-response-language-coverage.cjs +9 -3
  396. package/scripts/lint-retired-runtime-name.cjs +619 -0
  397. package/scripts/lint-source-test-name-collision.cjs +1 -1
  398. package/scripts/lint-state-write-path-drift.cjs +93 -0
  399. package/scripts/lint-test-file-count.allowlist.json +29 -9
  400. package/scripts/lint-vendored-deps.cjs +128 -17
  401. package/scripts/lint-workflow-shellcheck-baseline.json +100 -0
  402. package/scripts/prompt-injection-scan.sh +18 -0
  403. package/scripts/release-tarball-smoke.cjs +194 -1
  404. package/scripts/workflow-size.cjs +139 -0
  405. package/skills/gsd-autonomous/SKILL.md +2 -2
  406. package/skills/gsd-capture/SKILL.md +1 -1
  407. package/skills/gsd-cleanup/SKILL.md +1 -0
  408. package/skills/gsd-code-review/SKILL.md +2 -1
  409. package/skills/gsd-complete-milestone/SKILL.md +1 -0
  410. package/skills/gsd-config/SKILL.md +1 -0
  411. package/skills/gsd-debug/SKILL.md +1 -0
  412. package/skills/gsd-graphify/SKILL.md +1 -0
  413. package/skills/gsd-health/SKILL.md +1 -0
  414. package/skills/gsd-mempalace-capture/SKILL.md +8 -3
  415. package/skills/gsd-mempalace-recall/SKILL.md +1 -0
  416. package/skills/gsd-new-milestone/SKILL.md +1 -0
  417. package/skills/gsd-new-project/SKILL.md +1 -0
  418. package/skills/gsd-next/SKILL.md +1 -0
  419. package/skills/gsd-pause-work/SKILL.md +1 -0
  420. package/skills/gsd-phase/SKILL.md +1 -0
  421. package/skills/gsd-plan-review-convergence/SKILL.md +5 -5
  422. package/skills/gsd-pr-branch/SKILL.md +1 -0
  423. package/skills/gsd-progress/SKILL.md +1 -1
  424. package/skills/gsd-quick-batch/SKILL.md +1 -1
  425. package/skills/gsd-resume-work/SKILL.md +1 -0
  426. package/skills/gsd-review/SKILL.md +2 -3
  427. package/skills/gsd-review-backlog/SKILL.md +1 -0
  428. package/skills/gsd-settings/SKILL.md +2 -1
  429. package/skills/gsd-stats/SKILL.md +1 -0
  430. package/skills/gsd-thread/SKILL.md +1 -0
  431. package/skills/gsd-workspace/SKILL.md +1 -0
  432. package/skills/gsd-workstreams/SKILL.md +1 -0
  433. package/vscode/package.json +1 -1
  434. package/gsd-core/templates/claude-md.md +0 -145
  435. package/gsd-core/templates/codebase/concerns.md +0 -310
  436. package/gsd-core/templates/codebase/conventions.md +0 -307
  437. package/gsd-core/templates/codebase/integrations.md +0 -280
  438. package/gsd-core/templates/codebase/structure.md +0 -285
  439. package/gsd-core/templates/codebase/testing.md +0 -480
  440. package/gsd-core/templates/debug-subagent-prompt.md +0 -91
  441. package/gsd-core/templates/discovery.md +0 -146
@@ -20,6 +20,50 @@
20
20
  * directly above). Sites that build their regex from `PHASE_NUMBER_TOKEN_SOURCE`
21
21
  * carry no literal grammar and pass automatically.
22
22
  *
23
+ * #4634 extends the same pattern with two more detectors:
24
+ *
25
+ * - name-validity-guard drift: `hasNameableContent(s)` in `src/roadmap-parser.cts`
26
+ * is the sole owner of the "does this string have nameable content" predicate
27
+ * (`/[\p{L}\p{N}]/u.test(s)`). Any other `src/**` file re-deriving that exact
28
+ * character class (regex-literal or `new RegExp` template form) instead of
29
+ * calling `hasNameableContent` is drift, sanctioned the same way as the token
30
+ * and bracket rules (`// phase-id-owner:` on the nearest preceding non-blank
31
+ * line), with a line-level escape for a line that already calls
32
+ * `hasNameableContent(`.
33
+ *
34
+ * - shell phase-number-arithmetic ban: `$((10#...))` base-10-forced arithmetic
35
+ * inside `gsd-core/workflows/**\/*.md` and `gsd-core/references/**\/*.md` breaks
36
+ * on decimal or multi-segment phase ids and is banned outright. This scan runs
37
+ * over markdown, not `.cts` source, so its sanction is an HTML comment on the
38
+ * nearest preceding non-blank line: `<!-- phase-id-owner: <reason> -->`.
39
+ *
40
+ * - phase-heading-scan literal drift (#4906 Phase 5 / #4984): `findPhaseHeadingScanLiteralDrift`
41
+ * detects a hand-rolled `#{2,4}\s*Phase\s+` heading-SCAN literal, which bypasses
42
+ * `buildPhaseHeadingScanRegex` / `phaseHeadingPrefixSrcFor` (both in
43
+ * `src/phase-id.cts`) and so silently fails to recognize bracket-convention
44
+ * headings. Sanctioned the same way as the token/bracket/name-validity rules
45
+ * (`// phase-id-owner:` on the nearest preceding non-blank line), with a
46
+ * line-level escape for a line that already names either owner function.
47
+ * Wired into `scanRepo` (ADR-4910 §8): every pre-existing site this phase
48
+ * could not migrate (src/phase.cts x11, src/roadmap-parser.cts x3,
49
+ * src/planning-snapshot.cts x1) is grandfathered with a dedicated
50
+ * `// phase-id-owner:` sanction comment rather than a separate allowlist —
51
+ * the guard is ACTIVE from this phase forward: a fresh unsanctioned copy
52
+ * fails CI immediately, and each grandfathered comment is deleted as its
53
+ * site migrates in a later phase (ADR-4910 §8's census/grandfather/delete
54
+ * sequence, never renewed).
55
+ *
56
+ * - branch-slug fallback drift: a `.replace('{slug}', ... || 'phase')` call
57
+ * silently substitutes the literal string `'phase'` when a phase's slug
58
+ * can't be derived, producing a non-identifying branch name like
59
+ * `gsd/phase-08-phase` (#4126, now fixed via the shared renderPhaseBranchName
60
+ * owner in phase-id.cts, consumed by both prior call sites). Sanctioned
61
+ * the same way as the token/bracket/name-validity rules (`// phase-id-owner:`
62
+ * on the nearest preceding non-blank line), with a line-level escape for a
63
+ * line that already calls `renderPhaseBranchName(`. Unlike the other
64
+ * `.cts`-scanning rules, this one has no per-file exemption — it is a banned
65
+ * anti-pattern everywhere, not a grammar with one legitimate owner site.
66
+ *
23
67
  * Detection is intentionally NARROW: only the contiguous canonical token
24
68
  * (`\d+[A-Z]?(?:\.\d+)*`, its `[A-Za-z]` and `[.-]` near-variants, in both
25
69
  * regex-literal `\d` and `new RegExp` template `\\d` escaping) is drift. Bare
@@ -82,6 +126,18 @@ const BRACKET_CODE_DRIFT_RE = /\[A-Z(?:a-z)?\]\[A-Z(?:a-z)?0-9_\]\*/;
82
126
  const BRACKET_OWNER_HINT =
83
127
  'BRACKET_PROJECT_CODE_SRC / BRACKET_ID_SRC / bracketMilestoneIntroSrcFor / BRACKET_MILESTONE_INTRO_CAPTURING_SRC';
84
128
 
129
+ /**
130
+ * Pure: true if the nearest preceding non-blank line to `lines[i]` is a
131
+ * dedicated sanction comment matching `ownerRe`. Shared by every detector in
132
+ * this file so the "how do you sanction a finding" walk has one owner instead
133
+ * of four independent copies that could silently diverge.
134
+ */
135
+ function isSanctionedByPrecedingComment(lines, i, ownerRe) {
136
+ let j = i - 1;
137
+ while (j >= 0 && lines[j].trim() === '') j--; // nearest preceding non-blank line
138
+ return j >= 0 && ownerRe.test(lines[j]);
139
+ }
140
+
85
141
  /**
86
142
  * Pure: find every literal re-derivation of the canonical phase-number token in
87
143
  * `text` that is NOT sanctioned. A site is sanctioned when the nearest preceding
@@ -100,9 +156,7 @@ function findPhaseIdRegexDrift(text) {
100
156
  const m = TOKEN_DRIFT_RE.exec(line);
101
157
  if (!m) continue;
102
158
  if (line.includes(CANON_REF)) continue;
103
- let j = i - 1;
104
- while (j >= 0 && lines[j].trim() === '') j--; // nearest preceding non-blank line
105
- if (j >= 0 && OWNER_RE.test(lines[j])) continue;
159
+ if (isSanctionedByPrecedingComment(lines, i, OWNER_RE)) continue;
106
160
  out.push({ line: i + 1, found: m[0] });
107
161
  }
108
162
  return out;
@@ -122,21 +176,483 @@ function findBracketGrammarDrift(text) {
122
176
  for (let i = 0; i < lines.length; i++) {
123
177
  const m = BRACKET_CODE_DRIFT_RE.exec(lines[i]);
124
178
  if (!m) continue;
125
- let j = i - 1;
126
- while (j >= 0 && lines[j].trim() === '') j--; // nearest preceding non-blank line
127
- if (j >= 0 && OWNER_RE.test(lines[j])) continue;
179
+ if (isSanctionedByPrecedingComment(lines, i, OWNER_RE)) continue;
180
+ out.push({ line: i + 1, found: m[0] });
181
+ }
182
+ return out;
183
+ }
184
+
185
+ // #4634: the name-validity-guard grammar — `hasNameableContent(s)` in
186
+ // `src/roadmap-parser.cts` is `/[\p{L}\p{N}]/u.test(s)`. Tolerates both the
187
+ // regex-literal single-backslash form and the doubled-backslash template
188
+ // form (`new RegExp('[\\p{L}\\p{N}]'`), mirroring how TOKEN_DRIFT_RE tolerates
189
+ // both escapings.
190
+ const NAME_VALIDITY_DRIFT_RE = /\[\\{1,2}p\{L\}\\{1,2}p\{N\}\]/;
191
+ const NAME_VALIDITY_CANON_REF = 'hasNameableContent(';
192
+
193
+ /**
194
+ * Pure: find every literal re-derivation of the canonical name-validity
195
+ * character class in `text` that is NOT sanctioned. Same sanction mechanism
196
+ * as the token/bracket rules — a dedicated `// phase-id-owner:` comment on
197
+ * the nearest preceding non-blank line — plus a line-level escape for a line
198
+ * that already calls `hasNameableContent(`. Returns [{ line, found }].
199
+ */
200
+ function findNameValidityDrift(text) {
201
+ const out = [];
202
+ const lines = text.split('\n');
203
+ for (let i = 0; i < lines.length; i++) {
204
+ const line = lines[i];
205
+ const m = NAME_VALIDITY_DRIFT_RE.exec(line);
206
+ if (!m) continue;
207
+ if (line.includes(NAME_VALIDITY_CANON_REF)) continue;
208
+ if (isSanctionedByPrecedingComment(lines, i, OWNER_RE)) continue;
209
+ out.push({ line: i + 1, found: m[0] });
210
+ }
211
+ return out;
212
+ }
213
+
214
+ // #4634: the branch-slug fallback anti-pattern (#4126) — a
215
+ // `.replace('{slug}', ... || 'phase')` call silently falls back to the
216
+ // literal string `'phase'` when a phase's slug can't be derived, producing a
217
+ // non-identifying branch name like `gsd/phase-08-phase`. Now fixed at both
218
+ // prior call sites (commands.cts, init.cts) via the shared
219
+ // `renderPhaseBranchName` owner in phase-id.cts; this rule is the ratchet
220
+ // against a THIRD site reintroducing the inline fallback. Deliberately
221
+ // narrow: it requires the literal `'phase'` fallback on the same line as the
222
+ // `{slug}` template token, so it does NOT match the sibling milestone-branch
223
+ // fallback (`|| 'milestone'`), which is a different, correct-as-is case.
224
+ const BRANCH_SLUG_FALLBACK_DRIFT_RE = /\{slug\}'.*\|\|\s*'phase'/;
225
+
226
+ // The canonical fix is `renderPhaseBranchName(...)`. There is no "owner file"
227
+ // for this rule the way there is for the token/bracket/name-validity
228
+ // grammars above — it is a banned anti-pattern everywhere, so no per-file
229
+ // exemption exists.
230
+ const BRANCH_SLUG_FALLBACK_CANON_REF = 'renderPhaseBranchName(';
231
+
232
+ /**
233
+ * Pure: find every unsanctioned branch-slug `|| 'phase'` fallback in `text`.
234
+ * Same sanction mechanism as the token/bracket/name-validity rules — a
235
+ * dedicated `// phase-id-owner:` comment on the nearest preceding non-blank
236
+ * line — plus a line-level escape for a line that already calls
237
+ * `renderPhaseBranchName(`. Returns [{ line, found }].
238
+ */
239
+ function findBranchSlugFallbackDrift(text) {
240
+ const out = [];
241
+ const lines = text.split('\n');
242
+ for (let i = 0; i < lines.length; i++) {
243
+ const line = lines[i];
244
+ const m = BRANCH_SLUG_FALLBACK_DRIFT_RE.exec(line);
245
+ if (!m) continue;
246
+ if (line.includes(BRANCH_SLUG_FALLBACK_CANON_REF)) continue;
247
+ if (isSanctionedByPrecedingComment(lines, i, OWNER_RE)) continue;
248
+ out.push({ line: i + 1, found: m[0] });
249
+ }
250
+ return out;
251
+ }
252
+
253
+ // #4906 Phase 5 (#4984): a hand-rolled heading-SCAN literal `#{2,4}\s*Phase\s+`
254
+ // (in either regex-literal `\s` or template `\\s` escaping, mirroring
255
+ // TOKEN_DRIFT_RE's tolerance) bypasses `buildPhaseHeadingScanRegex` /
256
+ // `phaseHeadingPrefixSrcFor` (both in `src/phase-id.cts`) entirely, so it
257
+ // silently fails to recognize bracket-convention headings (`[GSD.03] Phase
258
+ // 03:`) — the exact defect this phase fixed at five call sites in init.cts
259
+ // and milestone.cts. A line naming either owner function counts as "built
260
+ // from the owner" and is not flagged, mirroring the token rule's
261
+ // `line.includes(CANON_REF)` escape.
262
+ const PHASE_HEADING_SCAN_LITERAL_DRIFT_RE = /#\{2,4\}\\{1,2}s\*Phase\\{1,2}s\+/;
263
+ const PHASE_HEADING_SCAN_LITERAL_CANON_REFS = ['buildPhaseHeadingScanRegex', 'phaseHeadingPrefixSrcFor'];
264
+
265
+ /**
266
+ * Pure: find every unsanctioned hand-rolled `#{2,4}\s*Phase\s+` heading-scan
267
+ * literal in `text`. Sanctioned when the line already names either owner
268
+ * function (`buildPhaseHeadingScanRegex(` / `phaseHeadingPrefixSrcFor(`), or
269
+ * by a `// phase-id-owner:` comment on the nearest preceding non-blank line
270
+ * (same mechanism as every other rule in this file). Returns [{ line, found }].
271
+ */
272
+ function findPhaseHeadingScanLiteralDrift(text) {
273
+ const out = [];
274
+ const lines = text.split('\n');
275
+ for (let i = 0; i < lines.length; i++) {
276
+ const line = lines[i];
277
+ const m = PHASE_HEADING_SCAN_LITERAL_DRIFT_RE.exec(line);
278
+ if (!m) continue;
279
+ if (PHASE_HEADING_SCAN_LITERAL_CANON_REFS.some((ref) => line.includes(ref))) continue;
280
+ if (isSanctionedByPrecedingComment(lines, i, OWNER_RE)) continue;
281
+ out.push({ line: i + 1, found: m[0] });
282
+ }
283
+ return out;
284
+ }
285
+
286
+ // #4634: ban base-10-forced shell arithmetic (`$((10#...))`) on a variable
287
+ // that still carries a possibly-decimal/multi-segment phase id — this
288
+ // construct is exactly the pattern that breaks on a value like `08.5`. The
289
+ // capture group grabs the token immediately inside the parens (after an
290
+ // optional `$` and/or `{`, stripping a trailing `}`) so callers can inspect
291
+ // *which* variable is being coerced, not merely that the substring occurred.
292
+ //
293
+ // Refined post-#4619: the original blunt "ban `$((10#` outright" version
294
+ // over-fired on three false-positive classes once #4619's fix landed:
295
+ // 1. Prose mentioning the literal pattern in a full-line `#`-comment
296
+ // (filtered by the caller, not this regex — see below).
297
+ // 2. `$((10#$PHASE_INT))` / `$((10#$SPOT_PHASE_INT))` — arithmetic on the
298
+ // NOW-safe variable the #4619 fix produces via a leading-digit-run split
299
+ // (`PHASE_INT=${PHASE_NUMBER%%[!0-9]*}` since #4748; `%%.*` before it);
300
+ // a digit-run value can never contain a dot OR a letter, so base-10
301
+ // arithmetic on it can never hit the #4619 / #4748 error classes. Any
302
+ // name ending in `_INT` (case-insensitive) is that established "already
303
+ // reduced to a safe integer" convention. That convention is a NAME, not
304
+ // a proof — `findDotOnlyIntegerSplitDrift` below polices that the split
305
+ // producing it actually stops at the first non-digit.
306
+ // 3. `$((10#{plan_padded}))` / `$((10#${PLAN_ID}))` — plan ids are plain
307
+ // integers and were never in scope; this rule only polices variables
308
+ // that carry a *phase* id.
309
+ // So a match is only a violation when the captured name contains `phase`
310
+ // case-insensitively (it is phase-carrying) AND does not end in `_int`
311
+ // case-insensitively (it has not already been reduced to a safe integer).
312
+ const SHELL_PHASE_ARITH_DRIFT_RE = /\$\(\(\s*10#\$?\{?([A-Za-z0-9_]+)\}?/;
313
+
314
+ // A markdown comment can't easily carry a `//` line, so the sanction for the
315
+ // shell-arithmetic rule is an HTML comment on the nearest preceding non-blank
316
+ // line: `<!-- phase-id-owner: <reason> -->`.
317
+ const MD_OWNER_RE = /^\s*<!--.*phase-id-owner:/;
318
+
319
+ /**
320
+ * Pure: find every unsanctioned `$((10#...))` base-10-forced shell arithmetic
321
+ * site in `text` that still coerces an un-reduced phase-carrying variable.
322
+ * Skips full-line `#` comments outright (pure prose mentioning the pattern,
323
+ * not executable code), and skips any captured variable name that either
324
+ * doesn't contain `phase` (never in scope — e.g. plan ids) or already ends
325
+ * in `_int` (the #4619-fix convention for "safely stripped to an integer").
326
+ * Sanctioned by an HTML comment `<!-- phase-id-owner: ... -->` on the
327
+ * nearest preceding non-blank line. Returns [{ line, found }].
328
+ */
329
+ function findShellPhaseArithDrift(text) {
330
+ const out = [];
331
+ const lines = text.split('\n');
332
+ for (let i = 0; i < lines.length; i++) {
333
+ const line = lines[i];
334
+ if (/^\s*#/.test(line)) continue;
335
+ const m = SHELL_PHASE_ARITH_DRIFT_RE.exec(line);
336
+ if (!m) continue;
337
+ const name = m[1];
338
+ if (!/phase/i.test(name)) continue;
339
+ if (/_int$/i.test(name)) continue;
340
+ if (isSanctionedByPrecedingComment(lines, i, MD_OWNER_RE)) continue;
341
+ out.push({ line: i + 1, found: m[0] });
342
+ }
343
+ return out;
344
+ }
345
+
346
+ // #4634: the markdown scan roots — shell embedded in workflow/reference docs.
347
+ const MD_SCAN_DIRS = [path.join('gsd-core', 'workflows'), path.join('gsd-core', 'references')];
348
+
349
+ // #4568 (epic #4634): the single-segment phase regex ban scans a THIRD root,
350
+ // `agents/**/*.md`, that the #4619 shell-arithmetic extension above never
351
+ // touched — the gsd-code-fixer agent prompts re-derive the phase-number
352
+ // grammar too. Reuses the same `walkMd` walker as the shell-arith scan.
353
+ const SINGLE_SEGMENT_SCAN_DIRS = [...MD_SCAN_DIRS, 'agents'];
354
+
355
+ /**
356
+ * Scan `gsd-core/workflows/**\/*.md` and `gsd-core/references/**\/*.md` for
357
+ * unsanctioned `$((10#...))` shell arithmetic. Returns [{ file, line, found }]
358
+ * with repo-relative paths.
359
+ */
360
+ function scanMarkdownShellArith(root) {
361
+ const violations = [];
362
+ for (const dir of MD_SCAN_DIRS) {
363
+ for (const file of walkMd(path.join(root, dir), [])) {
364
+ const rel = path.relative(root, file);
365
+ let text;
366
+ try {
367
+ text = fs.readFileSync(file, 'utf8');
368
+ } catch {
369
+ continue;
370
+ }
371
+ for (const d of findShellPhaseArithDrift(text)) {
372
+ violations.push({ file: rel, kind: 'shell-arith', ...d });
373
+ }
374
+ }
375
+ }
376
+ return violations;
377
+ }
378
+
379
+ // #4568 (epic #4634): ban the single-optional-dotted-segment phase regex
380
+ // shape `[0-9]+(\.[0-9]+)?` (and its `\d`/doubled-backslash near-variants)
381
+ // outright — this is exactly the grammar that hard-rejects or silently
382
+ // truncates a 3-or-more-segment phase id like `23.1.2`. The canonical
383
+ // grammar (`src/phase-id.cts`) uses the unbounded `(?:\.\d+)*` form; shell
384
+ // snippets embedded in markdown can't import that module, so textual parity
385
+ // (`*` in place of `?`) is the fix, and this rule is the ratchet against a
386
+ // future site re-deriving the bounded form. Deliberately narrow to the
387
+ // bounded ONE-optional-segment shape — the fixed `*`-form is not flagged.
388
+ const SINGLE_SEGMENT_PHASE_DRIFT_RE =
389
+ /(?:\\{1,2}d|\[0-9\])\+\(\\{1,2}\.(?:\\{1,2}d|\[0-9\])\+\)\?/;
390
+
391
+ // A single-segment shape like `[0-9]+(\.[0-9]+)?` is not inherently
392
+ // phase-specific (e.g. it could describe a version number), so the rule
393
+ // only fires on a line whose text plausibly carries a phase-number
394
+ // variable — a case-insensitive `phase` substring anywhere on the line,
395
+ // mirroring the phase-carrying filter `findShellPhaseArithDrift` already
396
+ // applies to its own variable-name capture.
397
+ const PHASE_CARRYING_LINE_RE = /phase/i;
398
+
399
+ /**
400
+ * Pure: find every unsanctioned single-optional-dotted-segment phase regex
401
+ * in `text`, restricted to lines that plausibly carry a phase-number
402
+ * variable. Sanctioned by an HTML comment `<!-- phase-id-owner: ... -->` on
403
+ * the nearest preceding non-blank line (same convention as the shell-arith
404
+ * rule). Returns [{ line, found }].
405
+ */
406
+ function findSingleSegmentPhaseRegexDrift(text) {
407
+ const out = [];
408
+ const lines = text.split('\n');
409
+ for (let i = 0; i < lines.length; i++) {
410
+ const line = lines[i];
411
+ const m = SINGLE_SEGMENT_PHASE_DRIFT_RE.exec(line);
412
+ if (!m) continue;
413
+ if (!PHASE_CARRYING_LINE_RE.test(line)) continue;
414
+ if (isSanctionedByPrecedingComment(lines, i, MD_OWNER_RE)) continue;
415
+ out.push({ line: i + 1, found: m[0] });
416
+ }
417
+ return out;
418
+ }
419
+
420
+ /**
421
+ * Scan `gsd-core/workflows/**\/*.md`, `gsd-core/references/**\/*.md`, and
422
+ * `agents/**\/*.md` for unsanctioned single-optional-dotted-segment phase
423
+ * regexes. Returns [{ file, line, found }] with repo-relative paths.
424
+ */
425
+ function scanMarkdownSingleSegmentPhaseRegex(root) {
426
+ const violations = [];
427
+ for (const dir of SINGLE_SEGMENT_SCAN_DIRS) {
428
+ for (const file of walkMd(path.join(root, dir), [])) {
429
+ const rel = path.relative(root, file);
430
+ let text;
431
+ try {
432
+ text = fs.readFileSync(file, 'utf8');
433
+ } catch {
434
+ continue;
435
+ }
436
+ for (const d of findSingleSegmentPhaseRegexDrift(text)) {
437
+ violations.push({ file: rel, kind: 'single-segment-phase-regex', ...d });
438
+ }
439
+ }
440
+ }
441
+ return violations;
442
+ }
443
+
444
+ // #4660 (epic #4634): the six shell/markdown mirrors #4568 widened on the
445
+ // segment-count axis stayed digit-only on the LETTER axis — the canonical
446
+ // grammar (`src/phase-id.cts`) is `\d+[A-Z]?(?:\.\d+)*`, with an optional
447
+ // single uppercase letter after the leading digits (`12A`, `3A`, `23A.1.2`,
448
+ // documented in docs/CONFIGURATION.md and relied on by `renameIntegerPhases`).
449
+ // A digit-only mirror `[0-9]+(\.[0-9]+)*` hard-rejects (validating sites) or
450
+ // silently truncates (extracting sites) a letter-suffixed id. This rule is the
451
+ // ratchet for that axis, the twin of the single-segment rule above: it flags
452
+ // the unbounded-segment shape whose digit run is NOT followed by the letter
453
+ // class. `[A-Z]` is the canonical spelling; the case-flexible `[A-Za-z]`
454
+ // directory-scanning variant is a deliberately separate axis and is tolerated
455
+ // here so this rule cannot force it to narrow.
456
+ const LETTERLESS_PHASE_MIRROR_DRIFT_RE =
457
+ /(?:\\{1,2}d|\[0-9\])\+(?!\[A-Z(?:a-z)?\]\?)\(\\{1,2}\.(?:\\{1,2}d|\[0-9\])\+\)\*/;
458
+
459
+ /**
460
+ * Pure: find every unsanctioned digit-only (letter-less) unbounded-segment
461
+ * phase regex in `text`, restricted to lines that plausibly carry a
462
+ * phase-number variable — the same `PHASE_CARRYING_LINE_RE` filter and the
463
+ * same `<!-- phase-id-owner: ... -->` sanction as the single-segment rule.
464
+ * Returns [{ line, found }].
465
+ */
466
+ function findLetterlessPhaseMirrorDrift(text) {
467
+ const out = [];
468
+ const lines = text.split('\n');
469
+ for (let i = 0; i < lines.length; i++) {
470
+ const line = lines[i];
471
+ const m = LETTERLESS_PHASE_MIRROR_DRIFT_RE.exec(line);
472
+ if (!m) continue;
473
+ if (!PHASE_CARRYING_LINE_RE.test(line)) continue;
474
+ if (isSanctionedByPrecedingComment(lines, i, MD_OWNER_RE)) continue;
475
+ out.push({ line: i + 1, found: m[0] });
476
+ }
477
+ return out;
478
+ }
479
+
480
+ /**
481
+ * Scan the same three markdown roots as the single-segment rule for
482
+ * unsanctioned letter-less phase-regex mirrors. Returns [{ file, line, found }]
483
+ * with repo-relative paths.
484
+ */
485
+ function scanMarkdownLetterlessPhaseMirror(root) {
486
+ const violations = [];
487
+ for (const dir of SINGLE_SEGMENT_SCAN_DIRS) {
488
+ for (const file of walkMd(path.join(root, dir), [])) {
489
+ const rel = path.relative(root, file);
490
+ let text;
491
+ try {
492
+ text = fs.readFileSync(file, 'utf8');
493
+ } catch {
494
+ continue;
495
+ }
496
+ for (const d of findLetterlessPhaseMirrorDrift(text)) {
497
+ violations.push({ file: rel, kind: 'letterless-phase-mirror', ...d });
498
+ }
499
+ }
500
+ }
501
+ return violations;
502
+ }
503
+
504
+ // #4748 (epic #4634): three shell shapes OUTSIDE the grammar-mirror family the
505
+ // rules above police — consumers of a phase id rather than regexes for one —
506
+ // each of which the letter axis broke while every rule above reported clean:
507
+ //
508
+ // a. `PHASE_INT=${PHASE_NUMBER%%.*}` — the post-#4619 dot-only split. The
509
+ // `_INT` name it produces satisfies the shell-arithmetic rule's escape,
510
+ // but on `03A` the "integer" is `03A` and `$((10#03A))` aborts. The safe
511
+ // split stops at the first NON-digit: `${PHASE_NUMBER%%[!0-9]*}`.
512
+ // b. `[0-9]+\.?[0-9]*` (and `\d+\.?\d*`) — a digit-then-optional-dot
513
+ // extraction that is neither the bounded `(\.[0-9]+)?` shape the
514
+ // single-segment rule bans nor the unbounded `(\.[0-9]+)*` shape the
515
+ // letterless rule inspects, so both were blind to it. It captures `12`
516
+ // from `12A` and `23.1` from `23.1.2`, silently.
517
+ // c. `printf "%02d" "$PHASE_NUMBER"` — re-padding the whole id in shell.
518
+ // Rejects a letter id (prints `03`, exit 1) and misreads an already
519
+ // padded `08` as octal (prints `00`). Padding belongs to the canonical
520
+ // normalizer (`padded_phase` from init, or `normalizePhaseName`); the one
521
+ // legitimate shell pad is of an `_INT` value via `$((10#…))`.
522
+ //
523
+ // Same sanction as the other markdown rules: `<!-- phase-id-owner: … -->` on
524
+ // the nearest preceding non-blank line. Same documented limit: a per-line
525
+ // textual scan for the common accidental shape, not an obfuscated one.
526
+
527
+ // a. `<name>_INT=${<phase-carrying>%%.*}` — the `_INT` destination is the
528
+ // discriminator, deliberately: it is the name the shell-arithmetic rule
529
+ // trusts as "already a safe integer", so a dot-only split INTO it is the
530
+ // exact promise this rule exists to check. A dot-only split into any other
531
+ // name is a different, legitimate operation — `PARENT_PHASE="${PHASE_NUMBER%%.*}"`
532
+ // (gap-closure-artifacts.md) wants everything before the first dot, letter
533
+ // included, and is correct. Widening to any destination was tried and flagged
534
+ // that site. The optional quote after `=` is the one spelling that site uses.
535
+ const DOT_ONLY_INT_SPLIT_DRIFT_RE = /[A-Za-z0-9_]*_INT="?\$\{([A-Za-z0-9_]+)%%\\?\.\*\}/i;
536
+
537
+ /**
538
+ * Pure: find every unsanctioned dot-only integer split of a phase-carrying
539
+ * variable in `text`. Skips full-line `#` comments (prose). Returns [{ line, found }].
540
+ */
541
+ function findDotOnlyIntegerSplitDrift(text) {
542
+ const out = [];
543
+ const lines = text.split('\n');
544
+ for (let i = 0; i < lines.length; i++) {
545
+ const line = lines[i];
546
+ if (/^\s*#/.test(line)) continue;
547
+ const m = DOT_ONLY_INT_SPLIT_DRIFT_RE.exec(line);
548
+ if (!m) continue;
549
+ if (!/phase/i.test(m[1])) continue;
550
+ if (isSanctionedByPrecedingComment(lines, i, MD_OWNER_RE)) continue;
551
+ out.push({ line: i + 1, found: m[0] });
552
+ }
553
+ return out;
554
+ }
555
+
556
+ // b. The digit-then-optional-dot shape, in both `[0-9]` and `\d` spellings.
557
+ // Anchored on the trailing `*` of the second digit class so a bare `[0-9]+`
558
+ // probe or a `[0-9]+\.[0-9]+` (mandatory-dot) shape is not matched.
559
+ const LOOSE_DOTTED_PHASE_DRIFT_RE = /(?:\\{1,2}d|\[0-9\])\+\\{1,2}\.\?(?:\\{1,2}d|\[0-9\])\*/;
560
+
561
+ /**
562
+ * Pure: find every unsanctioned `[0-9]+\.?[0-9]*`-shaped phase extraction in
563
+ * `text`, restricted to phase-carrying lines like its two sibling regex rules.
564
+ * Returns [{ line, found }].
565
+ */
566
+ function findLooseDottedPhaseRegexDrift(text) {
567
+ const out = [];
568
+ const lines = text.split('\n');
569
+ for (let i = 0; i < lines.length; i++) {
570
+ const line = lines[i];
571
+ const m = LOOSE_DOTTED_PHASE_DRIFT_RE.exec(line);
572
+ if (!m) continue;
573
+ if (!PHASE_CARRYING_LINE_RE.test(line)) continue;
574
+ if (isSanctionedByPrecedingComment(lines, i, MD_OWNER_RE)) continue;
128
575
  out.push({ line: i + 1, found: m[0] });
129
576
  }
130
577
  return out;
131
578
  }
132
579
 
580
+ // c. `printf "%Nd" …` / `printf '%0Nd' …` — any integer conversion, either
581
+ // quote, with or without the zero flag — whose argument list names a
582
+ // phase-carrying variable that is NOT an `_INT` (the `$((10#$PHASE_INT))`
583
+ // pad is the sanctioned shape). `%d` cannot parse a letter id under any
584
+ // width, so the flag is not the discriminator. Captures the first such name
585
+ // so the report says what was padded.
586
+ const SHELL_PHASE_PRINTF_PAD_RE = /printf\s+(?:"%0?\d*d[^"]*"|'%0?\d*d[^']*')\s+(.*)$/;
587
+ const SHELL_VAR_NAME_RE = /\$\{?([A-Za-z_][A-Za-z0-9_]*)/g;
588
+
589
+ /**
590
+ * Pure: find every unsanctioned `printf "%02d"` re-pad of a phase-carrying,
591
+ * non-`_INT` shell variable in `text`. Skips full-line `#` comments.
592
+ * Returns [{ line, found }].
593
+ */
594
+ function findShellPhasePrintfPadDrift(text) {
595
+ const out = [];
596
+ const lines = text.split('\n');
597
+ for (let i = 0; i < lines.length; i++) {
598
+ const line = lines[i];
599
+ if (/^\s*#/.test(line)) continue;
600
+ const m = SHELL_PHASE_PRINTF_PAD_RE.exec(line);
601
+ if (!m) continue;
602
+ const offender = [...m[1].matchAll(SHELL_VAR_NAME_RE)]
603
+ .map((v) => v[1])
604
+ .find((name) => /phase/i.test(name) && !/_int$/i.test(name));
605
+ if (!offender) continue;
606
+ if (isSanctionedByPrecedingComment(lines, i, MD_OWNER_RE)) continue;
607
+ out.push({ line: i + 1, found: `printf "%0…d" …$${offender}` });
608
+ }
609
+ return out;
610
+ }
611
+
612
+ /**
613
+ * Scan the shell roots (`gsd-core/workflows/**\/*.md`, `gsd-core/references/**\/*.md`)
614
+ * for the two shell-idiom rules (a, c) and the three regex roots (those plus
615
+ * `agents/**\/*.md`) for the extraction-shape rule (b). Returns
616
+ * [{ file, kind, line, found }] with repo-relative paths.
617
+ */
618
+ function scanMarkdownLetterAxisConsumers(root) {
619
+ const violations = [];
620
+ const collect = (dirs, finder, kind) => {
621
+ for (const dir of dirs) {
622
+ for (const file of walkMd(path.join(root, dir), [])) {
623
+ const rel = path.relative(root, file);
624
+ let text;
625
+ try {
626
+ text = fs.readFileSync(file, 'utf8');
627
+ } catch {
628
+ continue;
629
+ }
630
+ for (const d of finder(text)) {
631
+ violations.push({ file: rel, kind, ...d });
632
+ }
633
+ }
634
+ }
635
+ };
636
+ collect(MD_SCAN_DIRS, findDotOnlyIntegerSplitDrift, 'dot-only-int-split');
637
+ collect(SINGLE_SEGMENT_SCAN_DIRS, findLooseDottedPhaseRegexDrift, 'loose-dotted-phase-regex');
638
+ collect(MD_SCAN_DIRS, findShellPhasePrintfPadDrift, 'shell-phase-printf-pad');
639
+ return violations;
640
+ }
641
+
133
642
  // Authored TypeScript source only (the generated bin/lib/*.cjs mirror it).
134
643
  const SCAN_DIRS = ['src'];
135
644
  const SCAN_EXT = new Set(['.cts', '.ts', '.mts']);
136
645
  // The canonical owner defines the grammar; it is exempt by construction.
137
646
  const EXEMPT = new Set([path.join('src', 'phase-id.cts')]);
138
647
 
139
- function walk(dir, acc) {
648
+ // #4634: the name-validity-guard rule owns a DIFFERENT file (roadmap-parser.cts
649
+ // defines `hasNameableContent`), so it needs its own exemption set — the
650
+ // token/bracket rules above must NOT start exempting roadmap-parser.cts too,
651
+ // since it is not their owner.
652
+ const NAME_VALIDITY_EXEMPT = new Set([path.join('src', 'roadmap-parser.cts')]);
653
+
654
+ function walk(dir, acc, ext) {
655
+ const extSet = ext || SCAN_EXT;
140
656
  let entries;
141
657
  try {
142
658
  entries = fs.readdirSync(dir, { withFileTypes: true });
@@ -147,14 +663,22 @@ function walk(dir, acc) {
147
663
  const full = path.join(dir, entry.name);
148
664
  if (entry.isDirectory()) {
149
665
  if (entry.name === 'node_modules' || entry.name === 'dist' || entry.name === '.git') continue;
150
- walk(full, acc);
151
- } else if (entry.isFile() && SCAN_EXT.has(path.extname(entry.name))) {
666
+ walk(full, acc, extSet);
667
+ } else if (entry.isFile() && extSet.has(path.extname(entry.name))) {
152
668
  acc.push(full);
153
669
  }
154
670
  }
155
671
  return acc;
156
672
  }
157
673
 
674
+ // #4634: markdown scan for the shell phase-arithmetic ban walks a disjoint set
675
+ // of roots/extensions from the src/**/*.cts scan above, so it gets its own thin
676
+ // wrapper over the same `walk` rather than a parallel tree-walker.
677
+ const MD_EXT = new Set(['.md']);
678
+ function walkMd(dir, acc) {
679
+ return walk(dir, acc, MD_EXT);
680
+ }
681
+
158
682
  // ─── #2761 M4: the heading-baseline selector census ────────────────────────
159
683
  //
160
684
  // `phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.<MODE>)` is the other half
@@ -239,23 +763,86 @@ function scanRepo(root) {
239
763
  for (const d of findBracketGrammarDrift(text)) {
240
764
  violations.push({ file: rel, kind: 'bracket', ...d });
241
765
  }
766
+ // #4634: name-validity-guard drift, exempting only its own owner file.
767
+ if (!NAME_VALIDITY_EXEMPT.has(rel)) {
768
+ for (const d of findNameValidityDrift(text)) {
769
+ violations.push({ file: rel, kind: 'name-validity', ...d });
770
+ }
771
+ }
772
+ // #4634: branch-slug fallback anti-pattern, exempt nowhere.
773
+ for (const d of findBranchSlugFallbackDrift(text)) {
774
+ violations.push({ file: rel, kind: 'branch-slug-fallback', ...d });
775
+ }
776
+ // #4906 Phase 5 (#4984): heading-scan literal drift, owner file exempt
777
+ // via the shared `EXEMPT` check above. Pre-existing sites this phase
778
+ // could not reach (src/phase.cts, src/roadmap-parser.cts,
779
+ // src/planning-snapshot.cts) are grandfathered with a `// phase-id-owner:`
780
+ // sanction each, per ADR-4910 §8's census/grandfather/delete Phase 6 plan
781
+ // — the allowlist is those sanction comments, not a separate list here,
782
+ // and it is deleted entry-by-entry as each site migrates.
783
+ for (const d of findPhaseHeadingScanLiteralDrift(text)) {
784
+ violations.push({ file: rel, kind: 'phase-heading-scan-literal', ...d });
785
+ }
242
786
  }
243
787
  }
244
788
  return violations;
245
789
  }
246
790
 
791
+ /**
792
+ * Scan EVERYTHING this seam guards: the `src/**\/*.cts` token/bracket/
793
+ * name-validity invariants (`scanRepo`) plus the markdown shell
794
+ * phase-arithmetic ban (`scanMarkdownShellArith`). This is what the CLI runs;
795
+ * `scanRepo` alone stays narrowly scoped to its original src/** contract so
796
+ * a live, separately-tracked markdown defect (#4619) cannot make the
797
+ * pinned-clean `scanRepo` test spuriously fail.
798
+ */
799
+ function scanAll(root) {
800
+ return [
801
+ ...scanRepo(root),
802
+ ...scanMarkdownShellArith(root),
803
+ ...scanMarkdownSingleSegmentPhaseRegex(root),
804
+ ...scanMarkdownLetterlessPhaseMirror(root),
805
+ ...scanMarkdownLetterAxisConsumers(root),
806
+ ];
807
+ }
808
+
247
809
  function main() {
248
810
  const root = path.join(__dirname, '..');
249
- const violations = scanRepo(root);
811
+ const violations = scanAll(root);
250
812
  if (violations.length === 0) {
251
- process.stdout.write('ok phase-id-drift: no unsanctioned phase-token or bracket-grammar re-derivations outside phase-id.cts\n');
813
+ process.stdout.write(
814
+ 'ok phase-id-drift: no unsanctioned phase-token, bracket-grammar, name-validity, ' +
815
+ 'branch-slug-fallback, or shell phase-arithmetic re-derivations found\n',
816
+ );
252
817
  return;
253
818
  }
254
819
  process.stderr.write('phase-id-drift: literal re-derivation(s) of a canonical grammar found.\n');
255
820
  process.stderr.write(`Build the regex from phase-id.cjs \`${CANON_REF}\` (or phaseMarkdownRegexSource for a\n`);
256
821
  process.stderr.write(`known number) for the phase-number token, or from ${BRACKET_OWNER_HINT}\n`);
257
- process.stderr.write('for the bracket grammar — or sanction the site with a dedicated\n');
258
- process.stderr.write('`// phase-id-owner: <reason>` comment on the line directly above the regex:\n');
822
+ process.stderr.write('for the bracket grammar, or call `hasNameableContent(` (src/roadmap-parser.cts) for\n');
823
+ process.stderr.write('the name-validity predicate — or sanction the site with a dedicated\n');
824
+ process.stderr.write('`// phase-id-owner: <reason>` comment on the line directly above the regex.\n');
825
+ process.stderr.write('`$((10#...))` base-10-forced shell arithmetic is banned outright in\n');
826
+ process.stderr.write('gsd-core/workflows/**/*.md and gsd-core/references/**/*.md — sanction with\n');
827
+ process.stderr.write('`<!-- phase-id-owner: <reason> -->` on the line directly above.\n');
828
+ process.stderr.write('The single-optional-dotted-segment phase regex `[0-9]+(\\.[0-9]+)?` (or its \\d\n');
829
+ process.stderr.write('near-variant) is banned outright in gsd-core/workflows/**/*.md,\n');
830
+ process.stderr.write('gsd-core/references/**/*.md, and agents/**/*.md — widen it to `*` (unbounded\n');
831
+ process.stderr.write('segments) or sanction with `<!-- phase-id-owner: <reason> -->`.\n');
832
+ process.stderr.write('A digit-only unbounded-segment phase regex `[0-9]+(\\.[0-9]+)*` on the same roots\n');
833
+ process.stderr.write('is missing the canonical letter axis (#4660) — widen to `[0-9]+[A-Z]?(\\.[0-9]+)*`\n');
834
+ process.stderr.write('or sanction with `<!-- phase-id-owner: <reason> -->`.\n');
835
+ process.stderr.write('Three letter-hostile consumers of a phase id are banned on the same roots (#4748):\n');
836
+ process.stderr.write('a dot-only integer split `X_INT=${PHASE%%.*}` (split at the first non-digit,\n');
837
+ process.stderr.write('`${PHASE%%[!0-9]*}`); a `[0-9]+\\.?[0-9]*` extraction (use `[0-9]+[A-Z]?(\\.[0-9]+)*`);\n');
838
+ process.stderr.write('and a `printf "%02d"` re-pad of a phase variable (bind init\'s `padded_phase`, or pad\n');
839
+ process.stderr.write('only an `_INT` via `$((10#…))`) — or sanction with `<!-- phase-id-owner: <reason> -->`.\n');
840
+ process.stderr.write('A `.replace(\'{slug}\', ... || \'phase\')` fallback is banned outright (#4126) —\n');
841
+ process.stderr.write('use `renderPhaseBranchName(` or sanction with\n');
842
+ process.stderr.write('`// phase-id-owner: <reason>` on the line directly above.\n');
843
+ process.stderr.write('A hand-rolled `#{2,4}\\s*Phase\\s+` heading-scan literal is banned (#4906 Phase 5) —\n');
844
+ process.stderr.write('use `buildPhaseHeadingScanRegex(` / `phaseHeadingPrefixSrcFor(` or sanction with\n');
845
+ process.stderr.write('`// phase-id-owner: <reason>` on the line directly above:\n');
259
846
  for (const d of violations) {
260
847
  process.stderr.write(` [${d.kind}] ${d.file}:${d.line} ${d.found}\n`);
261
848
  }
@@ -267,9 +854,32 @@ if (require.main === module) main();
267
854
  module.exports = {
268
855
  findPhaseIdRegexDrift,
269
856
  findBracketGrammarDrift,
857
+ findNameValidityDrift,
858
+ findBranchSlugFallbackDrift,
859
+ findPhaseHeadingScanLiteralDrift,
860
+ findShellPhaseArithDrift,
861
+ findSingleSegmentPhaseRegexDrift,
862
+ findLetterlessPhaseMirrorDrift,
863
+ findDotOnlyIntegerSplitDrift,
864
+ findLooseDottedPhaseRegexDrift,
865
+ findShellPhasePrintfPadDrift,
866
+ scanMarkdownShellArith,
867
+ scanMarkdownSingleSegmentPhaseRegex,
868
+ scanMarkdownLetterlessPhaseMirror,
869
+ scanMarkdownLetterAxisConsumers,
270
870
  scanRepo,
871
+ scanAll,
271
872
  countSelectorBaselines,
272
873
  scanSelectorBaselines,
273
874
  TOKEN_DRIFT_RE,
274
875
  BRACKET_CODE_DRIFT_RE,
876
+ NAME_VALIDITY_DRIFT_RE,
877
+ BRANCH_SLUG_FALLBACK_DRIFT_RE,
878
+ PHASE_HEADING_SCAN_LITERAL_DRIFT_RE,
879
+ SHELL_PHASE_ARITH_DRIFT_RE,
880
+ SINGLE_SEGMENT_PHASE_DRIFT_RE,
881
+ LETTERLESS_PHASE_MIRROR_DRIFT_RE,
882
+ DOT_ONLY_INT_SPLIT_DRIFT_RE,
883
+ LOOSE_DOTTED_PHASE_DRIFT_RE,
884
+ SHELL_PHASE_PRINTF_PAD_RE,
275
885
  };