@opengsd/gsd-core 1.18.0 → 1.20.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 (308) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +2 -1
  3. package/.opencode/plugins/gsd-core.js +2 -3
  4. package/agents/gsd-code-fixer.compact.md +7 -7
  5. package/agents/gsd-code-fixer.md +13 -7
  6. package/agents/gsd-code-reviewer.compact.md +49 -8
  7. package/agents/gsd-code-reviewer.md +53 -14
  8. package/agents/gsd-executor.md +8 -4
  9. package/agents/gsd-verifier.md +1 -1
  10. package/bin/install.js +422 -103
  11. package/commands/gsd/update.md +5 -2
  12. package/gsd-core/bin/gsd-tools.cjs +179 -30
  13. package/gsd-core/bin/lib/agent-install-check.cjs +29 -3
  14. package/gsd-core/bin/lib/args-terminator.cjs +114 -0
  15. package/gsd-core/bin/lib/broken-windows.cjs +59 -1
  16. package/gsd-core/bin/lib/capability-lifecycle.cjs +1 -12
  17. package/gsd-core/bin/lib/capability-lock.cjs +13 -2
  18. package/gsd-core/bin/lib/capability-registry.cjs +376 -103
  19. package/gsd-core/bin/lib/capability-state.cjs +14 -8
  20. package/gsd-core/bin/lib/capability-validator.cjs +32 -0
  21. package/gsd-core/bin/lib/commands.cjs +27 -17
  22. package/gsd-core/bin/lib/config-loader.cjs +13 -3
  23. package/gsd-core/bin/lib/config.cjs +256 -58
  24. package/gsd-core/bin/lib/core-utils.cjs +45 -0
  25. package/gsd-core/bin/lib/decision-coverage-support.cjs +74 -0
  26. package/gsd-core/bin/lib/frontmatter.cjs +21 -3
  27. package/gsd-core/bin/lib/gate-api-coverage-verify-pre.cjs +2 -2
  28. package/gsd-core/bin/lib/gate-context-drift.cjs +9 -4
  29. package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +6 -1
  30. package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +6 -1
  31. package/gsd-core/bin/lib/gate-evaluation-scope.cjs +134 -13
  32. package/gsd-core/bin/lib/gate-phase-context.cjs +7 -8
  33. package/gsd-core/bin/lib/gate-schema-drift.cjs +1 -1
  34. package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +63 -22
  35. package/gsd-core/bin/lib/gate-ui-safety.cjs +23 -5
  36. package/gsd-core/bin/lib/git-info-exclude.cjs +52 -0
  37. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +31 -12
  38. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +9 -7
  39. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +3 -3
  40. package/gsd-core/bin/lib/health-diagnostic-types.cjs +4 -0
  41. package/gsd-core/bin/lib/health-diagnostic.cjs +52 -1
  42. package/gsd-core/bin/lib/host-integration.cjs +13 -6
  43. package/gsd-core/bin/lib/init.cjs +245 -68
  44. package/gsd-core/bin/lib/install-engine.cjs +246 -32
  45. package/gsd-core/bin/lib/install-profiles.cjs +9 -4
  46. package/gsd-core/bin/lib/installer-migration-report.cjs +2 -0
  47. package/gsd-core/bin/lib/io.cjs +6 -0
  48. package/gsd-core/bin/lib/markdown-line-diff.cjs +147 -0
  49. package/gsd-core/bin/lib/markdown-sectionizer.cjs +147 -1
  50. package/gsd-core/bin/lib/markdown-table.cjs +23 -0
  51. package/gsd-core/bin/lib/mcp-server.cjs +8 -5
  52. package/gsd-core/bin/lib/milestone-lock.cjs +8 -1
  53. package/gsd-core/bin/lib/milestone-release-map.cjs +244 -0
  54. package/gsd-core/bin/lib/milestone-release-tags.cjs +440 -0
  55. package/gsd-core/bin/lib/milestone.cjs +61 -51
  56. package/gsd-core/bin/lib/model-catalog.cjs +8 -0
  57. package/gsd-core/bin/lib/normalize-test-command.cjs +67 -0
  58. package/gsd-core/bin/lib/phase-entry-grammar.cjs +119 -0
  59. package/gsd-core/bin/lib/phase-id-display.cjs +2 -0
  60. package/gsd-core/bin/lib/phase-id.cjs +629 -38
  61. package/gsd-core/bin/lib/phase-lifecycle.cjs +12 -44
  62. package/gsd-core/bin/lib/phase-locator.cjs +328 -19
  63. package/gsd-core/bin/lib/phase.cjs +3066 -653
  64. package/gsd-core/bin/lib/planning-document.cjs +26 -5
  65. package/gsd-core/bin/lib/planning-inspect.cjs +27 -35
  66. package/gsd-core/bin/lib/planning-snapshot.cjs +15 -8
  67. package/gsd-core/bin/lib/planning-workspace.cjs +21 -3
  68. package/gsd-core/bin/lib/pristine-baseline.cjs +11 -3
  69. package/gsd-core/bin/lib/read-only-git-env.cjs +79 -0
  70. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +2 -2
  71. package/gsd-core/bin/lib/review-lane-descriptor.cjs +57 -2
  72. package/gsd-core/bin/lib/review-lane-invocation.cjs +125 -18
  73. package/gsd-core/bin/lib/review-lane-runner.cjs +33 -4
  74. package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +11 -9
  75. package/gsd-core/bin/lib/roadmap-parser.cjs +735 -137
  76. package/gsd-core/bin/lib/roadmap-progress.cjs +136 -0
  77. package/gsd-core/bin/lib/roadmap-upgrade.cjs +47 -7
  78. package/gsd-core/bin/lib/roadmap.cjs +383 -123
  79. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +381 -14
  80. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +8 -4
  81. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +60 -16
  82. package/gsd-core/bin/lib/shell-command-projection.cjs +151 -19
  83. package/gsd-core/bin/lib/smart-entry.cjs +35 -11
  84. package/gsd-core/bin/lib/state-contract.cjs +39 -15
  85. package/gsd-core/bin/lib/state-document.cjs +344 -27
  86. package/gsd-core/bin/lib/state-transition.cjs +156 -32
  87. package/gsd-core/bin/lib/state.cjs +527 -186
  88. package/gsd-core/bin/lib/surface.cjs +9 -0
  89. package/gsd-core/bin/lib/task-command-router.cjs +5 -25
  90. package/gsd-core/bin/lib/task-content-resolution.cjs +46 -1
  91. package/gsd-core/bin/lib/template.cjs +2 -2
  92. package/gsd-core/bin/lib/validate.cjs +3 -2
  93. package/gsd-core/bin/lib/vendor/re2js.cjs +863 -577
  94. package/gsd-core/bin/lib/vendor/re2js.d.cts +284 -219
  95. package/gsd-core/bin/lib/verification.cjs +96 -9
  96. package/gsd-core/bin/lib/verify.cjs +4 -2
  97. package/gsd-core/bin/lib/workstream-inventory.cjs +26 -20
  98. package/gsd-core/bin/lib/worktree-safety.cjs +22 -1
  99. package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
  100. package/gsd-core/bin/shared/config-schema.manifest.json +1 -0
  101. package/gsd-core/bin/verify-reapply-patches.cjs +411 -10
  102. package/gsd-core/references/cicd-review-patterns.md +45 -0
  103. package/gsd-core/references/git-integration.md +11 -0
  104. package/gsd-core/references/loop-hook-dispatch.md +13 -6
  105. package/gsd-core/references/necessity-review.md +48 -0
  106. package/gsd-core/references/planning-config.md +2 -0
  107. package/gsd-core/references/tdd.md +2 -2
  108. package/gsd-core/templates/DEBUG.md +16 -0
  109. package/gsd-core/templates/config.json +2 -1
  110. package/gsd-core/templates/verification-report.md +1 -1
  111. package/gsd-core/workflows/audit-fix.md +2 -1
  112. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +10 -1
  113. package/gsd-core/workflows/code-review-fix.md +46 -5
  114. package/gsd-core/workflows/code-review.md +64 -7
  115. package/gsd-core/workflows/complete-milestone/steps/project-md-evolution-review.md +145 -0
  116. package/gsd-core/workflows/complete-milestone/steps/release-map.md +80 -0
  117. package/gsd-core/workflows/complete-milestone.md +37 -150
  118. package/gsd-core/workflows/diagnose-issues.md +29 -5
  119. package/gsd-core/workflows/discuss-phase/modes/power.md +1 -1
  120. package/gsd-core/workflows/discuss-phase-assumptions.md +33 -2
  121. package/gsd-core/workflows/discuss-phase-power.md +19 -2
  122. package/gsd-core/workflows/discuss-phase.md +6 -10
  123. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +13 -7
  124. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +7 -2
  125. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -1
  126. package/gsd-core/workflows/execute-phase/steps/verify-phase-goal.md +2 -2
  127. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +11 -2
  128. package/gsd-core/workflows/execute-phase.md +10 -6
  129. package/gsd-core/workflows/execute-plan.md +1 -1
  130. package/gsd-core/workflows/health.md +2 -1
  131. package/gsd-core/workflows/import.md +1 -1
  132. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +2 -2
  133. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +13 -9
  134. package/gsd-core/workflows/plan-phase.md +14 -14
  135. package/gsd-core/workflows/pr-branch.md +2 -0
  136. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +1 -1
  137. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +8 -1
  138. package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +2 -1
  139. package/gsd-core/workflows/quick.md +1 -1
  140. package/gsd-core/workflows/reapply-patches.md +104 -0
  141. package/gsd-core/workflows/review.md +70 -154
  142. package/gsd-core/workflows/section-manifest.json +5 -0
  143. package/gsd-core/workflows/secure-phase.md +1 -1
  144. package/gsd-core/workflows/settings-advanced.md +57 -9
  145. package/gsd-core/workflows/settings.md +1 -0
  146. package/gsd-core/workflows/ship.md +15 -4
  147. package/gsd-core/workflows/spec-phase.md +3 -6
  148. package/gsd-core/workflows/verify-work.md +4 -3
  149. package/hooks/dist/gsd-agent-isolation-guard.js +11 -1
  150. package/hooks/dist/gsd-cursor-subagent-start.js +6 -2
  151. package/hooks/dist/gsd-graphify-update.sh +6 -0
  152. package/hooks/dist/gsd-path-export.js +95 -0
  153. package/hooks/dist/gsd-secret-read-guard.js +172 -39
  154. package/hooks/dist/gsd-statusline.js +37 -3
  155. package/hooks/dist/gsd-windsurf-pre-write.js +29 -2
  156. package/hooks/dist/gsd-workflow-guard.js +3 -2
  157. package/hooks/dist/gsd-worktree-path-guard.js +29 -2
  158. package/hooks/dist/lib/git-probe.js +16 -0
  159. package/hooks/dist/lib/isolation-sentinel.js +44 -0
  160. package/hooks/dist/managed-hooks-registry.cjs +1 -0
  161. package/hooks/gsd-agent-isolation-guard.js +11 -1
  162. package/hooks/gsd-cursor-subagent-start.js +6 -2
  163. package/hooks/gsd-graphify-update.sh +6 -0
  164. package/hooks/gsd-path-export.js +95 -0
  165. package/hooks/gsd-secret-read-guard.js +172 -39
  166. package/hooks/gsd-statusline.js +37 -3
  167. package/hooks/gsd-windsurf-pre-write.js +29 -2
  168. package/hooks/gsd-workflow-guard.js +3 -2
  169. package/hooks/gsd-worktree-path-guard.js +29 -2
  170. package/hooks/hooks.json +1 -0
  171. package/hooks/lib/git-probe.js +16 -0
  172. package/hooks/lib/isolation-sentinel.js +44 -0
  173. package/hooks/managed-hooks-registry.cjs +1 -0
  174. package/package.json +15 -8
  175. package/pi/gsd.cjs +1047 -10
  176. package/pi/package-agents/gsd-advisor-researcher.md +113 -0
  177. package/pi/package-agents/gsd-ai-researcher.md +111 -0
  178. package/pi/package-agents/gsd-assumptions-analyzer.md +110 -0
  179. package/pi/package-agents/gsd-code-fixer.md +774 -0
  180. package/pi/package-agents/gsd-code-reviewer.md +441 -0
  181. package/pi/package-agents/gsd-codebase-mapper.md +850 -0
  182. package/pi/package-agents/gsd-debug-session-manager.md +416 -0
  183. package/pi/package-agents/gsd-debugger.md +1274 -0
  184. package/pi/package-agents/gsd-doc-classifier.md +269 -0
  185. package/pi/package-agents/gsd-doc-synthesizer.md +259 -0
  186. package/pi/package-agents/gsd-doc-verifier.md +212 -0
  187. package/pi/package-agents/gsd-doc-writer.md +626 -0
  188. package/pi/package-agents/gsd-dom-verifier.md +164 -0
  189. package/pi/package-agents/gsd-domain-researcher.md +144 -0
  190. package/pi/package-agents/gsd-eval-auditor.md +185 -0
  191. package/pi/package-agents/gsd-eval-planner.md +149 -0
  192. package/pi/package-agents/gsd-executor.md +898 -0
  193. package/pi/package-agents/gsd-framework-selector.md +159 -0
  194. package/pi/package-agents/gsd-integration-checker.md +476 -0
  195. package/pi/package-agents/gsd-intel-updater.md +338 -0
  196. package/pi/package-agents/gsd-mempalace-curator.md +49 -0
  197. package/pi/package-agents/gsd-nyquist-auditor.md +200 -0
  198. package/pi/package-agents/gsd-pattern-mapper.md +340 -0
  199. package/pi/package-agents/gsd-phase-researcher.md +897 -0
  200. package/pi/package-agents/gsd-plan-checker.md +1077 -0
  201. package/pi/package-agents/gsd-planner.md +1003 -0
  202. package/pi/package-agents/gsd-project-researcher.md +611 -0
  203. package/pi/package-agents/gsd-research-synthesizer.md +259 -0
  204. package/pi/package-agents/gsd-roadmapper.md +779 -0
  205. package/pi/package-agents/gsd-security-auditor.md +171 -0
  206. package/pi/package-agents/gsd-ui-auditor.md +581 -0
  207. package/pi/package-agents/gsd-ui-checker.md +417 -0
  208. package/pi/package-agents/gsd-ui-researcher.md +469 -0
  209. package/pi/package-agents/gsd-user-profiler.md +174 -0
  210. package/pi/package-agents/gsd-verifier.md +963 -0
  211. package/pi/package-agents/manifest.json +39 -0
  212. package/pi/package-skills/gsd-add-tests/SKILL.md +39 -0
  213. package/pi/package-skills/gsd-ai-integration-phase/SKILL.md +36 -0
  214. package/pi/package-skills/gsd-audit-fix/SKILL.md +34 -0
  215. package/pi/package-skills/gsd-audit-milestone/SKILL.md +40 -0
  216. package/pi/package-skills/gsd-audit-uat/SKILL.md +26 -0
  217. package/pi/package-skills/gsd-autonomous/SKILL.md +52 -0
  218. package/pi/package-skills/gsd-capture/SKILL.md +68 -0
  219. package/pi/package-skills/gsd-cleanup/SKILL.md +25 -0
  220. package/pi/package-skills/gsd-code-review/SKILL.md +71 -0
  221. package/pi/package-skills/gsd-complete-milestone/SKILL.md +147 -0
  222. package/pi/package-skills/gsd-config/SKILL.md +60 -0
  223. package/pi/package-skills/gsd-debug/SKILL.md +64 -0
  224. package/pi/package-skills/gsd-discuss-phase/SKILL.md +74 -0
  225. package/pi/package-skills/gsd-docs-update/SKILL.md +50 -0
  226. package/pi/package-skills/gsd-eval-review/SKILL.md +35 -0
  227. package/pi/package-skills/gsd-execute-phase/SKILL.md +62 -0
  228. package/pi/package-skills/gsd-explore/SKILL.md +26 -0
  229. package/pi/package-skills/gsd-extract-learnings/SKILL.md +25 -0
  230. package/pi/package-skills/gsd-fast/SKILL.md +33 -0
  231. package/pi/package-skills/gsd-forensics/SKILL.md +59 -0
  232. package/pi/package-skills/gsd-graphify/SKILL.md +210 -0
  233. package/pi/package-skills/gsd-health/SKILL.md +36 -0
  234. package/pi/package-skills/gsd-help/SKILL.md +37 -0
  235. package/pi/package-skills/gsd-import/SKILL.md +46 -0
  236. package/pi/package-skills/gsd-inbox/SKILL.md +42 -0
  237. package/pi/package-skills/gsd-ingest-docs/SKILL.md +43 -0
  238. package/pi/package-skills/gsd-manager/SKILL.md +46 -0
  239. package/pi/package-skills/gsd-map-codebase/SKILL.md +85 -0
  240. package/pi/package-skills/gsd-mempalace-capture/SKILL.md +111 -0
  241. package/pi/package-skills/gsd-mempalace-recall/SKILL.md +111 -0
  242. package/pi/package-skills/gsd-milestone-summary/SKILL.md +54 -0
  243. package/pi/package-skills/gsd-mvp-phase/SKILL.md +45 -0
  244. package/pi/package-skills/gsd-new-milestone/SKILL.md +49 -0
  245. package/pi/package-skills/gsd-new-project/SKILL.md +49 -0
  246. package/pi/package-skills/gsd-next/SKILL.md +34 -0
  247. package/pi/package-skills/gsd-ns-context/SKILL.md +31 -0
  248. package/pi/package-skills/gsd-ns-ideate/SKILL.md +30 -0
  249. package/pi/package-skills/gsd-ns-manage/SKILL.md +42 -0
  250. package/pi/package-skills/gsd-ns-project/SKILL.md +34 -0
  251. package/pi/package-skills/gsd-ns-review/SKILL.md +35 -0
  252. package/pi/package-skills/gsd-ns-workflow/SKILL.md +44 -0
  253. package/pi/package-skills/gsd-onboard/SKILL.md +46 -0
  254. package/pi/package-skills/gsd-pause-work/SKILL.md +48 -0
  255. package/pi/package-skills/gsd-phase/SKILL.md +61 -0
  256. package/pi/package-skills/gsd-plan-phase/SKILL.md +62 -0
  257. package/pi/package-skills/gsd-plan-review-convergence/SKILL.md +63 -0
  258. package/pi/package-skills/gsd-pr-branch/SKILL.md +31 -0
  259. package/pi/package-skills/gsd-profile-user/SKILL.md +48 -0
  260. package/pi/package-skills/gsd-progress/SKILL.md +51 -0
  261. package/pi/package-skills/gsd-quick/SKILL.md +179 -0
  262. package/pi/package-skills/gsd-quick-batch/SKILL.md +114 -0
  263. package/pi/package-skills/gsd-resume-work/SKILL.md +31 -0
  264. package/pi/package-skills/gsd-review/SKILL.md +50 -0
  265. package/pi/package-skills/gsd-review-backlog/SKILL.md +65 -0
  266. package/pi/package-skills/gsd-secure-phase/SKILL.md +37 -0
  267. package/pi/package-skills/gsd-settings/SKILL.md +30 -0
  268. package/pi/package-skills/gsd-ship/SKILL.md +27 -0
  269. package/pi/package-skills/gsd-sketch/SKILL.md +56 -0
  270. package/pi/package-skills/gsd-spec-phase/SKILL.md +63 -0
  271. package/pi/package-skills/gsd-spike/SKILL.md +53 -0
  272. package/pi/package-skills/gsd-stats/SKILL.md +22 -0
  273. package/pi/package-skills/gsd-surface/SKILL.md +177 -0
  274. package/pi/package-skills/gsd-thread/SKILL.md +29 -0
  275. package/pi/package-skills/gsd-ui-phase/SKILL.md +35 -0
  276. package/pi/package-skills/gsd-ui-review/SKILL.md +35 -0
  277. package/pi/package-skills/gsd-ultraplan-phase/SKILL.md +38 -0
  278. package/pi/package-skills/gsd-undo/SKILL.md +38 -0
  279. package/pi/package-skills/gsd-update/SKILL.md +54 -0
  280. package/pi/package-skills/gsd-validate-phase/SKILL.md +37 -0
  281. package/pi/package-skills/gsd-verify-work/SKILL.md +41 -0
  282. package/pi/package-skills/gsd-workspace/SKILL.md +57 -0
  283. package/pi/package-skills/gsd-workstreams/SKILL.md +73 -0
  284. package/scripts/build-hooks.js +4 -0
  285. package/scripts/ci-next-health.cjs +246 -23
  286. package/scripts/command-contract-helpers.cjs +1 -1
  287. package/scripts/docs-guard-registry.cjs +22 -7
  288. package/scripts/gen-features.cjs +14 -2
  289. package/scripts/gen-health-docs.cjs +13 -1
  290. package/scripts/gen-pi-package-skills.cjs +217 -0
  291. package/scripts/lib/drift-scan.cjs +27 -0
  292. package/scripts/lib/macos-conformance-tier.generated.cjs +17 -1
  293. package/scripts/lib/platform-conformance-tier.generated.cjs +37 -1
  294. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +3 -3
  295. package/scripts/lint-phase-enumeration-drift.cjs +8 -4
  296. package/scripts/lint-retired-runtime-name.cjs +5 -0
  297. package/scripts/lint-roadmap-progress-drift.cjs +421 -0
  298. package/scripts/mutation-matrix.cjs +23 -1
  299. package/scripts/npm-audit-baseline.cjs +41 -7
  300. package/scripts/reviewed-or-maintainer-policy.cjs +181 -0
  301. package/scripts/select-docs-guards.cjs +1 -1
  302. package/scripts/self-test-report-policy.cjs +350 -0
  303. package/scripts/sync-next-version.cjs +38 -6
  304. package/skills/gsd-update/SKILL.md +5 -2
  305. package/vscode/package.json +1 -1
  306. package/scripts/diagnose-validate-commit-in-tier.cjs +0 -270
  307. package/scripts/diagnose-validate-commit-phases.cjs +0 -348
  308. package/scripts/xtrace-phase-timing.cjs +0 -205
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: gsd:update
3
3
  description: Update GSD to latest version with changelog display
