@opengsd/gsd-core 1.9.1 → 1.11.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 (426) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +2 -3
  3. package/.opencode/plugins/gsd-core.js +8 -1
  4. package/agents/gsd-code-fixer.md +27 -3
  5. package/agents/gsd-debug-session-manager.md +11 -0
  6. package/agents/gsd-debugger.md +12 -246
  7. package/agents/gsd-doc-synthesizer.md +2 -4
  8. package/agents/gsd-executor.md +12 -10
  9. package/agents/gsd-integration-checker.md +3 -0
  10. package/agents/gsd-mempalace-curator.md +5 -2
  11. package/agents/gsd-phase-researcher.md +20 -1
  12. package/agents/gsd-plan-checker.md +46 -0
  13. package/agents/gsd-planner.md +49 -54
  14. package/agents/gsd-roadmapper.md +21 -3
  15. package/agents/gsd-user-profiler.md +3 -0
  16. package/agents/gsd-verifier.md +26 -73
  17. package/bin/install.js +1272 -1238
  18. package/bin/lib/ui-safety-gate.cjs +2 -0
  19. package/commands/gsd/code-review.md +1 -1
  20. package/commands/gsd/execute-phase.md +1 -1
  21. package/commands/gsd/map-codebase.md +1 -1
  22. package/commands/gsd/mempalace-capture.md +2 -2
  23. package/commands/gsd/mempalace-recall.md +1 -1
  24. package/commands/gsd/new-milestone.md +2 -2
  25. package/commands/gsd/plan-phase.md +1 -1
  26. package/commands/gsd/quick.md +1 -1
  27. package/commands/gsd/review-backlog.md +2 -1
  28. package/commands/gsd/verify-work.md +1 -1
  29. package/gsd-core/bin/gsd-tools.cjs +1009 -115
  30. package/gsd-core/bin/lib/active-workstream-store.cjs +153 -12
  31. package/gsd-core/bin/lib/agent-install-check.cjs +268 -38
  32. package/gsd-core/bin/lib/api-coverage.cjs +123 -5
  33. package/gsd-core/bin/lib/artifacts.cjs +3 -0
  34. package/gsd-core/bin/lib/assumption-delta.cjs +2 -4
  35. package/gsd-core/bin/lib/audit-command-router.cjs +9 -2
  36. package/gsd-core/bin/lib/audit.cjs +926 -202
  37. package/gsd-core/bin/lib/broken-windows.cjs +36 -6
  38. package/gsd-core/bin/lib/capability-consent.cjs +149 -15
  39. package/gsd-core/bin/lib/capability-lifecycle.cjs +45 -0
  40. package/gsd-core/bin/lib/capability-registry.cjs +608 -148
  41. package/gsd-core/bin/lib/capability-source.cjs +92 -0
  42. package/gsd-core/bin/lib/capability-trust.cjs +444 -25
  43. package/gsd-core/bin/lib/capability-validator.cjs +507 -24
  44. package/gsd-core/bin/lib/capability-writer.cjs +3 -2
  45. package/gsd-core/bin/lib/check-command-router.cjs +114 -38
  46. package/gsd-core/bin/lib/claude-orchestration.cjs +56 -3
  47. package/gsd-core/bin/lib/codex-agent-toml.cjs +329 -0
  48. package/gsd-core/bin/lib/command-aliases.cjs +94 -0
  49. package/gsd-core/bin/lib/command-roster.cjs +44 -1
  50. package/gsd-core/bin/lib/commands.cjs +665 -99
  51. package/gsd-core/bin/lib/commonjs-marker.cjs +142 -0
  52. package/gsd-core/bin/lib/complexity-trigger.cjs +1172 -0
  53. package/gsd-core/bin/lib/config-loader.cjs +76 -0
  54. package/gsd-core/bin/lib/config.cjs +22 -2
  55. package/gsd-core/bin/lib/context-composer.cjs +278 -0
  56. package/gsd-core/bin/lib/context-predicates.cjs +506 -0
  57. package/gsd-core/bin/lib/core-utils.cjs +217 -40
  58. package/gsd-core/bin/lib/decisions.cjs +23 -0
  59. package/gsd-core/bin/lib/docs.cjs +3 -2
  60. package/gsd-core/bin/lib/external-job.cjs +19 -4
  61. package/gsd-core/bin/lib/fallow-runner.cjs +20 -44
  62. package/gsd-core/bin/lib/frontmatter.cjs +239 -32
  63. package/gsd-core/bin/lib/gap-checker.cjs +68 -7
  64. package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +57 -6
  65. package/gsd-core/bin/lib/git-base-branch.cjs +160 -15
  66. package/gsd-core/bin/lib/graphify.cjs +142 -27
  67. package/gsd-core/bin/lib/gsd2-import.cjs +37 -5
  68. package/gsd-core/bin/lib/health-diagnostic-rules/agent-install.cjs +101 -0
  69. package/gsd-core/bin/lib/health-diagnostic-rules/config-validation.cjs +348 -0
  70. package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +145 -0
  71. package/gsd-core/bin/lib/health-diagnostic-rules/install-surface-shadowing.cjs +98 -0
  72. package/gsd-core/bin/lib/health-diagnostic-rules/milestone-archive-hygiene.cjs +100 -0
  73. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +222 -0
  74. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +265 -0
  75. package/gsd-core/bin/lib/health-diagnostic-rules/root-existence.cjs +161 -0
  76. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +303 -0
  77. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +173 -0
  78. package/gsd-core/bin/lib/health-diagnostic-types.cjs +68 -0
  79. package/gsd-core/bin/lib/health-diagnostic.cjs +431 -0
  80. package/gsd-core/bin/lib/host-integration.cjs +13 -1
  81. package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
  82. package/gsd-core/bin/lib/init-command-router.cjs +83 -8
  83. package/gsd-core/bin/lib/init.cjs +1325 -169
  84. package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
  85. package/gsd-core/bin/lib/install-engine.cjs +805 -264
  86. package/gsd-core/bin/lib/install-fs-adapter.cjs +262 -0
  87. package/gsd-core/bin/lib/install-model-override-resolver.cjs +203 -0
  88. package/gsd-core/bin/lib/install-profiles.cjs +160 -57
  89. package/gsd-core/bin/lib/install-scope.cjs +270 -0
  90. package/gsd-core/bin/lib/install-shadow-report.cjs +385 -0
  91. package/gsd-core/bin/lib/installed-surface-resolver.cjs +381 -0
  92. package/gsd-core/bin/lib/installer-migration-authoring.cjs +3 -1
  93. package/gsd-core/bin/lib/installer-migration-report.cjs +4 -0
  94. package/gsd-core/bin/lib/installer-migrations/007-retire-config-root-commonjs-marker.cjs +149 -0
  95. package/gsd-core/bin/lib/installer-migrations/008-cursor-retire-commands-surface.cjs +55 -0
  96. package/gsd-core/bin/lib/installer-migrations/009-pi-retire-reserved-hooks-dir.cjs +199 -0
  97. package/gsd-core/bin/lib/installer-migrations.cjs +206 -13
  98. package/gsd-core/bin/lib/io.cjs +38 -3
  99. package/gsd-core/bin/lib/markdown-sectionizer.cjs +8 -1
  100. package/gsd-core/bin/lib/markdown-table.cjs +133 -20
  101. package/gsd-core/bin/lib/mcp-catalog.cjs +518 -0
  102. package/gsd-core/bin/lib/mcp-server.cjs +135 -3
  103. package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
  104. package/gsd-core/bin/lib/milestone.cjs +821 -109
  105. package/gsd-core/bin/lib/model-catalog.cjs +59 -1
  106. package/gsd-core/bin/lib/model-resolver.cjs +183 -40
  107. package/gsd-core/bin/lib/normalize-test-command.cjs +1 -1
  108. package/gsd-core/bin/lib/pattern.cjs +122 -0
  109. package/gsd-core/bin/lib/phase-estimation.cjs +1 -1
  110. package/gsd-core/bin/lib/phase-id.cjs +507 -36
  111. package/gsd-core/bin/lib/phase-lifecycle.cjs +28 -3
  112. package/gsd-core/bin/lib/phase-locator.cjs +258 -58
  113. package/gsd-core/bin/lib/phase.cjs +891 -156
  114. package/gsd-core/bin/lib/plan-dependency-graph.cjs +303 -0
  115. package/gsd-core/bin/lib/plan-drift-guard.cjs +120 -0
  116. package/gsd-core/bin/lib/plan-scan.cjs +86 -2
  117. package/gsd-core/bin/lib/planning-scope.cjs +31 -0
  118. package/gsd-core/bin/lib/planning-snapshot.cjs +890 -0
  119. package/gsd-core/bin/lib/planning-workspace.cjs +60 -6
  120. package/gsd-core/bin/lib/probe-core.cjs +1 -1
  121. package/gsd-core/bin/lib/profile-output.cjs +1 -1
  122. package/gsd-core/bin/lib/prompt-budget.cjs +128 -165
  123. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +740 -0
  124. package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +85 -0
  125. package/gsd-core/bin/lib/review-lane-descriptor.cjs +108 -0
  126. package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
  127. package/gsd-core/bin/lib/review-lane-runner.cjs +447 -68
  128. package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
  129. package/gsd-core/bin/lib/roadmap-command-router.cjs +76 -9
  130. package/gsd-core/bin/lib/roadmap-parser.cjs +1035 -194
  131. package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
  132. package/gsd-core/bin/lib/roadmap.cjs +405 -84
  133. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +795 -100
  134. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
  135. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +440 -57
  136. package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
  137. package/gsd-core/bin/lib/runtime-homes.cjs +220 -41
  138. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +220 -44
  139. package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
  140. package/gsd-core/bin/lib/runtime-slash.cjs +27 -9
  141. package/gsd-core/bin/lib/section-manifest.cjs +209 -0
  142. package/gsd-core/bin/lib/security.cjs +104 -5
  143. package/gsd-core/bin/lib/shell-command-projection.cjs +388 -30
  144. package/gsd-core/bin/lib/smart-entry.cjs +154 -22
  145. package/gsd-core/bin/lib/state-command-router.cjs +5 -1
  146. package/gsd-core/bin/lib/state-document.cjs +152 -8
  147. package/gsd-core/bin/lib/state-transition.cjs +424 -105
  148. package/gsd-core/bin/lib/state.cjs +1927 -401
  149. package/gsd-core/bin/lib/surface.cjs +35 -10
  150. package/gsd-core/bin/lib/text-lines.cjs +80 -0
  151. package/gsd-core/bin/lib/token-scanner.cjs +76 -0
  152. package/gsd-core/bin/lib/uat-predicate.cjs +20 -4
  153. package/gsd-core/bin/lib/uat.cjs +706 -64
  154. package/gsd-core/bin/lib/ui-frontend-evidence.cjs +157 -0
  155. package/gsd-core/bin/lib/ui-safety-gate.cjs +14 -5
  156. package/gsd-core/bin/lib/unusable-input.cjs +33 -0
  157. package/gsd-core/bin/lib/update-context.cjs +8 -2
  158. package/gsd-core/bin/lib/user-artifact-staging.cjs +705 -0
  159. package/gsd-core/bin/lib/validate.cjs +20 -6
  160. package/gsd-core/bin/lib/vendor/README.md +37 -0
  161. package/gsd-core/bin/lib/vendor/re2js.cjs +6480 -0
  162. package/gsd-core/bin/lib/vendor/re2js.d.cts +938 -0
  163. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  164. package/gsd-core/bin/lib/verification.cjs +287 -20
  165. package/gsd-core/bin/lib/verify.cjs +368 -880
  166. package/gsd-core/bin/lib/workflow-fragments.cjs +557 -0
  167. package/gsd-core/bin/lib/workstream-inventory-builder.cjs +203 -19
  168. package/gsd-core/bin/lib/workstream-inventory.cjs +576 -31
  169. package/gsd-core/bin/lib/workstream.cjs +8 -2
  170. package/gsd-core/bin/lib/worktree-base-ref.cjs +50 -6
  171. package/gsd-core/bin/lib/worktree-safety.cjs +450 -125
  172. package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
  173. package/gsd-core/bin/shared/config-schema.manifest.json +9 -1
  174. package/gsd-core/references/agent-contracts.md +43 -26
  175. package/gsd-core/references/artifact-types.md +10 -3
  176. package/gsd-core/references/autonomous-ui-design-contract.md +42 -0
  177. package/gsd-core/references/checkpoints.md +2 -2
  178. package/gsd-core/references/context-budget.md +1 -1
  179. package/gsd-core/references/debugger-techniques.md +255 -0
  180. package/gsd-core/references/dispatch-isolation-gate.md +138 -0
  181. package/gsd-core/references/doc-conflict-engine.md +1 -1
  182. package/gsd-core/references/execute-mvp-tdd.md +3 -3
  183. package/gsd-core/references/execute-phase-between-wave-reset.md +6 -2
  184. package/gsd-core/references/execute-phase-context-guard.md +1 -1
  185. package/gsd-core/references/execute-phase-response-language.md +1 -1
  186. package/gsd-core/references/execute-phase-wave-guard.md +6 -2
  187. package/gsd-core/references/gate-prompts.md +1 -1
  188. package/gsd-core/references/git-planning-commit.md +2 -1
  189. package/gsd-core/references/loop-hook-dispatch.md +39 -2
  190. package/gsd-core/references/model-profiles.md +12 -4
  191. package/gsd-core/references/mvp-concepts.md +9 -9
  192. package/gsd-core/references/planner-guidance.md +3 -9
  193. package/gsd-core/references/planner-preconditions.md +1 -1
  194. package/gsd-core/references/planner-reviews.md +1 -1
  195. package/gsd-core/references/planning-config.md +8 -6
  196. package/gsd-core/references/research-documentation-lookup.md +5 -3
  197. package/gsd-core/references/revision-loop.md +1 -1
  198. package/gsd-core/references/specless-probe-fallback.md +8 -7
  199. package/gsd-core/references/universal-anti-patterns.md +3 -3
  200. package/gsd-core/references/verifier-phase-gates.md +192 -0
  201. package/gsd-core/references/verifier-wiring-patterns.md +100 -0
  202. package/gsd-core/references/verify-mvp-mode.md +1 -1
  203. package/gsd-core/references/workstream-flag.md +22 -6
  204. package/gsd-core/references/worktree-branch-check.md +2 -2
  205. package/gsd-core/templates/discussion-log.md +1 -1
  206. package/gsd-core/templates/phase-prompt.md +2 -4
  207. package/gsd-core/templates/state.md +4 -4
  208. package/gsd-core/templates/summary-complex.md +2 -0
  209. package/gsd-core/templates/summary-minimal.md +2 -0
  210. package/gsd-core/templates/summary-standard.md +2 -0
  211. package/gsd-core/templates/summary.md +2 -0
  212. package/gsd-core/templates/verification-report.md +9 -1
  213. package/gsd-core/workflows/ai-integration-phase.md +9 -11
  214. package/gsd-core/workflows/audit-milestone.md +3 -0
  215. package/gsd-core/workflows/autonomous/steps/converge-banner.md +1 -0
  216. package/gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md +11 -0
  217. package/gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md +7 -0
  218. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +21 -0
  219. package/gsd-core/workflows/autonomous/steps/converge-loop.md +7 -0
  220. package/gsd-core/workflows/autonomous.md +33 -70
  221. package/gsd-core/workflows/cleanup.md +62 -3
  222. package/gsd-core/workflows/code-review/steps/dispatch-fix.md +39 -0
  223. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +93 -0
  224. package/gsd-core/workflows/code-review-fix.md +37 -10
  225. package/gsd-core/workflows/code-review.md +74 -166
  226. package/gsd-core/workflows/complete-milestone/steps/git-tag.md +29 -0
  227. package/gsd-core/workflows/complete-milestone.md +160 -95
  228. package/gsd-core/workflows/debug.md +16 -17
  229. package/gsd-core/workflows/diagnose-issues.md +56 -8
  230. package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -1
  231. package/gsd-core/workflows/discuss-phase/modes/default.md +1 -1
  232. package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +15 -0
  233. package/gsd-core/workflows/discuss-phase-assumptions.md +7 -17
  234. package/gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md +51 -0
  235. package/gsd-core/workflows/docs-update.md +8 -51
  236. package/gsd-core/workflows/edit-phase.md +26 -1
  237. package/gsd-core/workflows/eval-review.md +3 -5
  238. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +64 -7
  239. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +50 -0
  240. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +31 -0
  241. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +77 -0
  242. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +21 -0
  243. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +42 -0
  244. package/gsd-core/workflows/execute-phase/steps/regression-gate.md +43 -37
  245. package/gsd-core/workflows/execute-phase.md +103 -187
  246. package/gsd-core/workflows/execute-plan.md +36 -4
  247. package/gsd-core/workflows/explore.md +131 -4
  248. package/gsd-core/workflows/fast.md +10 -2
  249. package/gsd-core/workflows/health.md +73 -4
  250. package/gsd-core/workflows/help/modes/full.md +6 -1
  251. package/gsd-core/workflows/import.md +4 -4
  252. package/gsd-core/workflows/ingest-docs.md +7 -6
  253. package/gsd-core/workflows/mvp-phase.md +6 -3
  254. package/gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md +16 -0
  255. package/gsd-core/workflows/new-milestone/steps/reset-phase-safety.md +19 -0
  256. package/gsd-core/workflows/new-milestone.md +35 -47
  257. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +176 -0
  258. package/gsd-core/workflows/new-project/steps/auto-mode-detection.md +32 -0
  259. package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +18 -0
  260. package/gsd-core/workflows/new-project.md +27 -240
  261. package/gsd-core/workflows/next.md +12 -0
  262. package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +15 -0
  263. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +110 -0
  264. package/gsd-core/workflows/plan-phase/steps/prd-express-gate.md +8 -0
  265. package/gsd-core/workflows/plan-phase/steps/research-only-early-exit.md +17 -0
  266. package/gsd-core/workflows/plan-phase/steps/research-only-modifiers.md +16 -0
  267. package/gsd-core/workflows/plan-phase/steps/reviews-prerequisite.md +17 -0
  268. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +149 -0
  269. package/gsd-core/workflows/plan-phase.md +89 -209
  270. package/gsd-core/workflows/plan-review-convergence.md +50 -2
  271. package/gsd-core/workflows/progress/steps/forensic-audit.md +125 -0
  272. package/gsd-core/workflows/progress/steps/mvp-display.md +18 -0
  273. package/gsd-core/workflows/progress.md +45 -159
  274. package/gsd-core/workflows/quick/steps/discussion-phase.md +124 -0
  275. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +111 -0
  276. package/gsd-core/workflows/quick/steps/quick-verification.md +67 -0
  277. package/gsd-core/workflows/quick/steps/research-phase.md +72 -0
  278. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +37 -0
  279. package/gsd-core/workflows/quick.md +55 -405
  280. package/gsd-core/workflows/resume-project.md +3 -0
  281. package/gsd-core/workflows/review/steps/reviewer-instances-note-1.md +4 -0
  282. package/gsd-core/workflows/review/steps/reviewer-instances-note-2.md +3 -0
  283. package/gsd-core/workflows/review.md +41 -13
  284. package/gsd-core/workflows/section-manifest.json +219 -0
  285. package/gsd-core/workflows/secure-phase.md +1 -1
  286. package/gsd-core/workflows/session-report.md +2 -1
  287. package/gsd-core/workflows/settings.md +66 -2
  288. package/gsd-core/workflows/ship.md +104 -44
  289. package/gsd-core/workflows/sketch.md +1 -1
  290. package/gsd-core/workflows/spec-phase.md +41 -20
  291. package/gsd-core/workflows/spike-wrap-up.md +20 -5
  292. package/gsd-core/workflows/spike.md +50 -16
  293. package/gsd-core/workflows/sync-skills.md +106 -13
  294. package/gsd-core/workflows/transition/steps/workstream-collision-check.md +17 -0
  295. package/gsd-core/workflows/transition.md +53 -31
  296. package/gsd-core/workflows/ui-phase.md +13 -12
  297. package/gsd-core/workflows/ui-review.md +2 -2
  298. package/gsd-core/workflows/update/steps/channel-banner.md +7 -0
  299. package/gsd-core/workflows/update.md +19 -8
  300. package/gsd-core/workflows/validate-phase.md +1 -1
  301. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +36 -0
  302. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +21 -0
  303. package/gsd-core/workflows/verify-work.md +17 -65
  304. package/hooks/dist/gsd-agent-isolation-guard.js +517 -0
  305. package/hooks/dist/gsd-check-update-worker.js +64 -12
  306. package/hooks/dist/gsd-check-update.js +19 -1
  307. package/hooks/dist/gsd-cursor-pre-tool.js +0 -3
  308. package/hooks/dist/gsd-cursor-subagent-start.js +607 -26
  309. package/hooks/dist/gsd-cursor-subagent-stop.js +3 -2
  310. package/hooks/dist/gsd-prompt-guard.js +21 -20
  311. package/hooks/dist/gsd-read-injection-scanner.js +45 -24
  312. package/hooks/dist/gsd-statusline.js +90 -6
  313. package/hooks/dist/gsd-update-banner.js +22 -1
  314. package/hooks/dist/gsd-workflow-guard.js +134 -36
  315. package/hooks/dist/gsd-worktree-path-guard.js +2 -1
  316. package/hooks/dist/gsd-write-guard.js +359 -0
  317. package/hooks/dist/lib/git-cmd.js +92 -59
  318. package/hooks/dist/lib/injection-patterns.js +45 -0
  319. package/hooks/dist/lib/isolation-deny-reason.js +39 -0
  320. package/hooks/dist/lib/isolation-sentinel.js +277 -0
  321. package/hooks/dist/managed-hooks-registry.cjs +2 -0
  322. package/hooks/gsd-agent-isolation-guard.js +517 -0
  323. package/hooks/gsd-check-update-worker.js +64 -12
  324. package/hooks/gsd-check-update.js +19 -1
  325. package/hooks/gsd-cursor-pre-tool.js +0 -3
  326. package/hooks/gsd-cursor-subagent-start.js +607 -26
  327. package/hooks/gsd-cursor-subagent-stop.js +3 -2
  328. package/hooks/gsd-prompt-guard.js +21 -20
  329. package/hooks/gsd-read-injection-scanner.js +45 -24
  330. package/hooks/gsd-statusline.js +90 -6
  331. package/hooks/gsd-update-banner.js +22 -1
  332. package/hooks/gsd-workflow-guard.js +134 -36
  333. package/hooks/gsd-worktree-path-guard.js +2 -1
  334. package/hooks/gsd-write-guard.js +359 -0
  335. package/hooks/hooks.json +12 -0
  336. package/hooks/lib/git-cmd.js +92 -59
  337. package/hooks/lib/injection-patterns.js +45 -0
  338. package/hooks/lib/isolation-deny-reason.js +39 -0
  339. package/hooks/lib/isolation-sentinel.js +277 -0
  340. package/hooks/managed-hooks-registry.cjs +2 -0
  341. package/package.json +31 -10
  342. package/pi/gsd.cjs +71 -12
  343. package/scripts/baselines/planning-prompt-drift-baseline.json +4 -0
  344. package/scripts/baselines/planning-snapshot-bypass-baseline.json +12 -0
  345. package/scripts/baselines/unreachable-guard-drift-baseline.json +4 -0
  346. package/scripts/build-hooks.js +9 -0
  347. package/scripts/changeset/lint.cjs +68 -6
  348. package/scripts/changeset/serialize.cjs +5 -1
  349. package/scripts/check-alias-drift.cjs +7 -43
  350. package/scripts/check-contract-drift.cjs +297 -0
  351. package/scripts/ci-test-scope.cjs +19 -2
  352. package/scripts/command-contract-helpers.cjs +903 -1
  353. package/scripts/gen-adr-index.cjs +728 -38
  354. package/scripts/gen-capability-matrix.cjs +1 -1
  355. package/scripts/gen-capability-registry.cjs +3 -15
  356. package/scripts/gen-context-index.cjs +439 -0
  357. package/scripts/gen-health-docs.cjs +390 -0
  358. package/scripts/gen-inventory-manifest.cjs +150 -4
  359. package/scripts/gen-loop-host-contract.cjs +4 -24
  360. package/scripts/gen-prompt-budget-parity-corpus.cjs +645 -0
  361. package/scripts/gen-registry.cjs +3 -14
  362. package/scripts/gen-section-manifest.cjs +638 -0
  363. package/scripts/generate-package-identity.cjs +4 -2
  364. package/scripts/lib/alias-drift-families.cjs +46 -0
  365. package/scripts/lib/drift-scan.cjs +278 -0
  366. package/scripts/lint-allow-test-rule-refs.allowlist.json +15 -54
  367. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +4 -0
  368. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +3 -0
  369. package/scripts/lint-canary-version-leak.cjs +73 -0
  370. package/scripts/lint-command-contract.cjs +96 -13
  371. package/scripts/lint-compiled-artifact-sync.cjs +6 -1
  372. package/scripts/lint-completion-predicate-drift.cjs +933 -0
  373. package/scripts/lint-completion-ratio-drift.cjs +214 -0
  374. package/scripts/lint-default-flip-documentation.cjs +193 -0
  375. package/scripts/lint-docs-command-form.cjs +195 -0
  376. package/scripts/lint-docs-required.cjs +9 -1
  377. package/scripts/lint-emitted-drift-ack.cjs +215 -20
  378. package/scripts/lint-eslint-glob-coverage.allowlist.json +34 -0
  379. package/scripts/lint-eslint-glob-coverage.cjs +340 -0
  380. package/scripts/lint-example-parser-parity.cjs +395 -0
  381. package/scripts/lint-frontmatter-scalar-broad-grep.cjs +237 -0
  382. package/scripts/lint-health-diagnostic-rule-table.cjs +404 -0
  383. package/scripts/lint-hooks-runtime-build-seam.cjs +262 -0
  384. package/scripts/lint-milestone-window-drift.cjs +468 -0
  385. package/scripts/lint-phase-enumeration-drift.cjs +479 -0
  386. package/scripts/lint-plan-count-drift.cjs +318 -0
  387. package/scripts/lint-planning-artifact-writer-drift.cjs +398 -0
  388. package/scripts/lint-planning-prompt-drift.cjs +434 -0
  389. package/scripts/lint-planning-snapshot-bypass-drift.cjs +544 -0
  390. package/scripts/lint-regression-test-names.cjs +15 -13
  391. package/scripts/lint-removed-but-needed.cjs +320 -0
  392. package/scripts/lint-state-field-drift.cjs +805 -0
  393. package/scripts/lint-state-write-path-drift.cjs +1045 -0
  394. package/scripts/lint-test-file-count.allowlist.json +40 -3
  395. package/scripts/lint-unreachable-guard-drift.cjs +843 -0
  396. package/scripts/lint-vendored-deps.cjs +124 -0
  397. package/scripts/mutation-matrix.cjs +13 -0
  398. package/scripts/pr-changed-files.cjs +63 -0
  399. package/scripts/pr-template-policy.cjs +14 -4
  400. package/scripts/prompt-injection-scan.sh +52 -6
  401. package/scripts/require-issue-link-policy.cjs +192 -0
  402. package/scripts/state-write-path-drift-baseline.json +19 -0
  403. package/scripts/sync-runtime-launcher.cjs +2 -4
  404. package/skills/gsd-autonomous/SKILL.md +0 -1
  405. package/skills/gsd-code-review/SKILL.md +1 -1
  406. package/skills/gsd-execute-phase/SKILL.md +1 -2
  407. package/skills/gsd-map-codebase/SKILL.md +1 -1
  408. package/skills/gsd-mempalace-capture/SKILL.md +2 -2
  409. package/skills/gsd-mempalace-recall/SKILL.md +1 -1
  410. package/skills/gsd-new-milestone/SKILL.md +2 -2
  411. package/skills/gsd-next/SKILL.md +0 -1
  412. package/skills/gsd-plan-phase/SKILL.md +1 -2
  413. package/skills/gsd-progress/SKILL.md +0 -1
  414. package/skills/gsd-quick/SKILL.md +1 -1
  415. package/skills/gsd-review-backlog/SKILL.md +2 -1
  416. package/skills/gsd-stats/SKILL.md +0 -1
  417. package/skills/gsd-verify-work/SKILL.md +1 -1
  418. package/vscode/package.json +1 -1
  419. package/gsd-core/workflows/discovery-phase.md +0 -298
  420. package/gsd-core/workflows/plan-milestone-gaps.md +0 -281
  421. package/gsd-core/workflows/verify-phase.md +0 -577
  422. package/scripts/affected-tests-lib.cjs +0 -554
  423. package/scripts/gen-emitted-baseline.cjs +0 -145
  424. package/scripts/lint-allow-test-rule-refs.cjs +0 -162
  425. package/scripts/run-affected-tests.cjs +0 -7
  426. package/scripts/run-tests.cjs +0 -1050
