@opengsd/gsd-core 1.13.0 → 1.15.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 (441) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.ja-JP.md +3 -3
  4. package/README.ko-KR.md +3 -3
  5. package/README.pt-BR.md +3 -3
  6. package/README.zh-CN.md +3 -3
  7. package/agents/gsd-advisor-researcher.compact.md +85 -0
  8. package/agents/gsd-ai-researcher.compact.md +96 -0
  9. package/agents/gsd-assumptions-analyzer.compact.md +81 -0
  10. package/agents/gsd-code-fixer.compact.md +459 -0
  11. package/agents/gsd-code-fixer.md +9 -8
  12. package/agents/gsd-code-reviewer.compact.md +269 -0
  13. package/agents/gsd-code-reviewer.md +15 -3
  14. package/agents/gsd-codebase-mapper.compact.md +760 -0
  15. package/agents/gsd-debug-session-manager.compact.md +360 -0
  16. package/agents/gsd-debug-session-manager.md +17 -2
  17. package/agents/gsd-debugger.md +2 -2
  18. package/agents/gsd-doc-classifier.compact.md +192 -0
  19. package/agents/gsd-doc-synthesizer.compact.md +200 -0
  20. package/agents/gsd-doc-verifier.compact.md +143 -0
  21. package/agents/gsd-doc-writer.compact.md +440 -0
  22. package/agents/gsd-dom-verifier.compact.md +138 -0
  23. package/agents/gsd-domain-researcher.compact.md +141 -0
  24. package/agents/gsd-eval-auditor.compact.md +160 -0
  25. package/agents/gsd-eval-auditor.md +1 -1
  26. package/agents/gsd-eval-planner.compact.md +137 -0
  27. package/agents/gsd-executor.md +13 -8
  28. package/agents/gsd-framework-selector.compact.md +82 -0
  29. package/agents/gsd-integration-checker.compact.md +245 -0
  30. package/agents/gsd-intel-updater.compact.md +226 -0
  31. package/agents/gsd-intel-updater.md +1 -1
  32. package/agents/gsd-mempalace-curator.compact.md +45 -0
  33. package/agents/gsd-nyquist-auditor.compact.md +179 -0
  34. package/agents/gsd-pattern-mapper.compact.md +275 -0
  35. package/agents/gsd-phase-researcher.md +19 -11
  36. package/agents/gsd-plan-checker.md +8 -7
  37. package/agents/gsd-planner.md +12 -8
  38. package/agents/gsd-project-researcher.compact.md +587 -0
  39. package/agents/gsd-project-researcher.md +1 -1
  40. package/agents/gsd-research-synthesizer.compact.md +212 -0
  41. package/agents/gsd-research-synthesizer.md +1 -1
  42. package/agents/gsd-roadmapper.compact.md +454 -0
  43. package/agents/gsd-roadmapper.md +13 -0
  44. package/agents/gsd-security-auditor.compact.md +162 -0
  45. package/agents/gsd-ui-auditor.compact.md +404 -0
  46. package/agents/gsd-ui-auditor.md +155 -17
  47. package/agents/gsd-ui-checker.compact.md +277 -0
  48. package/agents/gsd-ui-researcher.compact.md +282 -0
  49. package/agents/gsd-ui-researcher.md +1 -1
  50. package/agents/gsd-user-profiler.compact.md +108 -0
  51. package/agents/gsd-verifier.md +10 -9
  52. package/bin/install.js +848 -163
  53. package/commands/gsd/autonomous.md +2 -2
  54. package/commands/gsd/capture.md +1 -1
  55. package/commands/gsd/cleanup.md +1 -0
  56. package/commands/gsd/code-review.md +2 -1
  57. package/commands/gsd/complete-milestone.md +1 -0
  58. package/commands/gsd/config.md +1 -0
  59. package/commands/gsd/debug.md +1 -0
  60. package/commands/gsd/graphify.md +1 -0
  61. package/commands/gsd/health.md +1 -0
  62. package/commands/gsd/mempalace-capture.md +8 -3
  63. package/commands/gsd/mempalace-recall.md +1 -0
  64. package/commands/gsd/new-milestone.md +1 -0
  65. package/commands/gsd/new-project.md +1 -0
  66. package/commands/gsd/next.md +1 -0
  67. package/commands/gsd/pause-work.md +1 -0
  68. package/commands/gsd/phase.md +1 -0
  69. package/commands/gsd/plan-review-convergence.md +6 -6
  70. package/commands/gsd/pr-branch.md +1 -0
  71. package/commands/gsd/progress.md +1 -1
  72. package/commands/gsd/quick-batch.md +1 -1
  73. package/commands/gsd/resume-work.md +1 -0
  74. package/commands/gsd/review-backlog.md +1 -0
  75. package/commands/gsd/review.md +2 -3
  76. package/commands/gsd/settings.md +2 -1
  77. package/commands/gsd/stats.md +1 -0
  78. package/commands/gsd/thread.md +1 -0
  79. package/commands/gsd/workspace.md +1 -0
  80. package/commands/gsd/workstreams.md +1 -0
  81. package/gsd-core/bin/check-latest-version.cjs +8 -3
  82. package/gsd-core/bin/gsd-tools.cjs +672 -146
  83. package/gsd-core/bin/lib/adr-parser.cjs +4 -2
  84. package/gsd-core/bin/lib/artifacts.cjs +2 -1
  85. package/gsd-core/bin/lib/audit.cjs +119 -34
  86. package/gsd-core/bin/lib/broken-windows.cjs +168 -49
  87. package/gsd-core/bin/lib/capability-lifecycle.cjs +10 -6
  88. package/gsd-core/bin/lib/capability-loader.cjs +135 -1
  89. package/gsd-core/bin/lib/capability-registry.cjs +96 -189
  90. package/gsd-core/bin/lib/capability-source.cjs +19 -2
  91. package/gsd-core/bin/lib/capability-validator.cjs +14 -2
  92. package/gsd-core/bin/lib/check-command-router.cjs +213 -49
  93. package/gsd-core/bin/lib/code-review-depth.cjs +2 -2
  94. package/gsd-core/bin/lib/codex-agent-toml.cjs +21 -25
  95. package/gsd-core/bin/lib/commands.cjs +823 -112
  96. package/gsd-core/bin/lib/config-loader.cjs +66 -4
  97. package/gsd-core/bin/lib/config.cjs +186 -45
  98. package/gsd-core/bin/lib/coverage.cjs +1 -1
  99. package/gsd-core/bin/lib/decisions.cjs +164 -45
  100. package/gsd-core/bin/lib/external-descriptor-trust.cjs +29 -14
  101. package/gsd-core/bin/lib/frontmatter.cjs +13 -0
  102. package/gsd-core/bin/lib/graphify.cjs +10 -2
  103. package/gsd-core/bin/lib/gsd2-import.cjs +1 -2
  104. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +12 -1
  105. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +1 -1
  106. package/gsd-core/bin/lib/host-runtime-detection.cjs +9 -0
  107. package/gsd-core/bin/lib/init.cjs +614 -86
  108. package/gsd-core/bin/lib/install-engine.cjs +29 -3
  109. package/gsd-core/bin/lib/install-profiles.cjs +14 -0
  110. package/gsd-core/bin/lib/installer-migrations.cjs +41 -5
  111. package/gsd-core/bin/lib/loop-resolver.cjs +50 -31
  112. package/gsd-core/bin/lib/mcp-catalog.cjs +2 -2
  113. package/gsd-core/bin/lib/milestone.cjs +37 -13
  114. package/gsd-core/bin/lib/model-resolver.cjs +253 -53
  115. package/gsd-core/bin/lib/phase-command-router.cjs +16 -2
  116. package/gsd-core/bin/lib/phase-id-card.cjs +32 -0
  117. package/gsd-core/bin/lib/phase-id-display.cjs +78 -0
  118. package/gsd-core/bin/lib/phase-id.cjs +268 -27
  119. package/gsd-core/bin/lib/phase-lifecycle.cjs +61 -0
  120. package/gsd-core/bin/lib/phase-locator.cjs +29 -10
  121. package/gsd-core/bin/lib/phase.cjs +393 -88
  122. package/gsd-core/bin/lib/plan-document.cjs +49 -1
  123. package/gsd-core/bin/lib/planning-document.cjs +459 -0
  124. package/gsd-core/bin/lib/planning-inspect.cjs +52 -19
  125. package/gsd-core/bin/lib/planning-snapshot.cjs +61 -12
  126. package/gsd-core/bin/lib/planning-workspace.cjs +57 -3
  127. package/gsd-core/bin/lib/pr-branch-patterns.cjs +57 -0
  128. package/gsd-core/bin/lib/pristine-baseline.cjs +182 -0
  129. package/gsd-core/bin/lib/probe-core.cjs +7 -1
  130. package/gsd-core/bin/lib/prohibition-enforcement.cjs +91 -4
  131. package/gsd-core/bin/lib/project-root.cjs +41 -2
  132. package/gsd-core/bin/lib/quick-batch.cjs +1 -1
  133. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +61 -2
  134. package/gsd-core/bin/lib/research-store.cjs +11 -12
  135. package/gsd-core/bin/lib/review-lane-descriptor.cjs +10 -30
  136. package/gsd-core/bin/lib/review-lane-invocation.cjs +23 -0
  137. package/gsd-core/bin/lib/review-reviewer-selection.cjs +2 -2
  138. package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +337 -0
  139. package/gsd-core/bin/lib/roadmap-command-router.cjs +12 -4
  140. package/gsd-core/bin/lib/roadmap-parser.cjs +219 -18
  141. package/gsd-core/bin/lib/roadmap-upgrade.cjs +1539 -13
  142. package/gsd-core/bin/lib/roadmap.cjs +356 -42
  143. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +310 -41
  144. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +15 -4
  145. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +13 -5
  146. package/gsd-core/bin/lib/runtime-homes.cjs +4 -0
  147. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +408 -37
  148. package/gsd-core/bin/lib/runtime-name-policy.cjs +111 -1
  149. package/gsd-core/bin/lib/security.cjs +126 -7
  150. package/gsd-core/bin/lib/shell-command-projection.cjs +10 -6
  151. package/gsd-core/bin/lib/state-document.cjs +130 -28
  152. package/gsd-core/bin/lib/state-md-schema.cjs +21 -14
  153. package/gsd-core/bin/lib/state-transition.cjs +181 -30
  154. package/gsd-core/bin/lib/state.cjs +265 -27
  155. package/gsd-core/bin/lib/surface.cjs +77 -3
  156. package/gsd-core/bin/lib/task-command-router.cjs +12 -6
  157. package/gsd-core/bin/lib/tdd-red-evidence.cjs +78 -5
  158. package/gsd-core/bin/lib/uat-predicate.cjs +47 -4
  159. package/gsd-core/bin/lib/uat.cjs +9 -1
  160. package/gsd-core/bin/lib/ui-consideration-probe.cjs +15 -2
  161. package/gsd-core/bin/lib/ui-frontend-evidence.cjs +100 -9
  162. package/gsd-core/bin/lib/undo-commit-selection.cjs +131 -0
  163. package/gsd-core/bin/lib/update-context.cjs +30 -24
  164. package/gsd-core/bin/lib/vendor/js-yaml.cjs +11 -3
  165. package/gsd-core/bin/lib/verification.cjs +315 -30
  166. package/gsd-core/bin/lib/verify-command-grounding.cjs +47 -3
  167. package/gsd-core/bin/lib/verify.cjs +320 -48
  168. package/gsd-core/bin/lib/workstream-inventory.cjs +1 -0
  169. package/gsd-core/bin/lib/worktree-base-ref.cjs +482 -73
  170. package/gsd-core/bin/lib/worktree-safety.cjs +797 -58
  171. package/gsd-core/bin/shared/config-defaults.manifest.json +4 -0
  172. package/gsd-core/bin/shared/config-schema.manifest.json +6 -0
  173. package/gsd-core/bin/verify-reapply-patches.cjs +439 -80
  174. package/gsd-core/references/checkpoints.md +5 -3
  175. package/gsd-core/references/compact-content-gate.md +66 -0
  176. package/gsd-core/references/edge-probe-fixtures/01-round-half-even/expected-coverage.json +28 -3
  177. package/gsd-core/references/edge-probe-fixtures/02-merge-intervals/expected-coverage.json +37 -4
  178. package/gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/expected-coverage.json +28 -3
  179. package/gsd-core/references/edge-probe-fixtures/04-money-rounding/expected-coverage.json +28 -3
  180. package/gsd-core/references/edge-probe-fixtures/05-list-dedupe/expected-coverage.json +37 -4
  181. package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/expected-coverage.json +37 -4
  182. package/gsd-core/references/edge-probe.md +195 -21
  183. package/gsd-core/references/execute-phase-between-wave-reset.md +7 -6
  184. package/gsd-core/references/execute-phase-wave-guard.md +22 -11
  185. package/gsd-core/references/gsd-run-resolver.md +1 -1
  186. package/gsd-core/references/loop-hook-dispatch.md +18 -0
  187. package/gsd-core/references/model-profiles.md +13 -4
  188. package/gsd-core/references/phase-argument-parsing.md +9 -7
  189. package/gsd-core/references/phase-id-convention.md +28 -0
  190. package/gsd-core/references/planner-gap-closure.md +2 -0
  191. package/gsd-core/references/planner-load-graph-context.md +24 -13
  192. package/gsd-core/references/planner-verify-command-grounding.md +14 -0
  193. package/gsd-core/references/planning-config.md +14 -2
  194. package/gsd-core/references/tdd.md +30 -4
  195. package/gsd-core/references/thinking-models-planning.md +18 -2
  196. package/gsd-core/references/ui-consideration-probe.md +10 -5
  197. package/gsd-core/references/verification-patterns.md +17 -4
  198. package/gsd-core/references/verify-command-path-resolvability.md +10 -2
  199. package/gsd-core/references/worktree-path-safety.md +433 -2
  200. package/gsd-core/templates/README.md +7 -1
  201. package/gsd-core/templates/state.md +6 -3
  202. package/gsd-core/templates/summary.compact.md +212 -0
  203. package/gsd-core/templates/user-setup.compact.md +199 -0
  204. package/gsd-core/templates/user-setup.md +0 -9
  205. package/gsd-core/templates/verification-report.md +1 -1
  206. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  207. package/gsd-core/workflows/add-backlog.md +1 -1
  208. package/gsd-core/workflows/add-phase.md +1 -1
  209. package/gsd-core/workflows/add-tests.md +2 -2
  210. package/gsd-core/workflows/add-todo.md +6 -5
  211. package/gsd-core/workflows/ai-integration-phase.md +11 -3
  212. package/gsd-core/workflows/audit-fix.md +1 -1
  213. package/gsd-core/workflows/audit-milestone.md +1 -1
  214. package/gsd-core/workflows/audit-uat.md +1 -1
  215. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +9 -18
  216. package/gsd-core/workflows/autonomous.md +29 -16
  217. package/gsd-core/workflows/check-todos.md +6 -4
  218. package/gsd-core/workflows/cleanup.md +5 -3
  219. package/gsd-core/workflows/code-review/steps/dispatch-fix.md +4 -3
  220. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +8 -1
  221. package/gsd-core/workflows/code-review-fix.md +108 -22
  222. package/gsd-core/workflows/code-review.md +216 -73
  223. package/gsd-core/workflows/complete-milestone/detail/elaboration.md +274 -0
  224. package/gsd-core/workflows/complete-milestone.md +41 -264
  225. package/gsd-core/workflows/debug.md +3 -3
  226. package/gsd-core/workflows/diagnose-issues.md +1 -1
  227. package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
  228. package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
  229. package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
  230. package/gsd-core/workflows/discuss-phase.md +1 -1
  231. package/gsd-core/workflows/do.md +2 -2
  232. package/gsd-core/workflows/docs-update/detail/elaboration.md +179 -0
  233. package/gsd-core/workflows/docs-update.md +17 -158
  234. package/gsd-core/workflows/edit-phase.md +1 -1
  235. package/gsd-core/workflows/eval-review.md +10 -3
  236. package/gsd-core/workflows/execute-phase/detail/elaboration.md +124 -0
  237. package/gsd-core/workflows/execute-phase/steps/code-review-disposition.md +1017 -0
  238. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +19 -4
  239. package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +56 -0
  240. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +43 -4
  241. package/gsd-core/workflows/execute-phase/steps/executor-progress-policy.md +43 -0
  242. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
  243. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
  244. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
  245. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +1 -1
  246. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +46 -9
  247. package/gsd-core/workflows/execute-phase/steps/protected-branch.md +1 -1
  248. package/gsd-core/workflows/execute-phase/steps/ready-wave-gate.md +37 -0
  249. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +1 -1
  250. package/gsd-core/workflows/execute-phase/steps/sequential-root-pin.md +35 -0
  251. package/gsd-core/workflows/execute-phase/steps/stale-reverification.md +24 -0
  252. package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +1 -1
  253. package/gsd-core/workflows/execute-phase/steps/threat-id-gate.md +28 -0
  254. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +1 -1
  255. package/gsd-core/workflows/execute-phase/steps/worktree-base-check.md +25 -0
  256. package/gsd-core/workflows/execute-phase.md +83 -172
  257. package/gsd-core/workflows/execute-plan.md +24 -10
  258. package/gsd-core/workflows/explore.md +3 -3
  259. package/gsd-core/workflows/extract-learnings.md +2 -1
  260. package/gsd-core/workflows/fast.md +1 -1
  261. package/gsd-core/workflows/forensics.md +1 -1
  262. package/gsd-core/workflows/graduation.md +1 -1
  263. package/gsd-core/workflows/health.md +2 -2
  264. package/gsd-core/workflows/help/modes/full.compact.md +398 -0
  265. package/gsd-core/workflows/help/modes/full.md +5 -5
  266. package/gsd-core/workflows/help/modes/topic.md +15 -5
  267. package/gsd-core/workflows/help.md +1 -1
  268. package/gsd-core/workflows/import.md +2 -2
  269. package/gsd-core/workflows/inbox.md +2 -2
  270. package/gsd-core/workflows/ingest-docs.md +3 -3
  271. package/gsd-core/workflows/insert-phase.md +1 -1
  272. package/gsd-core/workflows/list-seeds.md +1 -1
  273. package/gsd-core/workflows/list-workspaces.md +1 -1
  274. package/gsd-core/workflows/manager.md +2 -2
  275. package/gsd-core/workflows/map-codebase.md +52 -5
  276. package/gsd-core/workflows/milestone-summary.md +1 -1
  277. package/gsd-core/workflows/mvp-phase.md +1 -1
  278. package/gsd-core/workflows/new-milestone.md +56 -14
  279. package/gsd-core/workflows/new-project/detail/elaboration.md +216 -0
  280. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +3 -3
  281. package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +1 -1
  282. package/gsd-core/workflows/new-project.md +39 -209
  283. package/gsd-core/workflows/new-workspace.md +2 -2
  284. package/gsd-core/workflows/next.md +1 -1
  285. package/gsd-core/workflows/note.md +1 -1
  286. package/gsd-core/workflows/onboard.md +1 -1
  287. package/gsd-core/workflows/pause-work.md +1 -1
  288. package/gsd-core/workflows/plan-phase/detail/elaboration.md +209 -0
  289. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +17 -5
  290. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +1 -1
  291. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +23 -5
  292. package/gsd-core/workflows/plan-phase.md +45 -187
  293. package/gsd-core/workflows/plan-review-convergence.md +21 -5
  294. package/gsd-core/workflows/plant-seed.md +62 -20
  295. package/gsd-core/workflows/pr-branch.md +132 -20
  296. package/gsd-core/workflows/profile-user.md +2 -2
  297. package/gsd-core/workflows/progress.md +1 -1
  298. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +25 -0
  299. package/gsd-core/workflows/quick/steps/quick-verification.md +1 -1
  300. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +1 -1
  301. package/gsd-core/workflows/quick-batch/steps/batch-init.md +1 -1
  302. package/gsd-core/workflows/quick-batch/steps/completion.md +1 -1
  303. package/gsd-core/workflows/quick-batch/steps/merge-wave.md +1 -1
  304. package/gsd-core/workflows/quick-batch/steps/planner-wave.md +1 -1
  305. package/gsd-core/workflows/quick-batch/steps/research-phase.md +1 -1
  306. package/gsd-core/workflows/quick-batch/steps/resume-mode.md +1 -1
  307. package/gsd-core/workflows/quick-batch/steps/verification-wave.md +1 -1
  308. package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +1 -1
  309. package/gsd-core/workflows/quick-batch.md +1 -1
  310. package/gsd-core/workflows/quick.md +29 -10
  311. package/gsd-core/workflows/reapply-patches.md +86 -6
  312. package/gsd-core/workflows/remove-phase.md +1 -1
  313. package/gsd-core/workflows/remove-workspace.md +2 -2
  314. package/gsd-core/workflows/resume-project.md +1 -1
  315. package/gsd-core/workflows/review.md +31 -16
  316. package/gsd-core/workflows/scan.md +1 -1
  317. package/gsd-core/workflows/secure-phase.md +3 -2
  318. package/gsd-core/workflows/settings-advanced.md +30 -10
  319. package/gsd-core/workflows/settings-integrations.md +2 -3
  320. package/gsd-core/workflows/settings.md +22 -9
  321. package/gsd-core/workflows/ship.md +3 -2
  322. package/gsd-core/workflows/sketch-wrap-up.md +1 -1
  323. package/gsd-core/workflows/sketch.md +1 -1
  324. package/gsd-core/workflows/smart-entry.md +2 -2
  325. package/gsd-core/workflows/spec-phase.md +15 -5
  326. package/gsd-core/workflows/spike-wrap-up.md +1 -1
  327. package/gsd-core/workflows/spike.md +1 -1
  328. package/gsd-core/workflows/stats.md +1 -1
  329. package/gsd-core/workflows/sync-skills.md +5 -5
  330. package/gsd-core/workflows/thread.md +1 -1
  331. package/gsd-core/workflows/transition.md +1 -1
  332. package/gsd-core/workflows/ui-phase.md +44 -8
  333. package/gsd-core/workflows/ui-review.md +18 -4
  334. package/gsd-core/workflows/ultraplan-phase.md +1 -1
  335. package/gsd-core/workflows/undo.md +339 -20
  336. package/gsd-core/workflows/update.md +14 -12
  337. package/gsd-core/workflows/validate-phase.md +3 -2
  338. package/gsd-core/workflows/verify-work/detail/elaboration.md +230 -0
  339. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +1 -1
  340. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
  341. package/gsd-core/workflows/verify-work.md +101 -196
  342. package/hooks/dist/gsd-agent-isolation-guard.js +66 -16
  343. package/hooks/dist/gsd-context-monitor.js +88 -15
  344. package/hooks/dist/gsd-cursor-subagent-start.js +34 -14
  345. package/hooks/dist/gsd-secret-read-guard.js +71 -19
  346. package/hooks/dist/gsd-statusline.js +81 -20
  347. package/hooks/dist/gsd-validate-commit.sh +97 -8
  348. package/hooks/dist/gsd-worktree-path-guard.js +25 -14
  349. package/hooks/dist/gsd-write-guard.js +46 -1
  350. package/hooks/dist/lib/dispatch-identity.js +187 -0
  351. package/hooks/dist/lib/filename-classification.js +64 -0
  352. package/hooks/dist/lib/isolation-deny-reason.js +53 -1
  353. package/hooks/dist/lib/isolation-sentinel.js +58 -19
  354. package/hooks/gsd-agent-isolation-guard.js +66 -16
  355. package/hooks/gsd-context-monitor.js +88 -15
  356. package/hooks/gsd-cursor-subagent-start.js +34 -14
  357. package/hooks/gsd-secret-read-guard.js +71 -19
  358. package/hooks/gsd-statusline.js +81 -20
  359. package/hooks/gsd-validate-commit.sh +97 -8
  360. package/hooks/gsd-worktree-path-guard.js +25 -14
  361. package/hooks/gsd-write-guard.js +46 -1
  362. package/hooks/lib/dispatch-identity.js +187 -0
  363. package/hooks/lib/filename-classification.js +64 -0
  364. package/hooks/lib/isolation-deny-reason.js +53 -1
  365. package/hooks/lib/isolation-sentinel.js +58 -19
  366. package/package.json +11 -6
  367. package/scripts/benchmark-compact-content-variants.cjs +298 -0
  368. package/scripts/benchmark-compact-content.cjs +368 -0
  369. package/scripts/build-hooks.js +15 -6
  370. package/scripts/check-contract-drift.cjs +131 -12
  371. package/scripts/check-env.cjs +36 -8
  372. package/scripts/check-glossary-refs.cjs +25 -21
  373. package/scripts/ci-next-health.cjs +271 -0
  374. package/scripts/ci-prepare-test-scope.cjs +7 -7
  375. package/scripts/ci-test-scope.cjs +126 -20
  376. package/scripts/ci-timeout-report.cjs +1 -1
  377. package/scripts/command-contract-helpers.cjs +3 -0
  378. package/scripts/diff-touches-shipped-paths.cjs +1 -1
  379. package/scripts/docs-guard-registry.cjs +35 -2
  380. package/scripts/gen-adr-index.cjs +8 -2
  381. package/scripts/gen-inventory-manifest.cjs +12 -0
  382. package/scripts/gen-loop-host-contract.cjs +69 -0
  383. package/scripts/gen-platform-conformance-tier.cjs +557 -0
  384. package/scripts/lib/drift-scan.cjs +1 -1
  385. package/scripts/lib/macos-conformance-tier.generated.cjs +224 -0
  386. package/scripts/lib/ndjson-reporter.cjs +3 -2
  387. package/scripts/lib/npm-version-check-diagnosis.cjs +59 -0
  388. package/scripts/lib/platform-conformance-tier.generated.cjs +287 -0
  389. package/scripts/lib/suite-detection.cjs +32 -0
  390. package/scripts/lint-allowed-tools-parity.cjs +221 -0
  391. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +47 -3
  392. package/scripts/lint-phase-arg-assignment.cjs +257 -0
  393. package/scripts/lint-phase-id-drift.cjs +623 -13
  394. package/scripts/lint-pr-branch-pattern-drift.cjs +148 -0
  395. package/scripts/lint-response-language-coverage.cjs +9 -3
  396. package/scripts/lint-retired-runtime-name.cjs +619 -0
  397. package/scripts/lint-source-test-name-collision.cjs +1 -1
  398. package/scripts/lint-state-write-path-drift.cjs +93 -0
  399. package/scripts/lint-test-file-count.allowlist.json +29 -9
  400. package/scripts/lint-vendored-deps.cjs +128 -17
  401. package/scripts/lint-workflow-shellcheck-baseline.json +100 -0
  402. package/scripts/prompt-injection-scan.sh +18 -0
  403. package/scripts/release-tarball-smoke.cjs +194 -1
  404. package/scripts/workflow-size.cjs +139 -0
  405. package/skills/gsd-autonomous/SKILL.md +2 -2
  406. package/skills/gsd-capture/SKILL.md +1 -1
  407. package/skills/gsd-cleanup/SKILL.md +1 -0
  408. package/skills/gsd-code-review/SKILL.md +2 -1
  409. package/skills/gsd-complete-milestone/SKILL.md +1 -0
  410. package/skills/gsd-config/SKILL.md +1 -0
  411. package/skills/gsd-debug/SKILL.md +1 -0
  412. package/skills/gsd-graphify/SKILL.md +1 -0
  413. package/skills/gsd-health/SKILL.md +1 -0
  414. package/skills/gsd-mempalace-capture/SKILL.md +8 -3
  415. package/skills/gsd-mempalace-recall/SKILL.md +1 -0
  416. package/skills/gsd-new-milestone/SKILL.md +1 -0
  417. package/skills/gsd-new-project/SKILL.md +1 -0
  418. package/skills/gsd-next/SKILL.md +1 -0
  419. package/skills/gsd-pause-work/SKILL.md +1 -0
  420. package/skills/gsd-phase/SKILL.md +1 -0
  421. package/skills/gsd-plan-review-convergence/SKILL.md +5 -5
  422. package/skills/gsd-pr-branch/SKILL.md +1 -0
  423. package/skills/gsd-progress/SKILL.md +1 -1
  424. package/skills/gsd-quick-batch/SKILL.md +1 -1
  425. package/skills/gsd-resume-work/SKILL.md +1 -0
  426. package/skills/gsd-review/SKILL.md +2 -3
  427. package/skills/gsd-review-backlog/SKILL.md +1 -0
  428. package/skills/gsd-settings/SKILL.md +2 -1
  429. package/skills/gsd-stats/SKILL.md +1 -0
  430. package/skills/gsd-thread/SKILL.md +1 -0
  431. package/skills/gsd-workspace/SKILL.md +1 -0
  432. package/skills/gsd-workstreams/SKILL.md +1 -0
  433. package/vscode/package.json +1 -1
  434. package/gsd-core/templates/claude-md.md +0 -145
  435. package/gsd-core/templates/codebase/concerns.md +0 -310
  436. package/gsd-core/templates/codebase/conventions.md +0 -307
  437. package/gsd-core/templates/codebase/integrations.md +0 -280
  438. package/gsd-core/templates/codebase/structure.md +0 -285
  439. package/gsd-core/templates/codebase/testing.md +0 -480
  440. package/gsd-core/templates/debug-subagent-prompt.md +0 -91
  441. package/gsd-core/templates/discovery.md +0 -146