4
- argument-hint: "[--sync | --reapply | --next | --rc]"
4
+ argument-hint: "[--sync | --reapply | --next | --rc] [--interactive=exceptions|all]"
5
5
  allowed-tools:
6
6
  - Read
7
7
  - Write
@@ -35,14 +35,17 @@ Routes to the update workflow which handles:
35
35
  <flags>
36
36
  - **--sync**: Sync managed GSD skills across runtime roots so multi-runtime users stay aligned after an update. Runs the sync-skills workflow (--from, --to, --dry-run, --apply flags supported).
37
37
  - **--reapply**: Reapply local modifications after a GSD update. Uses three-way comparison (pristine baseline, user-modified backup, newly installed version) to merge user customizations back. Runs the reapply-patches workflow.
38
+ - **--interactive[=exceptions|all]** (with `--reapply` only): show each user edit next to the stock text with a recommended disposition and wait for a keep / retire / rewrite decision per edit. `all` shows every edit; `exceptions` (bare `--interactive`) shows only edits upstream adopted, partly adopted, reworded around, or orphaned, and keeps the rest. Without it, `--reapply` behaves exactly as before.
38
39
  - **--next** (alias **--rc**): Target the `@next` RC dist-tag instead of `@latest` so you can install or refresh a release candidate (e.g. `1.4.0-rc.1`) through the normal update flow — scope/runtime detection, changelog preview, custom-file backup, and cache clearing all still apply. Omitting it keeps targeting `@latest` (no change). See ADR #660 for the RC channel.
