@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,544 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Anti-divergence drift guard for the DIAGNOSTIC-RULE raw-`.planning/`-read
6
+ * bypass of the planning snapshot single owner (epic #3180, ADR-3180
7
+ * "Planning Semantic Model Single Owner", §8.1 rule 2).
8
+ *
9
+ * ADR-3180 §8.1 rule 2: a diagnostic rule may see only PARSED values coming
10
+ * off `src/planning-snapshot.cts`, never raw `.planning/` document text —
11
+ * every diagnostic rule is expected to consume the snapshot's already-parsed
12
+ * fields (via the ADR-3180 §7 owner functions: `getMilestoneInfo`,
13
+ * `listMilestonePhaseDirs`, `isPhaseComplete`, `scanPhasePlans`,
14
+ * `stateFieldValue`, etc.), not re-derive its own view of the filesystem with
15
+ * `platformReadSync(`/`readFileSync(`/`readdirSync(`.
16
+ *
17
+ * `cmdValidateHealth` (`src/verify.cts`) is the one diagnostic-rule-shaped
18
+ * function in the repo today that has NOT yet been migrated onto the
19
+ * snapshot — it predates ADR-3180 and still does its own raw reads for
20
+ * roughly thirty W0xx/W1xx diagnostic codes. Migrating it is Phase 11
21
+ * (#3309), not this phase (#3308) — this guard's job is only to make that
22
+ * acknowledged debt VISIBLE and SHRINK-ONLY via a ratchet baseline, exactly
23
+ * like `scripts/lint-planning-prompt-drift.cjs` and
24
+ * `scripts/lint-state-field-drift.cjs` do for their own re-derivations, so it
25
+ * cannot silently grow while Phase 11 is pending.
26
+ *
27
+ * FUNCTION-SCOPED, not whole-file or whole-repo. `DIAGNOSTIC_RULE_FUNCTIONS`
28
+ * (a `Map<repo-relative file, Set<function name>>`) names the exact functions
29
+ * this rule applies to — the semantic inverse of
30
+ * `lint-completion-ratio-drift.cjs`'s `FUNCTION_SCOPED_EXEMPTIONS` (which
31
+ * names functions a rule does NOT apply to), but the identical data shape and
32
+ * lookup pattern. A raw-read primitive anywhere OUTSIDE a registered function
33
+ * — including elsewhere in the very same file — is not this derivation and is
34
+ * never flagged; `src/verify.cts` itself is full of legitimate raw reads
35
+ * outside `cmdValidateHealth` (health-check plumbing, non-diagnostic-rule
36
+ * helpers) that this guard must not see.
37
+ *
38
+ * Line-to-enclosing-function attribution is a TRIMMED copy of
39
+ * `lint-state-field-drift.cjs`'s `buildFunctionInfo` — the same
40
+ * comment/string-stripping tokenizer (`scanCode`) feeding the same
41
+ * brace-depth function-frame stack, producing the same `innermostAt[line] ->
42
+ * function name | null` attribution — with that guard's LADDER WINDOW
43
+ * co-occurrence logic dropped entirely: this guard only ever needs "which
44
+ * function encloses this line", never a multi-line pattern within one
45
+ * function body.
46
+ *
47
+ * RATCHET, not an allowlist — mirrors `lint-planning-prompt-drift.cjs`'s
48
+ * `diffAgainstBaseline`/`writeBaseline`/`dedupeViolationsForBaseline`/
49
+ * `sortEntries` machinery verbatim (baseline path, owner issue, and
50
+ * `derivation` label are the only differences): a violation whose (file,
51
+ * text) pair is already RECORDED in the baseline is KNOWN and never fails; an
52
+ * unrecorded pair is FRESH and fails; a recorded pair that no longer fires is
53
+ * STALE and ALSO fails, forcing `--update` (run by a maintainer, expected to
54
+ * run to completion — zero remaining entries — once #3309 lands) to prune it.
55
+ *
56
+ * Tree-walk / root-confinement / symlink / sanitizer machinery is shared via
57
+ * `scripts/lib/drift-scan.cjs`, exactly like every sibling guard.
58
+ */
59
+
60
+ const fs = require('node:fs');
61
+ const path = require('node:path');
62
+ const driftScan = require('./lib/drift-scan.cjs');
63
+ const { sanitizeForReport, scanTree } = driftScan;
64
+
65
+ // Authored TypeScript source only (the generated bin/lib/*.cjs mirror it).
66
+ const SCAN_DIRS = ['src'];
67
+ const SCAN_EXT = new Set(['.cts']);
68
+
69
+ const BASELINE_REL_PATH = path.join('scripts', 'baselines', 'planning-snapshot-bypass-baseline.json');
70
+
71
+ // ADR-3180 §8.1 rule 2's acknowledged debt is owned by Phase 11 (#3309, "give
72
+ // cmdValidateHealth the snapshot"), NOT the epic (#3180) itself and NOT
73
+ // Phase 8 (#3218, the sibling prompt-layer guard's owner issue — a different
74
+ // derivation entirely).
75
+ const RATCHET_OWNER_ISSUE = '#3309';
76
+
77
+ // Per ADR-3180 §8.1 rule 2: function-scoped registry of exactly which
78
+ // diagnostic-rule-shaped functions this guard applies to. Not a bare file
79
+ // allowlist (ADR-3180 Decision 4(a) forbids that) — every other function in
80
+ // `src/verify.cts`, and every function in every other file, is scanned like
81
+ // normal code and simply never matches because it is not in this Map.
82
+ const DIAGNOSTIC_RULE_FUNCTIONS = new Map([[path.join('src', 'verify.cts'), new Set(['cmdValidateHealth'])]]);
83
+
84
+ // `scanTree` builds its repo-relative path via `path.relative()`, which uses
85
+ // NATIVE separators: on Windows that is `src\verify.cts`, while
86
+ // `DIAGNOSTIC_RULE_FUNCTIONS` above and the committed baseline both store
87
+ // POSIX paths (`src/verify.cts`). Normalized UNCONDITIONALLY — never gated on
88
+ // `process.platform` — so the POSIX path is never the only tested case (see
89
+ // `lint-planning-prompt-drift.cjs`'s `toPosixRel` for the full rationale;
90
+ // applied here at the same single seam: `findSnapshotBypassDrift` is the only
91
+ // place a repo-relative path enters this guard's violation objects).
92
+ function toPosixRel(relPath) {
93
+ return relPath.replace(/\\/g, '/');
94
+ }
95
+
96
+ // Looks up `DIAGNOSTIC_RULE_FUNCTIONS` by POSIX-normalizing BOTH the incoming
97
+ // `relPath` and each registered key before comparing, so a native-separator
98
+ // caller (Windows) and a POSIX-separator caller (every other platform, and
99
+ // every test in this repo) resolve to the same registered `Set` — see
100
+ // `toPosixRel` above.
101
+ function lookupRegisteredFunctions(relPath) {
102
+ const posix = toPosixRel(relPath);
103
+ for (const [key, fns] of DIAGNOSTIC_RULE_FUNCTIONS) {
104
+ if (toPosixRel(key) === posix) return fns;
105
+ }
106
+ return null;
107
+ }
108
+
109
+ // A raw `.planning/` filesystem read primitive. ADR-3180 §7 owner functions
110
+ // (`getMilestoneInfo`, `listMilestonePhaseDirs`, `isPhaseComplete`,
111
+ // `scanPhasePlans`, `stateFieldValue`, ...) never match this — none of their
112
+ // names or call shapes contain `platformReadSync(`, `readFileSync(`, or
113
+ // `readdirSync(`, so a registered function that has already been migrated
114
+ // onto the snapshot correctly stops producing violations without needing any
115
+ // separate allowlist of "safe" calls.
116
+ const RAW_READ_RE = /platformReadSync\(|readFileSync\(|readdirSync\(/;
117
+
118
+ // `function NAME(` — top-level or nested, matches the DECLARATION line
119
+ // itself (mirrors `lint-state-field-drift.cjs`'s `FUNCTION_DECL_RE`).
120
+ const FUNCTION_DECL_RE = /\bfunction\s+([A-Za-z_$][\w$]*)\s*\(/;
121
+
122
+ // `const NAME = (...): ReturnType => {` — an arrow function assigned to a
123
+ // const, whose own line already carries `=>\s*\{` (mirrors
124
+ // `lint-state-field-drift.cjs`'s `ARROW_CONST_RE`).
125
+ const ARROW_CONST_RE = /\bconst\s+([A-Za-z_$][\w$]*)\s*=\s*\([^)]*\)\s*(?::\s*[^=]+)?=>\s*\{/;
126
+
127
+ /**
128
+ * Strip line-comments, block comments, and the CONTENTS of string/template
129
+ * literals (excluded from brace-depth counting so a brace inside a string
130
+ * never desyncs `depth`) while keeping quoted text verbatim in `detect` (so
131
+ * declaration/call regexes can still see identifiers that happen to sit
132
+ * inside a template literal's interpolation-free text). Trimmed, unchanged
133
+ * copy of `lint-state-field-drift.cjs`'s `scanCode` — this guard needs the
134
+ * identical comment/string-safety, not a domain-specific variant.
135
+ * Returns `{ detect, braces }`, one string per input line.
136
+ */
137
+ function scanCode(lines) {
138
+ const detect = new Array(lines.length);
139
+ const braces = new Array(lines.length);
140
+ let inBlockComment = false;
141
+ let inTemplate = false;
142
+ for (let li = 0; li < lines.length; li++) {
143
+ const line = lines[li];
144
+ let outDetect = '';
145
+ let outBraces = '';
146
+ let i = 0;
147
+ if (inTemplate) {
148
+ const start = i;
149
+ while (i < line.length) {
150
+ if (line[i] === '\\') {
151
+ i += 2;
152
+ continue;
153
+ }
154
+ if (line[i] === '`') {
155
+ i++;
156
+ inTemplate = false;
157
+ break;
158
+ }
159
+ i++;
160
+ }
161
+ outDetect += line.slice(start, i);
162
+ if (inTemplate) {
163
+ detect[li] = outDetect;
164
+ braces[li] = '';
165
+ continue;
166
+ }
167
+ }
168
+ while (i < line.length) {
169
+ if (inBlockComment) {
170
+ const close = line.indexOf('*/', i);
171
+ if (close === -1) {
172
+ i = line.length;
173
+ break;
174
+ }
175
+ i = close + 2;
176
+ inBlockComment = false;
177
+ continue;
178
+ }
179
+ const ch = line[i];
180
+ if (ch === '/' && line[i + 1] === '/') {
181
+ i = line.length;
182
+ break;
183
+ }
184
+ if (ch === '/' && line[i + 1] === '*') {
185
+ inBlockComment = true;
186
+ i += 2;
187
+ continue;
188
+ }
189
+ if (ch === "'" || ch === '"') {
190
+ const quote = ch;
191
+ const start = i;
192
+ let j = i + 1;
193
+ while (j < line.length) {
194
+ if (line[j] === '\\') {
195
+ j += 2;
196
+ continue;
197
+ }
198
+ if (line[j] === quote) {
199
+ j++;
200
+ break;
201
+ }
202
+ j++;
203
+ }
204
+ outDetect += line.slice(start, j);
205
+ i = j;
206
+ continue;
207
+ }
208
+ if (ch === '`') {
209
+ const start = i;
210
+ let j = i + 1;
211
+ let closed = false;
212
+ while (j < line.length) {
213
+ if (line[j] === '\\') {
214
+ j += 2;
215
+ continue;
216
+ }
217
+ if (line[j] === '`') {
218
+ j++;
219
+ closed = true;
220
+ break;
221
+ }
222
+ j++;
223
+ }
224
+ if (!closed) {
225
+ outDetect += line.slice(start);
226
+ inTemplate = true;
227
+ i = line.length;
228
+ break;
229
+ }
230
+ outDetect += line.slice(start, j);
231
+ i = j;
232
+ continue;
233
+ }
234
+ outDetect += ch;
235
+ outBraces += ch;
236
+ i++;
237
+ }
238
+ detect[li] = outDetect;
239
+ braces[li] = outBraces;
240
+ }
241
+ return { detect, braces };
242
+ }
243
+
244
+ /**
245
+ * Trimmed copy of `lint-state-field-drift.cjs`'s `buildFunctionInfo`: a
246
+ * single pass over `lines` maintaining a brace-depth stack of open named
247
+ * function frames, producing `innermostAt[lineIndex] -> function name |
248
+ * null` — which function frame is innermost at each source line. The LADDER
249
+ * WINDOW co-occurrence tracking from the sibling guard is dropped entirely;
250
+ * this guard needs only line-to-enclosing-function attribution.
251
+ */
252
+ function buildFunctionInfo(lines) {
253
+ const { detect, braces } = scanCode(lines);
254
+ const innermostAt = new Array(lines.length).fill(null);
255
+ const stack = []; // { name, openDepth }
256
+ let depth = 0;
257
+ let pendingDeclName = null;
258
+ for (let i = 0; i < lines.length; i++) {
259
+ const detectCode = detect[i];
260
+ const braceCode = braces[i];
261
+
262
+ let immediateName = null;
263
+ if (detectCode.trim()) {
264
+ const arrowMatch = ARROW_CONST_RE.exec(detectCode);
265
+ if (arrowMatch) {
266
+ immediateName = arrowMatch[1];
267
+ } else {
268
+ const declMatch = FUNCTION_DECL_RE.exec(detectCode);
269
+ if (declMatch) pendingDeclName = declMatch[1];
270
+ }
271
+ }
272
+
273
+ const opens = (braceCode.match(/\{/g) || []).length;
274
+ const closes = (braceCode.match(/\}/g) || []).length;
275
+ depth += opens - closes;
276
+
277
+ if (immediateName) stack.push({ name: immediateName, openDepth: depth });
278
+
279
+ if (pendingDeclName) {
280
+ if (opens > 0) {
281
+ stack.push({ name: pendingDeclName, openDepth: depth });
282
+ pendingDeclName = null;
283
+ } else if (detectCode.includes(';')) {
284
+ pendingDeclName = null;
285
+ }
286
+ }
287
+
288
+ while (stack.length > 0 && depth < stack[stack.length - 1].openDepth) stack.pop();
289
+
290
+ innermostAt[i] = stack.length > 0 ? stack[stack.length - 1].name : null;
291
+ }
292
+ return { innermostAt };
293
+ }
294
+
295
+ /**
296
+ * Pure: find every raw `.planning/`-read line inside a
297
+ * `DIAGNOSTIC_RULE_FUNCTIONS`-registered function in `text`. `relPath` is the
298
+ * repo-relative path (native separators or POSIX, either is accepted) — used
299
+ * both as the (POSIX-normalized) `file` on every result and to look up the
300
+ * registered function set for this file. A file with no registered entry
301
+ * short-circuits to `[]` immediately, before any line is scanned.
302
+ * Returns [{ file, line, found, text }] — `file` is always POSIX-separated,
303
+ * `text` is the TRIMMED source line, the same value the baseline keys on.
304
+ */
305
+ function findSnapshotBypassDrift(text, relPath) {
306
+ const registeredFns = lookupRegisteredFunctions(relPath);
307
+ if (!registeredFns) return [];
308
+
309
+ const file = toPosixRel(relPath);
310
+ const lines = text.split('\n');
311
+ const { innermostAt } = buildFunctionInfo(lines);
312
+ const out = [];
313
+ for (let i = 0; i < lines.length; i++) {
314
+ const fn = innermostAt[i];
315
+ if (!fn || !registeredFns.has(fn)) continue;
316
+ const line = lines[i];
317
+ const match = RAW_READ_RE.exec(line);
318
+ if (!match) continue;
319
+ out.push({ file, line: i + 1, found: match[0], text: line.trim() });
320
+ }
321
+ return out;
322
+ }
323
+
324
+ /**
325
+ * Scan the authored source tree and return every registered-function raw-read
326
+ * bypass, each annotated with the (POSIX-normalized) repo-relative file path.
327
+ */
328
+ function scanRepo(root) {
329
+ return scanTree({
330
+ root,
331
+ scanDirs: SCAN_DIRS,
332
+ scanExt: SCAN_EXT,
333
+ onFile(rel, text) {
334
+ return findSnapshotBypassDrift(text, rel);
335
+ },
336
+ });
337
+ }
338
+
339
+ /**
340
+ * Read and parse the ratchet baseline. Returns `{ entries, errors }` —
341
+ * mirrors `lint-planning-prompt-drift.cjs`'s `loadBaseline` verbatim, adapted
342
+ * to this guard's baseline path.
343
+ */
344
+ function loadBaseline(root) {
345
+ const baselinePath = path.join(root, BASELINE_REL_PATH);
346
+ if (!fs.existsSync(baselinePath)) {
347
+ return { entries: [], errors: [`${BASELINE_REL_PATH} is missing — run \`node scripts/lint-planning-snapshot-bypass-drift.cjs --update\` to generate it`] };
348
+ }
349
+ const raw = fs.readFileSync(baselinePath, 'utf8');
350
+ if (raw.trim() === '') {
351
+ return { entries: [], errors: [`${BASELINE_REL_PATH} is present but empty`] };
352
+ }
353
+ let doc;
354
+ try {
355
+ doc = JSON.parse(raw);
356
+ } catch (err) {
357
+ return { entries: [], errors: [`${BASELINE_REL_PATH} is not valid JSON: ${err.message}`] };
358
+ }
359
+ if (doc === null || typeof doc !== 'object' || Array.isArray(doc)) {
360
+ return { entries: [], errors: [`${BASELINE_REL_PATH} must be a JSON object, got ${Array.isArray(doc) ? 'array' : typeof doc}`] };
361
+ }
362
+ if (!Array.isArray(doc.entries)) {
363
+ return { entries: [], errors: [`${BASELINE_REL_PATH}: "entries" must be an array, got ${JSON.stringify(doc.entries)}`] };
364
+ }
365
+ const errors = [];
366
+ const entries = [];
367
+ doc.entries.forEach((entry, i) => {
368
+ const where = `${BASELINE_REL_PATH}.entries[${i}]`;
369
+ if (entry === null || typeof entry !== 'object' || Array.isArray(entry)) {
370
+ errors.push(`${where} must be an object, got ${JSON.stringify(entry)}`);
371
+ return;
372
+ }
373
+ if (typeof entry.file !== 'string' || entry.file === '') {
374
+ errors.push(`${where}.file must be a non-empty string, got ${JSON.stringify(entry.file)}`);
375
+ return;
376
+ }
377
+ if (typeof entry.text !== 'string' || entry.text === '') {
378
+ errors.push(`${where}.text must be a non-empty string, got ${JSON.stringify(entry.text)}`);
379
+ return;
380
+ }
381
+ if (entry.count !== undefined && !(Number.isInteger(entry.count) && entry.count >= 1)) {
382
+ errors.push(`${where}.count must be a positive integer when present, got ${JSON.stringify(entry.count)}`);
383
+ return;
384
+ }
385
+ entries.push(entry);
386
+ });
387
+ return { entries, errors };
388
+ }
389
+
390
+ /**
391
+ * Diff scanned `violations` against baseline `entries`, matched by the pair
392
+ * (`file`, TRIMMED `text`), count-aware — mirrors
393
+ * `lint-planning-prompt-drift.cjs`'s `diffAgainstBaseline` verbatim. See that
394
+ * module's header for the full "COUNT, not duplicate rows" rationale.
395
+ */
396
+ function diffAgainstBaseline(violations, baseline) {
397
+ const key = (file, text) => `${file} ${text}`;
398
+
399
+ const actualByKey = new Map();
400
+ for (const v of violations) {
401
+ const k = key(v.file, v.text);
402
+ let vs = actualByKey.get(k);
403
+ if (!vs) { vs = []; actualByKey.set(k, vs); }
404
+ vs.push(v);
405
+ }
406
+
407
+ const knownKeys = new Set(baseline.map((e) => key(e.file, e.text)));
408
+
409
+ const fresh = [];
410
+ const stale = [];
411
+
412
+ for (const [k, vs] of actualByKey) {
413
+ if (!knownKeys.has(k)) fresh.push(...vs);
414
+ }
415
+
416
+ for (const entry of baseline) {
417
+ const k = key(entry.file, entry.text);
418
+ const expected = entry.count ?? 1;
419
+ const vs = actualByKey.get(k) || [];
420
+ const actual = vs.length;
421
+ if (actual < expected) {
422
+ stale.push({ ...entry, count: expected, actualCount: actual });
423
+ } else if (actual > expected) {
424
+ fresh.push(...vs.slice(expected));
425
+ }
426
+ }
427
+
428
+ return { fresh, stale };
429
+ }
430
+
431
+ /** Stable sort: by `file`, then by `text`. */
432
+ function sortEntries(entries) {
433
+ return [...entries].sort((a, b) => {
434
+ if (a.file !== b.file) return a.file < b.file ? -1 : 1;
435
+ if (a.text !== b.text) return a.text < b.text ? -1 : 1;
436
+ return 0;
437
+ });
438
+ }
439
+
440
+ /**
441
+ * Collapse `violations` into one baseline row per distinct (file, text) pair,
442
+ * carrying a `count` of how many occurrences that pair has in THIS run. Pure;
443
+ * no I/O.
444
+ */
445
+ function dedupeViolationsForBaseline(violations) {
446
+ const order = [];
447
+ const byKey = new Map();
448
+ for (const v of violations) {
449
+ const k = `${v.file} ${v.text}`;
450
+ let entry = byKey.get(k);
451
+ if (!entry) {
452
+ entry = { file: v.file, text: v.text, derivation: 'planning-snapshot-bypass', owner_issue: RATCHET_OWNER_ISSUE, count: 0 };
453
+ byKey.set(k, entry);
454
+ order.push(entry);
455
+ }
456
+ entry.count += 1;
457
+ }
458
+ return order;
459
+ }
460
+
461
+ function writeBaseline(root, violations) {
462
+ const entries = sortEntries(dedupeViolationsForBaseline(violations));
463
+ const doc = {
464
+ $comment:
465
+ 'ADR-3180 §8.1 rule 2 ratchet, owned by Phase 11 (#3309). See scripts/lint-planning-snapshot-bypass-drift.cjs. '
466
+ + 'SHRINK-ONLY: entries are removed as cmdValidateHealth migrates onto src/planning-snapshot.cts; new or '
467
+ + 'changed entries fail lint:ci. `count` is the number of byte-identical (file, text) occurrences '
468
+ + 'acknowledged at this site — a run producing fewer fails as a partial migration, more fails as an '
469
+ + 'unacknowledged new copy.',
470
+ entries,
471
+ };
472
+ const baselinePath = path.join(root, BASELINE_REL_PATH);
473
+ fs.mkdirSync(path.dirname(baselinePath), { recursive: true });
474
+ fs.writeFileSync(baselinePath, `${JSON.stringify(doc, null, 2)}\n`, 'utf8');
475
+ return entries;
476
+ }
477
+
478
+ function main() {
479
+ const root = path.join(__dirname, '..');
480
+ const update = process.argv.includes('--update');
481
+ const violations = scanRepo(root);
482
+
483
+ if (update) {
484
+ const entries = writeBaseline(root, violations);
485
+ process.stdout.write(`ok planning-snapshot-bypass: baseline regenerated with ${entries.length} entr${entries.length === 1 ? 'y' : 'ies'}\n`);
486
+ return;
487
+ }
488
+
489
+ const { entries: baseline, errors } = loadBaseline(root);
490
+ if (errors.length > 0) {
491
+ process.stderr.write('planning-snapshot-bypass: baseline load error(s):\n');
492
+ for (const e of errors) process.stderr.write(` ${e}\n`);
493
+ process.exitCode = 1;
494
+ return;
495
+ }
496
+
497
+ const { fresh, stale } = diffAgainstBaseline(violations, baseline);
498
+
499
+ if (fresh.length === 0 && stale.length === 0) {
500
+ process.stdout.write(`ok planning-snapshot-bypass: no unacknowledged raw .planning/ reads in registered diagnostic-rule functions (${baseline.length} known)\n`);
501
+ return;
502
+ }
503
+
504
+ if (fresh.length > 0) {
505
+ process.stderr.write('planning-snapshot-bypass: NEW raw .planning/ read(s) found inside a DIAGNOSTIC_RULE_FUNCTIONS-registered function.\n');
506
+ process.stderr.write('Route the read through src/planning-snapshot.cts (ADR-3180 §7 owner functions: getMilestoneInfo,\n');
507
+ process.stderr.write('listMilestonePhaseDirs, isPhaseComplete, scanPhasePlans, stateFieldValue, ...) instead of calling\n');
508
+ process.stderr.write(`platformReadSync(/readFileSync(/readdirSync( directly, or add an acknowledged entry to ${BASELINE_REL_PATH}\n`);
509
+ process.stderr.write('via --update:\n');
510
+ for (const v of fresh) {
511
+ process.stderr.write(` ${sanitizeForReport(v.file)}:${v.line} ${sanitizeForReport(v.found)} ${sanitizeForReport(v.text)}\n`);
512
+ }
513
+ }
514
+
515
+ if (stale.length > 0) {
516
+ process.stderr.write('\nplanning-snapshot-bypass: STALE baseline entr' + (stale.length === 1 ? 'y' : 'ies') + " (fully migrated, or a PARTIAL migration — fewer occurrences found than acknowledged; delete or re-record the row):\n");
517
+ for (const e of stale) {
518
+ process.stderr.write(` ${sanitizeForReport(e.file)} ${sanitizeForReport(e.text)} (found ${e.actualCount}/${e.count} acknowledged occurrence${e.count === 1 ? '' : 's'})\n`);
519
+ }
520
+ process.stderr.write(`\n remedy: node scripts/lint-planning-snapshot-bypass-drift.cjs --update\n`);
521
+ }
522
+
523
+ process.exitCode = 1;
524
+ }
525
+
526
+ if (require.main === module) main();
527
+
528
+ module.exports = {
529
+ findSnapshotBypassDrift,
530
+ scanRepo,
531
+ toPosixRel,
532
+ loadBaseline,
533
+ diffAgainstBaseline,
534
+ writeBaseline,
535
+ dedupeViolationsForBaseline,
536
+ sortEntries,
537
+ buildFunctionInfo,
538
+ DIAGNOSTIC_RULE_FUNCTIONS,
539
+ RAW_READ_RE,
540
+ SCAN_DIRS,
541
+ SCAN_EXT,
542
+ BASELINE_REL_PATH,
543
+ RATCHET_OWNER_ISSUE,
544
+ };
@@ -2,7 +2,7 @@
2
2
  'use strict';
3
3
 
4
4
  /**
5
- * lint-regression-test-names.cjs — ban NEW top-level bug-NNNN test files.
5
+ * lint-regression-test-names.cjs — ban NEW top-level bug-NNNN/fix-NNNN/issue-NNNN test files.
6
6
  *
7
7
  * ## Why
8
8
  *
@@ -17,20 +17,22 @@
17
17
  * ## What this enforces
18
18
  *
19
19
  * Identity ratchet (scripts/lib/allowlist-ratchet.cjs) over basenames matching
20
- * /^bug-\d+.*\.test\.cjs$/ in tests/:
21
- * - A NEW bug-* file (not in the allowlist) fails: fold the regression into
22
- * the owning module's test file instead.
23
- * - A REMOVED bug-* file with a stale allowlist entry also fails: prune the
24
- * entry from scripts/lint-regression-test-names.allowlist.json so the
25
- * baseline only ever shrinks.
20
+ * /^(?:bug|fix|issue)-\d+.*\.test\.cjs$/ in tests/:
21
+ * - A NEW bug-*, fix-*, or issue-* file (not in the allowlist) fails: fold
22
+ * the regression into the owning module's test file instead.
23
+ * - A REMOVED bug-*, fix-*, or issue-* file with a stale allowlist entry
24
+ * also fails: prune the entry from
25
+ * scripts/lint-regression-test-names.allowlist.json so the baseline only
26
+ * ever shrinks.
26
27
  *
27
28
  * ## --update (allowlist drift repair)
28
29
  *
29
30
  * `node scripts/lint-regression-test-names.cjs --update` regenerates the
30
31
  * allowlist from the files currently in tests/ and reports what changed.
31
32
  * Use it when the failure is INHERITED drift, not your own new file — e.g.
32
- * the base branch merged bug-* files without feeding the allowlist (the
33
- * #947/#948/#950 race after the ratchet landed), or after a rebase. The
33
+ * the base branch merged bug-*, fix-*, or issue-* files without feeding the
34
+ * allowlist (the #947/#948/#950 race after the ratchet landed), or after a
35
+ * rebase. The
34
36
  * allowlist is a snapshot artifact: regenerate it AFTER rebasing, never
35
37
  * carry a pre-rebase copy through.
36
38
  *
@@ -49,7 +51,7 @@ const ALLOWLIST_PATH =
49
51
  process.env.GSD_LINT_REGRESSION_ALLOWLIST ||
50
52
  path.join(__dirname, 'lint-regression-test-names.allowlist.json');
51
53
 
52
- const BUG_FILE_RE = /^bug-\d+.*\.test\.cjs$/;
54
+ const BUG_FILE_RE = /^(?:bug|fix|issue)-\d+.*\.test\.cjs$/;
53
55
 
54
56
  function main() {
55
57
  const args = process.argv.slice(2);
@@ -97,8 +99,8 @@ function main() {
97
99
  for (const msg of failures) console.error(msg);
98
100
  if (novel.length > 0) {
99
101
  console.error(
100
- '\nIf this PR added the file(s) above: new bug-NNNN test files are not ' +
101
- "accepted — add the regression case to the owning module's test file " +
102
+ '\nIf this PR added the file(s) above: new bug-NNNN/fix-NNNN/issue-NNNN ' +
103
+ "test files are not accepted — add the regression case to the owning module's test file " +
102
104
  "(e.g. a describe('regressions') block in tests/<module>.test.cjs) instead.\n" +
103
105
  'If the file(s) came from the base branch (inherited allowlist drift, ' +
104
106
  'e.g. after a rebase): run ' +
@@ -110,7 +112,7 @@ function main() {
110
112
  }
111
113
 
112
114
  console.log(
113
- `ok lint-regression-test-names: ${current.length} grandfathered bug-* file(s), no novel offenders`
115
+ `ok lint-regression-test-names: ${current.length} grandfathered bug-*/fix-*/issue-* file(s), no novel offenders`
114
116
  );
115
117
  }
116
118