@@ -7,7 +7,10 @@
7
7
  * When `workflow.windows_enforce` is true, `/gsd-ship` blocks while any entry is
8
8
  * `open`; an entry can be `waived` only with a recorded reason or `fixed`.
9
9
  *
10
- * LEAF MODULE — imports ONLY: node:fs, node:path. No other src/ imports.
10
+ * LEAF MODULE — imports node:fs + node:path, plus two compiled sibling lib
11
+ * modules require()d at runtime: workstream-inventory.cjs (the #4487
12
+ * milestone stamp) and capability-lock.cjs (the #3780 cross-process ledger
13
+ * lock). No other src/ imports.
11
14
  *
12
15
  * Storage format (`.planning/WINDOWS.md`):
13
16
  * ---
@@ -58,12 +61,15 @@ exports.parseLedger = parseLedger;
58
61
  exports.renderLedger = renderLedger;
59
62
  exports.renderTable = renderTable;
60
63
  exports.extractTableRegion = extractTableRegion;
64
+ exports.withLedgerLock = withLedgerLock;
61
65
  exports.cmdWindowsStatus = cmdWindowsStatus;
62
66
  exports.cmdWindowsAppend = cmdWindowsAppend;
63
67
  exports.cmdWindowsWaive = cmdWindowsWaive;