39
40
  - **(no flag)**: Standard update — check for new version, show changelog, install.
40
41
  </flags>
41
42
 
42
43
  <process>
44
+ If the arguments contain `--interactive` (bare or with a value) but not `--reapply`, STOP with: `--interactive applies only to --reapply (for example: /gsd:update --reapply --interactive=all)`. Do nothing else.
45
+
43
46
  Parse the first token of the `<arguments>` block:
44
47
  - If it is `--sync`: strip the flag, execute the sync-skills workflow (passing remaining args for --from/--to/--dry-run/--apply).
45
- - If it is `--reapply`: strip the flag, execute the reapply-patches workflow.
48
+ - If it is `--reapply`: strip the flag, execute the reapply-patches workflow, passing the remaining arguments (for example `--interactive=all`) to it.
46
49
  - Otherwise (including `--next` / `--rc`): execute the update workflow end-to-end, passing the `<arguments>` block through so the workflow's parse_update_channel step can select the release channel.
47
50
 
48
51
  </process>
@@ -85,6 +85,14 @@
85
85
  * [--no-archive-phases] Skip moving phase dirs to milestones/vX.Y-phases/ (archived by default)
86
86
  * [--archive-quick] Move .planning/quick/* dirs to milestones/vX.Y-quick/ + reset the
87
87
  * Quick Tasks Completed table (#2142; opt-in, default OFF)
88
+ * [--release-map <file>] Archive a per-repository release map (JSON array of
89
+ * {repo, release, reference, environment, status, tag}) as
90
+ * milestones/vX.Y-RELEASE-MAP.json and record it in MILESTONES.md
91
+ * (#5706; validated before any write, absent = unchanged)
92
+ *
93
+ * milestone release-tags --map <file.json> [--dry-run]
94
+ * Preflight, then create annotated tags per sub-repo from a JSON array of
95
+ * {repo, tag, ref?, message?, skip?}; never pushes, never overwrites (#5705)
88
96
  *
89
97
  * milestone archive-quick <version> Move .planning/quick/* dirs to milestones/vX.Y-quick/ + reset the
90
98
  * Quick Tasks Completed table, WITHOUT the milestone complete close-out
@@ -326,6 +334,7 @@ const config = require('./lib/config.cjs');
326
334
  const estimateCli = require('./lib/estimate-cli.cjs');
327
335
  const template = require('./lib/template.cjs');
328
336
  const milestone = require('./lib/milestone.cjs');
337
+ const milestoneReleaseTags = require('./lib/milestone-release-tags.cjs');
329
338
  const commands = require('./lib/commands.cjs');
330
339
  const runtimeIdentity = require('./lib/runtime-identity.cjs');
331
340
  const init = require('./lib/init.cjs');
@@ -848,7 +857,31 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
848
857
  error,
849
858
  output: output,
850
859
  });
851
- if (!handled) config.cmdConfigNewProject(cwd, args[1], raw);
860
+ if (handled) return;
861
+ // #5268: this verb's flag surface is closed, as `todo complete`'s is
862
+ // (#4096). `--dry-run` is parsed and plumbed, and any OTHER flag fails loudly
863
+ // instead of being silently ignored: an accepted-but-ignored `--dry-run`
864
+ // wrote the config it was meant to preview. (`--raw` is spliced by the
865
+ // dispatcher before routing; it stays in the set as a guard against that
866
+ // splice ever moving.) The choices JSON is the one positional argument,
867
+ // wherever the flag sits, and a second one is refused for the same reason.
868
+ // A flag is a dash followed by a letter: `-1` is a (negative-number) JSON
869
+ // argument, refused below as choices that are not an object, not a flag.
870
+ const CONFIG_NEW_PROJECT_KNOWN_FLAGS = new Set(['--dry-run', '--raw']);
871
+ const positionals = [];
872
+ for (const a of args.slice(1)) {
873
+ if (typeof a === 'string' && /^--?[A-Za-z]/.test(a)) {
874
+ if (!CONFIG_NEW_PROJECT_KNOWN_FLAGS.has(a)) {
875
+ error(`Unknown flag for config-new-project: ${a}`, ERROR_REASON.USAGE);
876
+ }
877
+ continue;
878
+ }
879
+ positionals.push(a);
880
+ }
881
+ if (positionals.length > 1) {
882
+ error(`Unexpected argument for config-new-project: ${positionals[1]}. Usage: config-new-project [choices-json] [--dry-run]`, ERROR_REASON.USAGE);
883
+ }
884
+ config.cmdConfigNewProject(cwd, positionals[0], raw, { dryRun: args.includes('--dry-run') });
852
885
  }
853
886
 
854
887
  function routeConfigPath({ cwd, raw, workstreamContext }) {
@@ -1598,7 +1631,8 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
1598
1631
  const fsx = require('node:fs');
1599
1632
  const os = require('node:os');
1600
1633
  const { REVIEWER_LANES, mergeReviewerLanes } = require('./lib/review-lane-descriptor.cjs');
1601
- const { resolveLanePlan: resolveLanePlanBase, resolveLaneEffort, resolveLaneBudget, applyPromptOverride } = require('./lib/review-lane-invocation.cjs');
1634
+ const { resolveLanePlan: resolveLanePlanBase, resolveLaneEffort, resolveLaneCap, refuseIfOverCap, renderPromptRefusalStub, artifactPaths, applyPromptOverride } = require('./lib/review-lane-invocation.cjs');
1635
+ const { estimateTokens } = require('./lib/prompt-budget.cjs');
1602
1636
  // OpenCode 2.x argv differs from 1.x (no --variant; effort rides on the model as `#variant`;
1603
1637
  // ADR-5331 D5/D8). The generation is probed lazily, only when the opencode lane is resolved.
1604
1638
  const resolveLanePlan = (laneInput) => {
@@ -1632,8 +1666,9 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
1632
1666
  // `plan`/`invoke` are the only subs that need the expensive plan-building path
1633
1667
  // below; `sections`/`flags` return earlier still. Anything else errors here, before
1634
1668
  // any of that work starts.
1635
- if (!['plan', 'invoke', 'sections', 'flags', 'dispatch-step', 'explicit-from-argv'].includes(sub)) {
1636
- error("Usage: review-lane <plan|invoke|sections|flags|dispatch-step|explicit-from-argv> [--selected a,b] [--run-dir D] [--repo-root R]");
1669
+ const REVIEW_LANE_USAGE = 'Usage: review-lane <plan|invoke|sections|flags|availability|dispatch-step|explicit-from-argv> [--selected a,b] [--run-dir D] [--repo-root R]';
1670
+ if (!['plan', 'invoke', 'sections', 'flags', 'availability', 'dispatch-step', 'explicit-from-argv'].includes(sub)) {
1671
+ error(REVIEW_LANE_USAGE);
1637
1672
  return;
1638
1673
  }
1639
1674
  const runDir = flag('--run-dir') || '.';
@@ -1709,8 +1744,34 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
1709
1744
  method: opts.method,
1710
1745
  headers: opts.body ? { 'Content-Type': 'application/json' } : undefined,
1711
1746
  body: opts.body,
1747
+ // Never follow a redirect: the egress check compares the CONFIGURED host, so a
1748
+ // followed redirect would send the review prompt to a host the user never named (#5433).
1749
+ redirect: 'manual',
1712
1750
  signal: AbortSignal.timeout(opts.timeoutMs),
1713
1751
  });
1752
+ // probe:true marks a reachability probe (returns status only, body ''): the status line is the answer, so do not
1753
+ // let a stalled body turn a replying server into `missing`.
1754
+ if (opts.probe) {
1755
+ res.body?.cancel().catch(() => {});
1756
+ return { ok: res.ok, status: res.status, body: '' };
1757
+ }
1758
+ if (res.status >= 300 && res.status < 400) {
1759
+ res.body?.cancel().catch(() => {});
1760
+ const location = res.headers.get('location');
1761
+ let target = 'without a Location header';
1762
+ if (location) {
1763
+ target = 'to an unparseable Location';
1764
+ try { target = `to ${new URL(location, url).host}`; } catch { /* keep the unparseable wording */ }
1765
+ }
1766
+ // Host and path only: a configured URL may carry credentials that must not reach a review file.
1767
+ const asked = (() => { try { const u = new URL(url); return `${u.host}${u.pathname}`; } catch { return 'the configured URL'; } })();
1768
+ return {
1769
+ ok: false,
1770
+ status: res.status,
1771
+ body: '',
1772
+ error: `${asked} answered HTTP ${res.status}, redirecting ${target}. The lane does not follow redirects; configure the final URL as the lane's host.`,
1773
+ };
1774
+ }
1714
1775
  return { ok: res.ok, status: res.status, body: await res.text() };
