@opengsd/gsd-core 1.11.0 → 1.12.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 (395) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/agents/gsd-code-fixer.md +1 -1
  4. package/agents/gsd-debug-session-manager.md +1 -1
  5. package/agents/gsd-debugger.md +1 -1
  6. package/agents/gsd-dom-verifier.md +169 -0
  7. package/agents/gsd-eval-auditor.md +1 -1
  8. package/agents/gsd-executor.md +17 -9
  9. package/agents/gsd-framework-selector.md +1 -3
  10. package/agents/gsd-intel-updater.md +1 -1
  11. package/agents/gsd-mempalace-curator.md +0 -1
  12. package/agents/gsd-pattern-mapper.md +11 -0
  13. package/agents/gsd-phase-researcher.md +3 -1
  14. package/agents/gsd-plan-checker.md +15 -55
  15. package/agents/gsd-planner.md +6 -4
  16. package/agents/gsd-project-researcher.md +1 -1
  17. package/agents/gsd-research-synthesizer.md +2 -2
  18. package/agents/gsd-roadmapper.md +15 -11
  19. package/agents/gsd-ui-checker.md +63 -4
  20. package/agents/gsd-ui-researcher.md +41 -3
  21. package/agents/gsd-verifier.md +1 -1
  22. package/bin/install.js +609 -134
  23. package/commands/gsd/discuss-phase.md +1 -1
  24. package/commands/gsd/import.md +1 -1
  25. package/commands/gsd/quick.md +8 -4
  26. package/gsd-core/bin/gsd-tools.cjs +567 -51
  27. package/gsd-core/bin/lib/active-workstream-store.cjs +8 -0
  28. package/gsd-core/bin/lib/adr-parser.cjs +13 -7
  29. package/gsd-core/bin/lib/agent-install-check.cjs +162 -0
  30. package/gsd-core/bin/lib/api-coverage.cjs +30 -9
  31. package/gsd-core/bin/lib/artifacts.cjs +2 -0
  32. package/gsd-core/bin/lib/assumption-delta.cjs +30 -11
  33. package/gsd-core/bin/lib/audit.cjs +163 -41
  34. package/gsd-core/bin/lib/broken-windows.cjs +306 -28
  35. package/gsd-core/bin/lib/capability-lock.cjs +10 -4
  36. package/gsd-core/bin/lib/capability-registry.cjs +336 -95
  37. package/gsd-core/bin/lib/capability-state.cjs +18 -3
  38. package/gsd-core/bin/lib/capability-validator.cjs +205 -18
  39. package/gsd-core/bin/lib/check-command-router.cjs +145 -5
  40. package/gsd-core/bin/lib/cli-exit.cjs +496 -10
  41. package/gsd-core/bin/lib/code-review-depth.cjs +288 -0
  42. package/gsd-core/bin/lib/codex-agent-toml.cjs +410 -4
  43. package/gsd-core/bin/lib/command-arg-projection.cjs +144 -14
  44. package/gsd-core/bin/lib/command-routing-hub.cjs +31 -2
  45. package/gsd-core/bin/lib/commands.cjs +543 -44
  46. package/gsd-core/bin/lib/complexity-trigger.cjs +26 -6
  47. package/gsd-core/bin/lib/config-loader.cjs +118 -29
  48. package/gsd-core/bin/lib/config.cjs +92 -2
  49. package/gsd-core/bin/lib/configuration.cjs +129 -37
  50. package/gsd-core/bin/lib/core-utils.cjs +84 -7
  51. package/gsd-core/bin/lib/edge-probe.cjs +9 -1
  52. package/gsd-core/bin/lib/estimate-cli.cjs +55 -11
  53. package/gsd-core/bin/lib/exit-code-registry.cjs +98 -0
  54. package/gsd-core/bin/lib/frontmatter.cjs +840 -305
  55. package/gsd-core/bin/lib/gap-checker.cjs +27 -3
  56. package/gsd-core/bin/lib/git-base-branch.cjs +174 -39
  57. package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +7 -3
  58. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +6 -3
  59. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +22 -8
  60. package/gsd-core/bin/lib/health-diagnostic.cjs +23 -3
  61. package/gsd-core/bin/lib/host-integration.cjs +39 -6
  62. package/gsd-core/bin/lib/init-command-router.cjs +118 -21
  63. package/gsd-core/bin/lib/init.cjs +120 -41
  64. package/gsd-core/bin/lib/install-engine.cjs +68 -3
  65. package/gsd-core/bin/lib/install-model-override-resolver.cjs +33 -1
  66. package/gsd-core/bin/lib/install-profiles.cjs +78 -4
  67. package/gsd-core/bin/lib/installer-migration-report.cjs +3 -0
  68. package/gsd-core/bin/lib/installer-migrations/010-antigravity-retire-confighome-artifacts.cjs +169 -0
  69. package/gsd-core/bin/lib/installer-migrations.cjs +10 -7
  70. package/gsd-core/bin/lib/intel.cjs +101 -26
  71. package/gsd-core/bin/lib/io.cjs +160 -15
  72. package/gsd-core/bin/lib/learnings.cjs +85 -14
  73. package/gsd-core/bin/lib/legacy-cleanup.cjs +8 -2
  74. package/gsd-core/bin/lib/markdown-table.cjs +52 -4
  75. package/gsd-core/bin/lib/milestone.cjs +90 -5
  76. package/gsd-core/bin/lib/model-catalog.cjs +177 -19
  77. package/gsd-core/bin/lib/model-resolver.cjs +10 -28
  78. package/gsd-core/bin/lib/onboard-projection.cjs +5 -1
  79. package/gsd-core/bin/lib/phase-estimation.cjs +17 -8
  80. package/gsd-core/bin/lib/phase-id.cjs +70 -4
  81. package/gsd-core/bin/lib/phase-lifecycle.cjs +24 -16
  82. package/gsd-core/bin/lib/phase-locator.cjs +138 -17
  83. package/gsd-core/bin/lib/phase.cjs +405 -84
  84. package/gsd-core/bin/lib/plan-document.cjs +263 -0
  85. package/gsd-core/bin/lib/plan-scan.cjs +13 -2
  86. package/gsd-core/bin/lib/planning-command-router.cjs +61 -0
  87. package/gsd-core/bin/lib/planning-inspect.cjs +1168 -0
  88. package/gsd-core/bin/lib/planning-snapshot.cjs +18 -14
  89. package/gsd-core/bin/lib/planning-workspace.cjs +56 -0
  90. package/gsd-core/bin/lib/probe-core.cjs +4 -1
  91. package/gsd-core/bin/lib/profile-pipeline-command-router.cjs +50 -7
  92. package/gsd-core/bin/lib/profile-pipeline.cjs +6 -3
  93. package/gsd-core/bin/lib/real-home-guard.cjs +419 -0
  94. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +71 -45
  95. package/gsd-core/bin/lib/review-lane-descriptor.cjs +9 -9
  96. package/gsd-core/bin/lib/roadmap-command-router.cjs +45 -31
  97. package/gsd-core/bin/lib/roadmap-parser.cjs +79 -16
  98. package/gsd-core/bin/lib/roadmap.cjs +74 -19
  99. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +96 -8
  100. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +34 -1
  101. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +287 -55
  102. package/gsd-core/bin/lib/runtime-identity.cjs +234 -0
  103. package/gsd-core/bin/lib/runtime-slash.cjs +72 -2
  104. package/gsd-core/bin/lib/shell-command-projection.cjs +71 -8
  105. package/gsd-core/bin/lib/smart-entry.cjs +12 -22
  106. package/gsd-core/bin/lib/spec-section.cjs +12 -7
  107. package/gsd-core/bin/lib/state-command-router.cjs +47 -18
  108. package/gsd-core/bin/lib/state-contract.cjs +359 -0
  109. package/gsd-core/bin/lib/state-document.cjs +186 -0
  110. package/gsd-core/bin/lib/state-md-schema.cjs +221 -0
  111. package/gsd-core/bin/lib/state-transition.cjs +517 -101
  112. package/gsd-core/bin/lib/state.cjs +946 -163
  113. package/gsd-core/bin/lib/surface.cjs +10 -2
  114. package/gsd-core/bin/lib/task-command-router.cjs +111 -1
  115. package/gsd-core/bin/lib/task-content-resolution.cjs +368 -0
  116. package/gsd-core/bin/lib/teams-status.cjs +4 -1
  117. package/gsd-core/bin/lib/uat-predicate.cjs +58 -20
  118. package/gsd-core/bin/lib/uat.cjs +1376 -125
  119. package/gsd-core/bin/lib/ui-consideration-probe.cjs +9 -1
  120. package/gsd-core/bin/lib/ui-safety-gate.cjs +37 -7
  121. package/gsd-core/bin/lib/unusable-input.cjs +13 -0
  122. package/gsd-core/bin/lib/validate-command-router.cjs +2 -2
  123. package/gsd-core/bin/lib/vendor/README.md +43 -5
  124. package/gsd-core/bin/lib/vendor/js-yaml.cjs +3014 -0
  125. package/gsd-core/bin/lib/verification.cjs +14 -1
  126. package/gsd-core/bin/lib/verify-command-grounding.cjs +846 -0
  127. package/gsd-core/bin/lib/verify.cjs +95 -40
  128. package/gsd-core/bin/lib/workstream-name-policy.cjs +25 -4
  129. package/gsd-core/bin/lib/worktree-base-ref.cjs +66 -12
  130. package/gsd-core/bin/lib/worktree-safety.cjs +177 -21
  131. package/gsd-core/bin/shared/config-defaults.manifest.json +7 -1
  132. package/gsd-core/bin/shared/config-schema.manifest.json +5 -0
  133. package/gsd-core/bin/shared/exit-codes.json +8 -0
  134. package/gsd-core/bin/shared/exit-codes.sh +20 -0
  135. package/gsd-core/bin/shared/model-catalog.json +8 -1
  136. package/gsd-core/references/agent-contracts.md +3 -2
  137. package/gsd-core/references/api-coverage.md +24 -2
  138. package/gsd-core/references/autonomous-smart-discuss.md +3 -3
  139. package/gsd-core/references/checkpoints.md +37 -19
  140. package/gsd-core/references/decimal-phase-calculation.md +5 -5
  141. package/gsd-core/references/edge-probe.md +8 -0
  142. package/gsd-core/references/execute-mvp-tdd.md +1 -3
  143. package/gsd-core/references/execute-phase-between-wave-reset.md +9 -12
  144. package/gsd-core/references/execute-phase-wave-guard.md +11 -9
  145. package/gsd-core/references/failing-direction.md +78 -0
  146. package/gsd-core/references/gate-prompts.md +1 -1
  147. package/gsd-core/references/git-integration.md +5 -5
  148. package/gsd-core/references/git-planning-commit.md +3 -3
  149. package/gsd-core/references/gsd-run-resolver.md +1 -1
  150. package/gsd-core/references/loop-hook-dispatch.md +22 -0
  151. package/gsd-core/references/model-profiles.md +1 -1
  152. package/gsd-core/references/nyquist-compliance.md +74 -0
  153. package/gsd-core/references/offer-next.md +3 -5
  154. package/gsd-core/references/phase-argument-parsing.md +3 -3
  155. package/gsd-core/references/planner-failing-direction.md +53 -0
  156. package/gsd-core/references/planner-human-verify-mode.md +15 -1
  157. package/gsd-core/references/planner-revision.md +1 -1
  158. package/gsd-core/references/planner-verify-command-grounding.md +17 -0
  159. package/gsd-core/references/planning-config.md +37 -8
  160. package/gsd-core/references/reviewer-instances.md +31 -0
  161. package/gsd-core/references/runtime-aware-dispatch.md +1 -1
  162. package/gsd-core/references/tdd.md +1 -3
  163. package/gsd-core/references/ui-brand.md +65 -21
  164. package/gsd-core/references/ui-consideration-probe.md +1 -1
  165. package/gsd-core/references/universal-anti-patterns.md +2 -2
  166. package/gsd-core/references/verify-command-path-resolvability.md +42 -0
  167. package/gsd-core/references/verify-mvp-mode.md +1 -1
  168. package/gsd-core/references/workstream-flag.md +11 -11
  169. package/gsd-core/templates/README.md +1 -1
  170. package/gsd-core/templates/SECURITY.md +3 -3
  171. package/gsd-core/templates/UI-SPEC.md +25 -3
  172. package/gsd-core/templates/VALIDATION.md +3 -3
  173. package/gsd-core/templates/phase-prompt.md +3 -0
  174. package/gsd-core/templates/state.md +7 -0
  175. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  176. package/gsd-core/workflows/add-backlog.md +1 -1
  177. package/gsd-core/workflows/add-phase.md +3 -3
  178. package/gsd-core/workflows/add-tests.md +3 -8
  179. package/gsd-core/workflows/add-todo.md +1 -1
  180. package/gsd-core/workflows/ai-integration-phase.md +4 -9
  181. package/gsd-core/workflows/audit-fix.md +12 -3
  182. package/gsd-core/workflows/audit-milestone.md +9 -9
  183. package/gsd-core/workflows/audit-uat.md +17 -2
  184. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +2 -2
  185. package/gsd-core/workflows/autonomous.md +10 -26
  186. package/gsd-core/workflows/check-todos.md +1 -1
  187. package/gsd-core/workflows/cleanup.md +2 -2
  188. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +1 -1
  189. package/gsd-core/workflows/code-review-fix.md +1 -1
  190. package/gsd-core/workflows/code-review.md +121 -40
  191. package/gsd-core/workflows/complete-milestone.md +15 -10
  192. package/gsd-core/workflows/debug.md +5 -3
  193. package/gsd-core/workflows/diagnose-issues.md +12 -6
  194. package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
  195. package/gsd-core/workflows/discuss-phase/modes/chain.md +3 -7
  196. package/gsd-core/workflows/discuss-phase/modes/text.md +1 -1
  197. package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +1 -3
  198. package/gsd-core/workflows/discuss-phase-assumptions.md +2 -2
  199. package/gsd-core/workflows/discuss-phase.md +1 -1
  200. package/gsd-core/workflows/do.md +3 -6
  201. package/gsd-core/workflows/docs-update.md +5 -4
  202. package/gsd-core/workflows/edit-phase.md +1 -1
  203. package/gsd-core/workflows/eval-review.md +4 -9
  204. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
  205. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +113 -11
  206. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
  207. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
  208. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
  209. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +22 -4
  210. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +2 -2
  211. package/gsd-core/workflows/execute-phase/steps/protected-branch.md +21 -0
  212. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -2
  213. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +39 -0
  214. package/gsd-core/workflows/execute-phase.md +38 -54
  215. package/gsd-core/workflows/execute-plan.md +17 -12
  216. package/gsd-core/workflows/explore.md +1 -1
  217. package/gsd-core/workflows/extract-learnings.md +1 -1
  218. package/gsd-core/workflows/fast.md +2 -2
  219. package/gsd-core/workflows/forensics.md +1 -1
  220. package/gsd-core/workflows/graduation.md +5 -5
  221. package/gsd-core/workflows/health.md +3 -6
  222. package/gsd-core/workflows/import.md +14 -11
  223. package/gsd-core/workflows/inbox.md +4 -5
  224. package/gsd-core/workflows/ingest-docs.md +44 -11
  225. package/gsd-core/workflows/insert-phase.md +5 -5
  226. package/gsd-core/workflows/list-seeds.md +5 -3
  227. package/gsd-core/workflows/list-workspaces.md +1 -1
  228. package/gsd-core/workflows/manager.md +12 -23
  229. package/gsd-core/workflows/map-codebase.md +1 -1
  230. package/gsd-core/workflows/milestone-summary.md +1 -1
  231. package/gsd-core/workflows/mvp-phase.md +2 -2
  232. package/gsd-core/workflows/new-milestone.md +9 -21
  233. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
  234. package/gsd-core/workflows/new-project.md +12 -26
  235. package/gsd-core/workflows/new-workspace.md +1 -1
  236. package/gsd-core/workflows/next.md +2 -2
  237. package/gsd-core/workflows/pause-work.md +1 -1
  238. package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +1 -1
  239. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +1 -1
  240. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -4
  241. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +3 -3
  242. package/gsd-core/workflows/plan-phase.md +121 -42
  243. package/gsd-core/workflows/plan-review-convergence.md +46 -9
  244. package/gsd-core/workflows/plant-seed.md +2 -2
  245. package/gsd-core/workflows/pr-branch.md +187 -51
  246. package/gsd-core/workflows/profile-user.md +16 -14
  247. package/gsd-core/workflows/progress.md +27 -12
  248. package/gsd-core/workflows/quick/steps/discussion-phase.md +1 -3
  249. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +1 -3
  250. package/gsd-core/workflows/quick/steps/quick-verification.md +2 -4
  251. package/gsd-core/workflows/quick/steps/research-phase.md +2 -4
  252. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +3 -3
  253. package/gsd-core/workflows/quick.md +20 -29
  254. package/gsd-core/workflows/remove-phase.md +4 -4
  255. package/gsd-core/workflows/remove-workspace.md +2 -2
  256. package/gsd-core/workflows/resume-project.md +8 -12
  257. package/gsd-core/workflows/review.md +193 -15
  258. package/gsd-core/workflows/scan.md +1 -1
  259. package/gsd-core/workflows/secure-phase.md +2 -2
  260. package/gsd-core/workflows/settings-advanced.md +7 -9
  261. package/gsd-core/workflows/settings-integrations.md +64 -31
  262. package/gsd-core/workflows/settings.md +3 -5
  263. package/gsd-core/workflows/ship.md +12 -6
  264. package/gsd-core/workflows/sketch-wrap-up.md +11 -17
  265. package/gsd-core/workflows/sketch.md +12 -18
  266. package/gsd-core/workflows/smart-entry.md +3 -5
  267. package/gsd-core/workflows/spec-phase.md +23 -1
  268. package/gsd-core/workflows/spike-wrap-up.md +7 -11
  269. package/gsd-core/workflows/spike.md +20 -31
  270. package/gsd-core/workflows/stats.md +2 -2
  271. package/gsd-core/workflows/sync-skills.md +1 -1
  272. package/gsd-core/workflows/thread.md +11 -7
  273. package/gsd-core/workflows/transition.md +5 -5
  274. package/gsd-core/workflows/ui-phase.md +10 -16
  275. package/gsd-core/workflows/ui-review.md +6 -10
  276. package/gsd-core/workflows/ultraplan-phase.md +5 -13
  277. package/gsd-core/workflows/undo.md +8 -16
  278. package/gsd-core/workflows/update.md +6 -10
  279. package/gsd-core/workflows/validate-phase.md +2 -2
  280. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +25 -1
  281. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
  282. package/gsd-core/workflows/verify-work.md +57 -18
  283. package/hooks/dist/gsd-agent-isolation-guard.js +77 -38
  284. package/hooks/dist/gsd-config-reload.js +18 -12
  285. package/hooks/dist/gsd-context-monitor.js +19 -10
  286. package/hooks/dist/gsd-cursor-post-tool.js +3 -1
  287. package/hooks/dist/gsd-cursor-pre-tool.js +3 -1
  288. package/hooks/dist/gsd-cursor-session-start.js +2 -1
  289. package/hooks/dist/gsd-cursor-stop.js +2 -1
  290. package/hooks/dist/gsd-cursor-subagent-start.js +28 -23
  291. package/hooks/dist/gsd-cursor-subagent-stop.js +3 -1
  292. package/hooks/dist/gsd-ensure-canonical-path.js +2 -1
  293. package/hooks/dist/gsd-graphify-update.sh +22 -18
  294. package/hooks/dist/gsd-node-runner.sh +76 -0
  295. package/hooks/dist/gsd-phase-boundary.sh +1 -0
  296. package/hooks/dist/gsd-prompt-guard.js +16 -7
  297. package/hooks/dist/gsd-read-guard.js +16 -7
  298. package/hooks/dist/gsd-read-injection-scanner.js +17 -8
  299. package/hooks/dist/gsd-session-state.sh +1 -0
  300. package/hooks/dist/gsd-statusline.js +215 -26
  301. package/hooks/dist/gsd-validate-commit.sh +80 -6
  302. package/hooks/dist/gsd-windsurf-pre-command.js +16 -11
  303. package/hooks/dist/gsd-windsurf-pre-write.js +22 -13
  304. package/hooks/dist/gsd-workflow-guard.js +34 -16
  305. package/hooks/dist/gsd-worktree-path-guard.js +36 -21
  306. package/hooks/dist/gsd-write-guard.js +35 -25
  307. package/hooks/dist/lib/cli-exit.js +560 -0
  308. package/hooks/dist/lib/exit-code-registry.js +98 -0
  309. package/hooks/dist/lib/git-probe.js +84 -0
  310. package/hooks/dist/lib/hook-exit.js +81 -0
  311. package/hooks/dist/managed-hooks-registry.cjs +3 -0
  312. package/hooks/gsd-agent-isolation-guard.js +77 -38
  313. package/hooks/gsd-config-reload.js +18 -12
  314. package/hooks/gsd-context-monitor.js +19 -10
  315. package/hooks/gsd-cursor-post-tool.js +3 -1
  316. package/hooks/gsd-cursor-pre-tool.js +3 -1
  317. package/hooks/gsd-cursor-session-start.js +2 -1
  318. package/hooks/gsd-cursor-stop.js +2 -1
  319. package/hooks/gsd-cursor-subagent-start.js +28 -23
  320. package/hooks/gsd-cursor-subagent-stop.js +3 -1
  321. package/hooks/gsd-ensure-canonical-path.js +2 -1
  322. package/hooks/gsd-graphify-update.sh +22 -18
  323. package/hooks/gsd-node-runner.sh +76 -0
  324. package/hooks/gsd-phase-boundary.sh +1 -0
  325. package/hooks/gsd-prompt-guard.js +16 -7
  326. package/hooks/gsd-read-guard.js +16 -7
  327. package/hooks/gsd-read-injection-scanner.js +17 -8
  328. package/hooks/gsd-session-state.sh +1 -0
  329. package/hooks/gsd-statusline.js +215 -26
  330. package/hooks/gsd-validate-commit.sh +80 -6
  331. package/hooks/gsd-windsurf-pre-command.js +16 -11
  332. package/hooks/gsd-windsurf-pre-write.js +22 -13
  333. package/hooks/gsd-workflow-guard.js +34 -16
  334. package/hooks/gsd-worktree-path-guard.js +36 -21
  335. package/hooks/gsd-write-guard.js +35 -25
  336. package/hooks/lib/cli-exit.js +560 -0
  337. package/hooks/lib/exit-code-registry.js +98 -0
  338. package/hooks/lib/git-probe.js +84 -0
  339. package/hooks/lib/hook-exit.js +81 -0
  340. package/hooks/managed-hooks-registry.cjs +3 -0
  341. package/package.json +12 -7
  342. package/scripts/base64-scan.sh +74 -12
  343. package/scripts/build-hooks.js +5 -0
  344. package/scripts/check-glossary-refs.cjs +77 -15
  345. package/scripts/check-mutation-score-ratchet.cjs +156 -0
  346. package/scripts/ci-check-job-near-cap.cjs +49 -0
  347. package/scripts/ci-pr-mergeability.cjs +262 -0
  348. package/scripts/ci-test-scope.cjs +45 -12
  349. package/scripts/ci-timeout-report.cjs +230 -0
  350. package/scripts/docs-guard-registry.cjs +396 -0
  351. package/scripts/gen-capability-registry.cjs +8 -6
  352. package/scripts/gen-exit-code-docs.cjs +318 -0
  353. package/scripts/gen-exit-code-registry.cjs +891 -0
  354. package/scripts/gen-features.cjs +836 -0
  355. package/scripts/gen-hooks-cli-exit.cjs +239 -0
  356. package/scripts/gen-install-tree-fixtures.cjs +2 -2
  357. package/scripts/gen-loop-host-contract.cjs +134 -1
  358. package/scripts/gen-scripts-cli-exit.cjs +185 -0
  359. package/scripts/gen-state-md-docs.cjs +727 -0
  360. package/scripts/{test-failure-reasons.cjs → gsd-test-gate-reasons.cjs} +6 -0
  361. package/scripts/lib/ci-job-timing.cjs +72 -0
  362. package/scripts/lib/cli-exit.cjs +546 -44
  363. package/scripts/lib/drift-scan.cjs +32 -2
  364. package/scripts/lib/exit-code-registry.cjs +98 -0
  365. package/scripts/lib/ndjson-reporter.cjs +119 -0
  366. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
  367. package/scripts/lint-docs-guard-registration.cjs +495 -0
  368. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +193 -0
  369. package/scripts/lint-eslint-glob-coverage.allowlist.json +4 -0
  370. package/scripts/{lint-fix-has-regression-test.cjs → lint-fix-has-regression-tests.cjs} +12 -6
  371. package/scripts/lint-health-diagnostic-rule-table.cjs +65 -8
  372. package/scripts/lint-mutation-test-derivation-drift.cjs +86 -0
  373. package/scripts/lint-phase-enumeration-drift.cjs +21 -8
  374. package/scripts/lint-planning-prompt-drift.cjs +38 -1
  375. package/scripts/lint-removed-but-needed.cjs +184 -16
  376. package/scripts/lint-seam-enforcement.cjs +182 -0
  377. package/scripts/lint-slug-derivation-drift.cjs +921 -0
  378. package/scripts/lint-source-test-name-collision.cjs +241 -0
  379. package/scripts/lint-state-write-path-drift.cjs +337 -432
  380. package/scripts/lint-test-file-count.allowlist.json +122 -4
  381. package/scripts/lint-test-file-count.cjs +25 -3
  382. package/scripts/lint-unreachable-guard-drift.cjs +51 -64
  383. package/scripts/lint-vendored-deps.cjs +208 -35
  384. package/scripts/mutation-matrix.cjs +599 -50
  385. package/scripts/prompt-injection-scan.sh +75 -14
  386. package/scripts/secret-scan.sh +75 -13
  387. package/scripts/select-docs-guards.cjs +56 -0
  388. package/scripts/sync-runtime-launcher.cjs +22 -3
  389. package/skills/gsd-discuss-phase/SKILL.md +1 -1
  390. package/skills/gsd-import/SKILL.md +1 -1
  391. package/skills/gsd-quick/SKILL.md +8 -4
  392. package/vscode/package.json +1 -1
  393. package/bin/lib/ui-safety-gate.cjs +0 -109
  394. package/scripts/lint-emitted-drift-ack.cjs +0 -344
  395. package/scripts/state-write-path-drift-baseline.json +0 -19