64
68
  exports.cmdWindowsMarkFixed = cmdWindowsMarkFixed;
65
69
  const node_fs_1 = __importDefault(require("node:fs"));
66
70
  const node_path_1 = __importDefault(require("node:path"));
71
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
72
+ const workstreamInventory = require("./workstream-inventory.cjs");
67
73
  // ─── Constants ─────────────────────────────────────────────────────────────
68
74
  exports.LEDGER_FILE_NAME = 'WINDOWS.md';
69
75
  exports.SCHEMA_VERSION = 1;
@@ -90,6 +96,11 @@ exports.REASON = Object.freeze({
90
96
  // sole source of truth) at the pre-write seam — refuse rather than silently
91
97
  // reconcile by overwriting the operator's hand-edit or dropping a row.
92
98
  WINDOWS_LEDGER_TABLE_DRIFT: 'windows_ledger_table_drift',
99
+ // #3780: the read-compute-write cycle is serialized on a cross-process
100
+ // ledger lock; this fires only when another writer held the lock past the
101
+ // whole bounded retry budget — a typed, actionable refusal instead of a
102
+ // silently-lost mutation reported as success.
103
+ WINDOWS_LEDGER_LOCK: 'windows_ledger_lock',
93
104
  });
94
105
  /** Allowed window kinds. Aligned with the issue's enumerated sources. */