1715
1776
  } catch (e) {
1716
1777
  return { ok: false, status: 0, body: '', error: e && e.message ? e.message : String(e) };
@@ -1737,6 +1798,23 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
1737
1798
 
1738
1799
  const selected = (flag('--selected') || '')
1739
1800
  .split(',').map((s) => s.trim()).filter(Boolean);
1801
+ // #5266: `<slug>:available|missing` per openai-http lane. Host + default come from the lane
1802
+ // descriptor (resolveLanePlan), probing is runner.probeLane — review.md keeps no host table.
1803
+ // Prerequisite binaries (jq) are reported separately by the workflow, not folded in here.
1804
+ // First-party lanes only: ADR-2782 D5 requires egress/consent before a third-party lane's host is
1805
+ // contacted; detection has no such check, so installed third-party lanes are never probed here.
1806
+ if (sub === 'availability') {
1807
+ const { rows, warnings } = await runner.probeHttpLanes(
1808
+ REVIEWER_LANES,
1809
+ selected,
1810
+ (lane) => resolveLanePlan({ lane, configGet, runDir, repoRoot }),
1811
+ buildLaneRunnerDeps(),
1812
+ );
1813
+ for (const w of warnings) process.stderr.write(`review-lane availability: ${w}\n`);
1814
+ process.stdout.write(rows.join('\n') + (rows.length ? '\n' : ''));
1815
+ return;
1816
+ }
1817
+
1740
1818
  // ADR-2782 D8 (#2927): the lane map is first-party ∪ INSTALLED overlay
1741
1819
  // `reviewer` bodies, first-party winning on slug collision. Before this merge
1742
1820
  // the map was built from the frozen REVIEWER_LANES array alone, so an installed,
@@ -1838,11 +1916,11 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
1838
1916
  };