@@ -32,6 +32,7 @@ const retiredArtifactCleanup = require("./retired-artifact-cleanup.cjs");
32
32
  const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
33
33
  const external_descriptor_trust_cjs_1 = require("./external-descriptor-trust.cjs");
34
34
  const commonjs_marker_cjs_1 = require("./commonjs-marker.cjs");
35
+ const testHomeGuard = require("./real-home-guard.cjs");
35
36
  // #2874 (ADR-58 cleanup phase): the injectable fs seam for the
36
37
  // installRuntimeArtifacts call tree. `installFs()` resolves to real
37
38
  // `node:fs` unless a call is wrapped in `withInstallFs(deps.fs, ...)` —
@@ -409,6 +410,15 @@ function _resolveDevPreferencesSkillTarget(targetDir, runtime, scope = 'global')
409
410
  // must confine against installRoot, not targetDir, or it would flag the
410
411
  // legitimate override destination as an escape.
411
412
  let installRoot = targetDir;
413
+ // Reported in Codex review of #3725: `installRoot !== targetDir` was used as the
414
+ // stand-in for "the skills kind declared a `home` override", and the two are NOT
415
+ // equivalent — a resolved `home` that happens to EQUAL targetDir (a configDir of
416
+ // `$HOME/.agents`, which is exactly where codex's override points) makes the
417
+ // inequality false while the override is very much declared, skipping the guard
418
+ // and writing SKILL.md into the real home. Report the declaration itself instead
419
+ // of inferring it from two paths, read off the SAME layout resolution the
420
+ // destination came from so the guard cannot vouch for a path this does not write.
421
+ let hasHomeOverride = false;
412
422
  if (runtime) {
413
423
  const layout = runtimeArtifactLayout.resolveRuntimeArtifactLayout(runtime, targetDir, scope);
414
424
  const skillsKindEntry = layout.kinds.find((k) => k.kind === 'skills');
@@ -420,6 +430,7 @@ function _resolveDevPreferencesSkillTarget(targetDir, runtime, scope = 'global')
420
430
  // -> $HOME/.agents) instead of always resolving against targetDir, so a
421
431
  // legacy dev-preferences migration lands in the SAME tree the installer
422
432
  // and surface-apply use. Runtimes with no `home` override are unaffected.
433
+ hasHomeOverride = skillsKindEntry.home != null;
423
434
  installRoot = skillsKindEntry.home ?? targetDir;
424
435
  skillDir = node_path_1.default.join(runtimeArtifactInstallPlan.assertDestWithinConfigHome(installRoot, skillsKindEntry.destSubpath), stemName);
425
436
  }