95
106
  exports.KINDS = Object.freeze([
@@ -219,12 +230,14 @@ function nextId(entries) {
219
230
  * Append a window to the ledger. Assigns the next dense id (max+1), sets
220
231
  * status=open, timestamps via opts.now.
221
232
  *
222
- * Concurrency (issue #1950 review L2): NOT safe for concurrent writers. Two
223
- * parallel `gsd_run windows append` invocations both read the same snapshot,
224
- * both compute the same nextId, both write — the second atomic rename wins
225
- * and the first append (and the entry it added) is silently lost. This is
226
- * acceptable in the current single-executor-per-phase model; document if the
227
- * executor ever gains parallel wave-level append.
233
+ * Concurrency (issue #1950 review L2, superseded by #3780): as a PURE
234
+ * function this operates on whatever ledger snapshot it is passed and cannot
235
+ * see concurrent writers — serialization is the CALLER's job. The I/O entry
236
+ * points below (cmdWindowsAppend/Waive/MarkFixed) now discharge that duty by
237
+ * holding the cross-process ledger lock across their whole
238
+ * read-compute-write cycle, so the previously-documented loss (two parallel
239
+ * writers, second rename wins, first entry silently gone) can no longer
240
+ * occur through the CLI.
228
241
  */
229
242
  function appendWindow(ledger, input, opts = { now: new Date().toISOString() }) {
230
243
  validateKind(input.kind);
@@ -243,6 +256,7 @@ function appendWindow(ledger, input, opts = { now: new Date().toISOString() }) {
243
256
  reason: '',
244
257
  recorded_at: opts.now,
245
258
  resolved_at: null,
259
+ milestone: input.milestone ?? null,
246
260
  };
247
261
  const entries = [...ledger.entries, entry];
248
262
  const result = recomputeCounts({ ...ledger, entries, last_updated: opts.now });
@@ -475,6 +489,19 @@ function validateEntryShape(e, i) {
475
489
  reason: o.reason,
476
490
  recorded_at: recordedStr,
477
491
  resolved_at: resolvedStr,
492
+ // #4487: NOT in `required` above -- an entry recorded before this field
493
+ // existed has no `milestone` key at all, and that must parse cleanly.
494
+ // Preserve the absence itself -- by not materializing the property at
495
+ // all, via the conditional spread below, rather than assigning it
496
+ // `milestone: undefined` (an object literal property set to `undefined`
497
+ // is still an OWN property; `'milestone' in entry` reads true either
498
+ // way) -- so a genuinely absent key stays absent both to `in` and to
499
+ // JSON.stringify on re-render. Collapsing absence to an explicit null
500
+ // instead would stamp every legacy entry with permanent
501
+ // `"milestone": null` noise the moment the ledger is next touched. A
502
+ // genuinely-recorded-but-unresolvable milestone (set by appendWindow)
503
+ // is still an explicit null and round-trips as one.
504
+ ...('milestone' in o ? { milestone: typeof o.milestone === 'string' ? o.milestone : null } : {}),
478
505
  };
479
506
  }
480
507
  function parseLedger(raw) {
@@ -685,6 +712,78 @@ function diffTableRowIds(expectedTable, actualTable) {
685
712
  function ledgerPath(cwd) {
686
713
  return node_path_1.default.join(cwd, '.planning', exports.LEDGER_FILE_NAME);
687
714
  }
715
+ /**
716
+ * The SHARED hardened cross-process lock primitive (single source of truth
717
+ * for capability-lifecycle + capability-consent, extracted so locks cannot
718
+ * diverge). Required LAZILY: capability-lock captures the process start time
719
+ * at module load — a `ps` subprocess on macOS, PowerShell on win32 — and
720
+ * this module is loaded by lock-free readers (`windows status`, the
721
+ * /gsd-ship gate) that must not pay that per-invocation cost (#3780
722
+ * review). `require` is cached, so writers pay it once per process.
723
+ */
724
+ let _lockMod = null;
725
+ function ledgerLock() {
726
+ if (_lockMod === null) {
727
+ /* eslint-disable @typescript-eslint/no-require-imports */
728
+ _lockMod = require('./capability-lock.cjs');
729
+ /* eslint-enable @typescript-eslint/no-require-imports */
730
+ }
731
+ return _lockMod;
732
+ }
733
+ /**
734
+ * Budget mirrors capability-consent's CONSENT_LOCK_MAX_ATTEMPTS: two
735
+ * genuinely-racing writers must SERIALIZE, not fail. The ledger's critical
736
+ * section is sub-millisecond and the primitive backs off ~25-50ms per
737
+ * attempt, so 50 attempts is orders of magnitude beyond any real contention
738
+ * while keeping the worst case (a holder that never releases until the
739
+ * primitive's own liveness/deadman protocol reclaims it) bounded at ~2s
740
+ * before the typed refusal below.
741
+ */
742
+ const LEDGER_LOCK_MAX_ATTEMPTS = 50;
743
+ function ledgerLockPath(cwd) {
744
+ return node_path_1.default.join(cwd, '.planning', '.WINDOWS.lock');
745
+ }
746
+ function acquireLedgerLock(cwd) {
747
+ return ledgerLock().acquireLock(ledgerLockPath(cwd), {
748
+ maxAttempts: LEDGER_LOCK_MAX_ATTEMPTS,
749
+ // A contended fresh/live holder is WAITED FOR (back off + retry), not
750
+ // failed-fast — racing wave-level executors serialize (issue #3780).
751
+ waitForFresh: true,
752
+ });
753
+ }
754
+ function releaseLedgerLock(handle) {
755
+ ledgerLock().releaseLock(handle);
756
+ }
757
+ /**
758
+ * Run `fn` (a full ledger read-compute-write cycle) while holding the
759
+ * cross-process ledger lock. Throws a typed WindowsError — never falls back
760
+ * to an unlocked mutation — when the lock cannot be acquired within the
761
+ * budget, mirroring capability-consent finding 3: a locked store must refuse
762
+ * the write rather than silently race for it. Readers (cmdWindowsStatus, the
763
+ * ship gate) deliberately do NOT take this lock: the atomic rename already
764
+ * gives them a whole-file snapshot.
765
+ *
766
+ * EXPORTED (#3780) because `withLedgerLock` is the ONE serialization seam
767
+ * for WINDOWS.md: every writer of the ledger — the cmd* entry points here
768
+ * and any sibling module with its own read-compute-write cycle on the same
769
+ * file (refactor-trigger-command-router's strict-window record/resolve) —
770
+ * must hold this lock, or the lost-update race #3780 fixed survives on that
771
+ * path.
772
+ */
773
+ function withLedgerLock(cwd, fn) {
774
+ const handle = acquireLedgerLock(cwd);
775
+ if (!handle) {
776
+ throw new WindowsError(exports.REASON.WINDOWS_LEDGER_LOCK, `Another writer holds the ledger lock at ${ledgerLockPath(cwd)}; WINDOWS.md ` +
777
+ 'mutations are serialized per project. Re-run the command once the other ' +
778
+ 'writer finishes — the lock is reclaimed automatically if its holder died.');
779
+ }
780
+ try {
781
+ return fn();
782
+ }
783
+ finally {
784
+ releaseLedgerLock(handle);
785
+ }
786
+ }
688
787
  function readLedgerOrNull(cwd) {
689
788
  const p = ledgerPath(cwd);
690
789
  let raw;
@@ -910,24 +1009,36 @@ function cmdWindowsAppend(cwd, args, opts = {}) {
910
1009
  flags: ['--kind', '--phase', '--file', '--line', '--description'],
911
1010
  required: ['--kind', '--phase', '--description'],
912
1011
  });
913
- let ledger;
914
- try {
915
- ledger = readLedgerOrNull(cwd) ?? emptyLedger(nowIso());
916
- }
917
- catch (e) {
918
- if (e instanceof WindowsError)
919
- throw e;
920
- throw new WindowsError(exports.REASON.WINDOWS_LEDGER_MALFORMED, e.message);
921
- }
922
- const result = appendWindow(ledger, {
923
- kind: parsed.values['--kind'],
924
- phase: parsed.values['--phase'] ?? '',
925
- file: parsed.values['--file'] ?? '',
926
- line: parsed.values['--line'] == null ? null : Number(parsed.values['--line']),
927
- description: parsed.values['--description'] ?? '',
928
- }, { now: nowIso() });
929
- writeLedgerAtomic(cwd, result.ledger);
930
- emit({ ok: true, ledger: result.ledger, entry: result.entry });
1012
+ // #3780: the whole read-compute-write cycle — snapshot, milestone stamp,
1013
+ // id allocation, atomic rename — holds the ledger lock, so two parallel
1014
+ // invocations can no longer compute the same nextId from the same snapshot
1015
+ // and silently lose the first append to the second rename.
1016
+ withLedgerLock(cwd, () => {
1017
+ let ledger;
1018
+ try {
1019
+ ledger = readLedgerOrNull(cwd) ?? emptyLedger(nowIso());
1020
+ }
1021
+ catch (e) {
1022
+ if (e instanceof WindowsError)
1023
+ throw e;
1024
+ throw new WindowsError(exports.REASON.WINDOWS_LEDGER_MALFORMED, e.message);
1025
+ }
1026
+ // #4487: stamp the workstream's resolved milestone at record time -- the
1027
+ // same STATE.md-first, ROADMAP-fallback resolution workstream-inventory.cts
1028
+ // already uses. Best-effort: an unreadable/missing STATE.md or ROADMAP.md
1029
+ // resolves to null, same as an entry recorded before this field existed.
1030
+ const milestone = workstreamInventory.readCurrentMilestoneVersion(node_path_1.default.join(cwd, '.planning', 'STATE.md'), node_path_1.default.join(cwd, '.planning', 'ROADMAP.md'));
1031
+ const result = appendWindow(ledger, {
1032
+ kind: parsed.values['--kind'],
1033
+ phase: parsed.values['--phase'] ?? '',
1034
+ file: parsed.values['--file'] ?? '',
1035
+ line: parsed.values['--line'] == null ? null : Number(parsed.values['--line']),
1036
+ description: parsed.values['--description'] ?? '',
1037
+ milestone,
1038
+ }, { now: nowIso() });
1039
+ writeLedgerAtomic(cwd, result.ledger);
1040
+ emit({ ok: true, ledger: result.ledger, entry: result.entry });
1041
+ });
931
1042
  }
932
1043
  /** `gsd-tools windows waive <id> "<reason>"`. */
933
1044
  function cmdWindowsWaive(cwd, args, opts = {}) {
@@ -936,36 +1047,44 @@ function cmdWindowsWaive(cwd, args, opts = {}) {
936
1047
  const idStr = positionals[0];
937
1048
  const reason = positionals[1];
938
1049
  const id = parseIdOrThrow(idStr);
939
- let ledger;
940
- try {
941
- ledger = readLedgerOrNull(cwd) ?? emptyLedger(nowIso());
942
- }
943
- catch (e) {
944
- if (e instanceof WindowsError)
945
- throw e;
946
- throw new WindowsError(exports.REASON.WINDOWS_LEDGER_MALFORMED, e.message);
947
- }
948
- const updated = markWaived(ledger, id, reason ?? '', { now: nowIso() });
949
- writeLedgerAtomic(cwd, updated);
950
- emit({ ok: true, ledger: updated });
1050
+ // #3780: same serialization as append — a concurrent append holding a stale
1051
+ // snapshot would otherwise overwrite the waive and resurrect the entry.
1052
+ withLedgerLock(cwd, () => {
1053
+ let ledger;
1054
+ try {
1055
+ ledger = readLedgerOrNull(cwd) ?? emptyLedger(nowIso());
1056
+ }
1057
+ catch (e) {
1058
+ if (e instanceof WindowsError)
1059
+ throw e;
1060
+ throw new WindowsError(exports.REASON.WINDOWS_LEDGER_MALFORMED, e.message);
1061
+ }
1062
+ const updated = markWaived(ledger, id, reason ?? '', { now: nowIso() });
1063
+ writeLedgerAtomic(cwd, updated);
1064
+ emit({ ok: true, ledger: updated });
1065
+ });
951
1066
  }
952
1067
  /** `gsd-tools windows fixed <id>`. */
953
1068
  function cmdWindowsMarkFixed(cwd, args, opts = {}) {
954
1069
  void opts;
955
1070
  const { positionals } = parseArgs(args, { flags: [], required: [], positionals: 1 });
956
1071
  const id = parseIdOrThrow(positionals[0]);
957
- let ledger;
958
- try {
959
- ledger = readLedgerOrNull(cwd) ?? emptyLedger(nowIso());
960
- }
961
- catch (e) {
962
- if (e instanceof WindowsError)
963
- throw e;
964
- throw new WindowsError(exports.REASON.WINDOWS_LEDGER_MALFORMED, e.message);
965
- }
966
- const updated = markFixed(ledger, id, { now: nowIso() });
967
- writeLedgerAtomic(cwd, updated);
968
- emit({ ok: true, ledger: updated });
1072
+ // #3780: same serialization as append — a concurrent writer holding a
1073
+ // stale snapshot would otherwise overwrite the resolved status.
1074
+ withLedgerLock(cwd, () => {
1075
+ let ledger;
1076
+ try {
1077
+ ledger = readLedgerOrNull(cwd) ?? emptyLedger(nowIso());
1078
+ }
1079
+ catch (e) {
1080
+ if (e instanceof WindowsError)
1081
+ throw e;
1082
+ throw new WindowsError(exports.REASON.WINDOWS_LEDGER_MALFORMED, e.message);
1083
+ }
1084
+ const updated = markFixed(ledger, id, { now: nowIso() });
1085
+ writeLedgerAtomic(cwd, updated);
1086
+ emit({ ok: true, ledger: updated });
1087
+ });
969
1088
  }
970
1089
  function parseIdOrThrow(raw) {
971
1090
  if (raw == null || raw === '') {
@@ -25,6 +25,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
25
25
  const node_fs_1 = __importDefault(require("node:fs"));
26
26
  const node_path_1 = __importDefault(require("node:path"));
27
27
  const node_crypto_1 = __importDefault(require("node:crypto"));
28
+ const security_cjs_1 = require("./security.cjs");
28
29
  /* eslint-disable @typescript-eslint/no-require-imports */
29
30
  const sourceMod = require('./capability-source.cjs');
30
31
  const ledgerMod = require('./capability-ledger.cjs');
@@ -202,7 +203,10 @@ function safeRmUnder(runtimeDir, rel) {
202
203
  catch {
203
204
  return false;
204
205
  }
205
- if (realParent !== realRoot && !realParent.startsWith(realRoot + node_path_1.default.sep))
206
+ // Containment decision is the canonical LEXICAL predicate (ADR-4650 decision 6); lexical because
207
+ // both operands are already realpath-resolved here and the final component is deliberately
208
+ // handled as a link (see above).
209
+ if ((0, security_cjs_1.tryWithinRootLexical)(realParent, realRoot) === null)
206
210
  return false;
207
211
  const realTarget = node_path_1.default.join(realParent, node_path_1.default.basename(target));
208
212
  let st;
@@ -250,11 +254,11 @@ function confinedSharedFile(runtimeDir, relFile) {
250
254
  catch {
251
255
  // Parent does not exist yet (created inside the scope on write): a non-existent path cannot be a
252
256
  // symlink escaping the root, so a lexical containment check is sufficient.
253
- if (parentDir !== realRoot && !parentDir.startsWith(realRoot + node_path_1.default.sep))
257
+ if ((0, security_cjs_1.tryWithinRootLexical)(parentDir, realRoot) === null)
254
258
  return null;
255
259
  return target;
256
260
  }
257
- if (realParent !== realRoot && !realParent.startsWith(realRoot + node_path_1.default.sep))
261
+ if ((0, security_cjs_1.tryWithinRootLexical)(realParent, realRoot) === null)
258
262
  return null;
259
263
  return node_path_1.default.join(realParent, node_path_1.default.basename(target));
260
264
  }
@@ -363,7 +367,7 @@ function confinedBundleScript(capDirPath, script) {
363
367
  // disk): a non-existent root cannot be a symlink escaping itself, so confine lexically.
364
368
  realCapRoot = node_path_1.default.resolve(capDirPath);
365
369
  const targetLex = node_path_1.default.resolve(realCapRoot, script);
366
- if (targetLex !== realCapRoot && !targetLex.startsWith(realCapRoot + node_path_1.default.sep))
370
+ if ((0, security_cjs_1.tryWithinRootLexical)(targetLex, realCapRoot) === null)
367
371
  return null;
368
372
  return targetLex;
369
373
  }
@@ -376,13 +380,13 @@ function confinedBundleScript(capDirPath, script) {
376
380
  catch {
377
381
  // Parent does not exist yet (created inside the bundle): lexical containment is sufficient
378
382
  // because a non-existent path cannot be a symlink escaping the root.
379
- if (parentDir !== realCapRoot && !parentDir.startsWith(realCapRoot + node_path_1.default.sep))
383
+ if ((0, security_cjs_1.tryWithinRootLexical)(parentDir, realCapRoot) === null)
380
384
  return null;
381
385
  return target;
382
386
  }
383
387
  // The realpath'd parent chain must remain inside the bundle — an ancestor symlink escaping the
384
388
  // bundle is refused here (the symlink is followed by realpathSync, so its real location is checked).
385
- if (realParent !== realCapRoot && !realParent.startsWith(realCapRoot + node_path_1.default.sep))
389
+ if ((0, security_cjs_1.tryWithinRootLexical)(realParent, realCapRoot) === null)
386
390
  return null;
387
391
  return node_path_1.default.join(realParent, node_path_1.default.basename(target));
388
392
  }
@@ -70,6 +70,7 @@ var __importStar = (this && this.__importStar) || (function () {
70
70
  })();
71
71
  Object.defineProperty(exports, "__esModule", { value: true });
72
72
  exports.readHostVersion = readHostVersion;
73
+ exports.crossValidationSeed = crossValidationSeed;
73
74
  exports.loadRegistry = loadRegistry;
74
75
  const fs = __importStar(require("node:fs"));
75
76
  const os = __importStar(require("node:os"));
@@ -366,6 +367,139 @@ function ledgerOverlayIds(ledger, rootDir) {
366
367
  catch { /* missing/invalid/non-regular/oversized ledger — no pending, no committed (fail closed) */ }
367
368
  return { pending, committed };
368
369
  }
370
+ // ─── #3929: install-time cross-capability validation seed ──────────────────
371
+ /** First-party capabilities from the frozen registry, keyed by id. */
372
+ function firstPartyCaps() {
373
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
374
+ const base = require('./capability-registry.cjs');
375
+ return base.capabilities ?? {};
376
+ }
377
+ /**
378
+ * Central config-schema validKeys for the ownership-exclusivity check — the
379
+ * same source `loadRegistry`'s generator path reads, with the same
380
+ * missing-schema fallback (empty set). Memoized per process: the manifest is
381
+ * static for the lifetime of the runtime.
382
+ */
383
+ let _centralKeysMemo = null;
384
+ function centralConfigKeys() {
385
+ if (_centralKeysMemo === null) {
386
+ // Same generator seam loadRegistry uses (including the test override), so
387
+ // install-time and load-time central keys cannot diverge when the
388
+ // generator is stubbed.
389
+ if (_generatorOverride) {
390
+ _centralKeysMemo = _generatorOverride.loadCentralConfigKeys();
391
+ }
392
+ else {
393
+ try {
394
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
395
+ const mod = require('../../../scripts/gen-capability-registry.cjs');
396
+ _centralKeysMemo = mod.loadCentralConfigKeys();
397
+ }
398
+ catch {
399
+ _centralKeysMemo = new Set();
400
+ }
401
+ }
402
+ }
403
+ return _centralKeysMemo;
404
+ }
405
+ /**
406
+ * #3929: the validation seed `capability-source.stageValidated` runs the
407
+ * cross-capability suite against. Built the way `loadRegistry` builds its
408
+ * accepted map: first-party registry capabilities, then each COMMITTED overlay
409
+ * of the TARGET (global) install scope accepted INCREMENTALLY — structural
410
+ * validation, engines.gsd against the running host, then the FULL
411
+ * cross-capability suite; an overlay joins only if the suite stays clean
412
+ * after adding it (load's invariant: first-party alone is clean, so any new
413
+ * error is that overlay's fault — skip it, never fail the seed). The seed is
414
+ * therefore CLEAN BY CONSTRUCTION: pre-existing junk in the install scope
415
+ * (colliding entries, shape-invalid manifests, engines-incompatible bundles)
416
+ * is skipped exactly as load skips it and can never fail or skew a
417
+ * candidate's install decision — a repo-planted ledger cannot veto installs
418
+ * (#1459 CB-3).
419
+ *
420
+ * Scope: only the install TARGET scope is walked — `stageValidated` promotes
421
+ * into `${gsdHome}/.gsd/capabilities`, so target scope == global.
422
+ * Project-scope overlays are deliberately NOT seeded (they additionally
423
+ * require user consent to activate at load; the issue asks for overlays "in
424
+ * the target scope"). The candidate is added by the caller LAST, mirroring
425
+ * `acceptedMap.set(id, cap)`.
426
+ *
427
+ * Exported (not inlined in the installer) so the semantics this reuses —
428
+ * `overlayRoots`, `ledgerOverlayIds`, the shared bounded manifest reader, and
429
+ * the incremental-accept rules — have exactly one owner and cannot drift from
430
+ * the loader's load-time rules.
431
+ */
432
+ function crossValidationSeed(cwd, gsdHome, hostVersion, validator, semver) {
433
+ const fp = firstPartyCaps();
434
+ const capMap = new Map(Object.entries(fp));
435
+ const centralKeys = centralConfigKeys();
436
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
437
+ const ledger = require('./capability-ledger.cjs');
438
+ for (const root of overlayRoots(cwd, gsdHome)) {
439
+ if (root.scope !== 'global')
440
+ continue; // seed the TARGET scope only
441
+ const { committed } = ledgerOverlayIds(ledger, root.dir);
442
+ for (const id of committed) {
443
+ // First-party always wins (CONTEXT.md capability-loader entry): an
444
+ // overlay claiming a first-party id — or a reserved first-party prefix
445
+ // — is rejected at load, so it must not join the validation set.
446
+ if (Object.prototype.hasOwnProperty.call(fp, id))
447
+ continue;
448
+ if (RESERVED_ID_PREFIX.test(id))
449
+ continue;
450
+ let cap;
451
+ try {
452
+ const manifestPath = path.join(root.dir, id, 'capability.json');
453
+ const raw = ledger.readSmallRegularFile(manifestPath, MANIFEST_MAX_BYTES);
454
+ if (raw === null)
455
+ continue; // missing/non-regular/oversized — skip fail-closed
456
+ cap = JSON.parse(raw);
457
+ }
458
+ catch {
459
+ continue; // unreadable overlay — skip (same rule as load)
460
+ }
461
+ // Same per-overlay pre-filters load applies before the cross suite:
462
+ // structural validity, then engines.gsd against the running host.
463
+ // validateCapability itself is not total over malformed array entries
464
+ // (#1461 finding 1) — a throw skips the overlay, as load skips it.
465
+ try {
466
+ if (validator.validateCapability(cap, id).length > 0)
467
+ continue;
468
+ }
469
+ catch {
470
+ continue;
471
+ }
472
+ const engines = cap['engines'];
473
+ if (engines && typeof engines === 'object' && !Array.isArray(engines)) {
474
+ const range = engines['gsd'];
475
+ if (typeof range === 'string' && range && !semver.semverSatisfies(hostVersion, range))
476
+ continue;
477
+ }
478
+ // Incremental accept: the overlay joins only if the FULL suite stays
479
+ // clean after adding it; any error is that overlay's fault — skip it.
480
+ capMap.set(id, cap);
481
+ let errs = [];
482
+ try {
483
+ errs = [
484
+ ...validator.validateConsumesGlobal(capMap),
485
+ ...validator.validateCrossCapability(capMap, centralKeys),
486
+ ];
487
+ }
488
+ catch {
489
+ // The cross validators are not total over arbitrary shapes (#1461
490
+ // finding 1 — e.g. the duplicate-producer invariant throws). A
491
+ // throwing overlay must be REMOVED here, exactly as load's
492
+ // acceptedMap.delete(id) does — retaining it would leave poisoning
493
+ // content in the seed and attribute pre-existing junk to the
494
+ // candidate.
495
+ capMap.delete(id);
496
+ }
497
+ if (errs.length > 0)
498
+ capMap.delete(id);
499
+ }
500
+ }
501
+ return { capMap, centralKeys };
502
+ }
369
503
  /** Shallow-attach overlay diagnostics WITHOUT mutating the frozen registry module. */
370
504
  function withOverlayMeta(reg, meta) {
371
505
  return Object.assign({}, reg, { _overlay: meta });
@@ -821,4 +955,4 @@ function loadRegistry(options = {}) {
821
955
  }
822
956
  }
823
957
  // readHostVersion is exported for the #1920 regression (VERSION-first host-version resolution).
824
- module.exports = { loadRegistry, readHostVersion, _setValidatorForTest, _setGeneratorForTest };
958
+ module.exports = { loadRegistry, readHostVersion, crossValidationSeed, _setValidatorForTest, _setGeneratorForTest };