1839
1917
  const effortFor = (lane) => resolveLaneEffort(lane, configGet, renderLaneEffort);
1840
1918
 
1841
- // Per-lane prompt budget: `resolveLaneBudget` (review-lane-invocation.cjs) owns the #2797
1919
+ // Per-lane prompt cap: `resolveLaneCap` (review-lane-invocation.cjs) owns the #2797 + #4559
1842
1920
  // resolution semantics (shared with src/reviewer-step-dispatch.cts, #4209 R3 — was two
1843
- // verbatim copies). Only the budget VALUE is resolved here; assembly/trimming stay in
1844
- // `prompt-budget`, which the workflow calls, handing the trimmed file back via `--prompt-file`.
1845
- const budgetFor = (lane) => resolveLaneBudget(lane, configGet);
1921
+ // verbatim copies). `plan` reports it; `invoke` REFUSES a lane whose prompt is over it
1922
+ // (#4559) rather than trimming — the prompt is never edited on the way to a lane.
1923
+ const capFor = (lane) => resolveLaneCap(lane, configGet);
1846
1924
 
1847
1925
  // #4209 (ADR-2782 seam) — the ONE interpreter route for a step that declared
1848
1926
  // `supportsReviewerLanes: true`. Wires `dispatchReviewerLanes` (src/reviewer-step-dispatch.cts)
@@ -1982,17 +2060,19 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
1982
2060
  } catch (e) {
1983
2061
  return { slug, ok: false, reason: 'malformed_lane', detail: `resolver threw: ${e && e.message ? e.message : String(e)}` };
1984
2062
  }
1985
- return r.ok
1986
- ? {
1987
- slug,
1988
- ok: true,
1989
- section: lane.reviewsSection,
1990
- transport: r.plan.transport,
1991
- promptBudget: budgetFor(lane),
1992
- promptPath: r.plan.transport === 'spawn' ? r.plan.stdin : r.plan.promptPath,
1993
- plan: r.plan,
1994
- }
1995
- : { slug, ok: false, reason: r.reason, detail: r.detail };
2063
+ if (!r.ok) return { slug, ok: false, reason: r.reason, detail: r.detail };
2064
+ const laneCap = capFor(lane);
2065
+ return {
2066
+ slug,
2067
+ ok: true,
2068
+ section: lane.reviewsSection,
2069
+ transport: r.plan.transport,
2070
+ promptBudget: laneCap.cap,
2071
+ promptCapSource: laneCap.source,
2072
+ contextWindowTokens: laneCap.contextWindowTokens,
2073
+ promptPath: r.plan.transport === 'spawn' ? r.plan.stdin : r.plan.promptPath,
2074
+ plan: r.plan,
2075
+ };
1996
2076
  });
1997
2077
 
1998
2078
  if (sub === 'plan') {
@@ -2001,7 +2081,7 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
2001
2081
  }
2002
2082
 
2003
2083
  if (sub !== 'invoke') {
2004
- error("Usage: review-lane <plan|invoke|sections|flags|dispatch-step|explicit-from-argv> [--selected a,b] [--run-dir D] [--repo-root R]");
2084
+ error(REVIEW_LANE_USAGE);
2005
2085
  return;
2006
2086
  }
2007
2087
 
@@ -2101,14 +2181,61 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
2101
2181
  ];
2102
2182
  }
2103
2183
 
2104
- // `--prompt-file` lets the caller substitute a budget-trimmed prompt. Applied to whichever
2105
- // channel this lane actually reads from (stdin, argv, an argv-embedded file reference, or the
2106
- // http promptPath), so one flag serves every lane (#5049).
2184
+ // `--prompt-file` lets the caller substitute a different prompt (a section subset, say). Applied
2185
+ // to whichever channel this lane actually reads from (stdin, argv, an argv-embedded file
2186
+ // reference, or the http promptPath), so one flag serves every lane (#5049). It is no longer a
2187
+ // trim hand-off: nothing in review.md trims a prompt to fit a lane since #4559.
2107
2188
  const promptOverride = flag('--prompt-file');
2108
2189
  if (promptOverride) {
2109
2190
  entry.plan = applyPromptOverride(entry.plan, promptOverride, repoRoot);
2110
2191
  }
2111
2192
 
2193
+ // #4559: refuse, never trim. Measure the prompt this lane would actually receive — the file
2194
+ // the RESOLVED plan reads (stdin file, or the plan's prompt path for argv / argv-file-ref /
2195
+ // http lanes; `--prompt-file` already retargeted the plan above) — against its cap BEFORE any
2196
+ // consent lookup, probe or spawn. An unreadable file is not a refusal: the runner reports its
2197
+ // own error for it. The decision itself lives in `refuseIfOverCap`; this only does the IO.
2198
+ {
2199
+ const plannedPrompt = entry.plan.transport === 'spawn' && entry.plan.stdin
2200
+ ? entry.plan.stdin
2201
+ : entry.plan.promptPath;
2202
+ let promptText = null;
2203
+ try {
2204
+ promptText = fs.readFileSync(plannedPrompt || artifactPaths(runDir, entry.slug).promptPath, 'utf8');
2205
+ } catch { promptText = null; }
2206
+ if (promptText !== null) {
2207
+ const refused = refuseIfOverCap({
2208
+ lane: laneBySlug.get(entry.slug),
2209
+ estimatedTokens: estimateTokens(promptText),
2210
+ configGet,
2211
+ });
2212
+ if (refused) {
2213
+ const reviewPath = entry.plan.reviewPath;
2214
+ let stubWritten = true;
2215
+ try {
2216
+ fs.mkdirSync(path.dirname(reviewPath), { recursive: true });
2217
+ fs.writeFileSync(reviewPath, renderPromptRefusalStub(entry.slug, refused.detail), 'utf8');
2218
+ } catch (e) {
2219
+ stubWritten = false;
2220
+ process.stderr.write(
2221
+ `review-lane invoke: could not write the refusal stub to ${reviewPath}: ` +
2222
+ `${e && e.message ? e.message : String(e)}\n`,
2223
+ );
2224
+ }
2225
+ output({
2226
+ slug: entry.slug,
2227
+ ok: false,
2228
+ reason: refused.reason,
2229
+ detail: refused.detail,
2230
+ reviewPath,
2231
+ stubWritten,
2232
+ refusal: refused.refusal,
2233
+ }, raw);
2234
+ return;
2235
+ }
2236
+ }
2237
+ }
2238
+
2112
2239
  // ADR-2782 D5 rule 4: re-resolve the consented destination at INVOCATION. `undefined` (no