@@ -0,0 +1,468 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Anti-divergence drift guard for the milestone-WINDOWING seam
6
+ * (epic #3180, issue #3184, ADR-3180 "Planning Semantic Model Single Owner").
7
+ *
8
+ * `src/roadmap-parser.cts` is the SINGLE canonical owner of "where does a
9
+ * given milestone's ROADMAP section begin and end" — `computeMilestoneSectionEnd`,
10
+ * `locateMilestoneHeadings`, and `isMilestoneBoundedInRoadmap`. Every other
11
+ * module that hand-rolls a heading-level quantifier together with the
12
+ * milestone-boundary shape (a non-Phase heading carrying a version token or a
13
+ * shipped/active marker) is a re-derivation that can silently drift from the
14
+ * owner — the exact defect class #2562 fixed in one copy and never reached
15
+ * the other two (design doc: `currentMilestoneRawRanges::computeSectionEnd`
16
+ * carried a "keep in sync" comment that was already evidence the risk was
17
+ * known, not controlled).
18
+ *
19
+ * Per ADR-3180 Decision 4(a) this guard discovers call sites by SCANNING THE
20
+ * WHOLE `src/` TREE, not by consulting an allowlist of known files — an
21
+ * allowlist only measures re-derivations in files someone remembered to
22
+ * list. Exemptions below are FUNCTION-SCOPED with a written reason, never a
23
+ * bare file allowlist, mirroring `lint-plan-count-drift.cjs`'s precedent.
24
+ *
25
+ * Detection is intentionally NARROW, mirroring the plan-count-drift and
26
+ * phase-id-drift precedents: a line is a re-derivation when it carries BOTH,
27
+ * in ONE source line:
28
+ * (a) a markdown heading-level quantifier token — `#{N,M}`, e.g. `#{1,3}`,
29
+ * `#{2,3}`, `#{2,4}` — inside either a regex literal or a
30
+ * quoted/backticked string, AND
31
+ * (b) a milestone-window token: either the negative-lookahead phase
32
+ * exclusion (`(?!Phase` / `(?!Phase\s+\S)`) or the milestone
33
+ * boundary-marker set — a `v\d+\.\d+`-shaped version token appearing
34
+ * together with any of the ✅ 📋 🚧 shipped/active markers.
35
+ * Token (b) is deliberately narrow: a PHASE-heading regex (`#{2,4}\s*Phase`)
36
+ * carries (a) alone, constantly, throughout this codebase (phase-numbering,
37
+ * plan-index, wave-scheduling call sites) and must NOT be flagged — it asks
38
+ * "where is phase N's heading", a different, already-single-owned question
39
+ * (#2121). Only a line that ALSO carries the milestone-boundary shape — the
40
+ * one `computeMilestoneSectionEnd`/`locateMilestoneHeadings` compute — is a
41
+ * candidate re-derivation of THIS derivation.
42
+ *
43
+ * Both `(a)` and `(b)` must be readable through JS regex-literal AND
44
+ * string/template-literal escaping: the two pre-#3184 `state.cts`
45
+ * re-derivations this design is modelled on were
46
+ * `new RegExp(\`^#{1,3}\\s+(?!Phase\\s+\\S)...\`)` — i.e. the SAME source
47
+ * text as a real `/.../ ` regex literal, just doubly backslash-escaped
48
+ * because it lives inside a template literal. `HEADING_QUANTIFIER_RE` and
49
+ * `PHASE_LOOKAHEAD_RE` match either escaping level unchanged (no backslash
50
+ * appears inside `#{`/`}`/`(?!Phase`'s literal characters); `VERSION_TOKEN_RE`
51
+ * explicitly tolerates ONE or TWO backslashes before each `d`/`.` for exactly
52
+ * this reason. Regex-LITERAL boundaries (used only to extract a reportable
53
+ * `found` fragment, never for detection itself, which tests the raw line) are
54
+ * located via the shared `readRegexLiteralAt` tokenizer
55
+ * (`scripts/lib/drift-scan.cjs`) — a single left-to-right, no-backtracking
56
+ * pass — never a backtracking "find the regex literal" regex (CodeQL js/redos
57
+ * runs on `lint:ci`; see that module's own header for the full rationale).
58
+ * `readStringLiteralAt` below is the same style, written locally for
59
+ * quoted/backticked strings (not shared — `lint-plan-count-drift.cjs` has no
60
+ * equivalent need, since its own literal-bearing shape is regex-only).
61
+ *
62
+ * Owner file: `src/roadmap-parser.cts` DEFINES this grammar, but it is NOT
63
+ * exempt as a whole file — that was the original design (a bare per-file
64
+ * allowlist) and it closed off exactly the blind spot ADR-3180 Decision 4(a)
65
+ * warns about: `getMilestoneInfo`, added later in this same owner file,
66
+ * hand-rolled its own milestone-heading regex (issues #3171, #3197) and the
67
+ * whole-file exemption made it invisible to this guard. The owner file is now
68
+ * scanned like every other file in SCAN_DIRS; only its named canonical
69
+ * functions are exempt (`FUNCTION_SCOPED_EXEMPTIONS`, keyed on `OWNER_FILE`),
70
+ * each with a written reason — `isMilestoneShippedInRoadmap`,
71
+ * `locateMilestoneHeadings`, `hasMilestoneSectioning`, and
72
+ * `extractCurrentMilestoneScoped` (whose `anyMilestonePattern`/
73
+ * `anyMilestoneOrDetails` locals compose `#{1,3}` with the `(?!Phase...)`/
74
+ * marker alternations as part of the canonical implementation, not a copy of
75
+ * it). `computeMilestoneSectionEnd` carries (a) and (b) on two DIFFERENT
76
+ * lines (the heading-quantifier match and the version/marker test are two
77
+ * separate statements) rather than one line carrying both, so this guard's
78
+ * own documented per-line-scan limit means it never fires there and it needs
79
+ * no listed exemption. An unrelated re-derivation added anywhere else
80
+ * in this file — including inside a function added after this guard, such as
81
+ * a future `getMilestoneInfo`-shaped one — is still caught.
82
+ *
83
+ * The tree-walk / root-confinement / regex-literal-tokenizer / sanitizer
84
+ * machinery is SHARED with `scripts/lint-plan-count-drift.cjs` via
85
+ * `scripts/lib/drift-scan.cjs` (ADR-3180 Decision 4, design doc's own
86
+ * "Rejected: let the new drift guard copy Phase 1's tree-walk /
87
+ * root-confinement / sanitizer") — see that module for the `isInsideRoot`
88
+ * case-sensitivity note, the `walk` symlink-confinement rationale, and the
89
+ * `readRegexLiteralAt` ReDoS-avoidance rationale.
90
+ *
91
+ * KNOWN, ACCEPTED limits of a per-line textual scan (same tradeoff the
92
+ * sibling drift guards document): a re-derivation whose `(a)`/`(b)` tokens
93
+ * are split across two DIFFERENT lines with no single line carrying both is
94
+ * not caught by this narrow shape, nor is one routed through dynamic
95
+ * dispatch. That is left to code review and the design's identity test
96
+ * (ADR-3180 Decision 4b/4c), not this regex.
97
+ */
98
+
99
+ const path = require('node:path');
100
+ const driftScan = require('./lib/drift-scan.cjs');
101
+ const { readRegexLiteralAt, MAX_REGEX_LITERAL_LEN, sanitizeForReport, scanTree } = driftScan;
102
+
103
+ // (a) A markdown heading-level quantifier: `#{N,M}` — e.g. `#{1,3}`,
104
+ // `#{2,3}`, `#{2,4}`. Bounded to 1-2 digit levels (real Markdown headings
105
+ // never exceed level 6) so this stays a small, fixed, linear test — no
106
+ // unbounded quantifier, nothing for CodeQL js/redos to flag.
107
+ const HEADING_QUANTIFIER_RE = /#\{\d{1,2},\d{1,2}\}/;
108
+
109
+ // (b1) The negative-lookahead phase exclusion `computeMilestoneSectionEnd`/
110
+ // `locateMilestoneHeadings` use to skip `### Phase N: …` headings while
111
+ // scanning for the NEXT milestone boundary.
112
+ const PHASE_LOOKAHEAD_RE = /\(\?!Phase\b/;
113
+
114
+ // (b2) A `v\d+\.\d+`-shaped version token, tolerant of ONE or TWO backslash
115
+ // escaping levels (a bare regex literal carries `\d`/`\.` with a single
116
+ // backslash; a template-literal regex SOURCE string carries the SAME source
117
+ // text doubly-escaped, `\\d`/`\\.`, because the template literal's own
118
+ // backslash must itself be escaped in the .cts source) and an OPTIONAL
119
+ // capturing group immediately around the digit run (`v(\d+)\.\d+`, the shape
120
+ // `roadmap-command-router.cts`'s `MILESTONE_RE` actually uses to capture the
121
+ // major version number).
122
+ const VERSION_TOKEN_RE = /v\(?\\{1,2}d\+\)?\\{1,2}\.\\{1,2}d\+/;
123
+
124
+ // (b2) The milestone shipped/active marker set `isClosedMilestoneHeading`/
125
+ // `computeMilestoneSectionEnd` test for. `(b)` fires when this appears on the
126
+ // SAME line as a VERSION_TOKEN_RE match — a version token alone is not
127
+ // milestone-boundary-specific (plenty of non-heading code compares version
128
+ // strings), and a marker alone is not either (it can appear in unrelated
129
+ // prose-matching code); together, on one line, they are the boundary shape.
130
+ const MARKER_EMOJI_RE = /[✅📋🚧]/u;
131
+
132
+ // (a-bis) #3216: a LITERAL markdown heading anchor — `##`, `###`, … — as
133
+ // opposed to the `#{N,M}` quantifier token (a) above. `getMilestoneInfo`
134
+ // hand-rolled its milestone-heading match with a literal `^##` / `## ` rather
135
+ // than a quantifier, so token (a) alone reported a clean zero on a file that
136
+ // carried two live re-derivations (#3171, #3197). The negative lookahead for
137
+ // `{` keeps this from double-matching the quantifier form.
138
+ //
139
+ // A `#` run is far more common in source than `#{N,M}` (private-field sigils,
140
+ // colour literals, fragment URLs, prose), so this token is admitted ONLY
141
+ // inside a heading-MATCHER literal — a regex literal, or a string/template
142
+ // literal handed to `new RegExp(` — never a bare line match. A template that
143
+ // BUILDS a heading for output (`## ${version}`) is not a re-derivation of
144
+ // where a milestone's section begins, and conflating the two would flag every
145
+ // heading writer in the tree.
146
+ const LITERAL_HEADING_RUN_RE = /#{2,6}(?!\{)/;
147
+
148
+ // A line that constructs a regex from a string/template literal, which is what
149
+ // admits the `new RegExp(`^##…${escapedVer}…`)` shape while leaving ordinary
150
+ // heading-building templates alone.
151
+ const NEW_REGEXP_RE = /new\s+RegExp\s*\(/;
152
+
153
+ // (b2-bis) #3216: the `v(\d+(?:\.\d+)+)` version shape — a capturing group
154
+ // around the major, then a NON-capturing `(?:\.\d+)+` repeat. VERSION_TOKEN_RE
155
+ // cannot see it: after `v(` + `\d+` it requires a backslash next, and this
156
+ // shape has `(` there instead.
157
+ const VERSION_TOKEN_GROUPED_RE = /v\(\\{1,2}d\+\(\?:\\{1,2}\.\\{1,2}d\+\)\+\)/;
158
+
159
+ // (b3) #3216: an INTERPOLATED version placeholder — `${escapedVer}`,
160
+ // `${escapedVersion}`, `${version}`. A regex that interpolates its version
161
+ // spells no literal `v\d+\.\d+` anywhere, so (b) could never fire. Inside a
162
+ // heading-matcher literal, "a heading anchor plus an interpolated version" IS
163
+ // the milestone-heading shape the canonical `locateMilestoneHeadings`
164
+ // composes — and so is a copy of it.
165
+ const INTERPOLATED_VERSION_RE = /\$\{[A-Za-z0-9_.]*[Vv]er[A-Za-z0-9_.]*\}/;
166
+
167
+ // Authored TypeScript source only (the generated bin/lib/*.cjs mirror it).
168
+ const SCAN_DIRS = ['src'];
169
+ const SCAN_EXT = new Set(['.cts', '.ts', '.mts']);
170
+
171
+ // The canonical owner defines the grammar; it is exempt by construction (see
172
+ // header comment for why its OWN internal composition of these tokens is not
173
+ // a re-derivation).
174
+ const OWNER_FILE = path.join('src', 'roadmap-parser.cts');
175
+
176
+ // Per ADR-3180 Decision 4(a): NOT a bare file allowlist — each entry below is
177
+ // scoped to the SPECIFIC function asking a documented, DIFFERENT question, so
178
+ // an unrelated re-derivation added anywhere else in these same files is still
179
+ // caught. Mirrors `lint-plan-count-drift.cjs`'s FUNCTION_SCOPED_EXEMPTIONS
180
+ // mechanism.
181
+ //
182
+ // - roadmap-command-router.cts checkW021: `MILESTONE_RE` CLASSIFIES a
183
+ // single heading LINE as "is this a milestone heading, and if so what is
184
+ // its major version" for the W021 phase/milestone-prefix-mismatch check
185
+ // — it is a per-line classifier consumed one line at a time via
186
+ // `content.split('\n')`, with no concept of a section END at all. It
187
+ // never computes "where does this milestone's content stop" — the
188
+ // question `computeMilestoneSectionEnd` answers — so it cannot diverge
189
+ // from that computation; it answers a narrower, different question this
190
+ // derivation does not own.
191
+ // - (Phase 11, #3309: `verify.cts`'s pre-migration `checkMilestonePrefixMismatches`
192
+ // — formerly exempted here — was DELETED when `cmdValidateHealth` migrated
193
+ // onto the rule table; its `sectionRx` walk relocated verbatim into
194
+ // `planning-snapshot.cts`'s `buildRoadmapDeclaredPhasesField`, which needs
195
+ // no exemption of its own: like the deleted function, its `sectionRx`
196
+ // never carries token (b) as this guard defines it — no `(?!Phase`
197
+ // lookahead, no marker-emoji pairing — so it was never a live match.)
198
+ // - roadmap-parser.cts isMilestoneShippedInRoadmap: composes the heading
199
+ // quantifier with the shipped/active MARKER check (via
200
+ // isClosedMilestoneHeading) to answer "is THIS milestone version marked
201
+ // shipped by the ROADMAP" — a documented, narrower question than
202
+ // computeMilestoneSectionEnd/locateMilestoneHeadings' "where does it
203
+ // end"/"which heading is it", not a copy of either.
204
+ // - roadmap-parser.cts locateMilestoneHeadings: this literally IS the
205
+ // canonical heading-locator this guard exists to protect (see the
206
+ // function's own header comment) — every other module's heading lookup
207
+ // is expected to call it, not re-express it.
208
+ // - roadmap-parser.cts hasMilestoneSectioning: the canonical "does this
209
+ // ROADMAP use milestone sectioning at all" predicate — a deliberately
210
+ // WEAKER, version-agnostic composition of the same two tokens, owned
211
+ // here per its own header comment so the milestone-heading vocabulary
212
+ // has one home rather than a third hand-rolled copy in state.cts.
213
+ // - roadmap-parser.cts extractCurrentMilestoneScoped: its
214
+ // `anyMilestoneOrDetails`/`anyMilestonePattern` locals are the two
215
+ // internal call sites the header comment already names as part of the
216
+ // canonical implementation (composing `#{1,3}` with the
217
+ // `(?!Phase...)`/marker alternations to find "the next milestone
218
+ // boundary" while assembling the current-milestone window) — not
219
+ // re-derivations of a question answered elsewhere.
220
+ // - roadmap-parser.cts listMilestoneHeadings: #3216 (epic #3180 §7.2's
221
+ // Scope amendment) — the version-AGNOSTIC sibling of
222
+ // `locateMilestoneHeadings`, and the function that textually DEFINES
223
+ // `MILESTONE_HEADING_LINE_SOURCE` (the one shared grammar constant both
224
+ // it and `locateMilestoneHeadings` build their pattern from) in its own
225
+ // source span. It is a named canonical function defining the grammar,
226
+ // not a copy of it — replacing the third independent re-derivation the
227
+ // widened guard found at `roadmap.cts:454`.
228
+ // - planning-snapshot.cts buildMilestoneArchiveStatusField (Phase 11,
229
+ // #3309): its `## <version>` heading scan reads `MILESTONES.md` — a
230
+ // FLAT version registry, not `ROADMAP.md` — asking "which versions does
231
+ // the registry already document", never "where does THIS milestone's
232
+ // ROADMAP section begin/end" (`computeMilestoneSectionEnd`/
233
+ // `locateMilestoneHeadings`'s own question). A different document, a
234
+ // different question; not a re-derivation of ROADMAP windowing.
235
+ // - health-diagnostic.cts computeMissingMilestoneVersions (Phase 11,
236
+ // #3309): `applyRepairs` is not a `Rule` and is not handed a
237
+ // `PlanningSnapshot` (see that file's header comment), so
238
+ // `backfillMilestones` recomputes the IDENTICAL `MILESTONES.md`
239
+ // heading-membership check `buildMilestoneArchiveStatusField` already
240
+ // performs for the W018 rule's read side — same non-ROADMAP-windowing
241
+ // question as that function, for the same reason.
242
+ const FUNCTION_SCOPED_EXEMPTIONS = new Map([
243
+ [path.join('src', 'roadmap-command-router.cts'), new Set(['checkW021'])],
244
+ [
245
+ OWNER_FILE,
246
+ new Set([
247
+ 'isMilestoneShippedInRoadmap',
248
+ 'locateMilestoneHeadings',
249
+ 'listMilestoneHeadings',
250
+ 'hasMilestoneSectioning',
251
+ 'extractCurrentMilestoneScoped',
252
+ ]),
253
+ ],
254
+ [path.join('src', 'planning-snapshot.cts'), new Set(['buildMilestoneArchiveStatusField'])],
255
+ [path.join('src', 'health-diagnostic.cts'), new Set(['computeMissingMilestoneVersions'])],
256
+ ]);
257
+
258
+ // Optional `export ` modifier, mirroring `lint-plan-count-drift.cjs`'s
259
+ // TOP_LEVEL_FUNCTION_RE — only a column-0 top-level `function` declaration
260
+ // updates the current-function tracker; a nested/arrow function does not
261
+ // reset it, matching every FUNCTION_SCOPED_EXEMPTIONS entry above (all
262
+ // top-level `function` declarations).
263
+ const TOP_LEVEL_FUNCTION_RE = /^(?:export\s+)?function\s+([A-Za-z0-9_]+)\s*\(/;
264
+
265
+ /**
266
+ * Read the quoted or backtick-delimited string/template literal starting at
267
+ * `line[start]` (which must be `'`, `"`, or `` ` ``). Returns `{ text, end }`
268
+ * — `text` includes both delimiters, `end` is the index one past the literal
269
+ * — or null if no matching close quote is found within MAX_REGEX_LITERAL_LEN
270
+ * characters. Same single left-to-right, no-backtracking, escape-aware style
271
+ * as the shared `readRegexLiteralAt` (`\x` escapes consume both characters,
272
+ * so an escaped quote never terminates the literal early) — written locally
273
+ * because `lint-plan-count-drift.cjs` has no equivalent need (its literal
274
+ * shape is regex-only), so it does not belong in the shared module.
275
+ */
276
+ function readStringLiteralAt(line, start) {
277
+ const quote = line[start];
278
+ if (quote !== "'" && quote !== '"' && quote !== '`') return null;
279
+ const limit = Math.min(line.length, start + MAX_REGEX_LITERAL_LEN);
280
+ for (let i = start + 1; i < limit; i++) {
281
+ const ch = line[i];
282
+ if (ch === '\\') {
283
+ i++; // escape consumes the next character, whatever it is
284
+ continue;
285
+ }
286
+ if (ch === '\r' || ch === '\n') return null; // a literal cannot span lines in this per-line scan
287
+ if (ch === quote) return { text: line.slice(start, i + 1), end: i + 1 };
288
+ }
289
+ return null;
290
+ }
291
+
292
+ /**
293
+ * Strip comment text from a line before detection. A guard that fires on a
294
+ * COMMENT — including a comment documenting that the code below uses the
295
+ * canonical owner, or prose quoting this guard's own detector shapes — reports
296
+ * prose as drift and trains readers to add exemptions for documentation.
297
+ * Handles the three shapes that appear in this codebase: a whole-line
298
+ * block-comment continuation (`*` or `/*` leading), a `//` line comment, and
299
+ * a trailing `//` after code. Mirrors `lint-phase-enumeration-drift.cjs`'s
300
+ * own copy (not shared — each guard applies it at a slightly different point
301
+ * in its detection pipeline).
302
+ *
303
+ * Deliberately simple and conservative: it does not attempt full block-comment
304
+ * state tracking across lines (this is a per-line scan, same tradeoff the
305
+ * sibling guards document). A `//` inside a string literal would be stripped
306
+ * early — accepted, because the effect is to UNDER-report on a pathological
307
+ * line, never to over-report prose as drift.
308
+ */
309
+ function stripComments(line) {
310
+ const trimmed = line.trim();
311
+ // Whole-line block comment or JSDoc continuation.
312
+ if (trimmed.startsWith('*') || trimmed.startsWith('/*') || trimmed.startsWith('//')) return '';
313
+ // Trailing line comment after code.
314
+ const idx = line.indexOf('//');
315
+ return idx === -1 ? line : line.slice(0, idx);
316
+ }
317
+
318
+ /**
319
+ * The first literal (regex OR quoted/backtick string) on `line` whose text
320
+ * contains a HEADING_QUANTIFIER_RE match — the "smoking gun" fragment worth
321
+ * reporting, mirroring `findRegexLiteralMdMatch`'s role in the sibling guard.
322
+ * Falls back to a bounded, trimmed slice of the raw line when the tokens are
323
+ * not both inside one located literal (not currently reachable against this
324
+ * repo — see the header comment's per-file audit — but a fail-safe rather
325
+ * than a thrown error if a future line splits them). Takes the RAW `line`
326
+ * (not comment-stripped) so a reported fragment still shows the actual source
327
+ * text — comment-stripping is applied only to the detection decision, never
328
+ * to the reported fragment.
329
+ */
330
+ /**
331
+ * All heading-MATCHER literals on `line` — a regex literal always counts; a
332
+ * quoted/backtick string literal counts only when `code` (the comment-stripped
333
+ * line passed in from the caller) constructs a regex via `new RegExp(`. A
334
+ * plain string/template literal that is not fed to `new RegExp(` is not a
335
+ * matcher — most commonly a heading BUILT for output, not one matched against.
336
+ */
337
+ function headingMatcherLiterals(line, code) {
338
+ const out = [];
339
+ const allowStrings = NEW_REGEXP_RE.test(code);
340
+ for (let i = 0; i < line.length; i++) {
341
+ const ch = line[i];
342
+ let literal = null;
343
+ let isRegex = false;
344
+ if (ch === '/') {
345
+ literal = readRegexLiteralAt(line, i);
346
+ isRegex = true;
347
+ } else if (ch === "'" || ch === '"' || ch === '`') {
348
+ literal = readStringLiteralAt(line, i);
349
+ }
350
+ if (!literal) continue;
351
+ if (isRegex || allowStrings) out.push(literal.text);
352
+ i = literal.end - 1;
353
+ }
354
+ return out;
355
+ }
356
+
357
+ function extractFragment(line) {
358
+ for (let i = 0; i < line.length; i++) {
359
+ const ch = line[i];
360
+ let literal = null;
361
+ if (ch === '/') literal = readRegexLiteralAt(line, i);
362
+ else if (ch === "'" || ch === '"' || ch === '`') literal = readStringLiteralAt(line, i);
363
+ if (!literal) continue;
364
+ if (HEADING_QUANTIFIER_RE.test(literal.text) || LITERAL_HEADING_RUN_RE.test(literal.text)) return literal.text;
365
+ i = literal.end - 1; // resume scanning just past this literal
366
+ }
367
+ return line.trim().slice(0, MAX_REGEX_LITERAL_LEN);
368
+ }
369
+
370
+ /**
371
+ * Pure: find every unsanctioned milestone-window re-derivation in `text`.
372
+ * `relPath` is the repo-relative path, used both to report file:line and to
373
+ * apply the narrow, function-scoped exemptions above.
374
+ * Returns [{ line, found }].
375
+ */
376
+ function findMilestoneWindowDrift(text, relPath) {
377
+ const out = [];
378
+ const lines = text.split('\n');
379
+ const exemptFunctions = FUNCTION_SCOPED_EXEMPTIONS.get(relPath) || null;
380
+ let currentFunction = null;
381
+ for (let i = 0; i < lines.length; i++) {
382
+ const line = lines[i];
383
+ const fnMatch = TOP_LEVEL_FUNCTION_RE.exec(line);
384
+ if (fnMatch) currentFunction = fnMatch[1];
385
+
386
+ const code = stripComments(line);
387
+ if (!code.trim()) continue;
388
+
389
+ const matcherLiterals = headingMatcherLiterals(line, code);
390
+ const hasQuantifier = HEADING_QUANTIFIER_RE.test(code);
391
+ const hasLiteralHeading = matcherLiterals.some((t) => LITERAL_HEADING_RUN_RE.test(t));
392
+ if (!hasQuantifier && !hasLiteralHeading) continue;
393
+
394
+ const anyVersionToken = VERSION_TOKEN_RE.test(code) || VERSION_TOKEN_GROUPED_RE.test(code);
395
+ const isMilestoneWindowToken =
396
+ PHASE_LOOKAHEAD_RE.test(code)
397
+ || (anyVersionToken && MARKER_EMOJI_RE.test(code))
398
+ || (hasLiteralHeading && (anyVersionToken || INTERPOLATED_VERSION_RE.test(code)));
399
+ if (!isMilestoneWindowToken) continue;
400
+
401
+ if (exemptFunctions && exemptFunctions.has(currentFunction)) continue;
402
+
403
+ out.push({ line: i + 1, found: extractFragment(line) });
404
+ }
405
+ return out;
406
+ }
407
+
408
+ /**
409
+ * Scan the authored source tree and return every unsanctioned re-derivation,
410
+ * each annotated with the repo-relative file path.
411
+ */
412
+ function scanRepo(root) {
413
+ return scanTree({
414
+ root,
415
+ scanDirs: SCAN_DIRS,
416
+ scanExt: SCAN_EXT,
417
+ onFile(rel, text) {
418
+ // `rel` is already the REAL (canonical) path (scanTree resolves
419
+ // symlinks before calling onFile), so this — and
420
+ // FUNCTION_SCOPED_EXEMPTIONS above, also keyed on `rel` — match
421
+ // consistently regardless of which symlink reached the file. The owner
422
+ // file is NOT short-circuited here; it is scanned like every other
423
+ // file, and only its named canonical functions are exempt (see
424
+ // FUNCTION_SCOPED_EXEMPTIONS).
425
+ return findMilestoneWindowDrift(text, rel).map((d) => ({ file: rel, ...d }));
426
+ },
427
+ });
428
+ }
429
+
430
+ function main() {
431
+ const root = path.join(__dirname, '..');
432
+ const violations = scanRepo(root);
433
+ if (violations.length === 0) {
434
+ process.stdout.write('ok milestone-window-drift: no unsanctioned milestone-window re-derivations outside roadmap-parser.cts\n');
435
+ return;
436
+ }
437
+ process.stderr.write('milestone-window-drift: independent re-derivation(s) of milestone-window bounding found.\n');
438
+ process.stderr.write('Use src/roadmap-parser.cjs `computeMilestoneSectionEnd` / `locateMilestoneHeadings` /\n');
439
+ process.stderr.write('`isMilestoneBoundedInRoadmap` instead of re-deriving the milestone heading/boundary regex:\n');
440
+ for (const d of violations) {
441
+ // `d.file` is exactly as attacker-controlled as `d.found`: a repo can
442
+ // legally track a filename containing control bytes / bidi overrides,
443
+ // and it is a fork-PR-authored value reaching a CI log the same way the
444
+ // matched literal does — sanitize it at the same reporting boundary.
445
+ process.stderr.write(` ${sanitizeForReport(d.file)}:${d.line} ${sanitizeForReport(d.found)}\n`);
446
+ }
447
+ process.exitCode = 1;
448
+ }
449
+
450
+ if (require.main === module) main();
451
+
452
+ module.exports = {
453
+ findMilestoneWindowDrift,
454
+ scanRepo,
455
+ HEADING_QUANTIFIER_RE,
456
+ PHASE_LOOKAHEAD_RE,
457
+ VERSION_TOKEN_RE,
458
+ MARKER_EMOJI_RE,
459
+ OWNER_FILE,
460
+ FUNCTION_SCOPED_EXEMPTIONS,
461
+ readStringLiteralAt,
462
+ extractFragment,
463
+ stripComments,
464
+ LITERAL_HEADING_RUN_RE,
465
+ VERSION_TOKEN_GROUPED_RE,
466
+ INTERPOLATED_VERSION_RE,
467
+ headingMatcherLiterals,
468
+ };