@@ -427,14 +438,42 @@ function _resolveDevPreferencesSkillTarget(targetDir, runtime, scope = 'global')
427
438
  // Legacy fallback for callers that have not yet been updated to pass runtime
428
439
  skillDir = node_path_1.default.join(runtimeArtifactInstallPlan.assertDestWithinConfigHome(targetDir, 'skills'), 'gsd-dev-preferences');
429
440
  }
430
- return { skillFile: node_path_1.default.join(skillDir, 'SKILL.md'), installRoot };
441
+ return { skillFile: node_path_1.default.join(skillDir, 'SKILL.md'), installRoot, hasHomeOverride };
431
442
  }
432
- function migrateLegacyDevPreferencesToSkill(targetDir, saved, runtime, scope = 'global') {
443
+ /**
444
+ * @param deps - #3712 test seam, mirroring the one on `installRuntimeArtifacts`
445
+ * and `uninstallRuntimeArtifacts`. This is the SIXTH writer that resolves a
446
+ * skills-kind `home`, and its guard's trigger condition — "HOME equals the
447
+ * passwd home" — cannot be reproduced without pointing at the developer's real
448
+ * home, so it is injected rather than simulated. Production callers pass
449
+ * nothing and bind real `os`/`process.env`.
450
+ */
451
+ function migrateLegacyDevPreferencesToSkill(targetDir, saved, runtime, scope = 'global', deps = {}) {
433
452
  if (!saved || !saved.has('dev-preferences.md'))
434
453
  return false;
435
454
  const target = _resolveDevPreferencesSkillTarget(targetDir, runtime, scope);
436
455
  if (!target)
437
456
  return false; // runtime has no skills layout at this scope (e.g. cline local)
457
+ // #3712 — the SIXTH writer that resolves a skills-kind `home` override.
458
+ // Exported and directly callable, and `_runLegacyInstallMigrations` runs it
459
+ // BEFORE installRuntimeArtifacts' own assertion, so a future runtime pairing a
460
+ // home override with this migration would write to the real home ahead of any
461
+ // guard. It creates rather than prunes, which is why it was missed.
462
+ //
463
+ // Guards the destination ALREADY RESOLVED above, never a second resolution of
464
+ // its own. An earlier revision re-ran resolveRuntimeArtifactLayout() here —
465
+ // and without `capabilityRegistry`, so a registry-dependent descriptor could
466
+ // make the two disagree and leave the guard vouching for a path the migration
467
+ // does not write. That is the generative-fix-divergence shape; reported in
468
+ // review of #3725. `target.hasHomeOverride` is that same resolution's own answer
469
+ // to "did the skills kind declare a `home`?" — not re-derived, and not inferred
470
+ // from `installRoot !== targetDir`, which is false whenever the override happens
471
+ // to resolve onto targetDir itself (Codex review of #3725).
472
+ if (runtime && target.hasHomeOverride) {
473
+ testHomeGuard.assertTestHomeSandboxed('migrateLegacyDevPreferencesToSkill', runtime, [
474
+ { kind: 'skills', home: node_path_1.default.dirname(target.skillFile) },
475
+ ], { os: deps.os, env: deps.env });
476
+ }
438
477
  const { skillFile, installRoot } = target;
439
478
  const skillDir = node_path_1.default.dirname(skillFile);
440
479
  // Security fix: `existsSync` FOLLOWS symlinks and reports `false` for a
@@ -960,6 +999,11 @@ function installRuntimeArtifacts(runtime, configDir, scope, resolvedProfile, res
960
999
  // Legacy cleanup before layout-driven writes
961
1000
  _runLegacyInstallMigrations(runtime, configDir, scope);
962
1001
  const layout = runtimeArtifactLayout.resolveRuntimeArtifactLayout(runtime, configDir, scope, capabilityRegistry);
1002
+ // #3712: a global `home` override escapes the sandboxed configDir. Refuse to
1003
+ // execute when a test run would land that escape in the developer's real home.
1004
+ testHomeGuard.assertTestHomeSandboxed('installRuntimeArtifacts', runtime, layout?.kinds, {
1005
+ os: deps.os, env: deps.env,
1006
+ });
963
1007
  const planResult = runtimeArtifactInstallPlan.createRuntimeArtifactInstallPlan({
964
1008
  // `Layout` is structurally identical across the layout/install-plan .cjs
965
1009
  // modules but nominally distinct to tsc (untyped .cjs boundary) — bridge it.
@@ -1183,6 +1227,13 @@ function installOpencodeFamilySkills(runtime, targetDir, rawCommandsDir, pathPre
1183
1227
  const skillsKindEntry = layout.kinds.find((k) => k.kind === 'skills');
1184
1228
  if (!skillsKindEntry)
1185
1229
  return 0;
1230
+ // #3712: combined-family runtimes take installRuntimeArtifacts' early return
1231
+ // BEFORE its guard runs, and this writer honors `skillsKindEntry.home` below and
1232
+ // then prunes that destination. opencode/kilo declare no `home` today, so there
1233
+ // is no live escape — but that makes this a bypass waiting on a descriptor
1234
+ // change rather than a safe omission, so it is guarded at the writer instead.
1235
+ // Scoped to the SKILLS kind alone, for the same reason as the agents writer.
1236
+ testHomeGuard.assertTestHomeSandboxed('installOpencodeFamilySkills', runtime, [skillsKindEntry]);
1186
1237
  const rawDir = rawCommandsDir;
1187
1238
  if (!rawDir || !installFs().existsSync(rawDir))
1188
1239
  return 0;
@@ -1345,6 +1396,14 @@ function installAgentsKindStandalone(runtime, targetDir, scope, resolvedProfile,
1345
1396
  const agentsKindEntry = layout.kinds.find((k) => k.kind === 'agents');
1346
1397
  if (!agentsKindEntry)
1347
1398
  return null;
1399
+ // #3712: this writer selects `agentsKindEntry.home` over targetDir below and then
1400
+ // prunes that destination via _removeGsdEntries, so it is a fifth route into the
1401
+ // developer's real home. No agents kind declares a `home` override today, so like
1402
+ // installOpencodeFamilySkills it is guarded against a descriptor change rather
1403
+ // than a present escape. Scoped to the AGENTS kind alone: passing the whole
1404
+ // layout made codex's unrelated skills-kind override trip a writer that never
1405
+ // touches it, which is a false refusal, not a tighter guard.
1406
+ testHomeGuard.assertTestHomeSandboxed('installAgentsKindStandalone', runtime, [agentsKindEntry]);
1348
1407
  // ADR-1235 §1: same agentCtx shape createRuntimeArtifactInstallPlan builds
1349
1408
  // for the generic layout-driven loop (runtime-artifact-install-plan.cts) —
1350
1409
  // targetDir IS the install root the inline agent loop called `targetDir`.
@@ -1683,7 +1742,7 @@ function installOpencodeFamilyArtifacts(runtime, configDir, scope, resolvedProfi
1683
1742
  * @param configDir resolved runtime config directory
1684
1743
  * @param scope
1685
1744
  */
1686
- function uninstallRuntimeArtifacts(runtime, configDir, scope) {
1745
+ function uninstallRuntimeArtifacts(runtime, configDir, scope, deps = {}) {
1687
1746
  // A retired descriptor kind is absent from the current uninstall plan, just
1688
1747
  // as it is absent from the install plan. Sweep manifest-proven output from
1689
1748
  // retired kinds before removing the current layout so a direct uninstall
@@ -1695,6 +1754,12 @@ function uninstallRuntimeArtifacts(runtime, configDir, scope) {
1695
1754
  // (the layout's gsd-* prefix pass would wipe a skill dir created here).
1696
1755
  const stagedLegacyArtifacts = _runLegacyUninstallCleanup(runtime, configDir, scope);
1697
1756
  const layout = runtimeArtifactLayout.resolveRuntimeArtifactLayout(runtime, configDir, scope);
1757
+ // #3712: uninstall resolves the SAME `kind.home` override as install and then
1758
+ // prunes it via _removeGsdEntries below, so it is a second escape route into
1759
+ // the developer's real home, not a read-only path. Guard it identically.
1760
+ testHomeGuard.assertTestHomeSandboxed('uninstallRuntimeArtifacts', runtime, layout?.kinds, {
1761
+ os: deps.os, env: deps.env,
1762
+ });
1698
1763
  const plan = runtimeArtifactInstallPlan.createRuntimeArtifactUninstallPlan(layout);
1699
1764
  const kindsByName = new Map(layout.kinds.map((kind) => [kind.kind, kind]));
1700
1765
  for (const item of plan.items) {
@@ -45,7 +45,7 @@ const modelCatalog = require("./model-catalog.cjs");
45
45
  const { MODEL_PROFILES: GSD_MODEL_PROFILES } = modelCatalog;
46
46
  // eslint-disable-next-line @typescript-eslint/no-require-imports -- model-resolver.cjs is an export= CommonJS module
47
47
  const modelResolverModule = require("./model-resolver.cjs");
48
- const { resolveTierEntry: gsdResolveTierEntry } = modelResolverModule;
48
+ const { resolveTierEntry: gsdResolveTierEntry, resolveModelPolicy: gsdResolveModelPolicy } = modelResolverModule;
49
49
  /**
50
50
  * Read `model_overrides` from `~/.gsd/defaults.json` at install time.
51
51
  * Returns an object mapping agent names to model IDs, or null if the file
@@ -145,6 +145,10 @@ function readGsdRuntimeProfileResolver(targetDir = null) {
145
145
  model_profile_overrides: (projectConfig && projectConfig.model_profile_overrides) ||
146
146
  (homeDefaults && homeDefaults.model_profile_overrides) ||
147
147
  null,
148
+ // #3705: same project-wins-over-home precedence as every other key here.
149
+ model_policy: (projectConfig && projectConfig.model_policy) ||
150
+ (homeDefaults && homeDefaults.model_policy) ||
151
+ null,
148
152
  };
149
153
  if (!merged.runtime)
150
154
  return null;
@@ -164,6 +168,34 @@ function readGsdRuntimeProfileResolver(targetDir = null) {
164
168
  const tier = agentModels[profile] || agentModels.balanced;
165
169
  if (!tier)
166
170
  return null;
171
+ // #3705: policy sits between the per-agent override and the tier table —
172
+ // the position dispatch uses. Measured against `query resolve-model` on the
173
+ // same config: policy-only -> the policy model; tier-overrides-only -> the
174
+ // override; BOTH -> the policy model. So policy outranks
175
+ // `model_profile_overrides`, and an explicit `model_overrides[agent]`
176
+ // (applied by resolveAgentModelOverride below) outranks policy.
177
+ //
178
+ // Calls the exported owner (#49) rather than re-deriving tier -> model here:
179
+ // a second implementation of that mapping is the very divergence this issue
180
+ // is, one layer down. A null from it (unknown provider, missing tier key, a
181
+ // `runtime_tiers` miss) falls through to the tier table — never to null,
182
+ // which would omit a frontmatter key that used to be written.
183
+ // The policy object must carry the effective runtime before it is resolved.
184
+ // `resolveModelPolicy`'s `runtime_tiers` branch reads `policy['runtime']`, but
185
+ // the documented config shape (docs/CONFIGURATION.md) puts `runtime` at the
186
+ // TOP level and keeps only `provider`/`runtime_tiers` inside `model_policy`.
187
+ // Dispatch injects it — `{ ...config.model_policy, runtime: effectiveRuntime }`
188
+ // (model-resolver.cts) — and passing the policy unmodified here silently
189
+ // skipped `runtime_tiers` entirely, falling through to the flat hi/med/lo keys
190
+ // or the catalog tier. That is the same "catalog Anthropic ID baked over a
191
+ // configured provider" defect this fix exists to close, so the injection is
192
+ // mirrored rather than assumed.
193
+ const policyForRuntime = merged.model_policy
194
+ ? { ...merged.model_policy, runtime }
195
+ : null;
196
+ const policyModel = gsdResolveModelPolicy(policyForRuntime, tier);
197
+ if (policyModel)
198
+ return { model: policyModel };
167
199
  return gsdResolveTierEntry({
168
200
  runtime,
169
201
  tier,
@@ -137,9 +137,79 @@ function parseCallsAgents(content) {
137
137
  * Also derives calls_agents for each skill by scanning the body text for
138
138
  * `gsd-*` agent name references. Agent stems are stored under the special
139
139
  * key `_calls_agents_<stem>` so they don't conflict with skill stems.
140
+ *
141
+ * #3798: command bodies are thin delegators — the actual `subagent_type=`
142
+ * spawns live in the workflow files each command references
143
+ * (`@…/workflows/<name>.md`). The agent derivation therefore ALSO reads every
144
+ * workflow file the command references (plus the workflow's steps/ fragments
145
+ * when it is split per the progressive-disclosure pattern) and unions their
146
+ * `gsd-*` tokens into the same `_calls_agents_<stem>` set. Without this,
147
+ * tiered profiles omitted agents their own installed skills spawn
148
+ * (gsd-verifier at execute-phase's verify_phase_goal was the filed repro).
149
+ * Over-inclusion fails safe: a tiered profile installing one extra agent
150
+ * costs bytes, never a broken spawn.
140
151
  */
141
152
  const DEFAULT_COMMANDS_DIR = node_path_1.default.resolve(__dirname, '..', '..', '..', 'commands', 'gsd');
142
- function loadSkillsManifest(commandsDir = DEFAULT_COMMANDS_DIR) {
153
+ const DEFAULT_WORKFLOWS_DIR = node_path_1.default.resolve(__dirname, '..', '..', 'workflows');
154
+ /**
155
+ * Collect the `gsd-*` agent tokens from every workflow file a command body
156
+ * references. References are the `workflows/<name>.md` path suffixes the
157
+ * delegating commands embed (`@…/gsd-core/workflows/<name>.md`). A
158
+ * split workflow (workflows/<name>/steps/*.md) contributes its fragments as
159
+ * well, because the parent dispatches into them and the spawns live there.
160
+ */
161
+ function workflowAgentRefs(content, workflowsDir) {
162
+ const refs = content.match(/workflows\/([a-z0-9][a-z0-9-]*)\.md/g) || [];
163
+ const names = [...new Set(refs.map((r) => r.slice('workflows/'.length)))];
164
+ const tokens = new Set();
165
+ for (const name of names) {
166
+ const direct = node_path_1.default.join(workflowsDir, name);
167
+ let body = null;
168
+ try {
169
+ body = node_fs_1.default.readFileSync(direct, 'utf8');
170
+ }
171
+ catch {
172
+ body = null;
173
+ }
174
+ if (body === null)
175
+ continue;
176
+ for (const tok of parseCallsAgents(body))
177
+ tokens.add(tok);
178
+ // Split workflow: union EVERY fragment under the workflow's directory
179
+ // (steps/, modes/, templates/…) — the parent dispatches into these per
180
+ // the progressive-disclosure pattern, and spawns live in all of them
181
+ // (#3798 review: modes/ carried gsd-advisor-researcher's spawn while the
182
+ // parent only named it incidentally in prose).
183
+ const fragDir = node_path_1.default.join(workflowsDir, name.slice(0, -3));
184
+ const walk = (dir) => {
185
+ let frags;
186
+ try {
187
+ frags = node_fs_1.default.readdirSync(dir, { withFileTypes: true });
188
+ }
189
+ catch {
190
+ return;
191
+ }
192
+ for (const frag of frags) {
193
+ const full = node_path_1.default.join(dir, frag.name);
194
+ if (frag.isDirectory()) {
195
+ walk(full);
196
+ continue;
197
+ }
198
+ if (!frag.isFile() || !frag.name.endsWith('.md'))
199
+ continue;
200
+ try {
201
+ const fragBody = node_fs_1.default.readFileSync(full, 'utf8');
202
+ for (const tok of parseCallsAgents(fragBody))
203
+ tokens.add(tok);
204
+ }
205
+ catch { /* unreadable fragment — skip */ }
206
+ }
207
+ };
208
+ walk(fragDir);
209
+ }
210
+ return [...tokens];
211
+ }
212
+ function loadSkillsManifest(commandsDir = DEFAULT_COMMANDS_DIR, workflowsDir = DEFAULT_WORKFLOWS_DIR) {
143
213
  const manifest = new Map();
144
214
  if (!node_fs_1.default.existsSync(commandsDir))
145
215
  return manifest;
@@ -153,9 +223,12 @@ function loadSkillsManifest(commandsDir = DEFAULT_COMMANDS_DIR) {
153
223
  try {
154
224
  const content = node_fs_1.default.readFileSync(node_path_1.default.join(commandsDir, entry.name), 'utf8');
155
225
  manifest.set(stem, parseRequires(content));
156
- // Derive agent references from body text
157
- const agentRefs = parseCallsAgents(content);
158
- manifest.set(`_calls_agents_${stem}`, agentRefs);
226
+ // Derive agent references from body text + the workflows it delegates to
227
+ const agentRefs = [
228
+ ...parseCallsAgents(content),
229
+ ...workflowAgentRefs(content, workflowsDir),
230
+ ];
231
+ manifest.set(`_calls_agents_${stem}`, [...new Set(agentRefs)]);
159
232
  }
160
233
  catch {
161
234
  manifest.set(stem, []);
@@ -1156,6 +1229,7 @@ module.exports = {
1156
1229
  // Shared internals
1157
1230
  parseRequires,
1158
1231
  parseCallsAgents,
1232
+ workflowAgentRefs,
1159
1233
  cleanupStagedSkills,
1160
1234
  // #2322: capability-skill security seams — exported for direct unit-testing
1161
1235
  // and for surface.cts's prune pass (CAPABILITY_SKILL_MARKER parity).
@@ -48,6 +48,9 @@ exports.BUNDLED_GSD_HOOK_FILES = Object.freeze(new Set([
48
48
  'hooks/gsd-windsurf-pre-command.js',
49
49
  'hooks/gsd-ensure-canonical-path.js',
50
50
  'hooks/gsd-graphify-update.sh',
51
+ // #3662: portable node resolver staged into hooks/ (not itself a lifecycle
52
+ // hook — managed JS hook commands route through it under --portable-hooks).
53
+ 'hooks/gsd-node-runner.sh',
51
54
  'hooks/gsd-phase-boundary.sh',
52
55
  'hooks/gsd-prompt-guard.js',
53
56
  'hooks/gsd-read-guard.js',
@@ -0,0 +1,169 @@
1
+ "use strict";
2
+ /**
3
+ * Installer migration: retire Antigravity's configHome skills/agents surfaces (#3738).
4
+ *
5
+ * Antigravity's machine-local discovery scans ~/.gemini/config/{skills,agents}
6
+ * (antigravity.google/docs/skills, /docs/subagents); the configHome
7
+ * (~/.gemini/antigravity{,-ide,-cli}) is not scanned for global artifacts, so
8
+ * skills installed there are silently ignored. Since #3738 the global layout
9
+ * declares a `home: ".gemini/config"` override for both kinds, and new
10
+ * installs land in the scanned dir. This migration converges an existing
11
+ * installation: manifest-managed files under the configHome's skills/ and
12
+ * agents/ subtrees are removed (modified ones backed up first), unmanifested
13
+ * files are preserved, and now-empty container directories are retired.
14
+ *
15
+ * Scope is GLOBAL only: the local workspace layout (.agents/skills,
16
+ * .agents/agents) is the documented, still-live surface and is never touched.
17
+ *
18
+ * Is the action safe in non-interactive install?
19
+ * Yes. Every emitted action type (`remove-managed`, `backup-and-remove`,
20
+ * `remove-empty-dir`) is non-interactive and journaled; none requires a
21
+ * user choice, and unknown files never produce an action.
22
+ *
23
+ * See docs/installer-migrations.md#shipped-migrations and the antigravity row
24
+ * of docs/installer-migrations.md#runtime-configuration-contract-registry.
25
+ */
26
+ var __importDefault = (this && this.__importDefault) || function (mod) {
27
+ return (mod && mod.__esModule) ? mod : { "default": mod };
28
+ };
29
+ const node_fs_1 = __importDefault(require("node:fs"));
30
+ const node_path_1 = __importDefault(require("node:path"));
31
+ /** The two retired configHome-relative artifact subpaths. */
32
+ const RETIRED_SUBPATHS = ['skills', 'agents'];
33
+ /** Only GSD-owned top-level entries are retirement candidates. */
34
+ const GSD_PREFIX = 'gsd-';
35
+ const FILE_REASON = 'Antigravity scans ~/.gemini/config/{skills,agents} for global discovery and does not scan the ' +
36
+ 'configHome, so these artifacts are invisible to the runtime (#3738); the same artifacts reinstall ' +
37
+ "under the global layout's .gemini/config home override";
38
+ const FILE_OWNERSHIP_EVIDENCE = 'pre-#3738 antigravity installs record skills/gsd-*/** and agents/gsd-*.md keys in ' +
39
+ 'gsd-file-manifest.json at the configHome root; the migration only walks gsd--prefixed entries ' +
40
+ 'under the two retired subpaths';
41
+ const DIR_REASON = 'the retired container directory is removed only once every GSD-owned entry inside it is gone; a ' +
42
+ 'directory that still holds an unmanifested user file — or any file-level action that failed to ' +
43
+ 'apply — is left in place by the emptiness re-check';
44
+ const DIR_OWNERSHIP_EVIDENCE = 'skills/ and agents/ at the antigravity configHome root are GSD-installed container directories ' +
45
+ 'from the pre-#3738 layout; removal is gated on emptiness by the shared remove-empty-dir action';
46
+ /**
47
+ * Recursively collect files and directories under `root/relDir`, never
48
+ * following a symlink (whether it names a file or a directory) and never
49
+ * emitting a path that resolves outside `baseResolved`. Mirrors the traversal
50
+ * guard in migration 009 (`walkPiHooksTree`).
51
+ */
52
+ function walkTree(root, relDir, baseResolved, files, dirs) {
53
+ const dir = node_path_1.default.join(root, relDir);
54
+ const entries = node_fs_1.default.readdirSync(dir, { withFileTypes: true });
55
+ for (const entry of entries) {
56
+ // Never follow a symlink into or through: it must not be traversed,
57
+ // hashed, or removed, regardless of what it points at.
58
+ if (entry.isSymbolicLink())
59
+ continue;
60
+ const relPath = node_path_1.default.posix.join(relDir, entry.name);
61
+ const resolved = node_path_1.default.resolve(root, relPath);
62
+ if (resolved !== baseResolved && !resolved.startsWith(baseResolved + node_path_1.default.sep))
63
+ continue;
64
+ if (entry.isDirectory()) {
65
+ dirs.push(relPath);
66
+ walkTree(root, relPath, baseResolved, files, dirs);
67
+ }
68
+ else if (entry.isFile()) {
69
+ files.push(relPath);
70
+ }
71
+ }
72
+ }
73
+ const migration = {
74
+ id: '2026-08-26-antigravity-retire-confighome-artifacts',
75
+ title: "Retire Antigravity's configHome skills/agents surfaces",
76
+ description: 'Remove manifest-managed skills/gsd-*/ and agents/gsd-*.md under the Antigravity configHome ' +
77
+ '(~/.gemini/antigravity{,-ide,-cli}) — a location Antigravity does not scan for global discovery — ' +
78
+ 'and retire the now-empty container directories, now that the global layout installs both kinds ' +
79
+ 'under the ~/.gemini/config home override (#3738). Global scope only; the local .agents workspace ' +
80
+ 'surface is untouched.',
81
+ introducedIn: '1.11.0',
82
+ runtimes: ['antigravity'],
83
+ scopes: ['global'],
84
+ destructive: true,
85
+ plan: (ctx) => {
86
+ // Defense in depth ahead of the framework's own runtimes/scope filters:
87
+ // a claude/kimi/etc. skills/ or agents/ directory is a live install
88
+ // surface, never a retirement target, and so is antigravity's LOCAL
89
+ // .agents/skills layout.
90
+ if (ctx.runtime !== 'antigravity')
91
+ return [];
92
+ if (ctx.scope !== 'global')
93
+ return [];
94
+ const actions = [];
95
+ for (const subpath of RETIRED_SUBPATHS) {
96
+ const surfaceRoot = node_path_1.default.join(ctx.configDir, subpath);
97
+ let rootLstat;
98
+ try {
99
+ rootLstat = node_fs_1.default.lstatSync(surfaceRoot);
100
+ }
101
+ catch {
102
+ continue; // absent -> nothing to retire for this surface, idempotent
103
+ }
104
+ // Never follow a symlinked surface root: walking through it could plan
105
+ // actions against paths outside the config directory entirely.
106
+ if (rootLstat.isSymbolicLink() || !rootLstat.isDirectory())
107
+ continue;
108
+ const baseResolved = node_path_1.default.resolve(ctx.configDir);
109
+ const files = [];
110
+ const dirs = [];
111
+ try {
112
+ const entries = node_fs_1.default.readdirSync(surfaceRoot, { withFileTypes: true });
113
+ for (const entry of entries) {
114
+ if (entry.isSymbolicLink() || !entry.name.startsWith(GSD_PREFIX))
115
+ continue;
116
+ const relPath = node_path_1.default.posix.join(subpath, entry.name);
117
+ if (entry.isDirectory()) {
118
+ dirs.push(relPath);
119
+ walkTree(ctx.configDir, relPath, baseResolved, files, dirs);
120
+ }
121
+ else if (entry.isFile()) {
122
+ files.push(relPath);
123
+ }
124
+ }
125
+ }
126
+ catch {
127
+ // Unreadable directory: nothing safe to plan for this surface.
128
+ continue;
129
+ }
130
+ for (const relPath of files) {
131
+ const { classification } = ctx.classifyArtifact(relPath);
132
+ if (classification === 'managed-pristine') {
133
+ actions.push({ type: 'remove-managed', relPath, reason: FILE_REASON, ownershipEvidence: FILE_OWNERSHIP_EVIDENCE });
134
+ }
135
+ else if (classification === 'managed-modified') {
136
+ actions.push({ type: 'backup-and-remove', relPath, reason: FILE_REASON, ownershipEvidence: FILE_OWNERSHIP_EVIDENCE });
137
+ }
138
+ // 'unknown' (user-added, not manifest-recorded): no action, preserved.
139
+ // 'missing' / 'managed-missing': impossible here — relPath was just
140
+ // discovered by walking the live filesystem, so it currently exists.
141
+ }
142
+ // Deepest directories first, so a child has already been evaluated
143
+ // (and possibly removed) before its parent's own emptiness is
144
+ // re-checked by the executor. The surface root itself is appended
145
+ // last, unconditionally: the executor's own emptiness re-check is what
146
+ // actually decides whether it goes, not this ordering.
147
+ const orderedDirs = [...dirs].sort((a, b) => b.split('/').length - a.split('/').length);
148
+ orderedDirs.push(subpath);
149
+ for (const relPath of orderedDirs) {
150
+ actions.push({
151
+ type: 'remove-empty-dir',
152
+ relPath,
153
+ reason: DIR_REASON,
154
+ ownershipEvidence: DIR_OWNERSHIP_EVIDENCE,
155
+ // Declared, not derived: classifyArtifact() hashes file contents
156
+ // via sha256File(), which throws EISDIR against a directory path.
157
+ // These relPaths name directories, so classification is stated
158
+ // directly (never 'unknown', so the planner's
159
+ // unknown-classification block never fires for them).
160
+ classification: 'managed-pristine',
161
+ originalHash: null,
162
+ currentHash: null,
163
+ });
164
+ }
165
+ }
166
+ return actions;
167
+ },
168
+ };
169
+ module.exports = migration;
@@ -505,17 +505,20 @@ function acquireInstallMigrationLock(configDir, { timeoutMs = DEFAULT_LOCK_TIMEO
505
505
  let lockCreatedByUs = false;
506
506
  try {
507
507
  fd = node_fs_1.default.openSync(lockPath, 'wx');
508
- // Close the open descriptor before writing so the file handle is
509
- // released on Windows before the release closure unlinks it.
510
- // Write payload via writeFileSync with the path (not the fd) so we
511
- // don't hold an open fd across the lifetime of the lock.
512
- node_fs_1.default.closeSync(fd);
513
- fd = null;
514
508
  lockCreatedByUs = true; // we own the file; clean it up on any subsequent error
515
- node_fs_1.default.writeFileSync(lockPath, JSON.stringify({
509
+ // Write the payload through the exclusively-created descriptor: a
510
+ // second open-by-path here would be a TOCTOU window (CWE-367) where a
511
+ // co-writer of the directory could symlink-swap the just-created empty
512
+ // lock file before the payload lands.
513
+ node_fs_1.default.writeFileSync(fd, JSON.stringify({
516
514
  pid: process.pid,
517
515
  acquiredAt: new Date().toISOString(),
518
516
  }) + '\n');
517
+ // Close before returning so no handle stays open across the lock's
518
+ // lifetime — Windows cannot unlink a file with an open handle when the
519
+ // release closure runs.
520
+ node_fs_1.default.closeSync(fd);
521
+ fd = null;
519
522
  lockCreatedByUs = false; // release closure owns cleanup from here
520
523
  return () => {
521
524
  const failures = [];