2113
2240
  // consent record, or a record predating rule 1) means "nothing to compare" and ALLOWS — a
2114
2241
  // first-party lane ships inside the SHA-pinned distribution and is never consent-gated, so
@@ -2141,6 +2268,13 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
2141
2268
  testCommandNormalizer.cmdNormalizeTestCommand(cwd, args[1]);
2142
2269
  }
2143
2270
 
2271
+ function routeResolvePackageManager({ args, cwd }) {
2272
+ // #5346: the gates' no-configured-command fallback asks here for the
2273
+ // project's package manager (packageManager field, then lockfile, else npm).
2274
+ // args[1] is the script name; --cwd (already parsed into `cwd`) locates package.json.
2275
+ require('./lib/normalize-test-command.cjs').cmdResolvePackageManager(cwd, args[1]);
2276
+ }
2277
+
2144
2278
  function routeDispatchShouldFlatten({ args, cwd, raw, error }) {
2145
2279
  // #1708 / #853: typed query replacing the `RUNTIME === 'codex'` prose rule.
2146
2280
  //
@@ -3142,7 +3276,18 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
3142
3276
  // --dry-run) the command refuses before touching anything. Distinct
3143
3277
  // from --force, which bypasses the narrow scope guards only.
3144
3278
  const confirm = args.includes('--confirm');
3145
- milestone.cmdMilestoneComplete(cwd, args[2], { name: milestoneName, archivePhases, force, dryRun, archiveQuick, confirm }, raw);
3279
+ // #5706 (epic #3628): optional caller-supplied release map file.
3280
+ // A flag with no usable value fails loud rather than being dropped.
3281
+ let releaseMap = null;
3282
+ const releaseMapIdx = args.indexOf('--release-map');
3283
+ if (releaseMapIdx !== -1) {
3284
+ const releaseMapValue = args[releaseMapIdx + 1];
3285
+ if (typeof releaseMapValue !== 'string' || releaseMapValue.startsWith('--') || releaseMapValue.trim() === '') {
3286
+ error('--release-map requires a file path (a JSON array of release-map entries)');
3287
+ }
3288
+ releaseMap = releaseMapValue;
3289
+ }
3290
+ milestone.cmdMilestoneComplete(cwd, args[2], { name: milestoneName, archivePhases, force, dryRun, archiveQuick, confirm, releaseMap }, raw);
3146
3291
  } else if (subcommand === 'archive-quick') {
3147
3292
  // #2142 escalation: narrow archival-only entry point (does NOT
3148
3293
  // touch ROADMAP/REQUIREMENTS/MILESTONES.md, runs no completion
@@ -3150,8 +3295,11 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
3150
3295
  // unlike `milestone complete --archive-quick`.
3151
3296
  const dryRun = args.includes('--dry-run');
3152
3297
  milestone.cmdQuickArchive(cwd, args[2], { dryRun }, raw);
3298
+ } else if (subcommand === 'release-tags') {
3299
+ // #5705 (epic #3628 Phase 1): repository-scoped annotated release tags.
3300
+ milestoneReleaseTags.cmdMilestoneReleaseTags(cwd, args, raw);
3153
3301
  } else {
3154
- error('Unknown milestone subcommand. Available: complete, archive-quick', ERROR_REASON.SDK_UNKNOWN_COMMAND);
3302
+ error('Unknown milestone subcommand. Available: complete, archive-quick, release-tags', ERROR_REASON.SDK_UNKNOWN_COMMAND);
3155
3303
  }
3156
3304
  }
3157
3305
 
@@ -4506,7 +4654,7 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
4506
4654
  // #1956: deterministic STATE.md-vs-ROADMAP.md phase-status drift.
4507
4655
  const { planningDir } = require('./lib/planning-workspace.cjs');
4508
4656
  const { stateExtractField, stateCurrentPositionSlice } = require('./lib/state-document.cjs');
4509
- const { findRoadmapProgressTable } = require('./lib/roadmap-parser.cjs');
4657
+ const { locateRoadmapProgress } = require('./lib/roadmap-progress.cjs');
4510
4658
  const { phaseKeyFromProse } = require('./lib/phase-id.cjs');
4511
4659
  // STATE.md's YAML frontmatter carries its own lowercase `status:`
4512
4660
  // scalar ahead of the body's `## Current Position` prose "Status:"
@@ -4604,9 +4752,9 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
4604
4752
 
4605
4753
  const stateStatus = stateExtractField(currentPositionBody, 'Status');
4606
4754
 
4607
- // #1956/#2012: scoped to `## Progress` first (decoy-avoidance) —
4608
- // see findRoadmapProgressTable's doc comment (roadmap-parser.cts).
4609
- const table = findRoadmapProgressTable(roadmapContent);
4755
+ // #1956/#2012: scoped to `## Progress` first (decoy-avoidance) — the
4756
+ // Progress owner's rule (roadmap-progress.cts), shared with every reader and writer (#5762).
4757
+ const { table } = locateRoadmapProgress(roadmapContent);
4610
4758
  const matchedRow = table
4611
4759
  ? table.rows.find((row) => phaseKeyFromProse(row.Phase) === phase && phase !== null)
4612
4760
  : undefined;
