@opengsd/gsd-core 1.12.0 → 1.14.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 (455) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.opencode/plugins/gsd-core.js +12 -0
  4. package/agents/gsd-advisor-researcher.compact.md +85 -0
  5. package/agents/gsd-ai-researcher.compact.md +96 -0
  6. package/agents/gsd-assumptions-analyzer.compact.md +81 -0
  7. package/agents/gsd-code-fixer.compact.md +458 -0
  8. package/agents/gsd-code-fixer.md +5 -5
  9. package/agents/gsd-code-reviewer.compact.md +269 -0
  10. package/agents/gsd-code-reviewer.md +15 -3
  11. package/agents/gsd-codebase-mapper.compact.md +760 -0
  12. package/agents/gsd-debug-session-manager.compact.md +345 -0
  13. package/agents/gsd-doc-classifier.compact.md +192 -0
  14. package/agents/gsd-doc-synthesizer.compact.md +200 -0
  15. package/agents/gsd-doc-verifier.compact.md +143 -0
  16. package/agents/gsd-doc-writer.compact.md +440 -0
  17. package/agents/gsd-dom-verifier.compact.md +138 -0
  18. package/agents/gsd-domain-researcher.compact.md +141 -0
  19. package/agents/gsd-eval-auditor.compact.md +160 -0
  20. package/agents/gsd-eval-planner.compact.md +137 -0
  21. package/agents/gsd-executor.md +63 -35
  22. package/agents/gsd-framework-selector.compact.md +82 -0
  23. package/agents/gsd-integration-checker.compact.md +245 -0
  24. package/agents/gsd-intel-updater.compact.md +226 -0
  25. package/agents/gsd-mempalace-curator.compact.md +45 -0
  26. package/agents/gsd-nyquist-auditor.compact.md +179 -0
  27. package/agents/gsd-pattern-mapper.compact.md +275 -0
  28. package/agents/gsd-plan-checker.md +76 -57
  29. package/agents/gsd-planner.md +14 -0
  30. package/agents/gsd-project-researcher.compact.md +587 -0
  31. package/agents/gsd-research-synthesizer.compact.md +212 -0
  32. package/agents/gsd-roadmapper.compact.md +454 -0
  33. package/agents/gsd-roadmapper.md +13 -0
  34. package/agents/gsd-security-auditor.compact.md +162 -0
  35. package/agents/gsd-ui-auditor.compact.md +404 -0
  36. package/agents/gsd-ui-checker.compact.md +277 -0
  37. package/agents/gsd-ui-checker.md +19 -3
  38. package/agents/gsd-ui-researcher.compact.md +282 -0
  39. package/agents/gsd-ui-researcher.md +29 -0
  40. package/agents/gsd-user-profiler.compact.md +108 -0
  41. package/agents/gsd-verifier.md +23 -1
  42. package/bin/install.js +444 -134
  43. package/commands/gsd/cleanup.md +1 -0
  44. package/commands/gsd/code-review.md +2 -1
  45. package/commands/gsd/complete-milestone.md +1 -0
  46. package/commands/gsd/config.md +1 -0
  47. package/commands/gsd/debug.md +1 -0
  48. package/commands/gsd/execute-phase.md +1 -1
  49. package/commands/gsd/graphify.md +1 -0
  50. package/commands/gsd/health.md +1 -0
  51. package/commands/gsd/mempalace-capture.md +1 -0
  52. package/commands/gsd/mempalace-recall.md +1 -0
  53. package/commands/gsd/new-milestone.md +1 -0
  54. package/commands/gsd/new-project.md +1 -0
  55. package/commands/gsd/next.md +1 -0
  56. package/commands/gsd/ns-workflow.md +2 -1
  57. package/commands/gsd/pause-work.md +1 -0
  58. package/commands/gsd/phase.md +2 -1
  59. package/commands/gsd/pr-branch.md +1 -0
  60. package/commands/gsd/quick-batch.md +105 -0
  61. package/commands/gsd/resume-work.md +1 -0
  62. package/commands/gsd/review-backlog.md +1 -0
  63. package/commands/gsd/settings.md +2 -1
  64. package/commands/gsd/stats.md +1 -0
  65. package/commands/gsd/surface.md +18 -8
  66. package/commands/gsd/thread.md +1 -0
  67. package/commands/gsd/workspace.md +1 -0
  68. package/commands/gsd/workstreams.md +1 -0
  69. package/gsd-core/bin/check-latest-version.cjs +8 -3
  70. package/gsd-core/bin/gsd-tools.cjs +532 -174
  71. package/gsd-core/bin/lib/adr-parser.cjs +1 -1
  72. package/gsd-core/bin/lib/artifacts.cjs +2 -1
  73. package/gsd-core/bin/lib/audit.cjs +39 -22
  74. package/gsd-core/bin/lib/broken-windows.cjs +168 -49
  75. package/gsd-core/bin/lib/capability-activation.cjs +27 -0
  76. package/gsd-core/bin/lib/capability-lifecycle.cjs +10 -6
  77. package/gsd-core/bin/lib/capability-loader.cjs +135 -1
  78. package/gsd-core/bin/lib/capability-registry.cjs +528 -116
  79. package/gsd-core/bin/lib/capability-source.cjs +19 -2
  80. package/gsd-core/bin/lib/capability-state.cjs +7 -1
  81. package/gsd-core/bin/lib/capability-validator.cjs +134 -5
  82. package/gsd-core/bin/lib/capability-writer.cjs +14 -4
  83. package/gsd-core/bin/lib/check-command-router.cjs +198 -38
  84. package/gsd-core/bin/lib/claude-orchestration.cjs +10 -25
  85. package/gsd-core/bin/lib/clusters.cjs +1 -0
  86. package/gsd-core/bin/lib/code-review-depth.cjs +2 -2
  87. package/gsd-core/bin/lib/command-aliases.cjs +16 -0
  88. package/gsd-core/bin/lib/commands.cjs +981 -79
  89. package/gsd-core/bin/lib/config-loader.cjs +4 -0
  90. package/gsd-core/bin/lib/config.cjs +153 -38
  91. package/gsd-core/bin/lib/core-utils.cjs +34 -7
  92. package/gsd-core/bin/lib/coverage.cjs +1 -1
  93. package/gsd-core/bin/lib/decisions.cjs +343 -28
  94. package/gsd-core/bin/lib/edge-probe.cjs +14 -1
  95. package/gsd-core/bin/lib/external-descriptor-trust.cjs +29 -14
  96. package/gsd-core/bin/lib/file-overlap-partitioner.cjs +74 -0
  97. package/gsd-core/bin/lib/frontmatter.cjs +137 -23
  98. package/gsd-core/bin/lib/gap-checker.cjs +22 -13
  99. package/gsd-core/bin/lib/git-base-branch.cjs +10 -2
  100. package/gsd-core/bin/lib/gsd2-import.cjs +1 -2
  101. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +8 -2
  102. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +54 -11
  103. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +87 -23
  104. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +1 -1
  105. package/gsd-core/bin/lib/host-integration.cjs +57 -5
  106. package/gsd-core/bin/lib/init-command-router.cjs +14 -0
  107. package/gsd-core/bin/lib/init.cjs +539 -60
  108. package/gsd-core/bin/lib/install-engine.cjs +199 -14
  109. package/gsd-core/bin/lib/install-model-override-resolver.cjs +45 -0
  110. package/gsd-core/bin/lib/install-profiles.cjs +36 -14
  111. package/gsd-core/bin/lib/installer-migration-report.cjs +1 -0
  112. package/gsd-core/bin/lib/installer-migrations.cjs +33 -4
  113. package/gsd-core/bin/lib/io.cjs +35 -0
  114. package/gsd-core/bin/lib/loop-resolver.cjs +64 -39
  115. package/gsd-core/bin/lib/markdown-table.cjs +123 -0
  116. package/gsd-core/bin/lib/mcp-catalog.cjs +2 -2
  117. package/gsd-core/bin/lib/milestone.cjs +41 -10
  118. package/gsd-core/bin/lib/model-resolver.cjs +101 -10
  119. package/gsd-core/bin/lib/phase-command-router.cjs +20 -7
  120. package/gsd-core/bin/lib/phase-id.cjs +412 -31
  121. package/gsd-core/bin/lib/phase-lifecycle.cjs +61 -0
  122. package/gsd-core/bin/lib/phase.cjs +941 -98
  123. package/gsd-core/bin/lib/plan-document.cjs +10 -0
  124. package/gsd-core/bin/lib/planning-inspect.cjs +34 -18
  125. package/gsd-core/bin/lib/planning-snapshot.cjs +206 -30
  126. package/gsd-core/bin/lib/planning-workspace.cjs +153 -29
  127. package/gsd-core/bin/lib/pristine-baseline.cjs +182 -0
  128. package/gsd-core/bin/lib/prohibition-enforcement.cjs +91 -4
  129. package/gsd-core/bin/lib/quick-batch-command-router.cjs +285 -0
  130. package/gsd-core/bin/lib/quick-batch-dispatch.cjs +250 -0
  131. package/gsd-core/bin/lib/quick-batch.cjs +840 -0
  132. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +61 -2
  133. package/gsd-core/bin/lib/research-store.cjs +11 -12
  134. package/gsd-core/bin/lib/review-lane-descriptor.cjs +53 -5
  135. package/gsd-core/bin/lib/review-lane-invocation.cjs +96 -1
  136. package/gsd-core/bin/lib/review-lane-runner.cjs +136 -10
  137. package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +337 -0
  138. package/gsd-core/bin/lib/roadmap-parser.cjs +555 -41
  139. package/gsd-core/bin/lib/roadmap.cjs +292 -69
  140. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +260 -43
  141. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +28 -20
  142. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +294 -108
  143. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +408 -47
  144. package/gsd-core/bin/lib/security.cjs +126 -7
  145. package/gsd-core/bin/lib/shell-command-projection.cjs +4 -0
  146. package/gsd-core/bin/lib/smart-entry.cjs +7 -9
  147. package/gsd-core/bin/lib/state-document.cjs +159 -32
  148. package/gsd-core/bin/lib/state-md-schema.cjs +44 -27
  149. package/gsd-core/bin/lib/state-transition.cjs +465 -62
  150. package/gsd-core/bin/lib/state.cjs +906 -151
  151. package/gsd-core/bin/lib/surface.cjs +83 -10
  152. package/gsd-core/bin/lib/task-command-router.cjs +12 -6
  153. package/gsd-core/bin/lib/tdd-red-evidence.cjs +133 -0
  154. package/gsd-core/bin/lib/uat.cjs +1420 -516
  155. package/gsd-core/bin/lib/update-context.cjs +36 -26
  156. package/gsd-core/bin/lib/validate.cjs +230 -12
  157. package/gsd-core/bin/lib/vendor/js-yaml.cjs +11 -3
  158. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  159. package/gsd-core/bin/lib/verification.cjs +316 -23
  160. package/gsd-core/bin/lib/verify-command-grounding.cjs +1 -1
  161. package/gsd-core/bin/lib/verify-command-router.cjs +1 -0
  162. package/gsd-core/bin/lib/verify.cjs +531 -36
  163. package/gsd-core/bin/lib/workstream-inventory.cjs +21 -2
  164. package/gsd-core/bin/lib/worktree-safety.cjs +21 -7
  165. package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
  166. package/gsd-core/bin/shared/config-schema.manifest.json +13 -0
  167. package/gsd-core/bin/verify-reapply-patches.cjs +507 -81
  168. package/gsd-core/references/agent-contracts.md +3 -3
  169. package/gsd-core/references/compact-content-gate.md +66 -0
  170. package/gsd-core/references/edge-probe.md +17 -13
  171. package/gsd-core/references/execute-mvp-tdd.md +18 -16
  172. package/gsd-core/references/execute-phase-response-language.md +6 -0
  173. package/gsd-core/references/executor-examples.md +42 -0
  174. package/gsd-core/references/few-shot-examples/plan-checker.md +15 -15
  175. package/gsd-core/references/loop-hook-dispatch.md +18 -0
  176. package/gsd-core/references/model-profiles.md +12 -3
  177. package/gsd-core/references/mvp-concepts.md +2 -2
  178. package/gsd-core/references/plan-checker-examples.md +41 -0
  179. package/gsd-core/references/planner-antipatterns.md +25 -0
  180. package/gsd-core/references/planner-chunked.md +5 -1
  181. package/gsd-core/references/planner-coupling.md +42 -0
  182. package/gsd-core/references/planner-quick-batch.md +71 -0
  183. package/gsd-core/references/planner-reviews.md +47 -0
  184. package/gsd-core/references/planner-revision.md +75 -2
  185. package/gsd-core/references/planning-config.md +5 -1
  186. package/gsd-core/references/response-language-directive.md +9 -0
  187. package/gsd-core/references/revision-loop.md +118 -11
  188. package/gsd-core/references/tdd.md +17 -9
  189. package/gsd-core/references/thinking-models-planning.md +18 -2
  190. package/gsd-core/references/verification-patterns.md +17 -4
  191. package/gsd-core/references/verifier-evidence-gate.md +160 -0
  192. package/gsd-core/references/worktree-path-safety.md +112 -2
  193. package/gsd-core/templates/README.md +7 -1
  194. package/gsd-core/templates/phase-prompt.md +4 -0
  195. package/gsd-core/templates/state.md +6 -3
  196. package/gsd-core/templates/summary.compact.md +212 -0
  197. package/gsd-core/templates/user-setup.compact.md +199 -0
  198. package/gsd-core/templates/user-setup.md +0 -9
  199. package/gsd-core/templates/verification-report.md +5 -0
  200. package/gsd-core/workflows/add-backlog.md +2 -0
  201. package/gsd-core/workflows/add-phase.md +2 -0
  202. package/gsd-core/workflows/add-tests.md +1 -1
  203. package/gsd-core/workflows/add-todo.md +4 -3
  204. package/gsd-core/workflows/ai-integration-phase.md +1 -1
  205. package/gsd-core/workflows/analyze-dependencies.md +2 -0
  206. package/gsd-core/workflows/audit-fix.md +2 -0
  207. package/gsd-core/workflows/audit-milestone.md +2 -0
  208. package/gsd-core/workflows/audit-uat.md +2 -0
  209. package/gsd-core/workflows/autonomous.md +15 -10
  210. package/gsd-core/workflows/check-todos.md +5 -3
  211. package/gsd-core/workflows/cleanup.md +4 -2
  212. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +22 -13
  213. package/gsd-core/workflows/code-review-fix.md +5 -3
  214. package/gsd-core/workflows/code-review.md +211 -43
  215. package/gsd-core/workflows/complete-milestone/detail/elaboration.md +274 -0
  216. package/gsd-core/workflows/complete-milestone.md +40 -254
  217. package/gsd-core/workflows/debug.md +1 -1
  218. package/gsd-core/workflows/diagnose-issues.md +5 -1
  219. package/gsd-core/workflows/discuss-phase/modes/advisor.md +2 -0
  220. package/gsd-core/workflows/discuss-phase/modes/all.md +2 -0
  221. package/gsd-core/workflows/discuss-phase/modes/analyze.md +2 -0
  222. package/gsd-core/workflows/discuss-phase/modes/auto.md +2 -0
  223. package/gsd-core/workflows/discuss-phase/modes/batch.md +2 -0
  224. package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -0
  225. package/gsd-core/workflows/discuss-phase/modes/default.md +2 -0
  226. package/gsd-core/workflows/discuss-phase/modes/power.md +2 -0
  227. package/gsd-core/workflows/discuss-phase/modes/text.md +2 -0
  228. package/gsd-core/workflows/discuss-phase/templates/context.md +2 -0
  229. package/gsd-core/workflows/discuss-phase/templates/discussion-log.md +2 -0
  230. package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
  231. package/gsd-core/workflows/discuss-phase-power.md +2 -0
  232. package/gsd-core/workflows/discuss-phase.md +1 -1
  233. package/gsd-core/workflows/do.md +43 -13
  234. package/gsd-core/workflows/docs-update/detail/elaboration.md +179 -0
  235. package/gsd-core/workflows/docs-update.md +15 -156
  236. package/gsd-core/workflows/edit-phase.md +2 -0
  237. package/gsd-core/workflows/eval-review.md +1 -1
  238. package/gsd-core/workflows/execute-phase/detail/elaboration.md +124 -0
  239. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +20 -3
  240. package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +56 -0
  241. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +24 -3
  242. package/gsd-core/workflows/execute-phase/steps/executor-progress-policy.md +43 -0
  243. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +8 -2
  244. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -0
  245. package/gsd-core/workflows/execute-phase/steps/sequential-root-pin.md +35 -0
  246. package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +25 -0
  247. package/gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md +2 -0
  248. package/gsd-core/workflows/execute-phase.md +78 -159
  249. package/gsd-core/workflows/execute-plan.md +28 -15
  250. package/gsd-core/workflows/explore.md +2 -0
  251. package/gsd-core/workflows/extract-learnings.md +2 -0
  252. package/gsd-core/workflows/fast.md +6 -0
  253. package/gsd-core/workflows/forensics.md +2 -0
  254. package/gsd-core/workflows/graduation.md +1 -1
  255. package/gsd-core/workflows/health.md +1 -1
  256. package/gsd-core/workflows/help/modes/brief.md +2 -0
  257. package/gsd-core/workflows/help/modes/default.md +2 -0
  258. package/gsd-core/workflows/help/modes/full.compact.md +398 -0
  259. package/gsd-core/workflows/help/modes/full.md +12 -0
  260. package/gsd-core/workflows/help/modes/topic.md +2 -0
  261. package/gsd-core/workflows/help.md +3 -1
  262. package/gsd-core/workflows/import.md +3 -3
  263. package/gsd-core/workflows/inbox.md +1 -1
  264. package/gsd-core/workflows/ingest-docs.md +1 -1
  265. package/gsd-core/workflows/insert-phase.md +2 -0
  266. package/gsd-core/workflows/list-phase-assumptions.md +2 -0
  267. package/gsd-core/workflows/list-seeds.md +2 -0
  268. package/gsd-core/workflows/list-workspaces.md +2 -0
  269. package/gsd-core/workflows/manager.md +3 -3
  270. package/gsd-core/workflows/map-codebase.md +52 -3
  271. package/gsd-core/workflows/milestone-summary.md +2 -0
  272. package/gsd-core/workflows/mvp-phase.md +1 -1
  273. package/gsd-core/workflows/new-milestone.md +55 -13
  274. package/gsd-core/workflows/new-project/detail/elaboration.md +216 -0
  275. package/gsd-core/workflows/new-project.md +37 -205
  276. package/gsd-core/workflows/new-workspace.md +1 -1
  277. package/gsd-core/workflows/next.md +2 -0
  278. package/gsd-core/workflows/node-repair.md +2 -0
  279. package/gsd-core/workflows/note.md +2 -0
  280. package/gsd-core/workflows/onboard.md +1 -1
  281. package/gsd-core/workflows/pause-work.md +19 -4
  282. package/gsd-core/workflows/plan-phase/detail/elaboration.md +209 -0
  283. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +100 -18
  284. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -0
  285. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +9 -0
  286. package/gsd-core/workflows/plan-phase.md +144 -185
  287. package/gsd-core/workflows/plan-review-convergence.md +102 -10
  288. package/gsd-core/workflows/plant-seed.md +1 -1
  289. package/gsd-core/workflows/pr-branch.md +30 -10
  290. package/gsd-core/workflows/profile-user.md +1 -1
  291. package/gsd-core/workflows/progress/steps/forensic-audit.md +1 -1
  292. package/gsd-core/workflows/progress.md +25 -3
  293. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +37 -2
  294. package/gsd-core/workflows/quick/steps/research-phase.md +3 -3
  295. package/gsd-core/workflows/quick-batch/steps/batch-init.md +55 -0
  296. package/gsd-core/workflows/quick-batch/steps/completion.md +65 -0
  297. package/gsd-core/workflows/quick-batch/steps/merge-wave.md +100 -0
  298. package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +147 -0
  299. package/gsd-core/workflows/quick-batch/steps/planner-wave.md +158 -0
  300. package/gsd-core/workflows/quick-batch/steps/research-phase.md +95 -0
  301. package/gsd-core/workflows/quick-batch/steps/resume-mode.md +49 -0
  302. package/gsd-core/workflows/quick-batch/steps/verification-wave.md +73 -0
  303. package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +169 -0
  304. package/gsd-core/workflows/quick-batch.md +203 -0
  305. package/gsd-core/workflows/quick.md +21 -4
  306. package/gsd-core/workflows/reapply-patches.md +79 -3
  307. package/gsd-core/workflows/remove-phase.md +2 -0
  308. package/gsd-core/workflows/remove-workspace.md +1 -1
  309. package/gsd-core/workflows/resume-project.md +6 -2
  310. package/gsd-core/workflows/review.md +215 -10
  311. package/gsd-core/workflows/scan.md +2 -0
  312. package/gsd-core/workflows/section-manifest.json +12 -0
  313. package/gsd-core/workflows/secure-phase.md +1 -1
  314. package/gsd-core/workflows/session-report.md +2 -0
  315. package/gsd-core/workflows/settings-advanced.md +2 -0
  316. package/gsd-core/workflows/settings-integrations.md +9 -8
  317. package/gsd-core/workflows/settings.md +19 -6
  318. package/gsd-core/workflows/ship.md +10 -10
  319. package/gsd-core/workflows/sketch-wrap-up.md +2 -0
  320. package/gsd-core/workflows/sketch.md +1 -1
  321. package/gsd-core/workflows/smart-entry.md +1 -1
  322. package/gsd-core/workflows/spec-phase.md +24 -19
  323. package/gsd-core/workflows/spike-wrap-up.md +2 -0
  324. package/gsd-core/workflows/spike.md +1 -1
  325. package/gsd-core/workflows/stats.md +2 -0
  326. package/gsd-core/workflows/sync-skills.md +12 -4
  327. package/gsd-core/workflows/thread.md +2 -0
  328. package/gsd-core/workflows/transition.md +2 -0
  329. package/gsd-core/workflows/ui-phase.md +26 -5
  330. package/gsd-core/workflows/ui-review.md +1 -1
  331. package/gsd-core/workflows/ultraplan-phase.md +2 -0
  332. package/gsd-core/workflows/undo.md +1 -1
  333. package/gsd-core/workflows/update.md +48 -43
  334. package/gsd-core/workflows/validate-phase.md +1 -1
  335. package/gsd-core/workflows/verify-work/detail/elaboration.md +230 -0
  336. package/gsd-core/workflows/verify-work.md +68 -182
  337. package/hooks/dist/gsd-agent-isolation-guard.js +42 -16
  338. package/hooks/dist/gsd-check-update-worker.js +19 -2
  339. package/hooks/dist/gsd-context-monitor.js +371 -27
  340. package/hooks/dist/gsd-cursor-subagent-start.js +34 -14
  341. package/hooks/dist/gsd-node-runner.sh +1 -0
  342. package/hooks/dist/gsd-prompt-guard.js +30 -5
  343. package/hooks/dist/gsd-read-guard.js +2 -0
  344. package/hooks/dist/gsd-read-injection-scanner.js +5 -5
  345. package/hooks/dist/gsd-secret-read-guard.js +1105 -0
  346. package/hooks/dist/gsd-statusline.js +18 -10
  347. package/hooks/dist/gsd-validate-commit.sh +474 -7
  348. package/hooks/dist/gsd-workflow-guard.js +2 -1
  349. package/hooks/dist/gsd-worktree-path-guard.js +25 -14
  350. package/hooks/dist/gsd-write-guard.js +46 -1
  351. package/hooks/dist/lib/dispatch-identity.js +187 -0
  352. package/hooks/dist/lib/filename-classification.js +64 -0
  353. package/hooks/dist/lib/git-cmd.js +210 -1
  354. package/hooks/dist/lib/injection-patterns.js +36 -6
  355. package/hooks/dist/lib/isolation-deny-reason.js +53 -1
  356. package/hooks/dist/lib/isolation-sentinel.js +58 -19
  357. package/hooks/dist/managed-hooks-registry.cjs +1 -0
  358. package/hooks/gsd-agent-isolation-guard.js +42 -16
  359. package/hooks/gsd-check-update-worker.js +19 -2
  360. package/hooks/gsd-context-monitor.js +371 -27
  361. package/hooks/gsd-cursor-subagent-start.js +34 -14
  362. package/hooks/gsd-node-runner.sh +1 -0
  363. package/hooks/gsd-prompt-guard.js +30 -5
  364. package/hooks/gsd-read-guard.js +2 -0
  365. package/hooks/gsd-read-injection-scanner.js +5 -5
  366. package/hooks/gsd-secret-read-guard.js +1105 -0
  367. package/hooks/gsd-statusline.js +18 -10
  368. package/hooks/gsd-validate-commit.sh +474 -7
  369. package/hooks/gsd-workflow-guard.js +2 -1
  370. package/hooks/gsd-worktree-path-guard.js +25 -14
  371. package/hooks/gsd-write-guard.js +46 -1
  372. package/hooks/hooks.json +6 -0
  373. package/hooks/lib/dispatch-identity.js +187 -0
  374. package/hooks/lib/filename-classification.js +64 -0
  375. package/hooks/lib/git-cmd.js +210 -1
  376. package/hooks/lib/injection-patterns.js +36 -6
  377. package/hooks/lib/isolation-deny-reason.js +53 -1
  378. package/hooks/lib/isolation-sentinel.js +58 -19
  379. package/hooks/managed-hooks-registry.cjs +1 -0
  380. package/package.json +13 -9
  381. package/scripts/benchmark-compact-content-variants.cjs +298 -0
  382. package/scripts/benchmark-compact-content.cjs +368 -0
  383. package/scripts/build-hooks.js +11 -4
  384. package/scripts/check-contract-drift.cjs +4 -1
  385. package/scripts/check-env.cjs +36 -8
  386. package/scripts/check-glossary-refs.cjs +25 -21
  387. package/scripts/ci-next-health.cjs +271 -0
  388. package/scripts/ci-prepare-test-scope.cjs +7 -7
  389. package/scripts/ci-test-scope.cjs +133 -20
  390. package/scripts/ci-timeout-report.cjs +1 -1
  391. package/scripts/diff-touches-shipped-paths.cjs +1 -1
  392. package/scripts/docs-guard-registry.cjs +17 -2
  393. package/scripts/gen-adr-index.cjs +8 -2
  394. package/scripts/gen-inventory-manifest.cjs +12 -0
  395. package/scripts/gen-loop-host-contract.cjs +67 -15
  396. package/scripts/gen-platform-conformance-tier.cjs +557 -0
  397. package/scripts/lib/drift-scan.cjs +1 -1
  398. package/scripts/lib/macos-conformance-tier.generated.cjs +210 -0
  399. package/scripts/lib/npm-version-check-diagnosis.cjs +59 -0
  400. package/scripts/lib/platform-conformance-tier.generated.cjs +276 -0
  401. package/scripts/lib/shellcheck-fetch.cjs +247 -0
  402. package/scripts/lib/suite-detection.cjs +32 -0
  403. package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -6
  404. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
  405. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
  406. package/scripts/lint-allowed-tools-parity.cjs +221 -0
  407. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +24 -2
  408. package/scripts/lint-phase-enumeration-drift.cjs +24 -6
  409. package/scripts/lint-phase-id-drift.cjs +465 -15
  410. package/scripts/lint-portable-grep.cjs +176 -0
  411. package/scripts/lint-response-language-coverage.cjs +530 -0
  412. package/scripts/lint-source-test-name-collision.cjs +1 -1
  413. package/scripts/lint-test-file-count.allowlist.json +4 -1
  414. package/scripts/lint-vendored-deps.cjs +128 -17
  415. package/scripts/lint-workflow-shellcheck-baseline.json +1112 -0
  416. package/scripts/lint-workflow-shellcheck.cjs +614 -0
  417. package/scripts/npm-audit-baseline.cjs +376 -0
  418. package/scripts/prompt-injection-scan.sh +22 -0
  419. package/scripts/require-issue-link-policy.cjs +16 -1
  420. package/scripts/workflow-size.cjs +139 -0
  421. package/skills/gsd-cleanup/SKILL.md +1 -0
  422. package/skills/gsd-code-review/SKILL.md +2 -1
  423. package/skills/gsd-complete-milestone/SKILL.md +1 -0
  424. package/skills/gsd-config/SKILL.md +1 -0
  425. package/skills/gsd-debug/SKILL.md +1 -0
  426. package/skills/gsd-execute-phase/SKILL.md +1 -1
  427. package/skills/gsd-graphify/SKILL.md +1 -0
  428. package/skills/gsd-health/SKILL.md +1 -0
  429. package/skills/gsd-mempalace-capture/SKILL.md +1 -0
  430. package/skills/gsd-mempalace-recall/SKILL.md +1 -0
  431. package/skills/gsd-new-milestone/SKILL.md +1 -0
  432. package/skills/gsd-new-project/SKILL.md +1 -0
  433. package/skills/gsd-next/SKILL.md +1 -0
  434. package/skills/gsd-ns-workflow/SKILL.md +1 -0
  435. package/skills/gsd-pause-work/SKILL.md +1 -0
  436. package/skills/gsd-phase/SKILL.md +2 -1
  437. package/skills/gsd-pr-branch/SKILL.md +1 -0
  438. package/skills/gsd-quick-batch/SKILL.md +105 -0
  439. package/skills/gsd-resume-work/SKILL.md +1 -0
  440. package/skills/gsd-review-backlog/SKILL.md +1 -0
  441. package/skills/gsd-settings/SKILL.md +2 -1
  442. package/skills/gsd-stats/SKILL.md +1 -0
  443. package/skills/gsd-surface/SKILL.md +18 -8
  444. package/skills/gsd-thread/SKILL.md +1 -0
  445. package/skills/gsd-workspace/SKILL.md +1 -0
  446. package/skills/gsd-workstreams/SKILL.md +1 -0
  447. package/vscode/package.json +1 -1
  448. package/gsd-core/templates/claude-md.md +0 -145
  449. package/gsd-core/templates/codebase/concerns.md +0 -310
  450. package/gsd-core/templates/codebase/conventions.md +0 -307
  451. package/gsd-core/templates/codebase/integrations.md +0 -280
  452. package/gsd-core/templates/codebase/structure.md +0 -285
  453. package/gsd-core/templates/codebase/testing.md +0 -480
  454. package/gsd-core/templates/debug-subagent-prompt.md +0 -91
  455. package/gsd-core/templates/discovery.md +0 -146
