@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,124 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * lint-vendored-deps.cjs — freshness gate for gsd-core/bin/lib/vendor/.
6
+ *
7
+ * #3477 follow-up: gsd-core/bin/** is copied by the installer into trees
8
+ * that have NO node_modules, so it must carry zero external requires
9
+ * (local/no-external-require-in-bin, eslint-rules/no-external-require-in-bin.cjs).
10
+ * `re2js` (src/pattern.cts's RE2 engine) is vendored verbatim under
11
+ * gsd-core/bin/lib/vendor/ instead — see gsd-core/bin/lib/vendor/README.md.
12
+ *
13
+ * A vendored artifact that silently drifts from its upstream package is
14
+ * just as dangerous as never vendoring it in the first place (a stale
15
+ * copy ships a different engine than the one actually reviewed/audited).
16
+ * This guard fails CI when:
17
+ * 1. gsd-core/bin/lib/vendor/re2js.cjs no longer matches
18
+ * node_modules/re2js/build/index.cjs byte-for-byte.
19
+ * 2. gsd-core/bin/lib/vendor/re2js.d.cts no longer matches
20
+ * node_modules/re2js/build/index.d.cts byte-for-byte.
21
+ * 3. src/vendor/re2js.d.cts (the source-side twin tsc needs to resolve
22
+ * types for src/pattern.cts's relative './vendor/re2js.cjs' import —
23
+ * module resolution for a .cts source is relative to src/, not the
24
+ * output dir) no longer matches gsd-core/bin/lib/vendor/re2js.d.cts.
25
+ * 4. The `re2js` version pinned in package.json `devDependencies` no
26
+ * longer matches the version actually installed at
27
+ * node_modules/re2js/package.json (read there, per the dispatch
28
+ * brief, rather than duplicating a second pin).
29
+ *
30
+ * Usage: node scripts/lint-vendored-deps.cjs
31
+ * Exit 0 when every vendored copy is fresh; 1 otherwise.
32
+ */
33
+
34
+ const fs = require('node:fs');
35
+ const path = require('node:path');
36
+ const { ExitError, runMain } = require('./lib/cli-exit.cjs');
37
+
38
+ const ROOT = path.join(__dirname, '..');
39
+
40
+ const REFRESH_COMMAND =
41
+ 'cp node_modules/re2js/build/index.cjs gsd-core/bin/lib/vendor/re2js.cjs && '
42
+ + 'cp node_modules/re2js/build/index.d.cts gsd-core/bin/lib/vendor/re2js.d.cts && '
43
+ + 'cp node_modules/re2js/build/index.d.cts src/vendor/re2js.d.cts';
44
+
45
+ /**
46
+ * Compare two files byte-for-byte. Returns null when equal, or a short
47
+ * mismatch description (missing file / byte-length delta) otherwise.
48
+ * @param {string} relA
49
+ * @param {string} relB
50
+ * @returns {string | null}
51
+ */
52
+ function compareFiles(relA, relB) {
53
+ const absA = path.join(ROOT, relA);
54
+ const absB = path.join(ROOT, relB);
55
+ if (!fs.existsSync(absA)) return `${relA} does not exist`;
56
+ if (!fs.existsSync(absB)) return `${relB} does not exist`;
57
+ const a = fs.readFileSync(absA);
58
+ const b = fs.readFileSync(absB);
59
+ if (a.equals(b)) return null;
60
+ return `${relA} (${a.length} bytes) != ${relB} (${b.length} bytes)`;
61
+ }
62
+
63
+ /**
64
+ * Strip a leading semver range operator (^, ~, >=, >, <=, <, =) from a
65
+ * package.json dependency spec, leaving a bare version.
66
+ * @param {string} spec
67
+ * @returns {string}
68
+ */
69
+ function stripRangeOperator(spec) {
70
+ return String(spec || '').trim().replace(/^[\^~]|^>=|^<=|^>|^<|^=/, '').trim();
71
+ }
72
+
73
+ function main() {
74
+ const findings = [];
75
+
76
+ const cjsDrift = compareFiles('gsd-core/bin/lib/vendor/re2js.cjs', 'node_modules/re2js/build/index.cjs');
77
+ if (cjsDrift) findings.push(cjsDrift);
78
+
79
+ const dctsDrift = compareFiles('gsd-core/bin/lib/vendor/re2js.d.cts', 'node_modules/re2js/build/index.d.cts');
80
+ if (dctsDrift) findings.push(dctsDrift);
81
+
82
+ const srcTwinDrift = compareFiles('src/vendor/re2js.d.cts', 'gsd-core/bin/lib/vendor/re2js.d.cts');
83
+ if (srcTwinDrift) findings.push(srcTwinDrift);
84
+
85
+ const pkgPath = path.join(ROOT, 'package.json');
86
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
87
+ const pinnedSpec = pkg.devDependencies && pkg.devDependencies.re2js;
88
+ if (!pinnedSpec) {
89
+ findings.push('package.json devDependencies.re2js is missing');
90
+ } else {
91
+ const installedPkgPath = path.join(ROOT, 'node_modules', 're2js', 'package.json');
92
+ if (!fs.existsSync(installedPkgPath)) {
93
+ findings.push('node_modules/re2js/package.json does not exist (run npm install)');
94
+ } else {
95
+ const installed = JSON.parse(fs.readFileSync(installedPkgPath, 'utf8'));
96
+ const pinned = stripRangeOperator(pinnedSpec);
97
+ if (pinned !== installed.version) {
98
+ findings.push(
99
+ `package.json devDependencies.re2js ("${pinnedSpec}" -> "${pinned}") != `
100
+ + `node_modules/re2js/package.json version ("${installed.version}")`,
101
+ );
102
+ }
103
+ }
104
+ }
105
+
106
+ if (findings.length > 0) {
107
+ const detail = findings.map((f) => ` ${f}`).join('\n');
108
+ throw new ExitError(
109
+ 1,
110
+ 'lint-vendored-deps: gsd-core/bin/lib/vendor/re2js.* has drifted from its\n'
111
+ + 'upstream package (or its version pin). Refresh with:\n'
112
+ + ` ${REFRESH_COMMAND}\n`
113
+ + 'Findings:\n'
114
+ + detail,
115
+ );
116
+ }
117
+
118
+ process.stdout.write('ok lint-vendored-deps: gsd-core/bin/lib/vendor/re2js.* matches node_modules/re2js and its pinned version\n');
119
+ return 0;
120
+ }
121
+
122
+ if (require.main === module) runMain(main);
123
+
124
+ module.exports = { compareFiles, stripRangeOperator };
@@ -130,6 +130,19 @@ const COVERED = {
130
130
  // minScore raised to TARGET (80) — module now meets ADR-456 goal.
131
131
  minScore: 80,
132
132
  },
133
+ // context-composer: extracted from prompt-budget by #2929. Needs its own entry because
134
+ // mutation coverage does not migrate with relocated code — scoring only prompt-budget.cjs
135
+ // would leave the extracted ladder unmeasured.
136
+ 'context-composer': {
137
+ cjs: 'gsd-core/bin/lib/context-composer.cjs',
138
+ tests: [
139
+ 'tests/prompt-budget-parity.test.cjs',
140
+ 'tests/prompt-budget.unit.test.cjs',
141
+ 'tests/context-composer.test.cjs',
142
+ 'tests/context-composer.property.test.cjs',
143
+ ],
144
+ minScore: 66,
145
+ },
133
146
  'prompt-budget': {
134
147
  cjs: 'gsd-core/bin/lib/prompt-budget.cjs',
135
148
  tests: [
@@ -0,0 +1,63 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Shared truncation-detection helper for `gh pr view --json files` (#3211).
5
+ *
6
+ * GitHub's GraphQL `files` connection on a pull request is a paginated
7
+ * connection. `gh pr view --json files` requests it with a single page at
8
+ * `first: 100` and does NOT paginate through the rest — so any PR touching
9
+ * more than 100 files silently returns only the first 100, with no error and
10
+ * no indication in the `files` array itself that entries are missing.
11
+ * `changedFiles` (a separate, non-paginated scalar field) reports the true
12
+ * total. A policy that relaxes a gate based on "every changed file matches
13
+ * pattern X" must treat a `files` list it cannot confirm is complete as
14
+ * incomplete — i.e. fail closed — rather than silently approving a PR whose
15
+ * 101st+ file might violate the policy.
16
+ *
17
+ * This file lives at the top level of scripts/ rather than in scripts/lib/
18
+ * because scripts/lib/** is enumerated in bin/install.js and ships to users
19
+ * on install; this is CI-only tooling with no reason to be installed.
20
+ */
21
+
22
+ // GitHub's GraphQL `files` connection is requested at `first: 100` by
23
+ // `gh pr view --json files`; `gh` does not paginate this field.
24
+ const FILE_LIST_PAGE_LIMIT = 100;
25
+
26
+ /**
27
+ * Returns true iff `changedFiles` can be trusted as the COMPLETE list of
28
+ * files changed in the PR (i.e. it was not silently truncated).
29
+ *
30
+ * - An empty/non-array list cannot confirm anything about the PR — mirror
31
+ * the fail-closed stance `allPathsAreTooling` already takes on an empty
32
+ * list in scripts/pr-template-policy.cjs.
33
+ * - When the true total is known it is the AUTHORITY, at every list size —
34
+ * not just when the list has hit the page cap. The 100-entry page cap is
35
+ * only ONE way a list can be short of the truth; a `$GITHUB_OUTPUT`
36
+ * heredoc terminated early by an attacker-named file, or a path
37
+ * containing a newline, truncates or inflates the list just as
38
+ * effectively, and at any length. Comparing against the total catches all
39
+ * of them without enumerating the mechanisms.
40
+ * - Only when no total is supplied do we fall back to the page-cap
41
+ * heuristic: a list below the cap cannot have been truncated BY THE CAP,
42
+ * which is the only mechanism a caller without a total can rule out.
43
+ */
44
+ function fileListIsComplete(changedFiles, changedFilesTotal) {
45
+ if (!Array.isArray(changedFiles) || changedFiles.length === 0) return false;
46
+ if (Number.isInteger(changedFilesTotal)) return changedFilesTotal === changedFiles.length;
47
+ return changedFiles.length < FILE_LIST_PAGE_LIMIT;
48
+ }
49
+
50
+ /**
51
+ * Parses a newline-delimited env var (as produced by `git diff --name-only`
52
+ * or `gh pr view --json files -q '.files[].path'`) into a trimmed,
53
+ * empty-line-free array. Returns [] for null/undefined/empty input.
54
+ */
55
+ function parseChangedFilesEnv(value) {
56
+ if (!value) return [];
57
+ return String(value)
58
+ .split(/\r?\n/)
59
+ .map((line) => line.trim())
60
+ .filter(Boolean);
61
+ }
62
+
63
+ module.exports = { fileListIsComplete, FILE_LIST_PAGE_LIMIT, parseChangedFilesEnv };
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  const { matchesGlob } = require('path');
4
+ const { fileListIsComplete, parseChangedFilesEnv } = require('./pr-changed-files.cjs');
4
5
 
5
6
  const TRUSTED_AUTHOR_ASSOCIATIONS = new Set([
6
7
  'CONTRIBUTOR',
@@ -142,8 +143,12 @@ function matchingTemplate(body) {
142
143
  * pattern in the allowlist. Returns false for an empty file list (no
143
144
  * files means we cannot confirm it is a tooling-only PR).
144
145
  */
145
- function allPathsAreTooling(changedFiles, allowlist) {
146
+ function allPathsAreTooling(changedFiles, allowlist, changedFilesTotal) {
146
147
  if (!Array.isArray(changedFiles) || changedFiles.length === 0) return false;
148
+ // #3211: `gh pr view --json files` truncates at 100 entries with no
149
+ // in-band signal. A truncated list must not relax enforcement — an unseen
150
+ // 101st+ file could fall outside the tooling allowlist entirely.
151
+ if (!fileListIsComplete(changedFiles, changedFilesTotal)) return false;
147
152
  return changedFiles.every((file) =>
148
153
  allowlist.some((pattern) => matchesGlob(file, pattern)),
149
154
  );
@@ -159,13 +164,13 @@ function hasExemptMarker(body, regex) {
159
164
  return match[1].trim().length > 0;
160
165
  }
161
166
 
162
- function evaluatePrTemplate(body, authorAssociation, changedFiles) {
167
+ function evaluatePrTemplate(body, authorAssociation, changedFiles, changedFilesTotal) {
163
168
  const association = String(authorAssociation || '').toUpperCase();
164
169
  const trusted = TRUSTED_AUTHOR_ASSOCIATIONS.has(association);
165
170
  const normalizedBody = String(body || '').trim();
166
171
 
167
172
  // --- Carve-out 1: all changed files are in the tooling allowlist ---
168
- if (allPathsAreTooling(changedFiles, TOOLING_PATH_ALLOWLIST)) {
173
+ if (allPathsAreTooling(changedFiles, TOOLING_PATH_ALLOWLIST, changedFilesTotal)) {
169
174
  return {
170
175
  valid: true,
171
176
  action: 'pass',
@@ -235,13 +240,18 @@ function evaluatePrTemplate(body, authorAssociation, changedFiles) {
235
240
  }
236
241
 
237
242
  function main() {
243
+ // Preserve the existing distinction between "unset" (undefined) and
244
+ // "set but empty" ([]) — allPathsAreTooling treats them differently.
238
245
  const changedFiles = process.env.CHANGED_FILES
239
- ? process.env.CHANGED_FILES.split('\n').map((f) => f.trim()).filter(Boolean)
246
+ ? parseChangedFilesEnv(process.env.CHANGED_FILES)
240
247
  : undefined;
248
+ const parsedTotal = Number.parseInt(process.env.CHANGED_FILES_TOTAL, 10);
249
+ const changedFilesTotal = Number.isNaN(parsedTotal) ? undefined : parsedTotal;
241
250
  const result = evaluatePrTemplate(
242
251
  process.env.PR_BODY || '',
243
252
  process.env.AUTHOR_ASSOCIATION || '',
244
253
  changedFiles,
254
+ changedFilesTotal,
245
255
  );
246
256
  process.stdout.write(`${JSON.stringify(result)}\n`);
247
257
  if (process.env.GITHUB_OUTPUT) {
@@ -14,6 +14,19 @@ set -euo pipefail
14
14
 
15
15
  # ─── Patterns ────────────────────────────────────────────────────────────────
16
16
  # Each pattern is a POSIX extended regex. Keep alphabetized by category.
17
+ #
18
+ # Left-boundary prefix `(^|[^[:alnum:]])`: several trigger words are also
19
+ # suffixes of ordinary English words or camelCase identifiers (fact/impact/
20
+ # contract/artifact/interact all end in "act"; retrieval/medieval end in
21
+ # "eval"; blueprint/reprint/fingerprint end in "print"; describeFunction/
22
+ # wrapFunction end in "Function"; Jordan/Sudan end in "dan"), so an
23
+ # unanchored keyword matches as a false-positive substring. `\b` is a GNU
24
+ # grep extension and this script must also run under BSD/macOS grep, so the
25
+ # boundary is spelled out as `(^|[^[:alnum:]])` instead. This never narrows
26
+ # real detections: a genuine attack phrase is always preceded by start-of-
27
+ # line, whitespace, or punctuation, never by another alnum character glued
28
+ # directly onto the keyword. Only patterns whose leading keyword is provably
29
+ # not a real-word suffix are left unanchored (#3175 audit).
17
30
 
18
31
  PATTERNS=(
19
32
  # Instruction override
@@ -27,7 +40,7 @@ PATTERNS=(
27
40
  'you[[:space:]]+are[[:space:]]+now[[:space:]]+(a|an|my)[[:space:]]'
28
41
  'from[[:space:]]+now[[:space:]]+on[[:space:]]+(you|pretend|act|behave)'
29
42
  'pretend[[:space:]]+(you[[:space:]]+are|to[[:space:]]+be)[[:space:]]'
30
- 'act[[:space:]]+as[[:space:]]+(a|an|if|my)[[:space:]]'
43
+ '(^|[^[:alnum:]])act[[:space:]]+as[[:space:]]+(a|an|if|my)[[:space:]]'
31
44
  'roleplay[[:space:]]+as[[:space:]]'
32
45
  'assume[[:space:]]+the[[:space:]]+role[[:space:]]+of[[:space:]]'
33
46
 
@@ -35,7 +48,7 @@ PATTERNS=(
35
48
  'output[[:space:]]+(your|the)[[:space:]]+(system[[:space:]]+)?(prompt|instructions)'
36
49
  'reveal[[:space:]]+(your|the)[[:space:]]+(system[[:space:]]+)?(prompt|instructions)'
37
50
  'show[[:space:]]+me[[:space:]]+(your|the)[[:space:]]+(system[[:space:]]+)?(prompt|instructions)'
38
- 'print[[:space:]]+(your|the)[[:space:]]+(system[[:space:]]+)?(prompt|instructions)'
51
+ '(^|[^[:alnum:]])print[[:space:]]+(your|the)[[:space:]]+(system[[:space:]]+)?(prompt|instructions)'
39
52
  'what[[:space:]]+(is|are)[[:space:]]+(your|the)[[:space:]]+(system[[:space:]]+)?(prompt|instructions)'
40
53
  'repeat[[:space:]]+(your|the|all)[[:space:]]+(system[[:space:]]+)?(prompt|instructions|rules)'
41
54
 
@@ -51,13 +64,30 @@ PATTERNS=(
51
64
  '<</SYS>>'
52
65
 
53
66
  # Tool call injection / code execution in markdown
54
- 'eval[[:space:]]*\([[:space:]]*["\x27]'
55
- 'exec[[:space:]]*\([[:space:]]*["\x27]'
56
- 'Function[[:space:]]*\([[:space:]]*["\x27].*return'
67
+ #
68
+ # The quote-or-apostrophe class below is spelled ["'"'"'"] (a literal `'`
69
+ # via bash's close-quote/escape/reopen idiom), not `["\x27]` — `\x27` is a
70
+ # GNU-grep-only hex escape; BSD/macOS grep treats it as four literal
71
+ # characters (", \, x, 2, 7) and never matches an actual apostrophe, so
72
+ # `eval('...')` (single-quoted) silently went undetected on macOS while
73
+ # passing on GNU-grep CI runners. Found auditing #3175; fixed here since it
74
+ # is the same unanchored/portability defect class as the boundary fix.
75
+ #
76
+ # `exec` stays receiver-blind on purpose. A left boundary that excludes a
77
+ # preceding `.` would drop every member-position `.exec('…')` — including
78
+ # `require('child_process').exec('…')`, the single most common Node spelling
79
+ # of the vector this pattern exists to catch — and a receiver allowlist
80
+ # cannot restore it, because the literal `child_process` is not adjacent to
81
+ # `.exec`. The cost is that `RegExp.prototype.exec`, which takes a subject
82
+ # string rather than code, also matches; files that legitimately call it are
83
+ # handled by ALLOWLIST below, never by narrowing the pattern.
84
+ '(^|[^[:alnum:]])eval[[:space:]]*\([[:space:]]*["'"'"']'
85
+ 'exec[[:space:]]*\([[:space:]]*["'"'"']'
86
+ '(^|[^[:alnum:]])Function[[:space:]]*\([[:space:]]*["'"'"'].*return'
57
87
 
58
88
  # Jailbreak / DAN patterns
59
89
  'do[[:space:]]+anything[[:space:]]+now'
60
- 'DAN[[:space:]]+mode'
90
+ '(^|[^[:alnum:]])DAN[[:space:]]+mode'
61
91
  'developer[[:space:]]+mode[[:space:]]+(enabled|output|activated)'
62
92
  'jailbreak'
63
93
  'bypass[[:space:]]+(safety|content|security)[[:space:]]+(filter|check|rule|guard)'
@@ -108,6 +138,22 @@ ALLOWLIST=(
108
138
  # asserts nothing: it is the payload the guard is required to catch, carried
109
139
  # as test DATA. Same class as the read-injection-scanner suites above.
110
140
  'tests/kimi-payload-field-shadowing.security.test.cjs'
141
+ # Phase-ID grammar regression tests exercise `RegExp.prototype.exec` via
142
+ # `re.exec('<phase-id>')` against fixtures like 'MANIFOLD-64-auth' / 'CK-64-auth'.
143
+ # The scanner's `exec('` code-execution pattern matches that benign method call,
144
+ # not an attack vector — same DEFECT.PROMPT-INJECTION-SCAN-COLLISION class as the
145
+ # test fixtures above. Pre-existing content (16 such calls on `next`); it surfaces
146
+ # here only because #2573's W024 `state_head` assertions make the file appear in
147
+ # the changed-file set the diff-mode scan walks.
148
+ 'tests/health-validation.test.cjs'
149
+ # #2528 — same collision, same disposition: the continuation-grammar suite
150
+ # drives the tokenizer regexes directly via `re.exec('05-80-20')`, so the
151
+ # argument is the subject string, not a command. Exempted per file rather
152
+ # than by narrowing the `exec(` pattern: a left boundary excluding a preceding
153
+ # `.` would drop `require('child_process').exec('…')`, and a receiver
154
+ # allowlist cannot reach it either, because the literal `child_process` is
155
+ # not adjacent to `.exec`. See the note at the pattern itself.
156
+ 'tests/continuation-grammar-parity.test.cjs'
111
157
  )
112
158
 
113
159
  is_allowlisted() {
@@ -0,0 +1,192 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Require-issue-link policy (#3211, preserving #1389).
6
+ *
7
+ * Replaces the shell-only `grep -qiE '(closes|fixes|resolves)\s+#[0-9]+'`
8
+ * step with a pure, testable `evaluateIssueLink` verdict function so a PR
9
+ * that only REFERENCES an issue (rather than closing it — e.g. a follow-up
10
+ * regression-coverage PR) is not forced to fabricate a closing keyword, while
11
+ * two pre-existing constraints are preserved exactly:
12
+ *
13
+ * 1. (#1389, anti-forgery) The backmerge exemption only fires when the
14
+ * head ref carries the backmerge prefix AND the PR is same-repo (not a
15
+ * fork) — dropping the `sameRepo` conjunct would let a fork forge a
16
+ * branch name to bypass the issue-link requirement entirely.
17
+ * 2. (#3211, truncation) `gh pr view --json files` truncates the file list
18
+ * at 100 entries with no in-band signal that it did so (see
19
+ * scripts/pr-changed-files.cjs). The reference-only carve-out below
20
+ * only applies when every changed file is a test/doc file, so a PR
21
+ * whose file list may be truncated must fail closed rather than let an
22
+ * unseen 101st+ file (which could touch `src/`) slip through.
23
+ *
24
+ * Tests assert on the typed ISSUE_LINK_REASON enum, never on free text.
25
+ */
26
+
27
+ const { fileListIsComplete, parseChangedFilesEnv } = require('./pr-changed-files.cjs');
28
+ const { runMain } = require('./lib/cli-exit.cjs');
29
+
30
+ const ISSUE_LINK_REASON = Object.freeze({
31
+ OK_CLOSING_KEYWORD: 'ok_closing_keyword',
32
+ OK_BACKMERGE_EXEMPT: 'ok_backmerge_exempt',
33
+ OK_FOLLOWUP_REFERENCE: 'ok_followup_reference',
34
+ FAIL_NO_ISSUE_REFERENCE: 'fail_no_issue_reference',
35
+ FAIL_REFERENCE_NEEDS_CLOSING: 'fail_reference_needs_closing',
36
+ FAIL_FILE_LIST_INCOMPLETE: 'fail_file_list_incomplete',
37
+ });
38
+
39
+ // #1389: backmerge PRs are opened by CI against `next`, never by a human or a
40
+ // fork, so they are exempt from the issue-link requirement outright — but
41
+ // ONLY when combined with `sameRepo === true` below (see header comment).
42
+ const BACKMERGE_BRANCH_PREFIX = 'chore/backmerge-main-to-next-';
43
+
44
+ // A follow-up-only PR (references an issue without closing it) is only
45
+ // allowed to skip the closing keyword when every changed file is a test or
46
+ // doc file — i.e. it cannot be the PR that actually implements the fix.
47
+ const EXEMPT_PATH_PREFIXES = ['tests/', 'docs/'];
48
+
49
+ // Root-level markdown is documentation — this mirrors the repo's own doc-only
50
+ // classifier (.claude/hooks/pre-pr-gate.sh:111), whose `[^/]+\.md` anchor is
51
+ // deliberately root-only so runtime-loaded text under a subdirectory
52
+ // (gsd-core/workflows/*.md, agents/*.md, commands/**/*.md) stays gated.
53
+ // CHANGELOG.md is excluded: scripts/changeset/lint.cjs classes a direct edit to
54
+ // it as user-facing precisely to close a bypass, so it must not ride in on the
55
+ // docs carve-out either.
56
+ const EXCLUDED_ROOT_DOCS = new Set(['CHANGELOG.md']);
57
+
58
+ // Case-insensitive lookup set derived from EXCLUDED_ROOT_DOCS. The exclusion
59
+ // check below is case-insensitive because the `.md` extension test above it
60
+ // already is (`/\.md$/i`) — `changelog.md` or `CHANGELOG.MD` would otherwise
61
+ // slip past the exclusion while still passing the extension test. In
62
+ // practice this is defense in depth rather than a live bypass: the GitHub
63
+ // API always reports the real path with its actual, fixed casing (the
64
+ // filesystem is case-sensitive on the runners this executes on), so a PR
65
+ // cannot rename CHANGELOG.md to bypass the check by casing alone.
66
+ const EXCLUDED_ROOT_DOCS_UPPER = new Set(
67
+ Array.from(EXCLUDED_ROOT_DOCS, (name) => name.toUpperCase()),
68
+ );
69
+
70
+ function isRootLevelDoc(normalizedPath) {
71
+ if (!normalizedPath) return false;
72
+ if (normalizedPath.includes('/')) return false;
73
+ if (!/\.md$/i.test(normalizedPath)) return false;
74
+ if (EXCLUDED_ROOT_DOCS_UPPER.has(normalizedPath.toUpperCase())) return false;
75
+ return true;
76
+ }
77
+
78
+ // Mirrors the shipped `grep -qiE '(closes|fixes|resolves)\s+#[0-9]+'` exactly:
79
+ // no additional keywords, no `\b` anchors (the shell grep it replaces has
80
+ // none either) — see the corpus-parity test in
81
+ // tests/require-issue-link-policy.test.cjs.
82
+ const CLOSING_KEYWORD_REGEX = /(?:closes|fixes|resolves)\s+#[0-9]+/i;
83
+
84
+ // Accepts a soft "this PR relates to #N" reference without claiming to close
85
+ // it. The leading `\b` prevents matching inside a longer word (e.g. `xref#1`,
86
+ // `prefs #1`) because there is no word-boundary transition between the
87
+ // preceding word character and the start of the alternative — verified
88
+ // explicitly for `preferences #1` / `unreferenced #1` in the test suite.
89
+ const FOLLOWUP_REFERENCE_REGEX = /\b(?:refs?|references?|relates\s+to|related\s+to|follow[-\s]?up\s+to)\s+#[0-9]+/i;
90
+
91
+ function hasClosingKeyword(body) {
92
+ return CLOSING_KEYWORD_REGEX.test(String(body || ''));
93
+ }
94
+
95
+ function hasFollowUpReference(body) {
96
+ return FOLLOWUP_REFERENCE_REGEX.test(String(body || ''));
97
+ }
98
+
99
+ // Path separator normalization — unconditional, per repo convention (see
100
+ // CLAUDE.md "Path-sep normalization"), so a Windows-style diff entry like
101
+ // `tests\windows\a.test.cjs` is still recognized as tests/-prefixed.
102
+ function normalizePath(p) {
103
+ return String(p).replace(/\\/g, '/');
104
+ }
105
+
106
+ /**
107
+ * Returns true iff every changed file qualifies as a test/doc file, i.e. each
108
+ * one is one of:
109
+ * 1. under one of EXEMPT_PATH_PREFIXES (`tests/`, `docs/`) — the trailing
110
+ * slash on each prefix makes this directory-boundary aware, so
111
+ * lookalikes like `tests-e2e/`, `testsuite/`, or `docsite/` are
112
+ * correctly rejected (they are not `tests/` or `docs/`); or
113
+ * 2. a root-level markdown file (no `/`, `.md` extension, case-insensitive)
114
+ * that is not in EXCLUDED_ROOT_DOCS — see isRootLevelDoc.
115
+ */
116
+ function allPathsAreTestsOrDocs(changedFiles) {
117
+ if (!Array.isArray(changedFiles) || changedFiles.length === 0) return false;
118
+ return changedFiles.every((file) => {
119
+ const normalized = normalizePath(file);
120
+ if (EXEMPT_PATH_PREFIXES.some((prefix) => normalized.startsWith(prefix))) return true;
121
+ return isRootLevelDoc(normalized);
122
+ });
123
+ }
124
+
125
+ function evaluateIssueLink({ prBody, headRef, sameRepo, changedFiles, changedFilesTotal }) {
126
+ if (hasClosingKeyword(prBody)) {
127
+ return { ok: true, reason: ISSUE_LINK_REASON.OK_CLOSING_KEYWORD };
128
+ }
129
+
130
+ // #1389: `sameRepo === true` is required, not just the branch-name prefix —
131
+ // a fork PR could otherwise name its branch to forge this exemption.
132
+ if (String(headRef || '').startsWith(BACKMERGE_BRANCH_PREFIX) && sameRepo === true) {
133
+ return { ok: true, reason: ISSUE_LINK_REASON.OK_BACKMERGE_EXEMPT };
134
+ }
135
+
136
+ if (!hasFollowUpReference(prBody)) {
137
+ return { ok: false, reason: ISSUE_LINK_REASON.FAIL_NO_ISSUE_REFERENCE };
138
+ }
139
+
140
+ // #3211: a truncated file list cannot be trusted to prove "tests/docs
141
+ // only" — fail closed rather than risk approving on an unseen file.
142
+ if (!fileListIsComplete(changedFiles, changedFilesTotal)) {
143
+ return { ok: false, reason: ISSUE_LINK_REASON.FAIL_FILE_LIST_INCOMPLETE };
144
+ }
145
+
146
+ if (allPathsAreTestsOrDocs(changedFiles)) {
147
+ return { ok: true, reason: ISSUE_LINK_REASON.OK_FOLLOWUP_REFERENCE };
148
+ }
149
+
150
+ return { ok: false, reason: ISSUE_LINK_REASON.FAIL_REFERENCE_NEEDS_CLOSING };
151
+ }
152
+
153
+ function main() {
154
+ const changedFiles = parseChangedFilesEnv(process.env.CHANGED_FILES);
155
+ const parsedTotal = Number.parseInt(process.env.CHANGED_FILES_TOTAL, 10);
156
+ const changedFilesTotal = Number.isNaN(parsedTotal) ? undefined : parsedTotal;
157
+
158
+ const result = evaluateIssueLink({
159
+ prBody: process.env.PR_BODY || '',
160
+ headRef: process.env.HEAD_REF || '',
161
+ sameRepo: process.env.SAME_REPO === 'true',
162
+ changedFiles,
163
+ changedFilesTotal,
164
+ });
165
+
166
+ process.stdout.write(`${JSON.stringify(result)}\n`);
167
+ if (process.env.GITHUB_OUTPUT) {
168
+ const fs = require('node:fs');
169
+ fs.appendFileSync(process.env.GITHUB_OUTPUT, `ok=${result.ok ? 'true' : 'false'}\n`);
170
+ fs.appendFileSync(process.env.GITHUB_OUTPUT, `reason=${result.reason}\n`);
171
+ fs.appendFileSync(process.env.GITHUB_OUTPUT, `result=${JSON.stringify(result)}\n`);
172
+ }
173
+
174
+ return result.ok ? 0 : 1;
175
+ }
176
+
177
+ if (require.main === module) runMain(main);
178
+
179
+ module.exports = {
180
+ ISSUE_LINK_REASON,
181
+ BACKMERGE_BRANCH_PREFIX,
182
+ EXEMPT_PATH_PREFIXES,
183
+ EXCLUDED_ROOT_DOCS,
184
+ CLOSING_KEYWORD_REGEX,
185
+ FOLLOWUP_REFERENCE_REGEX,
186
+ hasClosingKeyword,
187
+ hasFollowUpReference,
188
+ normalizePath,
189
+ isRootLevelDoc,
190
+ allPathsAreTestsOrDocs,
191
+ evaluateIssueLink,
192
+ };
@@ -0,0 +1,19 @@
1
+ {
2
+ "_comment": "ADR-3408 Decision 5 write-seam ratchet baseline (issue #3468, Phase 1; Phase 2 / #3469 lands the single write seam and Amendment 2). Every entry here is a `writeStateMd(`/`syncStateFrontmatter(`/`applyPostSyncPreservation(` bypass this guard found by a whole-repo scan (Decision 4(a)) — it is ACKNOWLEDGED, not endorsed: acknowledgment is in writing (this file), with the issue owning its removal recorded in the entry's \"owner\" field. This baseline is SHRINK-ONLY — an entry that stops firing goes STALE and fails the plain run until `--baseline` is re-run to drop it (ADR-3180 Decision 4(e)'s \"the baseline may only shrink\", adopted verbatim by ADR-3408). Phase 2 (#3469) removed the `cmdPhaseComplete` (`src/phase.cts`) and `cmdMilestoneComplete` (`src/milestone.cts`) entries by routing both through the single write-seam composition (`syncAndPreserveStateMd`, `src/state.cts`). ADR-3408 Amendment 2: \"0 bypasses\" was never this baseline's target — TWO entries are SANCTIONED PERMANENT, not debt, and Phase 4 (#3471) does NOT drive this file to empty: `cmdStateSync` (`src/state.cts`) exists precisely to let the body win (#905 — `state sync` re-derives frontmatter FROM the body), so routing it through preservation would invert the command rather than fix a bug; `REGENERATE_STATE` (`src/health-diagnostic.cts`) is `/gsd-health --repair`'s factory reset, which rebuilds STATE.md from scratch, so preservation would restore exactly the values it was invoked to discard. Neither entry may be \"consolidated\" away — a guard reporting them is reporting correctly, and a change that removes one is a regression, not progress.",
3
+ "entries": [
4
+ {
5
+ "file": "src/health-diagnostic.cts",
6
+ "source": "writeStateMd(statePath, stateContent, cwd);",
7
+ "symbol": "writeStateMd",
8
+ "count": 1,
9
+ "owner": "sanctioned-permanent"
10
+ },
11
+ {
12
+ "file": "src/state.cts",
13
+ "source": "writeStateMd(statePath, modified, cwd);",
14
+ "symbol": "writeStateMd",
15
+ "count": 1,
16
+ "owner": "sanctioned-permanent"
17
+ }
18
+ ]
19
+ }
@@ -19,6 +19,8 @@
19
19
  const fs = require('node:fs');
20
20
  const path = require('node:path');
21
21
 
22
+ const { escapeRegex: escapeRegExp } = require('../gsd-core/bin/lib/pattern.cjs');
23
+
22
24
  const WORKFLOWS_DIR = path.join(__dirname, '..', 'gsd-core', 'workflows');
23
25
  const AGENTS_DIR = path.join(__dirname, '..', 'agents');
24
26
  const SNIPPET_FILE = path.join(WORKFLOWS_DIR, '_runtime-launcher.snippet.sh');
@@ -373,10 +375,6 @@ function transformFile(content, preamble) {
373
375
  return outputLines.join('\n');
374
376
  }
375
377
 
376
- function escapeRegExp(str) {
377
- return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
378
- }
379
-
380
378
  /**
381
379
  * A file "delegates to the shared resolver" when it pulls the canonical gsd_run
382
380
  * preamble in from gsd-core/references/gsd-run-resolver.md via an @-include
@@ -2,7 +2,6 @@
2
2
  name: gsd-autonomous
3
3
  description: "Run all remaining phases autonomously — discuss→plan→execute per phase"
4
4
  argument-hint: "[--from N] [--to N] [--only N] [--interactive] [--converge]"
5
- effort: max
6
5
  allowed-tools:
7
6
  - Read
8
7
  - Write
@@ -41,7 +41,7 @@ Optional flags parsed from $ARGUMENTS:
41
41
  - `--depth=VALUE` — Depth override (quick|standard|deep). If provided, overrides workflow.code_review_depth config.
42
42
  - `--files=file1,file2,...` — Explicit file list override. Has highest precedence for file scoping per D-08. When provided, workflow skips SUMMARY.md extraction and git diff fallback entirely.
43
43
 
44
- Context files (CLAUDE.md, SUMMARY.md, phase state) are resolved inside the workflow via `gsd-tools query init.phase-op` and delegated to agent via `<files_to_read>` blocks.
44
+ Context files (CLAUDE.md, SUMMARY.md, phase state) are resolved inside the workflow via `gsd-tools query init.phase-op` and delegated to agent via `<required_reading>` blocks.
45
45
  </context>
46
46
 
47
47
  <process>
@@ -2,7 +2,6 @@
2
2
  name: gsd-execute-phase
3
3
  description: "Execute all plans in a phase with wave-based parallelization"
4
4
  argument-hint: "<phase-number> [--wave N] [--gaps-only] [--interactive] [--tdd]"
5
- effort: max
6
5
  allowed-tools:
7
6
  - Read
8
7
  - Write
@@ -56,7 +55,7 @@ Phase: $ARGUMENTS
56
55
  - If none of these tokens appear, run the standard full-phase execution flow with no flag-specific filtering
57
56
  - Do not infer that a flag is active just because it is documented in this prompt
58
57
 
59
- Context files are resolved inside the workflow via `gsd-tools query init.execute-phase` and per-subagent `<files_to_read>` blocks.
58
+ Context files are resolved inside the workflow via `gsd-tools query init.execute-phase` and per-subagent `<required_reading>` blocks.
60
59
  </context>
61
60
 
62
61
  <process>
@@ -34,7 +34,7 @@ Output: .planning/codebase/ folder with 7 structured documents about the codebas
34
34
  Arguments: $ARGUMENTS
35
35
 
36
36
  Parse the first token of $ARGUMENTS:
37
- - If it is `--fast`: strip the flag, run the scan workflow (passing remaining args including optional --focus).
37
+ - If it is `--fast`: strip the flag, then read and execute `~/.claude/gsd-core/workflows/scan.md` (passing remaining args including optional --focus). Load it on demand here — it is deliberately not in `<execution_context>`, so the common full-map path does not pay for it.
38
38
  - If it is `--query`: strip the flag, run the intel workflow (passing remaining args as the subcommand).
39
39
  - Otherwise: pass all of $ARGUMENTS as focus area to the map-codebase workflow.
40
40