@@ -4809,6 +4957,7 @@ const HOST_COMMAND_ROUTERS = {
4809
4957
  // #3676 (Phase 4, epic #3344): quick-batch coordination verbs.
4810
4958
  'quick-batch': routeQuickBatchCommand,
4811
4959
  'normalize-test-command': routeNormalizeTestCommand,
4960
+ 'resolve-package-manager': routeResolvePackageManager,
4812
4961
  'dispatch-should-flatten': routeDispatchShouldFlatten,
4813
4962
  'dispatch-isolation': routeDispatchIsolation,
4814
4963
  'dispatch-capacity': routeDispatchCapacity,
@@ -5114,7 +5263,7 @@ function runWithTimeout(argv) {
5114
5263
  // drifting apart when a query command was added to only one or two.
5115
5264
  const TOP_LEVEL_USAGE = 'Usage: gsd-tools <command> [args] [--raw] [--pick <field>] [--cwd <path>] [--project-dir <path>] [--ws <name>] [--json-errors] [--exit-contract=<v>]\n' +
5116
5265
  'Commands: agent, agent-skills, assumption-delta, audit-open, audit-uat, check, check-commit, commit, commit-docs-guard, commit-to-subrepo, pr-subrepo, ' +
5117
- 'config-ensure-section, config-get, config-new-project, config-path, config-set, migrate-config, normalize-test-command, ' +
5266
+ 'config-ensure-section, config-get, config-new-project, config-path, config-set, migrate-config, normalize-test-command, resolve-package-manager, ' +
5118
5267
  'context-predicates, current-timestamp, detect-custom-files, docs-init, drift-guard, effort, extract-messages, find-phase, ' +
5119
5268
  'from-gsd2, frontmatter, gap-analysis, generate-claude-md, generate-claude-profile, ' +
5120
5269
  'generate-dev-preferences, generate-slug, graphify, history-digest, init, intel, ' +
@@ -328,8 +328,33 @@ function checkCodexModelPosture(runtime, projectRoot) {
328
328
  // derives its sandbox_mode from actually lives, in every install shape
329
329
  // (repo-dev tree or npm-global package, where the package still ships its
330
330
  // own agents/ alongside bin/ and gsd-core/).
331
- function _canonicalAgentSourceDir() {
332
- return node_path_1.default.join(__dirname, '..', '..', '..', 'agents');
331
+ //
332
+ // #5496: "install-relative" has three shapes. This module compiles to
333
+ // `<root>/gsd-core/bin/lib/`, so `<root>` is three parents up. In the repository
334
+ // and the npm package the canonical sources are `<root>/agents`. In an INSTALLED
335
+ // config dir `<root>/agents` holds the CONVERTED runtime copies (no `tools:`
336
+ // declaration — every role then derived `read-only`, 26 false violations on a
337
+ // clean Codex install) and the canonical sources are `<root>/gsd-core/agents`.
338
+ // Prefer the nested directory when it actually holds agent sources; fall back to
339
+ // `<root>/agents`. `root` is a parameter so each layout is testable.
340
+ //
341
+ // #5636: "holds agent sources" is a recognized agent file, `gsd-<name>.md` (every file in the shipped
342
+ // roster) that is not a `.compact.md` variant. Any markdown would let a stray README in the nested
343
+ // directory hide the real sources, so the posture check found no role's canonical file and skipped all.
344
+ const CANONICAL_AGENT_SOURCE_FILE = /^gsd-.+\.md$/;
345
+ function isCanonicalAgentSourceFile(entry) {
346
+ return CANONICAL_AGENT_SOURCE_FILE.test(entry) && !entry.endsWith('.compact.md');
347
+ }
348
+ function canonicalAgentSourceDir(root = node_path_1.default.join(__dirname, '..', '..', '..')) {
349
+ const nested = node_path_1.default.join(root, 'gsd-core', 'agents');
350
+ try {
351
+ if (node_fs_1.default.readdirSync(nested).some(isCanonicalAgentSourceFile))
352
+ return nested;
353
+ }
354
+ catch {
355
+ // Absent or unreadable: not this layout.
356
+ }
357
+ return node_path_1.default.join(root, 'agents');
333
358
  }
334
359
  /**
335
360
  * Validate installed Codex `.toml` agents' `sandbox_mode` against what each
@@ -384,7 +409,7 @@ function checkCodexSandboxPosture(runtime, projectRoot) {
384
409
  reason: POSTURE_REASON.AGENTS_DIR_MISSING,
385
410
  };
386
411
  }
387
- const canonicalAgentsDir = _canonicalAgentSourceDir();
412
+ const canonicalAgentsDir = canonicalAgentSourceDir();
388
413
  // Same symlink guard as checkCodexModelPosture, same reason (never follow
389
414
  // an agents-dir symlink into an arbitrary file's content).
390
415
  const tomlFiles = node_fs_1.default
@@ -533,6 +558,7 @@ module.exports = {
533
558
  checkAgentsInstalled,
534
559
  checkCodexModelPosture,
535
560
  checkCodexSandboxPosture,
561
+ canonicalAgentSourceDir,
536
562
  POSTURE_REASON,
537
563
  resolveAgentHint,
538
564
  };
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ /**
3
+ * Per-install terminators for the typed-text capture heredocs (#5637).
4
+ *
5
+ * Every workflow that reads the typed text opens with
6
+ *
7
+ * IFS= read -rd '' GSD_ARGS <<'GSD_ARGS_END' || true
8
+ * $ARGUMENTS
9
+ * GSD_ARGS_END
10
+ *
11
+ * and `/gsd-quick-batch` feeds `parse-args` through the same shape with `GSD_QUICK_BATCH_ARGS_END`.
12
+ * The harness replaces `$ARGUMENTS` with the typed text before the shell parses the fence, so typed
13
+ * text with a terminator line of its own closes the heredoc early and the lines after it run as
14
+ * shell. A literal in a shipped file cannot be made unforgeable, so each install replaces it with
15
+ * `<TERMINATOR>_<nonce>`, a nonce the typed text has no way to know. The shipped source keeps the
16
+ * canonical tokens; only the files an install writes carry the nonce.
17
+ */
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.ARGS_NONCE_RE = exports.ARGS_TERMINATOR = exports.ARGS_TERMINATORS = void 0;
20
+ exports.generateArgsNonce = generateArgsNonce;
21
+ exports.stampArgsTerminator = stampArgsTerminator;
22
+ exports.readInstalledNonce = readInstalledNonce;
23
+ exports.armArgsNonce = armArgsNonce;
24
+ exports.disarmArgsNonce = disarmArgsNonce;
25
+ exports.stampInstalledContent = stampInstalledContent;
26
+ const node_crypto_1 = require("node:crypto");
27
+ const node_fs_1 = require("node:fs");
28
+ const node_path_1 = require("node:path");
29
+ /** The canonical terminators the shipped workflows and commands carry. */
30
+ exports.ARGS_TERMINATORS = Object.freeze(['GSD_ARGS_END', 'GSD_QUICK_BATCH_ARGS_END']);
31
+ /** The terminator every workflow carries. */
32
+ exports.ARGS_TERMINATOR = 'GSD_ARGS_END';
33
+ /** A nonce is 128 random bits as 32 lowercase hex characters. */
34
+ exports.ARGS_NONCE_RE = /^[0-9a-f]{32}$/;
35
+ const OPENER_RE = new RegExp(`<<'(${exports.ARGS_TERMINATORS.join('|')})'`, 'g');
36
+ const CLOSER_RE = new RegExp(`^(${exports.ARGS_TERMINATORS.join('|')})(?=\\r?$)`, 'gm');
37
+ /** A fresh nonce. `randomBytes` is a seam for tests. */
38
+ function generateArgsNonce(randomBytes = node_crypto_1.randomBytes) {
39
+ return randomBytes(16).toString('hex');
40
+ }
41
+ /**
42
+ * `content` with each canonical terminator replaced by the stamped one: the heredoc opener tag and
43
+ * the closing line. A terminator that is already stamped does not match and is left alone, and so
44
+ * is prose that names a token inside a line.
45
+ *
46
+ * @throws {TypeError} when `nonce` is not 32 lowercase hex characters
47
+ */
48
+ function stampArgsTerminator(content, nonce) {
49
+ if (typeof nonce !== 'string' || !exports.ARGS_NONCE_RE.test(nonce)) {
50
+ throw new TypeError('stampArgsTerminator: nonce must be 32 lowercase hex characters');
51
+ }
52
+ return content
53
+ .replace(OPENER_RE, (_m, token) => `<<'${token}_${nonce}'`)
54
+ .replace(CLOSER_RE, (_m, token) => `${token}_${nonce}`);
55
+ }
56
+ let armedNonce = null;
57
+ const INSTALLED_NONCE_RE = /^GSD_ARGS_END_([0-9a-f]{32})\r?$/m;
58
+ /** A workflow is tens of KiB; anything near this is not one, and is not read. */
59
+ const MAX_WORKFLOW_BYTES = 2 * 1024 * 1024;
60
+ /**
61
+ * The nonce a previous install left in `configDir`, read from the first installed workflow that
62
+ * carries one; null when there is none. Entries that are not regular files (a directory, a FIFO,
63
+ * a symlink) and files over MAX_WORKFLOW_BYTES are skipped, and one unreadable entry does not end
64
+ * the scan.
65
+ */
66
+ function readInstalledNonce(configDir) {
67
+ let dir;
68
+ let names;
69
+ try {
70
+ dir = (0, node_path_1.join)(configDir, 'gsd-core', 'workflows');
71
+ names = (0, node_fs_1.readdirSync)(dir).sort();
72
+ }
73
+ catch {
74
+ return null;
75
+ }
76
+ for (const name of names) {
77
+ if (!name.endsWith('.md'))
78
+ continue;
79
+ try {
80
+ const full = (0, node_path_1.join)(dir, name);
81
+ const stat = (0, node_fs_1.lstatSync)(full);
82
+ if (!stat.isFile() || stat.size > MAX_WORKFLOW_BYTES)
83
+ continue;
84
+ const match = INSTALLED_NONCE_RE.exec((0, node_fs_1.readFileSync)(full, 'utf8'));
85
+ if (match)
86
+ return match[1];
87
+ }
88
+ catch {
89
+ // This entry is unreadable; the next one may still carry the nonce.
90
+ }
91
+ }
92
+ return null;
93
+ }
94
+ /**
95
+ * Arm stamping for the install into `configDir`, before it writes anything. When `reuse` is true
96
+ * the directory's installed nonce is kept, so a reinstall changes nothing; otherwise, or when
97
+ * there is none, a fresh one is minted. Only a global install (a directory under the user's home,
98
+ * which a cloned repository cannot seed) may reuse: a project-local directory is often committed,
99
+ * and a nonce planted there would be adopted. Generators never arm it, so the committed generated
100
+ * skills stay canonical and deterministic.
101
+ */
102
+ function armArgsNonce(configDir, reuse = false) {
103
+ const prior = reuse && configDir !== undefined ? readInstalledNonce(configDir) : null;
104
+ armedNonce = prior ?? generateArgsNonce();
105
+ return armedNonce;
106
+ }
107
+ /** Stop stamping: the process is no longer writing for an install that has a nonce. */
108
+ function disarmArgsNonce() {
109
+ armedNonce = null;
110
+ }
111
+ /** `content` stamped with the armed nonce, or untouched when this process is not an installer. */
112
+ function stampInstalledContent(content) {
113
+ return armedNonce === null ? content : stampArgsTerminator(content, armedNonce);
114
+ }
@@ -504,6 +504,63 @@ function validateEntryShape(e, i) {
504
504
  ...('milestone' in o ? { milestone: typeof o.milestone === 'string' ? o.milestone : null } : {}),
505
505
  };
506
506
  }
507
+ /**
508
+ * #5418: when the frontmatter counts disagree with the parsed entries, the usual
509
+ * cause is a hand-edited table: a row added to the rendered table (and the counts
510
+ * bumped to match) while the fenced JSON block, the sole source of entries, was left
511
+ * behind. Names the row ids the table and the JSON block disagree about so the
512
+ * counts message does not point at the (correct) frontmatter. Returns '' when there
513
+ * is nothing to add. Best-effort by design: it runs while a WindowsError is already
514
+ * on its way out, so it must never throw and replace that error.
515
+ */
516
+ /** Row ids spelled out in the divergence note; the rest are counted. */
517
+ const ROW_ID_LIST_LIMIT = 20;
518
+ function describeTableJsonDivergence(raw, entries) {
519
+ try {
520
+ // The hint is the PARSED entry count, not frontmatter total_count: the latter
521
+ // is the number that is wrong here.
522
+ const table = extractTableRegion(raw, entries.length);
523
+ if (table === null)
524
+ return '';
525
+ const rowId = (line) => (line.split('|')[1] ?? '').trim();
526
+ // Ids are positive integers (the JSON side enforces it), so a first cell that is
527
+ // not one — the `_(none)_` empty-ledger placeholder, a continuation line of a
528
+ // multi-line description, control characters — is not a row id and is never
529
+ // echoed into the message.
530
+ const tableIds = new Set();
531
+ const tableLines = table.split('\n');
532
+ for (let i = 2; i < tableLines.length; i++) {
533
+ if (!tableLines[i].startsWith('|'))
534
+ continue;
535
+ const id = rowId(tableLines[i]);
536
+ if (/^\d{1,15}$/.test(id))
537
+ tableIds.add(id);
538
+ }
539
+ const jsonIds = new Set(entries.map((e) => String(e.id)));
540
+ const byNumber = (a, b) => Number(a) - Number(b);
541
+ const list = (ids) => {
542
+ const shown = ids.slice(0, ROW_ID_LIST_LIMIT).join(', ');
543
+ return ids.length > ROW_ID_LIST_LIMIT ? `${shown} and ${ids.length - ROW_ID_LIST_LIMIT} more` : shown;
544
+ };
545
+ const tableOnly = [...tableIds].filter((id) => !jsonIds.has(id)).sort(byNumber);
546
+ const jsonOnly = [...jsonIds].filter((id) => !tableIds.has(id)).sort(byNumber);
547
+ const parts = [];
548
+ if (tableOnly.length > 0) {
549
+ parts.push(`row id(s) in the table but not in the fenced JSON block: ${list(tableOnly)}`);
550
+ }
551
+ if (jsonOnly.length > 0) {
552
+ parts.push(`row id(s) in the fenced JSON block but not in the table: ${list(jsonOnly)}`);
553
+ }
554
+ if (parts.length === 0)
555
+ return '';
556
+ return ` The table and the fenced JSON block disagree (${parts.join('; ')}). The JSON block is the ` +
557
+ 'authoritative entries source: add the missing entries there (or remove the stray table rows) ' +
558
+ 'and keep the frontmatter counts in step with it.';
559
+ }
560
+ catch {
561
+ return '';
562
+ }
563
+ }
507
564
  function parseLedger(raw) {
508
565
  const fm = parseFrontmatterStrict(raw);
509
566
  if (fm.schema_version !== exports.SCHEMA_VERSION) {
@@ -537,7 +594,8 @@ function parseLedger(raw) {
537
594
  recomputed.total_count !== ledger.total_count) {
538
595
  throw new WindowsError(exports.REASON.WINDOWS_LEDGER_MALFORMED, `Ledger counts disagree with entries: frontmatter open/waived/fixed/total=` +
539
596
  `${ledger.open_count}/${ledger.waived_count}/${ledger.fixed_count}/${ledger.total_count}` +
540
- ` but entries yield ${recomputed.open_count}/${recomputed.waived_count}/${recomputed.fixed_count}/${recomputed.total_count}.`);
597
+ ` but entries yield ${recomputed.open_count}/${recomputed.waived_count}/${recomputed.fixed_count}/${recomputed.total_count}.` +
598
+ describeTableJsonDivergence(raw, entries));
541
599
  }
542
600
  return ledger;
543
601
  }
@@ -34,7 +34,7 @@ const consentMod = require('./capability-consent.cjs');
34
34
  const projectRootMod = require('./project-root.cjs');
35
35
  // #1459 finding 4: the SHARED hardened lock primitive (single source of truth for lifecycle + consent).
36
36
  const lockMod = require('./capability-lock.cjs');
37
- const { platformWriteSync, retryRenameSync } = require('./shell-command-projection.cjs');
37
+ const { platformWriteSync, retryRenameSync, shellSingleQuote } = require('./shell-command-projection.cjs');
38
38
  // #1463: numeric major.minor.patch comparison for the outdated check (the SAME compare the resolver
39
39
  // and capability list use). -1 (a<b), 0 (equal), 1 (a>b).
40
40
  const semverMod = require('./semver-compare.cjs');
@@ -308,17 +308,6 @@ function isSafeHookScriptPath(script) {
308
308
  return false;
309
309
  return true;
310
310
  }
311
- /**
312
- * #1460 (R) HIGH: POSIX single-quote an arbitrary string for safe inclusion in a shell command.
313
- * The emitted hook `command` is the ABSOLUTE confined script path, which begins with the
314
- * (non-manifest) install-prefix — commonly a home dir containing spaces/special chars (e.g.
315
- * "/Users/Bob Smith/.claude/..."). Written unquoted it would word-split (and, with a hostile
316
- * prefix, could inject). Wrapping in single quotes — with each embedded `'` escaped as `'\''` —
317
- * makes the whole path a single shell token that no metacharacter inside it can break.
318
- */
319
- function shellSingleQuote(value) {
320
- return "'" + value.replace(/'/g, "'\\''") + "'";
321
- }
322
311
  /**
323
312
  * #1634: build the emitted hook `command` for an ABSOLUTE confined script path. For `.js`-family
324
313
  * hooks (`.js`/`.cjs`/`.mjs`) prefix with `node` so the hook runs regardless of the source's