@@ -84,9 +84,13 @@
84
84
  * shape as `collectDiskPhases` and the `audit.cts` scanners; it must see
85
85
  * every phase directory regardless of milestone window to catch a
86
86
  * naming/duplicate defect wherever it lives.
87
- * - `src/verify.cts` `cmdVerifySchemaDrift`: resolves ONE caller-supplied
87
+ * - `src/verify.cts` `resolvePhaseDirByToken`: resolves ONE caller-supplied
88
88
  * `phase` argument to its directory (falling back to an exact-name
89
89
  * match) — a single-phase LOOKUP, not a current-milestone enumeration.
90
+ * Originally `cmdVerifySchemaDrift`'s own inline block; #3348 lifted it
91
+ * into this shared helper (also used by the new `cmdVerifyContextDrift`)
92
+ * without changing what question it asks, so the exemption moved with
93
+ * the call site rather than multiplying.
90
94
  * - `src/init.cts` `detectHasPriorPhases`: answers "has this project EVER
91
95
  * completed a phase", explicitly excluding the current one. A history
92
96
  * probe across all milestones, not a current-milestone enumeration.
@@ -128,8 +132,10 @@
128
132
  * `--include-archived` merge are phase LOCATION and archive
129
133
  * enumeration, not current-milestone enumeration; both legitimately
130
134
  * read the physical set. Its ENUMERATION path routes through the owner.
131
- * - `src/roadmap-parser.cts` `getMilestonePhaseFilter` and its #3262-extracted
132
- * set-building owner `scanMilestonePhaseIds` (the same two heading/
135
+ * - `src/roadmap-parser.cts` `getMilestonePhaseFilter` and its shared
136
+ * set-building implementation `scanMilestonePhaseIdSets` (the public
137
+ * `scanMilestonePhaseIds` wrapper remains a directly iterable Set; the
138
+ * same two heading/
133
139
  * #3577 `collectTablePhaseRows` — the table-scan sibling feeding the same
134
140
  * membership set; its local 999-only exclusion mirrors the owner's
135
141
  * deliberate NOT-isSentinelPhaseId choice (a leading 0 is a real decimal
@@ -142,6 +148,18 @@
142
148
  * ("00.1" is a real phase, not milestone 0). This scan asks a narrower
143
149
  * question — "which phase ids does this milestone's window declare" —
144
150
  * where only the 999 icebox range is excluded.
151
+ * - `src/state.cts` `countRoadmapPhaseHeadings`: its bracket branch composes
152
+ * bracket-milestone sentinel detection through `isSentinelPhaseId`, but
153
+ * retains a separate bare-token `999` rule. That rule intentionally does
154
+ * NOT use the convention-blind canonical predicate: the latter also reads
155
+ * leading `0`/`00.1` as milestone-0 sentinels, while a legacy-spelled bare
156
+ * zero token inside an opted-in bracket project is a real mid-migration
157
+ * phase. This is the state-counter twin of the roadmap-parser exemption.
158
+ * The guard has no per-detector exemption scope: it ORs the enumeration
159
+ * and sentinel detectors before consulting this function-only map. The
160
+ * whole-function exemption is therefore accepted; it is bounded because
161
+ * this counter performs no phases-directory `readdirSync` and needs the
162
+ * exemption only for its intentional bare-token `999` sentinel literal.
145
163
  * - `src/state.cts` `cmdStateValidate` ("Gate 1: Validate STATE.md against
146
164
  * filesystem"): resolves ONE directory — the disk match for STATE.md's
147
165
  * own `Current Phase` field — by prefix, a single-phase LOOKUP, not an
@@ -291,7 +309,7 @@ const OWNER_FILES = new Set([
291
309
  // `lint-milestone-window-drift.cjs`'s FUNCTION_SCOPED_EXEMPTIONS mechanism.
292
310
  // See the header comment for the full written reason behind each entry.
293
311
  const FUNCTION_SCOPED_EXEMPTIONS = new Map([
294
- [path.join('src', 'verify.cts'), new Set(['cmdValidateHealth', 'cmdVerifySchemaDrift'])],
312
+ [path.join('src', 'verify.cts'), new Set(['cmdValidateHealth', 'resolvePhaseDirByToken'])],
295
313
  [path.join('src', 'init.cts'), new Set(['detectHasPriorPhases', 'detectUiPhaseActive'])],
296
314
  [path.join('src', 'milestone.cts'), new Set(['archivePhaseDirectories', 'cmdMilestoneComplete', 'cmdPhasesClear'])],
297
315
  // #3849: collectSiblingWorktreePhaseNums reads a SIBLING worktree's phases dir —
@@ -300,10 +318,10 @@ const FUNCTION_SCOPED_EXEMPTIONS = new Map([
300
318
  [path.join('src', 'phase.cts'), new Set(['cmdPhasesList', 'cmdPhaseNextDecimal', 'cmdPhasePlanIndex', 'cmdPhaseInsert', 'renameDecimalPhases', 'renameIntegerPhases', 'collectSiblingWorktreePhaseNums'])],
301
319
  [path.join('src', 'audit.cts'), new Set(['listAuditPhaseTargets'])],
302
320
  [path.join('src', 'commands.cts'), new Set(['cmdHistoryDigest'])],
303
- [path.join('src', 'state.cts'), new Set(['cmdStateValidate', 'cmdStateSync', 'cmdStateRebuild'])],
321
+ [path.join('src', 'state.cts'), new Set(['cmdStateValidate', 'cmdStateSync', 'cmdStateRebuild', 'countRoadmapPhaseHeadings'])],
304
322
  [path.join('src', 'roadmap-upgrade.cts'), new Set(['computeMigrationPlan'])],
305
323
  [path.join('src', 'smart-entry.cts'), new Set(['detectVerifyFailed'])],
306
- [path.join('src', 'roadmap-parser.cts'), new Set(['getMilestonePhaseFilter', 'scanMilestonePhaseIds', 'collectTablePhaseRows'])],
324
+ [path.join('src', 'roadmap-parser.cts'), new Set(['getMilestonePhaseFilter', 'scanMilestonePhaseIdSets', 'collectTablePhaseRows'])],
307
325
  [path.join('src', 'planning-snapshot.cts'), new Set(['buildAllPhaseDirNamesField'])],
308
326
  ]);
309
327
 
@@ -20,6 +20,34 @@
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
+ * - branch-slug fallback drift: a `.replace('{slug}', ... || 'phase')` call
41
+ * silently substitutes the literal string `'phase'` when a phase's slug
42
+ * can't be derived, producing a non-identifying branch name like
43
+ * `gsd/phase-08-phase` (#4126, now fixed via the shared renderPhaseBranchName
44
+ * owner in phase-id.cts, consumed by both prior call sites). Sanctioned
45
+ * the same way as the token/bracket/name-validity rules (`// phase-id-owner:`
46
+ * on the nearest preceding non-blank line), with a line-level escape for a
47
+ * line that already calls `renderPhaseBranchName(`. Unlike the other
48
+ * `.cts`-scanning rules, this one has no per-file exemption — it is a banned
49
+ * anti-pattern everywhere, not a grammar with one legitimate owner site.
50
+ *
23
51
  * Detection is intentionally NARROW: only the contiguous canonical token
24
52
  * (`\d+[A-Z]?(?:\.\d+)*`, its `[A-Za-z]` and `[.-]` near-variants, in both
25
53
  * regex-literal `\d` and `new RegExp` template `\\d` escaping) is drift. Bare
@@ -53,6 +81,47 @@ const TOKEN_DRIFT_RE = /(?:\\{1,2}d|\[0-9\])\+\[A-Z(?:a-z)?\]\??\(\?:(?:\\{1,2}\
53
81
  const OWNER_RE = /^\s*\/\/.*phase-id-owner:/;
54
82
  const CANON_REF = 'PHASE_NUMBER_TOKEN_SOURCE';
55
83
 
84
+ // #2761 M3 (trek-e review): the SECOND grammar this seam owns — the BRACKET
85
+ // project-code class of `[CODE.MM]`, spelled `[A-Z][A-Z0-9_]*` (with its
86
+ // case-widened `[A-Za-z]`/`[A-Za-z0-9_]` variant tolerated so a trivial rewrite
87
+ // does not evade the rule). The token guard above only ever knew the phase-
88
+ // NUMBER grammar, so three files re-typed this class verbatim — roadmap-parser's
89
+ // bracket-fallback selector, state's `isMilestoneBounded`, verify's
90
+ // `checkBracketCoherence` — and `check:phase-id-drift` reported clean the whole
91
+ // time. That is the blind spot which let #2761's own "no token literal outside
92
+ // src/phase-id.cts" gate pass while being violated. Build from
93
+ // `BRACKET_PROJECT_CODE_SRC`, `BRACKET_ID_SRC`, `bracketMilestoneIntroSrcFor`
94
+ // or `BRACKET_MILESTONE_INTRO_CAPTURING_SRC` instead.
95
+ const BRACKET_CODE_DRIFT_RE = /\[A-Z(?:a-z)?\]\[A-Z(?:a-z)?0-9_\]\*/;
96
+
97
+ // This rule has NO counterpart to the token rule's `line.includes(CANON_REF)`
98
+ // escape, and that omission is the point.
99
+ //
100
+ // That escape is LINE-level: a line naming the canonical source anywhere on it
101
+ // is taken as built-from-the-owner. verify.cts's copy read
102
+ //
103
+ // new RegExp(`^\\[[A-Z][A-Z0-9_]*\\.(${BRACKET_MILESTONE_NUMERIC_SRC})\\]`, 'i')
104
+ //
105
+ // — an owner reference for the MILESTONE field sharing a line with a re-typed
106
+ // PROJECT-CODE class. A line-level escape waves that through, so a bracket rule
107
+ // that copied it would have kept reporting clean on the very site under review.
108
+ // Partial ownership IS the drift. Only a `// phase-id-owner:` sanction
109
+ // suppresses this rule, and a sanction has to state which half is deliberate.
110
+ const BRACKET_OWNER_HINT =
111
+ 'BRACKET_PROJECT_CODE_SRC / BRACKET_ID_SRC / bracketMilestoneIntroSrcFor / BRACKET_MILESTONE_INTRO_CAPTURING_SRC';
112
+
113
+ /**
114
+ * Pure: true if the nearest preceding non-blank line to `lines[i]` is a
115
+ * dedicated sanction comment matching `ownerRe`. Shared by every detector in
116
+ * this file so the "how do you sanction a finding" walk has one owner instead
117
+ * of four independent copies that could silently diverge.
118
+ */
119
+ function isSanctionedByPrecedingComment(lines, i, ownerRe) {
120
+ let j = i - 1;
121
+ while (j >= 0 && lines[j].trim() === '') j--; // nearest preceding non-blank line
122
+ return j >= 0 && ownerRe.test(lines[j]);
123
+ }
124
+
56
125
  /**
57
126
  * Pure: find every literal re-derivation of the canonical phase-number token in
58
127
  * `text` that is NOT sanctioned. A site is sanctioned when the nearest preceding
@@ -71,21 +140,269 @@ function findPhaseIdRegexDrift(text) {
71
140
  const m = TOKEN_DRIFT_RE.exec(line);
72
141
  if (!m) continue;
73
142
  if (line.includes(CANON_REF)) continue;
74
- let j = i - 1;
75
- while (j >= 0 && lines[j].trim() === '') j--; // nearest preceding non-blank line
76
- if (j >= 0 && OWNER_RE.test(lines[j])) continue;
143
+ if (isSanctionedByPrecedingComment(lines, i, OWNER_RE)) continue;
144
+ out.push({ line: i + 1, found: m[0] });
145
+ }
146
+ return out;
147
+ }
148
+
149
+ /**
150
+ * Pure: find every literal re-derivation of the BRACKET project-code grammar in
151
+ * `text` that is NOT sanctioned. Same sanction mechanism as the token rule — a
152
+ * dedicated `// phase-id-owner:` comment on the nearest preceding non-blank
153
+ * line — but deliberately WITHOUT its line-level owner-reference escape, so a
154
+ * site that references the owner for one field while re-typing the other is
155
+ * still reported (see BRACKET_CODE_DRIFT_RE's note). Returns [{ line, found }].
156
+ */
157
+ function findBracketGrammarDrift(text) {
158
+ const out = [];
159
+ const lines = text.split('\n');
160
+ for (let i = 0; i < lines.length; i++) {
161
+ const m = BRACKET_CODE_DRIFT_RE.exec(lines[i]);
162
+ if (!m) continue;
163
+ if (isSanctionedByPrecedingComment(lines, i, OWNER_RE)) continue;
164
+ out.push({ line: i + 1, found: m[0] });
165
+ }
166
+ return out;
167
+ }
168
+
169
+ // #4634: the name-validity-guard grammar — `hasNameableContent(s)` in
170
+ // `src/roadmap-parser.cts` is `/[\p{L}\p{N}]/u.test(s)`. Tolerates both the
171
+ // regex-literal single-backslash form and the doubled-backslash template
172
+ // form (`new RegExp('[\\p{L}\\p{N}]'`), mirroring how TOKEN_DRIFT_RE tolerates
173
+ // both escapings.
174
+ const NAME_VALIDITY_DRIFT_RE = /\[\\{1,2}p\{L\}\\{1,2}p\{N\}\]/;
175
+ const NAME_VALIDITY_CANON_REF = 'hasNameableContent(';
176
+
177
+ /**
178
+ * Pure: find every literal re-derivation of the canonical name-validity
179
+ * character class in `text` that is NOT sanctioned. Same sanction mechanism
180
+ * as the token/bracket rules — a dedicated `// phase-id-owner:` comment on
181
+ * the nearest preceding non-blank line — plus a line-level escape for a line
182
+ * that already calls `hasNameableContent(`. Returns [{ line, found }].
183
+ */
184
+ function findNameValidityDrift(text) {
185
+ const out = [];
186
+ const lines = text.split('\n');
187
+ for (let i = 0; i < lines.length; i++) {
188
+ const line = lines[i];
189
+ const m = NAME_VALIDITY_DRIFT_RE.exec(line);
190
+ if (!m) continue;
191
+ if (line.includes(NAME_VALIDITY_CANON_REF)) continue;
192
+ if (isSanctionedByPrecedingComment(lines, i, OWNER_RE)) continue;
193
+ out.push({ line: i + 1, found: m[0] });
194
+ }
195
+ return out;
196
+ }
197
+
198
+ // #4634: the branch-slug fallback anti-pattern (#4126) — a
199
+ // `.replace('{slug}', ... || 'phase')` call silently falls back to the
200
+ // literal string `'phase'` when a phase's slug can't be derived, producing a
201
+ // non-identifying branch name like `gsd/phase-08-phase`. Now fixed at both
202
+ // prior call sites (commands.cts, init.cts) via the shared
203
+ // `renderPhaseBranchName` owner in phase-id.cts; this rule is the ratchet
204
+ // against a THIRD site reintroducing the inline fallback. Deliberately
205
+ // narrow: it requires the literal `'phase'` fallback on the same line as the
206
+ // `{slug}` template token, so it does NOT match the sibling milestone-branch
207
+ // fallback (`|| 'milestone'`), which is a different, correct-as-is case.
208
+ const BRANCH_SLUG_FALLBACK_DRIFT_RE = /\{slug\}'.*\|\|\s*'phase'/;
209
+
210
+ // The canonical fix is `renderPhaseBranchName(...)`. There is no "owner file"
211
+ // for this rule the way there is for the token/bracket/name-validity
212
+ // grammars above — it is a banned anti-pattern everywhere, so no per-file
213
+ // exemption exists.
214
+ const BRANCH_SLUG_FALLBACK_CANON_REF = 'renderPhaseBranchName(';
215
+
216
+ /**
217
+ * Pure: find every unsanctioned branch-slug `|| 'phase'` fallback in `text`.
218
+ * Same sanction mechanism as the token/bracket/name-validity rules — a
219
+ * dedicated `// phase-id-owner:` comment on the nearest preceding non-blank
220
+ * line — plus a line-level escape for a line that already calls
221
+ * `renderPhaseBranchName(`. Returns [{ line, found }].
222
+ */
223
+ function findBranchSlugFallbackDrift(text) {
224
+ const out = [];
225
+ const lines = text.split('\n');
226
+ for (let i = 0; i < lines.length; i++) {
227
+ const line = lines[i];
228
+ const m = BRANCH_SLUG_FALLBACK_DRIFT_RE.exec(line);
229
+ if (!m) continue;
230
+ if (line.includes(BRANCH_SLUG_FALLBACK_CANON_REF)) continue;
231
+ if (isSanctionedByPrecedingComment(lines, i, OWNER_RE)) continue;
232
+ out.push({ line: i + 1, found: m[0] });
233
+ }
234
+ return out;
235
+ }
236
+
237
+ // #4634: ban base-10-forced shell arithmetic (`$((10#...))`) on a variable
238
+ // that still carries a possibly-decimal/multi-segment phase id — this
239
+ // construct is exactly the pattern that breaks on a value like `08.5`. The
240
+ // capture group grabs the token immediately inside the parens (after an
241
+ // optional `$` and/or `{`, stripping a trailing `}`) so callers can inspect
242
+ // *which* variable is being coerced, not merely that the substring occurred.
243
+ //
244
+ // Refined post-#4619: the original blunt "ban `$((10#` outright" version
245
+ // over-fired on three false-positive classes once #4619's fix landed:
246
+ // 1. Prose mentioning the literal pattern in a full-line `#`-comment
247
+ // (filtered by the caller, not this regex — see below).
248
+ // 2. `$((10#$PHASE_INT))` / `$((10#$SPOT_PHASE_INT))` — arithmetic on the
249
+ // NOW-safe variable the #4619 fix produces via `PHASE_INT=${PHASE_NUMBER%%.*}`;
250
+ // a `%%.*`-stripped value can never contain a dot, so base-10 arithmetic
251
+ // on it can never hit the #4619 syntax-error class. Any name ending in
252
+ // `_INT` (case-insensitive) is that established "already reduced to a
253
+ // safe integer" convention.
254
+ // 3. `$((10#{plan_padded}))` / `$((10#${PLAN_ID}))` — plan ids are plain
255
+ // integers and were never in scope; this rule only polices variables
256
+ // that carry a *phase* id.
257
+ // So a match is only a violation when the captured name contains `phase`
258
+ // case-insensitively (it is phase-carrying) AND does not end in `_int`
259
+ // case-insensitively (it has not already been reduced to a safe integer).
260
+ const SHELL_PHASE_ARITH_DRIFT_RE = /\$\(\(\s*10#\$?\{?([A-Za-z0-9_]+)\}?/;
261
+
262
+ // A markdown comment can't easily carry a `//` line, so the sanction for the
263
+ // shell-arithmetic rule is an HTML comment on the nearest preceding non-blank
264
+ // line: `<!-- phase-id-owner: <reason> -->`.
265
+ const MD_OWNER_RE = /^\s*<!--.*phase-id-owner:/;
266
+
267
+ /**
268
+ * Pure: find every unsanctioned `$((10#...))` base-10-forced shell arithmetic
269
+ * site in `text` that still coerces an un-reduced phase-carrying variable.
270
+ * Skips full-line `#` comments outright (pure prose mentioning the pattern,
271
+ * not executable code), and skips any captured variable name that either
272
+ * doesn't contain `phase` (never in scope — e.g. plan ids) or already ends
273
+ * in `_int` (the #4619-fix convention for "safely stripped to an integer").
274
+ * Sanctioned by an HTML comment `<!-- phase-id-owner: ... -->` on the
275
+ * nearest preceding non-blank line. Returns [{ line, found }].
276
+ */
277
+ function findShellPhaseArithDrift(text) {
278
+ const out = [];
279
+ const lines = text.split('\n');
280
+ for (let i = 0; i < lines.length; i++) {
281
+ const line = lines[i];
282
+ if (/^\s*#/.test(line)) continue;
283
+ const m = SHELL_PHASE_ARITH_DRIFT_RE.exec(line);
284
+ if (!m) continue;
285
+ const name = m[1];
286
+ if (!/phase/i.test(name)) continue;
287
+ if (/_int$/i.test(name)) continue;
288
+ if (isSanctionedByPrecedingComment(lines, i, MD_OWNER_RE)) continue;
77
289
  out.push({ line: i + 1, found: m[0] });
78
290
  }
79
291
  return out;
80
292
  }
81
293
 
294
+ // #4634: the markdown scan roots — shell embedded in workflow/reference docs.
295
+ const MD_SCAN_DIRS = [path.join('gsd-core', 'workflows'), path.join('gsd-core', 'references')];
296
+
297
+ // #4568 (epic #4634): the single-segment phase regex ban scans a THIRD root,
298
+ // `agents/**/*.md`, that the #4619 shell-arithmetic extension above never
299
+ // touched — the gsd-code-fixer agent prompts re-derive the phase-number
300
+ // grammar too. Reuses the same `walkMd` walker as the shell-arith scan.
301
+ const SINGLE_SEGMENT_SCAN_DIRS = [...MD_SCAN_DIRS, 'agents'];
302
+
303
+ /**
304
+ * Scan `gsd-core/workflows/**\/*.md` and `gsd-core/references/**\/*.md` for
305
+ * unsanctioned `$((10#...))` shell arithmetic. Returns [{ file, line, found }]
306
+ * with repo-relative paths.
307
+ */
308
+ function scanMarkdownShellArith(root) {
309
+ const violations = [];
310
+ for (const dir of MD_SCAN_DIRS) {
311
+ for (const file of walkMd(path.join(root, dir), [])) {
312
+ const rel = path.relative(root, file);
313
+ let text;
314
+ try {
315
+ text = fs.readFileSync(file, 'utf8');
316
+ } catch {
317
+ continue;
318
+ }
319
+ for (const d of findShellPhaseArithDrift(text)) {
320
+ violations.push({ file: rel, kind: 'shell-arith', ...d });
321
+ }
322
+ }
323
+ }
324
+ return violations;
325
+ }
326
+
327
+ // #4568 (epic #4634): ban the single-optional-dotted-segment phase regex
328
+ // shape `[0-9]+(\.[0-9]+)?` (and its `\d`/doubled-backslash near-variants)
329
+ // outright — this is exactly the grammar that hard-rejects or silently
330
+ // truncates a 3-or-more-segment phase id like `23.1.2`. The canonical
331
+ // grammar (`src/phase-id.cts`) uses the unbounded `(?:\.\d+)*` form; shell
332
+ // snippets embedded in markdown can't import that module, so textual parity
333
+ // (`*` in place of `?`) is the fix, and this rule is the ratchet against a
334
+ // future site re-deriving the bounded form. Deliberately narrow to the
335
+ // bounded ONE-optional-segment shape — the fixed `*`-form is not flagged.
336
+ const SINGLE_SEGMENT_PHASE_DRIFT_RE =
337
+ /(?:\\{1,2}d|\[0-9\])\+\(\\{1,2}\.(?:\\{1,2}d|\[0-9\])\+\)\?/;
338
+
339
+ // A single-segment shape like `[0-9]+(\.[0-9]+)?` is not inherently
340
+ // phase-specific (e.g. it could describe a version number), so the rule
341
+ // only fires on a line whose text plausibly carries a phase-number
342
+ // variable — a case-insensitive `phase` substring anywhere on the line,
343
+ // mirroring the phase-carrying filter `findShellPhaseArithDrift` already
344
+ // applies to its own variable-name capture.
345
+ const PHASE_CARRYING_LINE_RE = /phase/i;
346
+
347
+ /**
348
+ * Pure: find every unsanctioned single-optional-dotted-segment phase regex
349
+ * in `text`, restricted to lines that plausibly carry a phase-number
350
+ * variable. Sanctioned by an HTML comment `<!-- phase-id-owner: ... -->` on
351
+ * the nearest preceding non-blank line (same convention as the shell-arith
352
+ * rule). Returns [{ line, found }].
353
+ */
354
+ function findSingleSegmentPhaseRegexDrift(text) {
355
+ const out = [];
356
+ const lines = text.split('\n');
357
+ for (let i = 0; i < lines.length; i++) {
358
+ const line = lines[i];
359
+ const m = SINGLE_SEGMENT_PHASE_DRIFT_RE.exec(line);
360
+ if (!m) continue;
361
+ if (!PHASE_CARRYING_LINE_RE.test(line)) continue;
362
+ if (isSanctionedByPrecedingComment(lines, i, MD_OWNER_RE)) continue;
363
+ out.push({ line: i + 1, found: m[0] });
364
+ }
365
+ return out;
366
+ }
367
+
368
+ /**
369
+ * Scan `gsd-core/workflows/**\/*.md`, `gsd-core/references/**\/*.md`, and
370
+ * `agents/**\/*.md` for unsanctioned single-optional-dotted-segment phase
371
+ * regexes. Returns [{ file, line, found }] with repo-relative paths.
372
+ */
373
+ function scanMarkdownSingleSegmentPhaseRegex(root) {
374
+ const violations = [];
375
+ for (const dir of SINGLE_SEGMENT_SCAN_DIRS) {
376
+ for (const file of walkMd(path.join(root, dir), [])) {
377
+ const rel = path.relative(root, file);
378
+ let text;
379
+ try {
380
+ text = fs.readFileSync(file, 'utf8');
381
+ } catch {
382
+ continue;
383
+ }
384
+ for (const d of findSingleSegmentPhaseRegexDrift(text)) {
385
+ violations.push({ file: rel, kind: 'single-segment-phase-regex', ...d });
386
+ }
387
+ }
388
+ }
389
+ return violations;
390
+ }
391
+
82
392
  // Authored TypeScript source only (the generated bin/lib/*.cjs mirror it).
83
393
  const SCAN_DIRS = ['src'];
84
394
  const SCAN_EXT = new Set(['.cts', '.ts', '.mts']);
85
395
  // The canonical owner defines the grammar; it is exempt by construction.
86
396
  const EXEMPT = new Set([path.join('src', 'phase-id.cts')]);
87
397
 
88
- function walk(dir, acc) {
398
+ // #4634: the name-validity-guard rule owns a DIFFERENT file (roadmap-parser.cts
399
+ // defines `hasNameableContent`), so it needs its own exemption set — the
400
+ // token/bracket rules above must NOT start exempting roadmap-parser.cts too,
401
+ // since it is not their owner.
402
+ const NAME_VALIDITY_EXEMPT = new Set([path.join('src', 'roadmap-parser.cts')]);
403
+
404
+ function walk(dir, acc, ext) {
405
+ const extSet = ext || SCAN_EXT;
89
406
  let entries;
90
407
  try {
91
408
  entries = fs.readdirSync(dir, { withFileTypes: true });
@@ -96,14 +413,83 @@ function walk(dir, acc) {
96
413
  const full = path.join(dir, entry.name);
97
414
  if (entry.isDirectory()) {
98
415
  if (entry.name === 'node_modules' || entry.name === 'dist' || entry.name === '.git') continue;
99
- walk(full, acc);
100
- } else if (entry.isFile() && SCAN_EXT.has(path.extname(entry.name))) {
416
+ walk(full, acc, extSet);
417
+ } else if (entry.isFile() && extSet.has(path.extname(entry.name))) {
101
418
  acc.push(full);
102
419
  }
103
420
  }
104
421
  return acc;
105
422
  }
106
423
 
424
+ // #4634: markdown scan for the shell phase-arithmetic ban walks a disjoint set
425
+ // of roots/extensions from the src/**/*.cts scan above, so it gets its own thin
426
+ // wrapper over the same `walk` rather than a parallel tree-walker.
427
+ const MD_EXT = new Set(['.md']);
428
+ function walkMd(dir, acc) {
429
+ return walk(dir, acc, MD_EXT);
430
+ }
431
+
432
+ // ─── #2761 M4: the heading-baseline selector census ────────────────────────
433
+ //
434
+ // `phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.<MODE>)` is the other half
435
+ // of this seam: it decides which intro grammar a call site compiles, and the
436
+ // MODE argument is a fact about that site's history that no behavioural test
437
+ // can recover — flipping verify's milestone-complete site from LABEL_ONLY to
438
+ // ANY_BRACKET grants a tolerance it has never had, and every behavioural test
439
+ // still passes. Pinning it therefore requires reading the authored source.
440
+ //
441
+ // That reading lives HERE, not in the test suite. `tests/**` runs
442
+ // `local/no-source-grep` at ERROR, and its documented exemption
443
+ // (CONTEXT.md: RULESET.TESTS.no-source-grep.exemption) is reserved for tests
444
+ // whose subject is a runtime CONTRACT FILE — STATE.md, config.toml,
445
+ // hooks.json, agent .md — which `src/*.cts` is not. The suite had claimed that
446
+ // exemption anyway. Scripts are the sanctioned home for source scanning (the
447
+ // rule runs at `warn` in `scripts/**`, and this file already scans src/ for the
448
+ // grammar rules above), so the scan is exported as structured data and the test
449
+ // asserts on the returned census instead of on file text.
450
+ const SELECTOR_CALL_RE = /phaseHeadingPrefixSrcFor\(/g;
451
+ const SELECTOR_BASELINE_RE = /phaseHeadingPrefixSrcFor\(\s*PHASE_HEADING_BASELINE\.(ANY_BRACKET|LABEL_ONLY)/g;
452
+
453
+ /**
454
+ * Pure: census the heading-baseline selector calls in `text`.
455
+ *
456
+ * `total` counts EVERY invocation, so a call that does not name a
457
+ * `PHASE_HEADING_BASELINE` member shows up as `total > ANY_BRACKET +
458
+ * LABEL_ONLY` — a hole in the pin rather than a silently uncounted site.
459
+ * Returns { ANY_BRACKET, LABEL_ONLY, total }.
460
+ */
461
+ function countSelectorBaselines(text) {
462
+ const out = { ANY_BRACKET: 0, LABEL_ONLY: 0, total: 0 };
463
+ for (const m of text.matchAll(SELECTOR_BASELINE_RE)) out[m[1]] += 1;
464
+ out.total = (text.match(SELECTOR_CALL_RE) || []).length;
465
+ return out;
466
+ }
467
+
468
+ /**
469
+ * Scan the authored source tree and return the selector census keyed by
470
+ * repo-relative path, for every file that consumes the selector at least once.
471
+ * `phase-id.cts` is excluded: it DEFINES the selector, so its own occurrences
472
+ * are the declaration, not a consumer's choice of baseline.
473
+ */
474
+ function scanSelectorBaselines(root) {
475
+ const census = {};
476
+ for (const dir of SCAN_DIRS) {
477
+ for (const file of walk(path.join(root, dir), [])) {
478
+ const rel = path.relative(root, file);
479
+ if (EXEMPT.has(rel)) continue;
480
+ let text;
481
+ try {
482
+ text = fs.readFileSync(file, 'utf8');
483
+ } catch {
484
+ continue;
485
+ }
486
+ const counts = countSelectorBaselines(text);
487
+ if (counts.total > 0) census[path.basename(file)] = counts;
488
+ }
489
+ }
490
+ return census;
491
+ }
492
+
107
493
  /**
108
494
  * Scan the authored source tree and return every unsanctioned phase-token
109
495
  * re-derivation, each annotated with the repo-relative file path.
@@ -121,30 +507,94 @@ function scanRepo(root) {
121
507
  continue;
122
508
  }
123
509
  for (const d of findPhaseIdRegexDrift(text)) {
124
- violations.push({ file: rel, ...d });
510
+ violations.push({ file: rel, kind: 'token', ...d });
511
+ }
512
+ // #2761 M3: the bracket grammar is the second thing this seam owns.
513
+ for (const d of findBracketGrammarDrift(text)) {
514
+ violations.push({ file: rel, kind: 'bracket', ...d });
515
+ }
516
+ // #4634: name-validity-guard drift, exempting only its own owner file.
517
+ if (!NAME_VALIDITY_EXEMPT.has(rel)) {
518
+ for (const d of findNameValidityDrift(text)) {
519
+ violations.push({ file: rel, kind: 'name-validity', ...d });
520
+ }
521
+ }
522
+ // #4634: branch-slug fallback anti-pattern, exempt nowhere.
523
+ for (const d of findBranchSlugFallbackDrift(text)) {
524
+ violations.push({ file: rel, kind: 'branch-slug-fallback', ...d });
125
525
  }
126
526
  }
127
527
  }
128
528
  return violations;
129
529
  }
130
530
 
531
+ /**
532
+ * Scan EVERYTHING this seam guards: the `src/**\/*.cts` token/bracket/
533
+ * name-validity invariants (`scanRepo`) plus the markdown shell
534
+ * phase-arithmetic ban (`scanMarkdownShellArith`). This is what the CLI runs;
535
+ * `scanRepo` alone stays narrowly scoped to its original src/** contract so
536
+ * a live, separately-tracked markdown defect (#4619) cannot make the
537
+ * pinned-clean `scanRepo` test spuriously fail.
538
+ */
539
+ function scanAll(root) {
540
+ return [
541
+ ...scanRepo(root),
542
+ ...scanMarkdownShellArith(root),
543
+ ...scanMarkdownSingleSegmentPhaseRegex(root),
544
+ ];
545
+ }
546
+
131
547
  function main() {
132
548
  const root = path.join(__dirname, '..');
133
- const violations = scanRepo(root);
549
+ const violations = scanAll(root);
134
550
  if (violations.length === 0) {
135
- process.stdout.write('ok phase-id-drift: no unsanctioned phase-token re-derivations outside phase-id.cts\n');
551
+ process.stdout.write(
552
+ 'ok phase-id-drift: no unsanctioned phase-token, bracket-grammar, name-validity, ' +
553
+ 'branch-slug-fallback, or shell phase-arithmetic re-derivations found\n',
554
+ );
136
555
  return;
137
556
  }
138
- process.stderr.write('phase-id-drift: literal re-derivation(s) of the canonical phase-number token found.\n');
139
- process.stderr.write('Build the regex from phase-id.cjs `PHASE_NUMBER_TOKEN_SOURCE` (or phaseMarkdownRegexSource for a\n');
140
- process.stderr.write('known number), or sanction the site with a dedicated `// phase-id-owner: <reason>`\n');
141
- process.stderr.write('comment on the line directly above the regex:\n');
557
+ process.stderr.write('phase-id-drift: literal re-derivation(s) of a canonical grammar found.\n');
558
+ process.stderr.write(`Build the regex from phase-id.cjs \`${CANON_REF}\` (or phaseMarkdownRegexSource for a\n`);
559
+ process.stderr.write(`known number) for the phase-number token, or from ${BRACKET_OWNER_HINT}\n`);
560
+ process.stderr.write('for the bracket grammar, or call `hasNameableContent(` (src/roadmap-parser.cts) for\n');
561
+ process.stderr.write('the name-validity predicate — or sanction the site with a dedicated\n');
562
+ process.stderr.write('`// phase-id-owner: <reason>` comment on the line directly above the regex.\n');
563
+ process.stderr.write('`$((10#...))` base-10-forced shell arithmetic is banned outright in\n');
564
+ process.stderr.write('gsd-core/workflows/**/*.md and gsd-core/references/**/*.md — sanction with\n');
565
+ process.stderr.write('`<!-- phase-id-owner: <reason> -->` on the line directly above.\n');
566
+ process.stderr.write('The single-optional-dotted-segment phase regex `[0-9]+(\\.[0-9]+)?` (or its \\d\n');
567
+ process.stderr.write('near-variant) is banned outright in gsd-core/workflows/**/*.md,\n');
568
+ process.stderr.write('gsd-core/references/**/*.md, and agents/**/*.md — widen it to `*` (unbounded\n');
569
+ process.stderr.write('segments) or sanction with `<!-- phase-id-owner: <reason> -->`.\n');
570
+ process.stderr.write('A `.replace(\'{slug}\', ... || \'phase\')` fallback is banned outright (#4126) —\n');
571
+ process.stderr.write('use `renderPhaseBranchName(` or sanction with\n');
572
+ process.stderr.write('`// phase-id-owner: <reason>` on the line directly above:\n');
142
573
  for (const d of violations) {
143
- process.stderr.write(` ${d.file}:${d.line} ${d.found}\n`);
574
+ process.stderr.write(` [${d.kind}] ${d.file}:${d.line} ${d.found}\n`);
144
575
  }
145
576
  process.exitCode = 1;
146
577
  }
147
578
 
148
579
  if (require.main === module) main();
149
580
 
150
- module.exports = { findPhaseIdRegexDrift, scanRepo, TOKEN_DRIFT_RE };
581
+ module.exports = {
582
+ findPhaseIdRegexDrift,
583
+ findBracketGrammarDrift,
584
+ findNameValidityDrift,
585
+ findBranchSlugFallbackDrift,
586
+ findShellPhaseArithDrift,
587
+ findSingleSegmentPhaseRegexDrift,
588
+ scanMarkdownShellArith,
589
+ scanMarkdownSingleSegmentPhaseRegex,
590
+ scanRepo,
591
+ scanAll,
592
+ countSelectorBaselines,
593
+ scanSelectorBaselines,
594
+ TOKEN_DRIFT_RE,
595
+ BRACKET_CODE_DRIFT_RE,
596
+ NAME_VALIDITY_DRIFT_RE,
597
+ BRANCH_SLUG_FALLBACK_DRIFT_RE,
598
+ SHELL_PHASE_ARITH_DRIFT_RE,
599
+ SINGLE_SEGMENT_PHASE_DRIFT_RE,
600
+ };