@opengsd/gsd-core 1.14.0 → 1.16.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 (551) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.opencode/plugins/gsd-core.js +85 -5
  4. package/README.ja-JP.md +3 -3
  5. package/README.ko-KR.md +3 -3
  6. package/README.pt-BR.md +3 -3
  7. package/README.zh-CN.md +3 -3
  8. package/agents/gsd-code-fixer.compact.md +7 -6
  9. package/agents/gsd-code-fixer.md +9 -8
  10. package/agents/gsd-code-reviewer.compact.md +5 -3
  11. package/agents/gsd-code-reviewer.md +8 -6
  12. package/agents/gsd-debug-session-manager.compact.md +17 -2
  13. package/agents/gsd-debug-session-manager.md +17 -2
  14. package/agents/gsd-debugger.md +3 -3
  15. package/agents/gsd-eval-auditor.compact.md +1 -1
  16. package/agents/gsd-eval-auditor.md +1 -1
  17. package/agents/gsd-executor.md +17 -12
  18. package/agents/gsd-intel-updater.compact.md +1 -1
  19. package/agents/gsd-intel-updater.md +1 -1
  20. package/agents/gsd-mempalace-curator.md +2 -2
  21. package/agents/gsd-phase-researcher.md +19 -11
  22. package/agents/gsd-plan-checker.md +15 -9
  23. package/agents/gsd-planner.md +15 -11
  24. package/agents/gsd-project-researcher.compact.md +1 -1
  25. package/agents/gsd-project-researcher.md +1 -1
  26. package/agents/gsd-research-synthesizer.compact.md +1 -1
  27. package/agents/gsd-research-synthesizer.md +1 -1
  28. package/agents/gsd-ui-auditor.compact.md +21 -30
  29. package/agents/gsd-ui-auditor.md +166 -37
  30. package/agents/gsd-ui-researcher.compact.md +1 -1
  31. package/agents/gsd-ui-researcher.md +1 -1
  32. package/agents/gsd-verifier.md +37 -14
  33. package/bin/install.js +764 -287
  34. package/commands/gsd/add-tests.md +6 -1
  35. package/commands/gsd/ai-integration-phase.md +6 -1
  36. package/commands/gsd/audit-fix.md +5 -0
  37. package/commands/gsd/audit-milestone.md +6 -1
  38. package/commands/gsd/autonomous.md +7 -2
  39. package/commands/gsd/capture.md +9 -5
  40. package/commands/gsd/code-review.md +7 -2
  41. package/commands/gsd/complete-milestone.md +4 -0
  42. package/commands/gsd/config.md +7 -3
  43. package/commands/gsd/debug.md +11 -7
  44. package/commands/gsd/discuss-phase.md +7 -3
  45. package/commands/gsd/docs-update.md +12 -7
  46. package/commands/gsd/eval-review.md +6 -1
  47. package/commands/gsd/execute-phase.md +12 -7
  48. package/commands/gsd/extract-learnings.md +5 -0
  49. package/commands/gsd/fast.md +4 -0
  50. package/commands/gsd/forensics.md +5 -1
  51. package/commands/gsd/graphify.md +10 -6
  52. package/commands/gsd/health.md +5 -0
  53. package/commands/gsd/help.md +7 -2
  54. package/commands/gsd/import.md +7 -3
  55. package/commands/gsd/inbox.md +5 -0
  56. package/commands/gsd/ingest-docs.md +5 -1
  57. package/commands/gsd/manager.md +6 -1
  58. package/commands/gsd/map-codebase.md +7 -3
  59. package/commands/gsd/mempalace-capture.md +12 -4
  60. package/commands/gsd/mempalace-recall.md +5 -1
  61. package/commands/gsd/milestone-summary.md +5 -1
  62. package/commands/gsd/mvp-phase.md +8 -3
  63. package/commands/gsd/new-milestone.md +6 -1
  64. package/commands/gsd/new-project.md +5 -0
  65. package/commands/gsd/next.md +6 -1
  66. package/commands/gsd/ns-context.md +4 -0
  67. package/commands/gsd/ns-ideate.md +4 -0
  68. package/commands/gsd/ns-manage.md +4 -0
  69. package/commands/gsd/ns-project.md +4 -0
  70. package/commands/gsd/ns-review.md +4 -0
  71. package/commands/gsd/ns-workflow.md +4 -0
  72. package/commands/gsd/onboard.md +6 -1
  73. package/commands/gsd/pause-work.md +5 -1
  74. package/commands/gsd/phase.md +8 -4
  75. package/commands/gsd/plan-phase.md +6 -1
  76. package/commands/gsd/plan-review-convergence.md +11 -7
  77. package/commands/gsd/pr-branch.md +4 -0
  78. package/commands/gsd/profile-user.md +5 -1
  79. package/commands/gsd/progress.md +7 -2
  80. package/commands/gsd/quick-batch.md +21 -9
  81. package/commands/gsd/quick.md +12 -7
  82. package/commands/gsd/review.md +7 -4
  83. package/commands/gsd/secure-phase.md +6 -1
  84. package/commands/gsd/ship.md +5 -0
  85. package/commands/gsd/sketch.md +7 -2
  86. package/commands/gsd/spec-phase.md +5 -1
  87. package/commands/gsd/spike.md +8 -3
  88. package/commands/gsd/surface.md +5 -1
  89. package/commands/gsd/thread.md +4 -0
  90. package/commands/gsd/ui-phase.md +6 -1
  91. package/commands/gsd/ui-review.md +6 -1
  92. package/commands/gsd/ultraplan-phase.md +5 -1
  93. package/commands/gsd/undo.md +5 -1
  94. package/commands/gsd/update.md +6 -2
  95. package/commands/gsd/validate-phase.md +6 -1
  96. package/commands/gsd/verify-work.md +6 -1
  97. package/commands/gsd/workspace.md +7 -3
  98. package/gsd-core/bin/gsd-tools.cjs +477 -78
  99. package/gsd-core/bin/lib/active-workstream-store.cjs +15 -0
  100. package/gsd-core/bin/lib/adr-parser.cjs +3 -1
  101. package/gsd-core/bin/lib/agent-install-check.cjs +4 -1
  102. package/gsd-core/bin/lib/audit.cjs +144 -42
  103. package/gsd-core/bin/lib/broken-windows.cjs +13 -13
  104. package/gsd-core/bin/lib/capability-activation.cjs +9 -4
  105. package/gsd-core/bin/lib/capability-registry.cjs +197 -222
  106. package/gsd-core/bin/lib/capability-validator.cjs +16 -1
  107. package/gsd-core/bin/lib/check-auto-mode.cjs +35 -0
  108. package/gsd-core/bin/lib/check-command-router.cjs +164 -1625
  109. package/gsd-core/bin/lib/cjs-command-router-adapter.cjs +13 -2
  110. package/gsd-core/bin/lib/cli-exit.cjs +12 -0
  111. package/gsd-core/bin/lib/codex-agent-toml.cjs +32 -33
  112. package/gsd-core/bin/lib/command-aliases.cjs +7 -0
  113. package/gsd-core/bin/lib/command-routing-hub.cjs +48 -1
  114. package/gsd-core/bin/lib/commands.cjs +343 -207
  115. package/gsd-core/bin/lib/complexity-trigger.cjs +8 -7
  116. package/gsd-core/bin/lib/config-loader.cjs +65 -4
  117. package/gsd-core/bin/lib/config.cjs +76 -19
  118. package/gsd-core/bin/lib/core-utils.cjs +6 -1
  119. package/gsd-core/bin/lib/coverage.cjs +4 -8
  120. package/gsd-core/bin/lib/decision-coverage-support.cjs +259 -0
  121. package/gsd-core/bin/lib/decisions.cjs +30 -14
  122. package/gsd-core/bin/lib/drift.cjs +177 -42
  123. package/gsd-core/bin/lib/frontmatter-fence.cjs +90 -0
  124. package/gsd-core/bin/lib/frontmatter-splice.cjs +494 -0
  125. package/gsd-core/bin/lib/frontmatter.cjs +426 -234
  126. package/gsd-core/bin/lib/gap-checker.cjs +72 -29
  127. package/gsd-core/bin/lib/gate-api-coverage-verify-pre.cjs +381 -0
  128. package/gsd-core/bin/lib/gate-args.cjs +53 -0
  129. package/gsd-core/bin/lib/gate-codebase-drift.cjs +285 -0
  130. package/gsd-core/bin/lib/gate-config.cjs +46 -0
  131. package/gsd-core/bin/lib/gate-context-drift.cjs +141 -0
  132. package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +169 -0
  133. package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +126 -0
  134. package/gsd-core/bin/lib/gate-evaluation-scope.cjs +555 -0
  135. package/gsd-core/bin/lib/gate-evidence.cjs +138 -0
  136. package/gsd-core/bin/lib/gate-exit.cjs +27 -0
  137. package/gsd-core/bin/lib/gate-gap-analysis-plan-post.cjs +61 -0
  138. package/gsd-core/bin/lib/gate-phase-context.cjs +170 -0
  139. package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +1 -1
  140. package/gsd-core/bin/lib/gate-predicate.cjs +165 -0
  141. package/gsd-core/bin/lib/gate-prohibition-enforcement.cjs +94 -0
  142. package/gsd-core/bin/lib/gate-schema-drift.cjs +165 -0
  143. package/gsd-core/bin/lib/gate-tdd-red-evidence.cjs +100 -0
  144. package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +182 -0
  145. package/gsd-core/bin/lib/gate-ui-plan.cjs +86 -0
  146. package/gsd-core/bin/lib/gate-ui-safety.cjs +80 -0
  147. package/gsd-core/bin/lib/gate-verdict.cjs +64 -0
  148. package/gsd-core/bin/lib/gate-verify-command-paths.cjs +78 -0
  149. package/gsd-core/bin/lib/gate-verify-failure-directions.cjs +41 -0
  150. package/gsd-core/bin/lib/graphify.cjs +10 -2
  151. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +43 -47
  152. package/gsd-core/bin/lib/health-diagnostic.cjs +45 -8
  153. package/gsd-core/bin/lib/host-runtime-detection.cjs +9 -0
  154. package/gsd-core/bin/lib/init.cjs +364 -132
  155. package/gsd-core/bin/lib/install-engine.cjs +30 -33
  156. package/gsd-core/bin/lib/install-profiles.cjs +7 -4
  157. package/gsd-core/bin/lib/installer-migrations.cjs +8 -1
  158. package/gsd-core/bin/lib/io.cjs +122 -3
  159. package/gsd-core/bin/lib/loop-resolver.cjs +95 -0
  160. package/gsd-core/bin/lib/markdown-sectionizer.cjs +75 -1
  161. package/gsd-core/bin/lib/milestone.cjs +37 -6
  162. package/gsd-core/bin/lib/model-resolver.cjs +171 -62
  163. package/gsd-core/bin/lib/observability/event.cjs +1 -1
  164. package/gsd-core/bin/lib/observability/logger.cjs +46 -1
  165. package/gsd-core/bin/lib/pattern.cjs +10 -0
  166. package/gsd-core/bin/lib/phase-command-router.cjs +20 -5
  167. package/gsd-core/bin/lib/phase-estimation.cjs +5 -4
  168. package/gsd-core/bin/lib/phase-id-card.cjs +32 -0
  169. package/gsd-core/bin/lib/phase-id-display.cjs +78 -0
  170. package/gsd-core/bin/lib/phase-id.cjs +110 -8
  171. package/gsd-core/bin/lib/phase-lifecycle.cjs +9 -2
  172. package/gsd-core/bin/lib/phase-locator.cjs +29 -10
  173. package/gsd-core/bin/lib/phase-status.cjs +360 -0
  174. package/gsd-core/bin/lib/phase.cjs +489 -88
  175. package/gsd-core/bin/lib/plan-document.cjs +142 -20
  176. package/gsd-core/bin/lib/plan-drift-guard.cjs +5 -0
  177. package/gsd-core/bin/lib/planning-document.cjs +692 -0
  178. package/gsd-core/bin/lib/planning-inspect.cjs +60 -9
  179. package/gsd-core/bin/lib/planning-snapshot.cjs +18 -0
  180. package/gsd-core/bin/lib/planning-workspace.cjs +83 -55
  181. package/gsd-core/bin/lib/pr-branch-patterns.cjs +57 -0
  182. package/gsd-core/bin/lib/pristine-baseline.cjs +10 -0
  183. package/gsd-core/bin/lib/probe-core.cjs +7 -1
  184. package/gsd-core/bin/lib/profile-output.cjs +6 -3
  185. package/gsd-core/bin/lib/prohibition-enforcement.cjs +0 -55
  186. package/gsd-core/bin/lib/project-root.cjs +41 -2
  187. package/gsd-core/bin/lib/quick-batch-command-router.cjs +35 -9
  188. package/gsd-core/bin/lib/quick-batch-dispatch.cjs +11 -8
  189. package/gsd-core/bin/lib/real-home-guard.cjs +9 -1
  190. package/gsd-core/bin/lib/report-parser.cjs +269 -0
  191. package/gsd-core/bin/lib/review-lane-descriptor.cjs +10 -30
  192. package/gsd-core/bin/lib/review-reviewer-selection.cjs +2 -2
  193. package/gsd-core/bin/lib/roadmap-command-router.cjs +25 -19
  194. package/gsd-core/bin/lib/roadmap-parser.cjs +242 -15
  195. package/gsd-core/bin/lib/roadmap-upgrade.cjs +1653 -65
  196. package/gsd-core/bin/lib/roadmap.cjs +405 -88
  197. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +373 -187
  198. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +5 -2
  199. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +57 -1
  200. package/gsd-core/bin/lib/runtime-homes.cjs +14 -7
  201. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +522 -624
  202. package/gsd-core/bin/lib/runtime-name-policy.cjs +246 -21
  203. package/gsd-core/bin/lib/runtime-slash.cjs +47 -30
  204. package/gsd-core/bin/lib/shell-command-projection.cjs +47 -8
  205. package/gsd-core/bin/lib/smart-entry.cjs +19 -3
  206. package/gsd-core/bin/lib/stale-bake-guard.cjs +32 -48
  207. package/gsd-core/bin/lib/state-contract.cjs +15 -18
  208. package/gsd-core/bin/lib/state-document.cjs +100 -22
  209. package/gsd-core/bin/lib/state-transition.cjs +39 -2
  210. package/gsd-core/bin/lib/state.cjs +256 -105
  211. package/gsd-core/bin/lib/surface.cjs +19 -2
  212. package/gsd-core/bin/lib/tdd-red-evidence.cjs +48 -79
  213. package/gsd-core/bin/lib/uat-predicate.cjs +359 -38
  214. package/gsd-core/bin/lib/uat.cjs +432 -7
  215. package/gsd-core/bin/lib/ui-consideration-probe.cjs +15 -2
  216. package/gsd-core/bin/lib/ui-frontend-evidence.cjs +167 -40
  217. package/gsd-core/bin/lib/undo-commit-selection.cjs +131 -0
  218. package/gsd-core/bin/lib/vendor/README.md +31 -9
  219. package/gsd-core/bin/lib/vendor/saxes.cjs +1934 -0
  220. package/gsd-core/bin/lib/vendor/saxes.cjs.LICENSE.txt +92 -0
  221. package/gsd-core/bin/lib/vendor/tap-parser.cjs +8927 -0
  222. package/gsd-core/bin/lib/vendor/tap-parser.cjs.LICENSE.txt +152 -0
  223. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  224. package/gsd-core/bin/lib/verification.cjs +1378 -274
  225. package/gsd-core/bin/lib/verify-command-grounding.cjs +46 -2
  226. package/gsd-core/bin/lib/verify-command-router.cjs +18 -7
  227. package/gsd-core/bin/lib/verify.cjs +357 -531
  228. package/gsd-core/bin/lib/workstream-inventory-builder.cjs +14 -6
  229. package/gsd-core/bin/lib/workstream-inventory.cjs +31 -21
  230. package/gsd-core/bin/lib/workstream-name-policy.cjs +31 -1
  231. package/gsd-core/bin/lib/workstream.cjs +11 -2
  232. package/gsd-core/bin/lib/worktree-base-ref.cjs +482 -73
  233. package/gsd-core/bin/lib/worktree-safety.cjs +784 -51
  234. package/gsd-core/bin/shared/config-defaults.manifest.json +8 -0
  235. package/gsd-core/bin/shared/config-schema.manifest.json +5 -0
  236. package/gsd-core/references/autonomous-smart-discuss.md +2 -1
  237. package/gsd-core/references/autonomous-ui-design-contract.md +3 -3
  238. package/gsd-core/references/checkpoints.md +5 -3
  239. package/gsd-core/references/edge-probe-fixtures/01-round-half-even/expected-coverage.json +28 -3
  240. package/gsd-core/references/edge-probe-fixtures/02-merge-intervals/expected-coverage.json +37 -4
  241. package/gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/expected-coverage.json +28 -3
  242. package/gsd-core/references/edge-probe-fixtures/04-money-rounding/expected-coverage.json +28 -3
  243. package/gsd-core/references/edge-probe-fixtures/05-list-dedupe/expected-coverage.json +37 -4
  244. package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/expected-coverage.json +37 -4
  245. package/gsd-core/references/edge-probe.md +195 -21
  246. package/gsd-core/references/execute-mvp-tdd.md +5 -10
  247. package/gsd-core/references/execute-phase-between-wave-reset.md +10 -6
  248. package/gsd-core/references/execute-phase-response-language.md +1 -1
  249. package/gsd-core/references/execute-phase-wave-guard.md +22 -11
  250. package/gsd-core/references/gsd-run-resolver.md +1 -1
  251. package/gsd-core/references/loop-hook-dispatch.md +7 -1
  252. package/gsd-core/references/model-profiles.md +1 -1
  253. package/gsd-core/references/offer-next.md +1 -1
  254. package/gsd-core/references/phase-argument-parsing.md +9 -7
  255. package/gsd-core/references/phase-id-convention.md +28 -0
  256. package/gsd-core/references/planner-gap-closure.md +2 -0
  257. package/gsd-core/references/planner-load-graph-context.md +24 -13
  258. package/gsd-core/references/planner-verify-command-grounding.md +14 -0
  259. package/gsd-core/references/planning-config.md +12 -3
  260. package/gsd-core/references/spidr-splitting.md +1 -1
  261. package/gsd-core/references/tdd.md +37 -8
  262. package/gsd-core/references/ui-consideration-probe.md +10 -5
  263. package/gsd-core/references/verifier-phase-gates.md +5 -2
  264. package/gsd-core/references/verify-command-path-resolvability.md +10 -2
  265. package/gsd-core/references/verify-mvp-mode.md +2 -2
  266. package/gsd-core/references/workstream-flag.md +33 -3
  267. package/gsd-core/references/worktree-path-safety.md +321 -0
  268. package/gsd-core/templates/README.md +1 -1
  269. package/gsd-core/templates/UAT.md +17 -1
  270. package/gsd-core/templates/config.json +2 -11
  271. package/gsd-core/templates/verification-report.md +1 -1
  272. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  273. package/gsd-core/workflows/add-backlog.md +1 -1
  274. package/gsd-core/workflows/add-phase.md +8 -7
  275. package/gsd-core/workflows/add-tests.md +4 -3
  276. package/gsd-core/workflows/add-todo.md +6 -5
  277. package/gsd-core/workflows/ai-integration-phase.md +13 -4
  278. package/gsd-core/workflows/audit-fix.md +1 -1
  279. package/gsd-core/workflows/audit-milestone.md +4 -3
  280. package/gsd-core/workflows/audit-uat.md +1 -1
  281. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +9 -18
  282. package/gsd-core/workflows/autonomous.md +43 -23
  283. package/gsd-core/workflows/check-todos.md +7 -6
  284. package/gsd-core/workflows/cleanup.md +2 -2
  285. package/gsd-core/workflows/code-review/steps/dispatch-fix.md +4 -3
  286. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +20 -13
  287. package/gsd-core/workflows/code-review-fix.md +112 -25
  288. package/gsd-core/workflows/code-review.md +146 -135
  289. package/gsd-core/workflows/complete-milestone/detail/elaboration.md +4 -3
  290. package/gsd-core/workflows/complete-milestone.md +13 -8
  291. package/gsd-core/workflows/debug.md +32 -7
  292. package/gsd-core/workflows/diagnose-issues.md +3 -2
  293. package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
  294. package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
  295. package/gsd-core/workflows/discuss-phase-assumptions.md +4 -3
  296. package/gsd-core/workflows/discuss-phase.md +4 -3
  297. package/gsd-core/workflows/do.md +2 -2
  298. package/gsd-core/workflows/docs-update.md +6 -5
  299. package/gsd-core/workflows/edit-phase.md +4 -3
  300. package/gsd-core/workflows/eval-review.md +14 -5
  301. package/gsd-core/workflows/execute-phase/detail/elaboration.md +2 -2
  302. package/gsd-core/workflows/execute-phase/steps/code-review-disposition.md +1019 -0
  303. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +15 -4
  304. package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +10 -7
  305. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +37 -3
  306. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +3 -1
  307. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +2 -2
  308. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
  309. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +1 -1
  310. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +46 -9
  311. package/gsd-core/workflows/execute-phase/steps/protected-branch.md +1 -1
  312. package/gsd-core/workflows/execute-phase/steps/ready-wave-gate.md +37 -0
  313. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +1 -1
  314. package/gsd-core/workflows/execute-phase/steps/regression-gate.md +2 -0
  315. package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +1 -1
  316. package/gsd-core/workflows/execute-phase/steps/threat-id-gate.md +28 -0
  317. package/gsd-core/workflows/execute-phase/steps/verify-phase-goal.md +187 -0
  318. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +2 -3
  319. package/gsd-core/workflows/execute-phase/steps/worktree-base-check.md +25 -0
  320. package/gsd-core/workflows/execute-phase.md +96 -178
  321. package/gsd-core/workflows/execute-plan.md +18 -23
  322. package/gsd-core/workflows/explore.md +4 -4
  323. package/gsd-core/workflows/extract-learnings.md +4 -2
  324. package/gsd-core/workflows/fast.md +1 -1
  325. package/gsd-core/workflows/forensics.md +1 -1
  326. package/gsd-core/workflows/graduation.md +1 -1
  327. package/gsd-core/workflows/health.md +3 -2
  328. package/gsd-core/workflows/help/modes/full.compact.md +3 -3
  329. package/gsd-core/workflows/help/modes/full.md +5 -5
  330. package/gsd-core/workflows/help/modes/topic.md +15 -5
  331. package/gsd-core/workflows/import.md +4 -3
  332. package/gsd-core/workflows/inbox.md +2 -2
  333. package/gsd-core/workflows/ingest-docs.md +3 -3
  334. package/gsd-core/workflows/insert-phase.md +4 -3
  335. package/gsd-core/workflows/list-seeds.md +1 -1
  336. package/gsd-core/workflows/list-workspaces.md +1 -1
  337. package/gsd-core/workflows/manager.md +6 -4
  338. package/gsd-core/workflows/map-codebase.md +5 -4
  339. package/gsd-core/workflows/milestone-summary.md +3 -2
  340. package/gsd-core/workflows/mvp-phase.md +14 -14
  341. package/gsd-core/workflows/new-milestone.md +11 -11
  342. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +3 -3
  343. package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +1 -1
  344. package/gsd-core/workflows/new-project.md +7 -7
  345. package/gsd-core/workflows/new-workspace.md +2 -2
  346. package/gsd-core/workflows/next.md +1 -1
  347. package/gsd-core/workflows/note.md +1 -1
  348. package/gsd-core/workflows/onboard.md +1 -1
  349. package/gsd-core/workflows/pause-work.md +2 -2
  350. package/gsd-core/workflows/plan-phase/detail/elaboration.md +1 -1
  351. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +17 -5
  352. package/gsd-core/workflows/plan-phase/steps/closed-phase-gate.md +1 -1
  353. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +1 -1
  354. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +23 -5
  355. package/gsd-core/workflows/plan-phase.md +50 -24
  356. package/gsd-core/workflows/plan-review-convergence.md +21 -5
  357. package/gsd-core/workflows/plant-seed.md +62 -20
  358. package/gsd-core/workflows/pr-branch.md +113 -13
  359. package/gsd-core/workflows/profile-user.md +2 -2
  360. package/gsd-core/workflows/progress.md +19 -49
  361. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +27 -0
  362. package/gsd-core/workflows/quick/steps/quick-verification.md +4 -4
  363. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +30 -11
  364. package/gsd-core/workflows/quick-batch/steps/batch-init.md +1 -1
  365. package/gsd-core/workflows/quick-batch/steps/completion.md +1 -1
  366. package/gsd-core/workflows/quick-batch/steps/merge-wave.md +1 -1
  367. package/gsd-core/workflows/quick-batch/steps/planner-wave.md +1 -1
  368. package/gsd-core/workflows/quick-batch/steps/research-phase.md +1 -1
  369. package/gsd-core/workflows/quick-batch/steps/resume-mode.md +1 -1
  370. package/gsd-core/workflows/quick-batch/steps/verification-wave.md +9 -3
  371. package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +1 -1
  372. package/gsd-core/workflows/quick-batch.md +15 -10
  373. package/gsd-core/workflows/quick.md +62 -39
  374. package/gsd-core/workflows/reapply-patches.md +9 -3
  375. package/gsd-core/workflows/remove-phase.md +3 -2
  376. package/gsd-core/workflows/remove-workspace.md +2 -2
  377. package/gsd-core/workflows/resume-project.md +3 -2
  378. package/gsd-core/workflows/review.md +33 -17
  379. package/gsd-core/workflows/scan.md +3 -2
  380. package/gsd-core/workflows/secure-phase.md +13 -13
  381. package/gsd-core/workflows/settings-advanced.md +30 -10
  382. package/gsd-core/workflows/settings-integrations.md +2 -3
  383. package/gsd-core/workflows/settings.md +4 -4
  384. package/gsd-core/workflows/ship.md +14 -13
  385. package/gsd-core/workflows/sketch-wrap-up.md +1 -1
  386. package/gsd-core/workflows/sketch.md +1 -1
  387. package/gsd-core/workflows/smart-entry.md +2 -2
  388. package/gsd-core/workflows/spec-phase.md +15 -5
  389. package/gsd-core/workflows/spike-wrap-up.md +1 -1
  390. package/gsd-core/workflows/spike.md +1 -1
  391. package/gsd-core/workflows/stats.md +1 -1
  392. package/gsd-core/workflows/sync-skills.md +5 -5
  393. package/gsd-core/workflows/thread.md +2 -2
  394. package/gsd-core/workflows/transition.md +13 -23
  395. package/gsd-core/workflows/ui-phase.md +48 -11
  396. package/gsd-core/workflows/ui-review.md +21 -6
  397. package/gsd-core/workflows/ultraplan-phase.md +3 -2
  398. package/gsd-core/workflows/undo.md +339 -20
  399. package/gsd-core/workflows/update.md +7 -7
  400. package/gsd-core/workflows/validate-phase.md +12 -13
  401. package/gsd-core/workflows/verify-work/detail/elaboration.md +43 -3
  402. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +1 -1
  403. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +5 -3
  404. package/gsd-core/workflows/verify-work.md +129 -55
  405. package/hooks/dist/gsd-agent-isolation-guard.js +32 -0
  406. package/hooks/dist/gsd-check-update-worker.js +8 -0
  407. package/hooks/dist/gsd-check-update.js +8 -0
  408. package/hooks/dist/gsd-context-monitor.js +31 -8
  409. package/hooks/dist/gsd-cursor-subagent-start.js +8 -0
  410. package/hooks/dist/gsd-secret-read-guard.js +161 -4
  411. package/hooks/dist/gsd-statusline.js +85 -20
  412. package/hooks/dist/gsd-update-banner.js +8 -0
  413. package/hooks/dist/gsd-validate-commit.sh +63 -4
  414. package/hooks/dist/gsd-windsurf-pre-write.js +11 -2
  415. package/hooks/dist/gsd-workflow-guard.js +5 -4
  416. package/hooks/dist/gsd-worktree-path-guard.js +6 -2
  417. package/hooks/dist/lib/cli-exit.js +12 -0
  418. package/hooks/dist/lib/git-probe.js +17 -1
  419. package/hooks/dist/lib/isolation-sentinel.js +2 -2
  420. package/hooks/gsd-agent-isolation-guard.js +32 -0
  421. package/hooks/gsd-check-update-worker.js +8 -0
  422. package/hooks/gsd-check-update.js +8 -0
  423. package/hooks/gsd-context-monitor.js +31 -8
  424. package/hooks/gsd-cursor-subagent-start.js +8 -0
  425. package/hooks/gsd-secret-read-guard.js +161 -4
  426. package/hooks/gsd-statusline.js +85 -20
  427. package/hooks/gsd-update-banner.js +8 -0
  428. package/hooks/gsd-validate-commit.sh +63 -4
  429. package/hooks/gsd-windsurf-pre-write.js +11 -2
  430. package/hooks/gsd-workflow-guard.js +5 -4
  431. package/hooks/gsd-worktree-path-guard.js +6 -2
  432. package/hooks/hooks.json +5 -5
  433. package/hooks/lib/cli-exit.js +12 -0
  434. package/hooks/lib/git-probe.js +17 -1
  435. package/hooks/lib/isolation-sentinel.js +2 -2
  436. package/package.json +22 -4
  437. package/scripts/build-hooks.js +15 -6
  438. package/scripts/changeset/parse.cjs +52 -4
  439. package/scripts/check-contract-drift.cjs +127 -11
  440. package/scripts/ci-timeout-report.cjs +770 -4
  441. package/scripts/command-contract-helpers.cjs +15 -8
  442. package/scripts/docs-guard-registry.cjs +34 -0
  443. package/scripts/gen-features.cjs +13 -8
  444. package/scripts/gen-hooks-cli-exit.cjs +12 -28
  445. package/scripts/gen-loop-host-contract.cjs +79 -1
  446. package/scripts/gen-platform-conformance-tier.cjs +187 -1
  447. package/scripts/gen-plugin-skills.cjs +87 -1
  448. package/scripts/gen-research-agents.cjs +24 -31
  449. package/scripts/gen-scripts-cli-exit.cjs +30 -3
  450. package/scripts/gen-test-timings.cjs +32 -7
  451. package/scripts/lib/cli-exit.cjs +12 -0
  452. package/scripts/lib/macos-conformance-tier.generated.cjs +34 -2
  453. package/scripts/lib/ndjson-reporter.cjs +31 -5
  454. package/scripts/lib/platform-conformance-tier.generated.cjs +45 -5
  455. package/scripts/lib/registration-ledger-preload.cjs +155 -0
  456. package/scripts/lib/vendor-bundle.cjs +59 -0
  457. package/scripts/lib/vendor-licenses/saxes-6.0.0.txt +64 -0
  458. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
  459. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
  460. package/scripts/lint-completion-predicate-drift.cjs +18 -19
  461. package/scripts/lint-descriptions.cjs +7 -3
  462. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +38 -1
  463. package/scripts/lint-eslint-glob-coverage.allowlist.json +20 -0
  464. package/scripts/lint-frontmatter-fence-drift.cjs +313 -0
  465. package/scripts/lint-frontmatter-scalar-broad-grep.cjs +122 -16
  466. package/scripts/lint-phase-arg-assignment.cjs +257 -0
  467. package/scripts/lint-phase-enumeration-drift.cjs +12 -8
  468. package/scripts/lint-phase-id-drift.cjs +319 -5
  469. package/scripts/lint-planning-document-positive-control.cjs +329 -0
  470. package/scripts/lint-pr-branch-pattern-drift.cjs +148 -0
  471. package/scripts/lint-response-language-coverage.cjs +3 -0
  472. package/scripts/lint-retired-runtime-name.cjs +619 -0
  473. package/scripts/lint-skill-deps.cjs +7 -3
  474. package/scripts/lint-state-write-path-drift.cjs +93 -0
  475. package/scripts/lint-test-file-count.allowlist.json +38 -9
  476. package/scripts/lint-test-file-count.cjs +34 -1
  477. package/scripts/lint-vendored-deps.cjs +41 -5
  478. package/scripts/lint-workflow-shellcheck-baseline.json +25 -10
  479. package/scripts/mutation-matrix.cjs +50 -5
  480. package/scripts/prompt-injection-scan.sh +4 -0
  481. package/scripts/release-tarball-smoke.cjs +194 -1
  482. package/scripts/require-issue-link-policy.cjs +6 -2
  483. package/scripts/sync-runtime-launcher.cjs +184 -2
  484. package/scripts/verify-npm-publish.cjs +76 -20
  485. package/skills/gsd-add-tests/SKILL.md +6 -1
  486. package/skills/gsd-ai-integration-phase/SKILL.md +6 -1
  487. package/skills/gsd-audit-fix/SKILL.md +5 -0
  488. package/skills/gsd-audit-milestone/SKILL.md +6 -1
  489. package/skills/gsd-autonomous/SKILL.md +7 -2
  490. package/skills/gsd-capture/SKILL.md +9 -5
  491. package/skills/gsd-code-review/SKILL.md +7 -2
  492. package/skills/gsd-complete-milestone/SKILL.md +4 -0
  493. package/skills/gsd-config/SKILL.md +8 -4
  494. package/skills/gsd-debug/SKILL.md +11 -7
  495. package/skills/gsd-discuss-phase/SKILL.md +7 -3
  496. package/skills/gsd-docs-update/SKILL.md +12 -7
  497. package/skills/gsd-eval-review/SKILL.md +6 -1
  498. package/skills/gsd-execute-phase/SKILL.md +12 -7
  499. package/skills/gsd-extract-learnings/SKILL.md +5 -0
  500. package/skills/gsd-fast/SKILL.md +4 -0
  501. package/skills/gsd-forensics/SKILL.md +5 -1
  502. package/skills/gsd-graphify/SKILL.md +10 -6
  503. package/skills/gsd-health/SKILL.md +5 -0
  504. package/skills/gsd-help/SKILL.md +7 -2
  505. package/skills/gsd-import/SKILL.md +7 -3
  506. package/skills/gsd-inbox/SKILL.md +5 -0
  507. package/skills/gsd-ingest-docs/SKILL.md +5 -1
  508. package/skills/gsd-manager/SKILL.md +6 -1
  509. package/skills/gsd-map-codebase/SKILL.md +7 -3
  510. package/skills/gsd-mempalace-capture/SKILL.md +12 -4
  511. package/skills/gsd-mempalace-recall/SKILL.md +5 -1
  512. package/skills/gsd-milestone-summary/SKILL.md +5 -1
  513. package/skills/gsd-mvp-phase/SKILL.md +8 -3
  514. package/skills/gsd-new-milestone/SKILL.md +6 -1
  515. package/skills/gsd-new-project/SKILL.md +5 -0
  516. package/skills/gsd-next/SKILL.md +6 -1
  517. package/skills/gsd-ns-context/SKILL.md +4 -0
  518. package/skills/gsd-ns-ideate/SKILL.md +4 -0
  519. package/skills/gsd-ns-manage/SKILL.md +4 -0
  520. package/skills/gsd-ns-project/SKILL.md +4 -0
  521. package/skills/gsd-ns-review/SKILL.md +4 -0
  522. package/skills/gsd-ns-workflow/SKILL.md +4 -0
  523. package/skills/gsd-onboard/SKILL.md +6 -1
  524. package/skills/gsd-pause-work/SKILL.md +5 -1
  525. package/skills/gsd-phase/SKILL.md +8 -4
  526. package/skills/gsd-plan-phase/SKILL.md +6 -1
  527. package/skills/gsd-plan-review-convergence/SKILL.md +10 -6
  528. package/skills/gsd-pr-branch/SKILL.md +4 -0
  529. package/skills/gsd-profile-user/SKILL.md +5 -1
  530. package/skills/gsd-progress/SKILL.md +7 -2
  531. package/skills/gsd-quick/SKILL.md +16 -10
  532. package/skills/gsd-quick-batch/SKILL.md +21 -9
  533. package/skills/gsd-review/SKILL.md +7 -4
  534. package/skills/gsd-review-backlog/SKILL.md +3 -2
  535. package/skills/gsd-secure-phase/SKILL.md +6 -1
  536. package/skills/gsd-ship/SKILL.md +5 -0
  537. package/skills/gsd-sketch/SKILL.md +7 -2
  538. package/skills/gsd-spec-phase/SKILL.md +5 -1
  539. package/skills/gsd-spike/SKILL.md +8 -3
  540. package/skills/gsd-surface/SKILL.md +5 -1
  541. package/skills/gsd-thread/SKILL.md +4 -0
  542. package/skills/gsd-ui-phase/SKILL.md +6 -1
  543. package/skills/gsd-ui-review/SKILL.md +6 -1
  544. package/skills/gsd-ultraplan-phase/SKILL.md +5 -1
  545. package/skills/gsd-undo/SKILL.md +5 -1
  546. package/skills/gsd-update/SKILL.md +6 -2
  547. package/skills/gsd-validate-phase/SKILL.md +6 -1
  548. package/skills/gsd-verify-work/SKILL.md +6 -1
  549. package/skills/gsd-workspace/SKILL.md +7 -3
  550. package/skills/gsd-workstreams/SKILL.md +6 -6
  551. package/vscode/package.json +1 -1
@@ -33,11 +33,12 @@ const { installFs, mkInstallTempDir } = installFsAdapter;
33
33
  const commandRoster = require("./command-roster.cjs");
34
34
  const { readGsdCommandNames, transformContentToHyphen } = commandRoster;
35
35
  const runtimeNamePolicy = require("./runtime-name-policy.cjs");
36
- const { getDirName } = runtimeNamePolicy;
36
+ const { getDirName, hostBehaviorsFor } = runtimeNamePolicy;
37
37
  const capabilityRegistry = require("./capability-registry.cjs");
38
38
  const hostIntegration = require("./host-integration.cjs");
39
39
  const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
40
40
  const frontmatterModule = require("./frontmatter.cjs");
41
+ const frontmatter_fence_cjs_1 = require("./frontmatter-fence.cjs");
41
42
  const pattern_cjs_1 = require("./pattern.cjs");
42
43
  const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
43
44
  // #2870: install-scope.cts is a leaf-tier sibling (imports only
@@ -93,24 +94,6 @@ function gsdVersion() {
93
94
  cachedVersion = resolveVersionFrom(__dirname);
94
95
  return cachedVersion;
95
96
  }
96
- /**
97
- * Host-specific install behaviors declared on the runtime descriptor
98
- * (capabilities/<runtime>/capability.json -> runtime.hostBehaviors). Mirrors
99
- * bin/install.js's / install-engine.cts's `_hostBehaviors` (ADR-1239 / #2086
100
- * / #2092). Returns {} for runtimes that declare none, so every behavior
101
- * branch degrades to the generic path by default. Unlike the bin/install.js
102
- * and install-engine.cts variants, this module already imports
103
- * `capabilityRegistry` statically (see NON_CLAUDE_RUNTIMES below), so this
104
- * reads it directly rather than re-require()-ing inside a try/catch.
105
- */
106
- function _hostBehaviors(runtime) {
107
- return ((capabilityRegistry &&
108
- capabilityRegistry.runtimes &&
109
- capabilityRegistry.runtimes[runtime] &&
110
- capabilityRegistry.runtimes[runtime].runtime &&
111
- capabilityRegistry.runtimes[runtime].runtime.hostBehaviors) ||
112
- {});
113
- }
114
97
  /**
115
98
  * Public accessor for the `hostBehaviors.agentFileExtension` descriptor field
116
99
  * (ADR-1239 / #2099 / #2103). Returns the runtime's declared agent-file
@@ -122,7 +105,7 @@ function _hostBehaviors(runtime) {
122
105
  * duplicating a hardcoded `runtime === 'copilot'` check (#2103 fold).
123
106
  */
124
107
  function agentFileExtensionFor(runtime) {
125
- const ext = _hostBehaviors(runtime).agentFileExtension;
108
+ const ext = hostBehaviorsFor(runtime).agentFileExtension;
126
109
  return typeof ext === 'string' ? ext : undefined;
127
110
  }
128
111
  const colorNameToHex = {
@@ -457,7 +440,10 @@ function convertClaudeCommandToClaudeSkill(content, skillName, runtime = null, c
457
440
  // calls fall back to reading the list.
458
441
  const names = cmdNames || readGsdCommandNames();
459
442
  const normalizedBody = transformContentToHyphen(body, names);
460
- const description = extractFrontmatterField(frontmatter, 'description') || '';
443
+ // #4324: the description is the text the host's skill picker renders, so it
444
+ // needs the same hyphen normalisation the body gets — otherwise a `/gsd:<cmd>`
445
+ // mention in a command description ships the retired colon form to the user.
446
+ const description = transformContentToHyphen(extractFrontmatterField(frontmatter, 'description') || '', names);
461
447
  const argumentHint = extractFrontmatterField(frontmatter, 'argument-hint');
462
448
  const agent = extractFrontmatterField(frontmatter, 'agent');
463
449
  // #769: preserve context: from source command files so it is emitted into
@@ -482,7 +468,7 @@ function convertClaudeCommandToClaudeSkill(content, skillName, runtime = null, c
482
468
  // Hermes' SKILL.md spec lists `version` as a required frontmatter field.
483
469
  // Track GSD's package version so Hermes' skill_view() reports a stable
484
470
  // identifier per install.
485
- if (runtime === 'hermes') {
471
+ if (hostBehaviorsFor(runtime).skillFrontmatterVersion) {
486
472
  const version = gsdVersion();
487
473
  if (version)
488
474
  fm += `version: ${yamlQuote(version)}\n`;
@@ -492,7 +478,7 @@ function convertClaudeCommandToClaudeSkill(content, skillName, runtime = null, c
492
478
  // runtimes that declare the flag so Claude/Hermes skill frontmatter is
493
479
  // unchanged (they ignore the field, but we keep their output byte-stable).
494
480
  // skillName is the `gsd-<stem>` dir name. (ADR-1239 / #2092)
495
- if (_hostBehaviors(runtime).skillPriorityFrontmatter) {
481
+ if (hostBehaviorsFor(runtime).skillPriorityFrontmatter) {
496
482
  const stem = typeof skillName === 'string' && skillName.startsWith('gsd-')
497
483
  ? skillName.slice(4)
498
484
  : skillName;
@@ -727,10 +713,13 @@ function appendAgentTools(content, grants) {
727
713
  return content;
728
714
  const eol = content.includes('\r\n') ? '\r\n' : '\n';
729
715
  const lines = content.split(eol);
730
- if (lines[0] !== '---')
716
+ // The block is the one the one fence owner finds; `frontmatterEnd` is its closing fence's
717
+ // line index in `lines`.
718
+ const fence = (0, frontmatter_fence_cjs_1.locateFrontmatterFence)(content);
719
+ if (!fence?.closed)
731
720
  return content;
732
- const frontmatterEnd = lines.indexOf('---', 1);
733
- if (frontmatterEnd === -1)
721
+ const frontmatterEnd = content.slice(0, fence.closingStart).split(eol).length - 1;
722
+ if (frontmatterEnd < 1)
734
723
  return content;
735
724
  const toolsIndex = lines.findIndex((line, index) => index < frontmatterEnd && /^tools:[ \t]*(.*)$/.test(line));
736
725
  if (toolsIndex === -1)
@@ -1077,17 +1066,19 @@ function yamlIdentifier(value) {
1077
1066
  }
1078
1067
  return yamlQuote(text);
1079
1068
  }
1069
+ /**
1070
+ * The frontmatter YAML (trimmed) and everything after the closing fence line's text, for the
1071
+ * block the one fence owner (`locateFrontmatterFence`) finds — so a `---` inside a value (a
1072
+ * description that mentions `a---b`) cannot end the block early.
1073
+ */
1080
1074
  function extractFrontmatterAndBody(content) {
1081
- if (!content.startsWith('---')) {
1082
- return { frontmatter: null, body: content };
1083
- }
1084
- const endIndex = content.indexOf('---', 3);
1085
- if (endIndex === -1) {
1075
+ const fence = (0, frontmatter_fence_cjs_1.locateFrontmatterFence)(content);
1076
+ if (!fence?.closed) {
1086
1077
  return { frontmatter: null, body: content };
1087
1078
  }
1088
1079
  return {
1089
- frontmatter: content.substring(3, endIndex).trim(),
1090
- body: content.substring(endIndex + 3),
1080
+ frontmatter: content.slice(fence.openEnd, fence.bodyEnd).trim(),
1081
+ body: content.slice(fence.closingFenceEnd),
1091
1082
  };
1092
1083
  }
1093
1084
  function extractFrontmatterField(frontmatter, fieldName) {
@@ -1536,7 +1527,7 @@ function convertClaudeCommandToTraeSkill(content, skillName) {
1536
1527
  // is not formally documented (thin SPA docs) — descriptor-driven, single
1537
1528
  // fixed GSD-side value (runtime.hostBehaviors.soloStageMetadata), inferred/
1538
1529
  // best-effort.
1539
- const soloStage = _hostBehaviors('trae').soloStageMetadata;
1530
+ const soloStage = hostBehaviorsFor('trae').soloStageMetadata;
1540
1531
  if (soloStage)
1541
1532
  fm += `stage: ${soloStage}\n`;
1542
1533
  fm += '---';
@@ -1670,6 +1661,8 @@ function convertClaudeCommandToClineSkill(content, skillName, _runtime = null, c
1670
1661
  if (!description)
1671
1662
  description = `Run GSD workflow ${skillName}.`;
1672
1663
  description = toSingleLine(description);
1664
+ // #4324: same reason as the Claude skill converter above.
1665
+ description = transformContentToHyphen(description, names);
1673
1666
  // Cline documented max is 1024 code points (not UTF-16 code units).
1674
1667
  // Use Array.from to iterate by code point so that multibyte characters
1675
1668
  // (e.g. emoji, astral-plane chars) are never split, which would produce
@@ -1818,6 +1811,22 @@ Typed mapping (agent_type-capable schema only):
1818
1811
  never fabricate a manual worktree protocol — route through the negotiated
1819
1812
  isolation adapter, which still fails closed for hosts declaring \`none\` (#3360).
1820
1813
 
1814
+ Foreground handoffs:
1815
+ - spawn_agent is asynchronous. When the source Agent(...) or Task(...) declares
1816
+ run_in_background=false, call collaboration.wait_agent(timeout_ms=...) immediately after
1817
+ spawn and keep the parent turn active until that child returns a terminal result.
1818
+ - collaboration.wait_agent is a mailbox wakeup, NOT a completion oracle: "Wait completed"
1819
+ can mean only that a child sent an interim MESSAGE or status update. After every wakeup,
1820
+ inspect the named child's update/status. Only a FINAL_ANSWER or a terminal agent status
1821
+ (completed, failed, or cancelled) ends the foreground handoff.
1822
+ - On an interim MESSAGE or any non-terminal status, do not report an outcome, send a
1823
+ continuation, start parent work, or end the parent turn. Call collaboration.wait_agent
1824
+ again for the same child. If a terminal response is absent after an abnormal end, reconcile
1825
+ the workflow's durable artifacts before classifying the child.
1826
+ - This applies to one foreground child as well as fan-out. The child retains its workflow's
1827
+ own checkpoint loop; do not report an outcome or start any further parent work before its
1828
+ terminal result is available.
1829
+
1821
1830
  Generic-agent workaround (multi_agent_v1 schema — NO agent_type field):
1822
1831
  When only the generic \`multi_agent_v1\` schema is available, typed GSD agent dispatch
1823
1832
  (\`gsd-planner\`, \`gsd-executor\`, etc.) is NOT possible. This is a known Codex limitation
@@ -1845,6 +1854,9 @@ Spawn restriction:
1845
1854
  defaulting to inline execution.
1846
1855
 
1847
1856
  Parallel fan-out:
1857
+ - For each child, loop on collaboration.wait_agent(timeout_ms=...) until its own terminal
1858
+ result is observed. A mailbox update from one child never completes another child, and an
1859
+ interim MESSAGE never completes its sender.
1848
1860
  - Spawn multiple agents → collect agent IDs → \`collaboration.wait_agent(timeout_ms=...)\` for each to complete
1849
1861
  - Do NOT use \`functions.wait(cell_id=...)\` — that is an unrelated exec-cell tool, not the collaboration wait
1850
1862
 
@@ -1931,17 +1943,11 @@ function convertClaudeToOpencodeFrontmatter(content, { isAgent = false, modelOve
1931
1943
  convertedContent = convertedContent.replace(/subagent_type="general-purpose"/g, 'subagent_type="general"');
1932
1944
  // Runtime-neutral agent name replacement (#766)
1933
1945
  convertedContent = neutralizeAgentReferences(convertedContent, 'AGENTS.md');
1934
- // Check if content has frontmatter
1935
- if (!convertedContent.startsWith('---')) {
1936
- return convertedContent;
1937
- }
1938
- // Find the end of frontmatter
1939
- const endIndex = convertedContent.indexOf('---', 3);
1940
- if (endIndex === -1) {
1946
+ // The frontmatter block, as the one fence owner finds it (none → nothing to convert).
1947
+ const { frontmatter, body } = extractFrontmatterAndBody(convertedContent);
1948
+ if (frontmatter === null) {
1941
1949
  return convertedContent;
1942
1950
  }
1943
- const frontmatter = convertedContent.substring(3, endIndex).trim();
1944
- const body = convertedContent.substring(endIndex + 3);
1945
1951
  // Parse frontmatter line by line (simple YAML parsing)
1946
1952
  const lines = frontmatter.split('\n');
1947
1953
  const newLines = [];
@@ -2097,17 +2103,11 @@ function convertClaudeToKiloFrontmatter(content, { isAgent = false, modelOverrid
2097
2103
  convertedContent = convertedContent.replace(/subagent_type="general-purpose"/g, 'subagent_type="general"');
2098
2104
  // Runtime-neutral agent name replacement (#766)
2099
2105
  convertedContent = neutralizeAgentReferences(convertedContent, 'AGENTS.md');
2100
- // Check if content has frontmatter
2101
- if (!convertedContent.startsWith('---')) {
2106
+ // The frontmatter block, as the one fence owner finds it (none → nothing to convert).
2107
+ const { frontmatter, body } = extractFrontmatterAndBody(convertedContent);
2108
+ if (frontmatter === null) {
2102
2109
  return convertedContent;
2103
2110
  }
2104
- // Find the end of frontmatter
2105
- const endIndex = convertedContent.indexOf('---', 3);
2106
- if (endIndex === -1) {
2107
- return convertedContent;
2108
- }
2109
- const frontmatter = convertedContent.substring(3, endIndex).trim();
2110
- const body = convertedContent.substring(endIndex + 3);
2111
2111
  // Parse frontmatter line by line (simple YAML parsing)
2112
2112
  const lines = frontmatter.split('\n');
2113
2113
  const newLines = [];
@@ -2273,38 +2273,45 @@ const claudeToCopilotTools = {
2273
2273
  AskUserQuestion: 'ask_user',
2274
2274
  SlashCommand: 'skill',
2275
2275
  };
2276
- // Tool name mapping from Claude Code to Gemini CLI
2277
- // Gemini CLI uses snake_case built-in tool names
2278
- const claudeToGeminiTools = {
2279
- Read: 'read_file',
2276
+ // Tool name mapping from Claude Code to Antigravity
2277
+ // Antigravity uses Gemini's snake_case built-in tool names
2278
+ // #4705: values are Antigravity-NATIVE tool names per the documented subagent
2279
+ // contract (antigravity.google/docs/subagents: view_file, replace_file_content,
2280
+ // grep_search, run_command are the documented examples; the catalog is
2281
+ // non-exhaustive, so unmapped entries keep their best-known grant rather than
2282
+ // being dropped — dropping would silently remove a restriction). The old
2283
+ // values were Gemini CLI dialect names, which Antigravity's tool validation
2284
+ // does not document and may hang the subagent on.
2285
+ const claudeToAntigravityTools = {
2286
+ Read: 'view_file',
2280
2287
  Write: 'write_file',
2281
- Edit: 'replace',
2282
- Bash: 'run_shell_command',
2288
+ Edit: 'replace_file_content',
2289
+ Bash: 'run_command',
2283
2290
  Glob: 'glob',
2284
- Grep: 'search_file_content',
2291
+ Grep: 'grep_search',
2285
2292
  WebSearch: 'google_web_search',
2286
2293
  WebFetch: 'web_fetch',
2287
2294
  TodoWrite: 'write_todos',
2288
2295
  };
2289
2296
  /**
2290
- * Convert a Claude Code tool name to Gemini CLI format
2291
- * - Applies Claude→Gemini mapping (Read→read_file, Bash→run_shell_command, etc.)
2292
- * - Filters out MCP tools (mcp__*) — they are auto-discovered at runtime in Gemini
2293
- * - Filters out Task/Agent — agents are auto-registered as tools in Gemini
2294
- * @returns {string|null} Gemini tool name, or null if tool should be excluded
2297
+ * Convert a Claude Code tool name to Antigravity format
2298
+ * - Applies Claude→Antigravity mapping (Read→read_file, Bash→run_shell_command, etc.)
2299
+ * - Filters out MCP tools (mcp__*) — they are auto-discovered at runtime in Antigravity
2300
+ * - Filters out Task/Agent — agents are auto-registered as tools in Antigravity
2301
+ * @returns {string|null} Antigravity tool name, or null if tool should be excluded
2295
2302
  */
2296
- function convertGeminiToolName(claudeTool) {
2303
+ function convertAntigravityToolName(claudeTool) {
2297
2304
  // MCP tools: exclude — auto-discovered from mcpServers config at runtime
2298
2305
  if (claudeTool.startsWith('mcp__')) {
2299
2306
  return null;
2300
2307
  }
2301
2308
  // Task/Agent: exclude — agents are auto-registered as callable tools.
2302
- // AskUserQuestion: exclude — Gemini CLI does not expose an ask_user tool;
2303
- // emitting it causes frontmatter validation errors (#3362).
2304
- // Skill/SlashCommand: exclude — Gemini CLI has no 'skill' built-in tool;
2305
- // the lowercase fallback would emit an invalid 'skill'/'slashcommand' name
2306
- // that fails frontmatter validation (tools.N: Invalid tool name) and aborts
2307
- // the entire agent load (#1394).
2309
+ // AskUserQuestion: exclude — Antigravity (Gemini tool dialect) does not expose
2310
+ // an ask_user tool; emitting it causes frontmatter validation errors (#3362).
2311
+ // Skill/SlashCommand: exclude — Antigravity (Gemini tool dialect) has no 'skill'
2312
+ // built-in tool; the lowercase fallback would emit an invalid
2313
+ // 'skill'/'slashcommand' name that fails frontmatter validation
2314
+ // (tools.N: Invalid tool name) and aborts the entire agent load (#1394).
2308
2315
  if (claudeTool === 'Task' ||
2309
2316
  claudeTool === 'Agent' ||
2310
2317
  claudeTool === 'AskUserQuestion' ||
@@ -2314,8 +2321,8 @@ function convertGeminiToolName(claudeTool) {
2314
2321
  return null;
2315
2322
  }
2316
2323
  // Check for explicit mapping
2317
- if (claudeToGeminiTools[claudeTool]) {
2318
- return claudeToGeminiTools[claudeTool];
2324
+ if (claudeToAntigravityTools[claudeTool]) {
2325
+ return claudeToAntigravityTools[claudeTool];
2319
2326
  }
2320
2327
  // Default: lowercase
2321
2328
  return claudeTool.toLowerCase();
@@ -2371,7 +2378,7 @@ function convertClaudeAgentToCopilotAgent(content, isGlobal = false) {
2371
2378
  }
2372
2379
  /**
2373
2380
  * Convert a Claude agent (.md) to an Antigravity agent.
2374
- * Uses Gemini tool names since Antigravity runs on Gemini 3 backend.
2381
+ * Uses Antigravity's Gemini tool dialect since Antigravity runs on Gemini 3 backend.
2375
2382
  */
2376
2383
  function convertClaudeAgentToAntigravityAgent(content, isGlobal = false) {
2377
2384
  const converted = convertClaudeToAntigravityContent(content, isGlobal);
@@ -2382,11 +2389,17 @@ function convertClaudeAgentToAntigravityAgent(content, isGlobal = false) {
2382
2389
  const description = extractFrontmatterField(frontmatter, 'description') || '';
2383
2390
  const color = extractFrontmatterField(frontmatter, 'color');
2384
2391
  const toolsRaw = extractFrontmatterField(frontmatter, 'tools') || '';
2385
- // Map tools to Gemini equivalents (reuse existing convertGeminiToolName)
2392
+ // Map tools to Antigravity equivalents (reuse existing convertAntigravityToolName)
2386
2393
  const claudeTools = toolsRaw.split(',').map(t => t.trim()).filter(Boolean);
2387
- const mappedTools = claudeTools.map(t => convertGeminiToolName(t)).filter(Boolean);
2394
+ const mappedTools = claudeTools.map(t => convertAntigravityToolName(t)).filter(Boolean);
2388
2395
  // #2876: quote description for the same reason as the skill variant.
2389
- let fm = `---\nname: ${name}\ndescription: ${yamlQuote(description)}\ntools: ${mappedTools.join(', ')}\n`;
2396
+ // #4705: tools is a YAML SEQUENCE of native names (one `- name` item per
2397
+ // line), not a comma-separated scalar. Empty mapped set -> `tools: []` so an
2398
+ // agent whose every tool was filtered stays explicitly restricted.
2399
+ const toolsBlock = mappedTools.length > 0
2400
+ ? `tools:\n${mappedTools.map((t) => `- ${t}`).join('\n')}\n`
2401
+ : 'tools: []\n';
2402
+ let fm = `---\nname: ${name}\ndescription: ${yamlQuote(description)}\n${toolsBlock}`;
2390
2403
  if (color)
2391
2404
  fm += `color: ${color}\n`;
2392
2405
  fm += '---';
@@ -2489,7 +2502,7 @@ function convertClaudeAgentToTraeAgent(content) {
2489
2502
  * forms to avoid double-rewriting the same substring.
2490
2503
  */
2491
2504
  function convertClaudeAgentToQwenAgent(content) {
2492
- const _b = _hostBehaviors('qwen').brandingRewrites || {};
2505
+ const _b = hostBehaviorsFor('qwen').brandingRewrites || {};
2493
2506
  let converted = content;
2494
2507
  if (_b['CLAUDE.md'])
2495
2508
  converted = converted.replace(/CLAUDE\.md/g, _b['CLAUDE.md']);
@@ -2554,18 +2567,14 @@ function convertClaudeAgentToZcodeAgent(content) {
2554
2567
  // present. The unchanged scan below still preserves byte-identical content.
2555
2568
  if (!content.includes('mcp__') && !content.includes('\\'))
2556
2569
  return content;
2557
- const lines = content.split('\n');
2558
- if (lines[0] !== '---')
2570
+ // The block is the one the one fence owner finds; unterminated (or absent) → leave verbatim.
2571
+ // The rewrite below re-joins lines on '\n', so a CRLF block is left verbatim too rather than
2572
+ // given mixed line endings.
2573
+ const fence = (0, frontmatter_fence_cjs_1.locateFrontmatterFence)(content);
2574
+ if (!fence?.closed || fence.eol !== '\n')
2559
2575
  return content;
2560
- let fmEnd = -1;
2561
- for (let i = 1; i < lines.length; i++) {
2562
- if (lines[i] === '---') {
2563
- fmEnd = i;
2564
- break;
2565
- }
2566
- }
2567
- if (fmEnd === -1)
2568
- return content; // unterminated frontmatter — leave verbatim
2576
+ const lines = content.split('\n');
2577
+ const fmEnd = content.slice(0, fence.closingStart).split('\n').length - 1;
2569
2578
  const out = [];
2570
2579
  let changed = false;
2571
2580
  let i = 1;
@@ -2662,12 +2671,12 @@ function convertClaudeAgentToClineAgent(content) {
2662
2671
  * rewrites applied automatically. A runtime with no `brandingRewrites`
2663
2672
  * declared returns `content` unchanged (no rewrite table to apply).
2664
2673
  *
2665
- * Byte-identical to the inline loop's `else if (_hostBehaviors(runtime).brandingRewrites)`
2674
+ * Byte-identical to the inline loop's `else if (hostBehaviorsFor(runtime).brandingRewrites)`
2666
2675
  * branch, including plain (non-word-boundary) `.replace(/\bClaude Code\b/g, ...)`
2667
2676
  * semantics — J9.
2668
2677
  */
2669
2678
  function applyAgentBrandingRewrites(content, runtime) {
2670
- const _b = _hostBehaviors(runtime).brandingRewrites;
2679
+ const _b = hostBehaviorsFor(runtime).brandingRewrites;
2671
2680
  if (!_b)
2672
2681
  return content;
2673
2682
  let converted = content;
@@ -2769,15 +2778,53 @@ function convertClaudeCommandToKiloSkill(content, skillName) {
2769
2778
  // Relocated from bin/install.js (#1511). Behavior is byte-for-behavior identical
2770
2779
  // to the originals; the only change is the injected `attribution` 5th param in
2771
2780
  // _applyRuntimeRewrites (replacing the internal getCommitAttribution() call).
2781
+ /**
2782
+ * #4377: is the project-relative include style opted in?
2783
+ *
2784
+ * Dual-sourced exactly like `--portable-hooks`/`GSD_PORTABLE_HOOKS`:
2785
+ * `bin/install.js` sets the variable when the flag is passed, so the flag and
2786
+ * the environment cannot disagree, and every seam that computes a path prefix
2787
+ * (the installer copy path, the install engine, the two rewrite entry points,
2788
+ * the install plan, and `applySurface`) reads the same answer without six signatures having to
2789
+ * grow a parameter each and stay in sync.
2790
+ *
2791
+ * Opt-in, not the new default. Making relative the default would change every
2792
+ * existing single-checkout local install — which works today — to fix a
2793
+ * multi-worktree case those users do not have.
2794
+ *
2795
+ * @param env - Environment to read (injectable for tests).
2796
+ * @returns Whether local installs should emit project-relative includes.
2797
+ */
2798
+ function relativeIncludesEnabled(env = process.env) {
2799
+ return env.GSD_RELATIVE_INCLUDES === '1';
2800
+ }
2772
2801
  /**
2773
2802
  * Compute the path prefix for a runtime install.
2774
2803
  * Global installs under $HOME use $HOME/... form; others use the resolved target.
2775
2804
  * isOpencode excludes OpenCode (uses ~/.config/opencode which breaks $HOME shorthand).
2776
2805
  * isWindowsHost is not used today but reserved for future Windows-specific logic.
2777
2806
  *
2807
+ * #4377: a LOCAL install can instead emit a project-relative prefix, so a repo
2808
+ * worked from several git worktrees does not get every worktree's `@` includes
2809
+ * baked to whichever checkout happened to run the installer. Absolute is still
2810
+ * the default; `projectRelative` opts in.
2811
+ *
2812
+ * `localDirName` is the runtime's own `localConfigDir` descriptor value
2813
+ * (via `getDirName`), never a hardcoded literal — the same value the rewrite
2814
+ * engine already uses for its `./.claude/` -> `./<dir>/` substitutions, and
2815
+ * exactly what `resolveScope` joins onto the cwd to produce `resolvedTarget`
2816
+ * for a local install. Copilot and Antigravity have shipped this shape for
2817
+ * local installs since they were added, with hardcoded `.github/` and
2818
+ * `.agents/`; this is the same behavior, derived instead of written down.
2819
+ *
2820
+ * Fails safe to the absolute prefix: no opt-in, a global install, a missing
2821
+ * dir name, or the `configHome.kind === 'none'` sentinel all fall through. A
2822
+ * wrong-but-absolute include still resolves to a real file; a wrong relative
2823
+ * one silently resolves against whatever the reader's cwd happens to be.
2824
+ *
2778
2825
  * @private — exported as `_computePathPrefix` for tests.
2779
2826
  */
2780
- function computePathPrefix({ isGlobal, isOpencode, isWindowsHost: _isWindowsHost, resolvedTarget, homeDir }) {
2827
+ function computePathPrefix({ isGlobal, isOpencode, isWindowsHost: _isWindowsHost, resolvedTarget, homeDir, projectRelative = relativeIncludesEnabled(), localDirName, projectRoot = process.cwd(), projectRelativePath, }) {
2781
2828
  // #1615: normalize Windows backslashes to forward slashes. This prefix is
2782
2829
  // substituted into markdown @-references (e.g. Windsurf workflow files),
2783
2830
  // which use POSIX paths universally. Idempotent on POSIX (no backslashes).
@@ -2789,8 +2836,62 @@ function computePathPrefix({ isGlobal, isOpencode, isWindowsHost: _isWindowsHost
2789
2836
  if (isGlobal && posixTarget.startsWith(posixHome) && !isOpencode) {
2790
2837
  return '$HOME' + posixTarget.slice(posixHome.length) + '/';
2791
2838
  }
2839
+ if (!isGlobal && projectRelative) {
2840
+ // An explicit local config dir need not be the descriptor's conventional
2841
+ // `.claude`-style directory. Derive from the resolved target first; only
2842
+ // use the descriptor as the legacy fallback when no project root exists.
2843
+ const relative = projectRelativePrefix(projectRelativePath)
2844
+ || projectRelativePrefixFromProjectRoot(projectRoot, resolvedTarget)
2845
+ || projectRelativePrefix(localDirName);
2846
+ if (relative)
2847
+ return relative;
2848
+ }
2792
2849
  return `${posixTarget}/`;
2793
2850
  }
2851
+ /** Return a safe project-relative prefix for a resolved install target. */
2852
+ function projectRelativePrefixFromProjectRoot(projectRoot, resolvedTarget) {
2853
+ if (typeof projectRoot !== 'string' || typeof resolvedTarget !== 'string')
2854
+ return '';
2855
+ const relative = (0, shell_command_projection_cjs_1.posixNormalize)(node_path_1.default.relative(projectRoot, resolvedTarget));
2856
+ if (!relative || relative === '.' || relative === '..' || relative.startsWith('../') || node_path_1.default.isAbsolute(relative))
2857
+ return '';
2858
+ return projectRelativePrefix(relative);
2859
+ }
2860
+ /**
2861
+ * A runtime installed directly at the project root cannot use its descriptor
2862
+ * directory in a project-relative include: that directory was never created.
2863
+ */
2864
+ function localIncludeDirName(runtime) {
2865
+ return hostBehaviorsFor(runtime).localTargetIsProjectRoot === true ? undefined : getDirName(runtime);
2866
+ }
2867
+ /**
2868
+ * #4377: the project-relative prefix for a local install, or `''` when the
2869
+ * runtime cannot express one and the caller must fall back to absolute.
2870
+ *
2871
+ * Rejects the `configHome.kind === 'none'` sentinel (a runtime with no local
2872
+ * config dir at all — interpolating it would produce a literal
2873
+ * `(no-local-config-dir)/` path segment), anything absolute, and anything that
2874
+ * climbs out of the project with `..`. Trailing slashes are normalized so a
2875
+ * descriptor value written either way yields one prefix.
2876
+ *
2877
+ * @param localDirName - The runtime's `localConfigDir` descriptor value.
2878
+ * @returns A `dir/` prefix, or `''` to signal "use the absolute form".
2879
+ */
2880
+ function projectRelativePrefix(localDirName) {
2881
+ if (typeof localDirName !== 'string' || localDirName.length === 0)
2882
+ return '';
2883
+ if (localDirName === runtimeNamePolicy.NO_LOCAL_CONFIG_DIR_SENTINEL)
2884
+ return '';
2885
+ const normalized = (0, shell_command_projection_cjs_1.posixNormalize)(localDirName).replace(/\/+$/, '');
2886
+ if (!normalized || normalized.startsWith('/') || /^[A-Za-z]:/.test(normalized))
2887
+ return '';
2888
+ // Reject a climb in ANY segment, not only at the beginning. posixNormalize
2889
+ // deliberately normalizes separators rather than resolving path segments,
2890
+ // so `nested/../../outside` must be caught explicitly here.
2891
+ if (normalized.split('/').includes('..'))
2892
+ return '';
2893
+ return `${normalized}/`;
2894
+ }
2794
2895
  /**
2795
2896
  * Canonical list of every non-Claude runtime that gsd-core emits artifacts for.
2796
2897
  * DERIVED from the capability registry (ADR-1239 Phase B, #1679) — the registry's
@@ -2957,18 +3058,124 @@ function restoreClaudeGlobalAtRefTilde(content, pathPrefix) {
2957
3058
  *
2958
3059
  * @private — exported as `_applyRuntimeRewrites` for tests.
2959
3060
  */
3061
+ /**
3062
+ * #4377: is this prefix a project-relative one?
3063
+ *
3064
+ * Anything not rooted -- no leading `/`, no `$HOME`, no `~`, no drive letter.
3065
+ * Used only to decide whether the shell-default guard below needs to run, so
3066
+ * an absolute install is byte-for-byte untouched by any of this.
3067
+ *
3068
+ * @param pathPrefix - The computed prefix.
3069
+ * @returns Whether it resolves relative to something.
3070
+ */
3071
+ function isRelativePathPrefix(pathPrefix) {
3072
+ if (typeof pathPrefix !== 'string' || pathPrefix.length === 0)
3073
+ return false;
3074
+ return !(pathPrefix.startsWith('/')
3075
+ || pathPrefix.startsWith('$HOME')
3076
+ || pathPrefix.startsWith('~')
3077
+ || /^[A-Za-z]:/.test(pathPrefix));
3078
+ }
3079
+ /**
3080
+ * #4377: shield `${VAR:-default}` shell defaults from a RELATIVE path prefix.
3081
+ *
3082
+ * The runtime launcher snippet probes for gsd-tools through a chain of shell
3083
+ * defaults -- `${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/...`, one per
3084
+ * runtime. Those are shell word expansions, not markdown `@` includes, and
3085
+ * they are the one place where substituting a project-relative prefix makes
3086
+ * things WORSE rather than better: `$HOME/.claude` resolves the same from
3087
+ * anywhere, while a bare `.claude` resolves against whatever directory the
3088
+ * shell happens to be sitting in. Trading an include that points at the wrong
3089
+ * checkout for a path that points at nothing is not a fix.
3090
+ *
3091
+ * The issue asked for project-relative INCLUDES, and this keeps the change to
3092
+ * exactly that. The launcher already handles the multi-worktree case on its
3093
+ * own, and better -- it probes `$(git rev-parse --show-toplevel)/.claude/...`
3094
+ * first, so it finds the current worktree's copy long before it reaches these
3095
+ * defaults.
3096
+ *
3097
+ * The mask token is `@@GSD4377:<n>@@`. It has to survive every substitution in
3098
+ * the rewrite body untouched, so it deliberately contains no `.claude`, no
3099
+ * `~`, no `$HOME` and no path separator -- there is nothing in it for those
3100
+ * regexes to match. A collision would need that literal to already exist in
3101
+ * the shipped corpus, which is asserted against in the tests.
3102
+ *
3103
+ * Only runs when the prefix is relative, so an absolute install never sees
3104
+ * this transformation at all.
3105
+ *
3106
+ * @param content - The body being rewritten.
3107
+ * @param rewrite - The substitution pass to run on the unguarded remainder.
3108
+ * @returns The rewritten body, with every shell default restored verbatim.
3109
+ */
3110
+ function withShellDefaultsPreserved(content, rewrite) {
3111
+ const preserved = [];
3112
+ let masked = '';
3113
+ let copiedThrough = 0;
3114
+ let searchFrom = 0;
3115
+ // Scan balanced `${...}` expansions instead of stopping at the first `}`.
3116
+ // The launcher has nested defaults such as `${A:-${B:-$HOME/.x}}`; a
3117
+ // single `[^}]*` regex only recognizes a prefix of that expression and
3118
+ // makes preservation depend accidentally on where the rewritten text sits.
3119
+ while (searchFrom < content.length) {
3120
+ const start = content.indexOf('${', searchFrom);
3121
+ if (start === -1)
3122
+ break;
3123
+ const opener = /^\$\{[A-Za-z_][A-Za-z0-9_]*:-/.exec(content.slice(start));
3124
+ if (!opener) {
3125
+ searchFrom = start + 2;
3126
+ continue;
3127
+ }
3128
+ let depth = 1;
3129
+ let end = start + opener[0].length;
3130
+ while (end < content.length && depth > 0) {
3131
+ if (content.startsWith('${', end)) {
3132
+ depth += 1;
3133
+ end += 2;
3134
+ continue;
3135
+ }
3136
+ if (content[end] === '}')
3137
+ depth -= 1;
3138
+ end += 1;
3139
+ }
3140
+ if (depth !== 0) {
3141
+ searchFrom = start + 2;
3142
+ continue;
3143
+ }
3144
+ masked += content.slice(copiedThrough, start);
3145
+ preserved.push(content.slice(start, end));
3146
+ masked += `@@GSD4377:${preserved.length - 1}@@`;
3147
+ copiedThrough = end;
3148
+ searchFrom = end;
3149
+ }
3150
+ masked += content.slice(copiedThrough);
3151
+ return rewrite(masked).replace(/@@GSD4377:(\d+)@@/g, (_m, i) => preserved[Number(i)]);
3152
+ }
2960
3153
  function _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal = false, attribution = undefined) {
3154
+ // #4377: with a project-relative prefix, run the whole substitution body
3155
+ // with `${VAR:-default}` shell defaults masked out, so the launcher shim
3156
+ // keeps its absolute fallbacks. A no-op for the absolute (default) prefix.
3157
+ if (isRelativePathPrefix(pathPrefix)) {
3158
+ return withShellDefaultsPreserved(content, (masked) => _applyRuntimeRewritesInner(masked, runtime, pathPrefix, isGlobal, attribution));
3159
+ }
3160
+ return _applyRuntimeRewritesInner(content, runtime, pathPrefix, isGlobal, attribution);
3161
+ }
3162
+ function _applyRuntimeRewritesInner(content, runtime, pathPrefix, isGlobal = false, attribution = undefined) {
2961
3163
  const dirName = getDirName(runtime);
2962
3164
  const normalizedPathPrefix = pathPrefix.replace(/\/$/, '');
2963
3165
  content = filterRuntimeNotesForTarget(content, runtime);
2964
3166
  // #1521: stamp runtime identity + use_worktrees=false for every non-Claude runtime
2965
3167
  // before brand-specific path rewrites, so the replace operates on the pristine
2966
3168
  // source line and is idempotent regardless of subsequent path substitutions.
2967
- if (runtime !== 'claude') {
3169
+ // #5169: the descriptor declares which hosts skip the stamp
3170
+ // (`hostBehaviors.skipRuntimeDefaultsStamp`) and which rewrite profile applies
3171
+ // (`hostBehaviors.contentRewriteProfile`). The switch below is keyed by PROFILE
3172
+ // name, not runtime id, so two hosts that share a body share one case.
3173
+ const rewriteBehaviors = hostBehaviorsFor(runtime);
3174
+ if (!rewriteBehaviors.skipRuntimeDefaultsStamp) {
2968
3175
  content = _stampNonClaudeRuntimeDefaults(content, runtime);
2969
3176
  }
2970
- switch (runtime) {
2971
- case 'codex':
3177
+ switch (rewriteBehaviors.contentRewriteProfile) {
3178
+ case 'slash-own-tilde':
2972
3179
  content = content.replace(/~\/\.claude\//g, pathPrefix);
2973
3180
  content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
2974
3181
  content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
@@ -2976,7 +3183,7 @@ function _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal = false, a
2976
3183
  // #1515 stamp moved to _stampNonClaudeRuntimeDefaults (#1521 generalisation).
2977
3184
  content = processAttribution(content, attribution);
2978
3185
  break;
2979
- case 'cline':
3186
+ case 'slash-bare-own-tilde':
2980
3187
  content = content.replace(/~\/\.claude\//g, pathPrefix);
2981
3188
  content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
2982
3189
  content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
@@ -2988,7 +3195,7 @@ function _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal = false, a
2988
3195
  content = content.replace(/\$HOME\/\.cline\b/g, normalizedPathPrefix);
2989
3196
  content = processAttribution(content, attribution);
2990
3197
  break;
2991
- case 'cursor':
3198
+ case 'slash-lookahead-own-tilde':
2992
3199
  content = content.replace(/~\/\.claude\//g, pathPrefix);
2993
3200
  content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
2994
3201
  content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
@@ -2998,7 +3205,7 @@ function _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal = false, a
2998
3205
  content = content.replace(/~\/\.cursor\//g, pathPrefix);
2999
3206
  content = processAttribution(content, attribution);
3000
3207
  break;
3001
- case 'windsurf': {
3208
+ case 'slash-lookahead-nested-own-devin': {
3002
3209
  content = content.replace(/~\/\.claude\//g, pathPrefix);
3003
3210
  content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
3004
3211
  content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
@@ -3014,7 +3221,7 @@ function _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal = false, a
3014
3221
  content = processAttribution(content, attribution);
3015
3222
  break;
3016
3223
  }
3017
- case 'augment': {
3224
+ case 'slash-lookahead-own-derived': {
3018
3225
  content = content.replace(/~\/\.claude\//g, pathPrefix);
3019
3226
  content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
3020
3227
  content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
@@ -3032,7 +3239,7 @@ function _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal = false, a
3032
3239
  content = processAttribution(content, attribution);
3033
3240
  break;
3034
3241
  }
3035
- case 'trae':
3242
+ case 'slash-bare-own-derived':
3036
3243
  content = content.replace(/~\/\.claude\//g, pathPrefix);
3037
3244
  content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
3038
3245
  content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
@@ -3045,7 +3252,7 @@ function _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal = false, a
3045
3252
  content = content.replace(new RegExp('~/' + (0, pattern_cjs_1.escapeRegex)(dirName) + '/', 'g'), pathPrefix);
3046
3253
  content = processAttribution(content, attribution);
3047
3254
  break;
3048
- case 'codebuddy':
3255
+ case 'slash-bare-own-tilde-bare':
3049
3256
  content = content.replace(/~\/\.claude\//g, pathPrefix);
3050
3257
  content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
3051
3258
  content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
@@ -3058,13 +3265,14 @@ function _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal = false, a
3058
3265
  content = content.replace(/\$HOME\/\.codebuddy\b/g, normalizedPathPrefix);
3059
3266
  content = processAttribution(content, attribution);
3060
3267
  break;
3061
- case 'copilot':
3268
+ case 'attribution-only':
3062
3269
  content = processAttribution(content, attribution);
3063
3270
  break;
3064
- case 'antigravity':
3065
- content = processAttribution(content, attribution);
3066
- break;
3067
- case 'claude':
3271
+ // claude and zcode share this profile. #4002: ZCode is a Claude-Code-shaped
3272
+ // host (dot-home `.zcode`, `@~`-ref expansion, `~/.zcode/...` documented
3273
+ // paths) whose commands install with `converter: null` — this pass is their
3274
+ // only chance to receive runtime-correct paths, including the tilde restore.
3275
+ case 'slash-tilde-restore':
3068
3276
  content = content.replace(/~\/\.claude\//g, pathPrefix);
3069
3277
  content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
3070
3278
  content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
@@ -3078,12 +3286,15 @@ function _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal = false, a
3078
3286
  // brand VALUES (CLAUDE.md/Claude Code/.claude/ replacements) now read from
3079
3287
  // runtime.hostBehaviors.brandingRewrites instead of hardcoded literals.
3080
3288
  // EXACT regexes/order preserved — only the replacement values changed.
3081
- case 'qwen': {
3289
+ // qwen and hermes share this profile; their own dot-dir is derived from the
3290
+ // descriptor's `localConfigDir` (`getDirName`) rather than spelled per host.
3291
+ case 'branded-lookahead-own-derived': {
3082
3292
  // Guarded (post-review #2092): brandingRewrites is undefined if the
3083
3293
  // capability registry fails to load — degrade closed (skip the
3084
3294
  // brand-literal replacements, still apply the non-branding path
3085
3295
  // rewrites below) instead of throwing on `_b['CLAUDE.md']`.
3086
- const _b = _hostBehaviors(runtime).brandingRewrites;
3296
+ const _b = rewriteBehaviors.brandingRewrites;
3297
+ const _own = (0, pattern_cjs_1.escapeRegex)(dirName);
3087
3298
  if (_b) {
3088
3299
  content = content.replace(/CLAUDE\.md/g, _b['CLAUDE.md']);
3089
3300
  // #2284(b): skips <runtime_compatibility> comparison-table content (protected region).
@@ -3091,46 +3302,21 @@ function _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal = false, a
3091
3302
  }
3092
3303
  content = content.replace(/~\/\.claude\//g, pathPrefix);
3093
3304
  content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
3094
- content = content.replace(/~\/\.qwen\//g, pathPrefix);
3095
- content = content.replace(/\$HOME\/\.qwen\//g, pathPrefix);
3305
+ content = content.replace(new RegExp('~/' + _own + '/', 'g'), pathPrefix);
3306
+ content = content.replace(new RegExp('\\$HOME/' + _own + '/', 'g'), pathPrefix);
3096
3307
  content = content.replace(/~\/\.claude(?![\w-])/g, normalizedPathPrefix);
3097
3308
  content = content.replace(/\$HOME\/\.claude(?![\w-])/g, normalizedPathPrefix);
3098
- content = content.replace(/~\/\.qwen(?![\w-])/g, normalizedPathPrefix);
3099
- content = content.replace(/\$HOME\/\.qwen(?![\w-])/g, normalizedPathPrefix);
3309
+ content = content.replace(new RegExp('~/' + _own + '(?![\\w-])', 'g'), normalizedPathPrefix);
3310
+ content = content.replace(new RegExp('\\$HOME/' + _own + '(?![\\w-])', 'g'), normalizedPathPrefix);
3100
3311
  if (_b) {
3101
3312
  content = content.replace(/\.claude\//g, _b['.claude/']);
3102
3313
  }
3103
3314
  content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
3104
- content = content.replace(/\.\/\.qwen\//g, `./${dirName}/`);
3315
+ content = content.replace(new RegExp('\\./' + _own + '/', 'g'), `./${dirName}/`);
3105
3316
  content = processAttribution(content, attribution);
3106
3317
  break;
3107
3318
  }
3108
- case 'hermes': {
3109
- // Guarded (post-review #2092): see qwen case above — same degrade-closed
3110
- // rationale.
3111
- const _b = _hostBehaviors(runtime).brandingRewrites;
3112
- if (_b) {
3113
- content = content.replace(/CLAUDE\.md/g, _b['CLAUDE.md']);
3114
- // #2284(b): skips <runtime_compatibility> comparison-table content (protected region).
3115
- content = applyClaudeCodeBrandSwap(content, _b['Claude Code']);
3116
- }
3117
- content = content.replace(/~\/\.claude\//g, pathPrefix);
3118
- content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
3119
- content = content.replace(/~\/\.hermes\//g, pathPrefix);
3120
- content = content.replace(/\$HOME\/\.hermes\//g, pathPrefix);
3121
- content = content.replace(/~\/\.claude(?![\w-])/g, normalizedPathPrefix);
3122
- content = content.replace(/\$HOME\/\.claude(?![\w-])/g, normalizedPathPrefix);
3123
- content = content.replace(/~\/\.hermes(?![\w-])/g, normalizedPathPrefix);
3124
- content = content.replace(/\$HOME\/\.hermes(?![\w-])/g, normalizedPathPrefix);
3125
- if (_b) {
3126
- content = content.replace(/\.claude\//g, _b['.claude/']);
3127
- }
3128
- content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
3129
- content = content.replace(/\.\/\.hermes\//g, `./${dirName}/`);
3130
- content = processAttribution(content, attribution);
3131
- break;
3132
- }
3133
- case 'kimi':
3319
+ case 'slash-bare':
3134
3320
  content = content.replace(/~\/\.claude\//g, pathPrefix);
3135
3321
  content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
3136
3322
  content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
@@ -3139,22 +3325,8 @@ function _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal = false, a
3139
3325
  content = content.replace(/\.\/\.claude\b/g, `./${dirName}`);
3140
3326
  content = processAttribution(content, attribution);
3141
3327
  break;
3142
- case 'zcode':
3143
- // #4002: ZCode is a Claude-Code-shaped host (dot-home `.zcode`, `@~`-ref
3144
- // expansion, `~/.zcode/...` documented paths) whose commands install with
3145
- // `converter: null` — this pass is their only chance to receive
3146
- // runtime-correct paths. Same shape as `claude`, including the tilde
3147
- // restore: the tilde form is what ZCode expands and what its docs use.
3148
- // `${_GSD_RUNTIME_ROOT}/.claude/…` matches none of these regexes and
3149
- // survives as the project-local fallback, exactly as on every sibling.
3150
- content = content.replace(/~\/\.claude\//g, pathPrefix);
3151
- content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
3152
- content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
3153
- content = restoreClaudeGlobalAtRefTilde(content, pathPrefix);
3154
- content = processAttribution(content, attribution);
3155
- break;
3156
3328
  default:
3157
- // Unknown runtime — no rewrites (OpenCode/Kilo handled by their own install path).
3329
+ // No profile declared — no rewrites (OpenCode/Kilo handled by their own install path).
3158
3330
  break;
3159
3331
  }
3160
3332
  return content;
@@ -3220,7 +3392,7 @@ function applyRuntimeContentRewritesForCommandsInPlace(stagedDir, runtime, pathP
3220
3392
  content = _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal, attribution);
3221
3393
  // #2097 (ADR-1239): descriptor-driven — commandBodyConverter name comes
3222
3394
  // from runtime.hostBehaviors instead of a hardcoded runtime-name branch.
3223
- const _cmdConv = _hostBehaviors(runtime).commandBodyConverter;
3395
+ const _cmdConv = hostBehaviorsFor(runtime).commandBodyConverter;
3224
3396
  if (_cmdConv && COMMAND_BODY_CONVERTERS[_cmdConv]) {
3225
3397
  content = COMMAND_BODY_CONVERTERS[_cmdConv](content);
3226
3398
  }
@@ -3296,7 +3468,9 @@ function rewriteStagedSkillBodies(stagedDir, opts) {
3296
3468
  const isGlobal = (0, install_scope_cjs_1.isGlobalScope)(scope);
3297
3469
  const isOpencode = false; // #2087: opencode installs via the combined-family engine path, never through the generic rewrite
3298
3470
  const isWindowsHost = platform === 'win32';
3299
- const pathPrefix = computePathPrefix({ isGlobal, isOpencode, isWindowsHost, resolvedTarget, homeDir });
3471
+ // #4377: localDirName lets a local install emit a project-relative prefix
3472
+ // when opted in; ignored for a global install and when the opt-in is off.
3473
+ const pathPrefix = computePathPrefix({ isGlobal, isOpencode, isWindowsHost, resolvedTarget, homeDir, localDirName: localIncludeDirName(runtime) });
3300
3474
  const attribution = resolveAttribution ? resolveAttribution(runtime) : undefined;
3301
3475
  applyRuntimeContentRewritesInPlace(stagedDir, runtime, pathPrefix, isGlobal, attribution);
3302
3476
  // #2873 (4b): claude, global scope only — see
@@ -3307,7 +3481,7 @@ function rewriteStagedSkillBodies(stagedDir, opts) {
3307
3481
  // no skills-kind entry at local scope, so this is already structurally
3308
3482
  // scoped to global (row 23) — the explicit isGlobal check is defense-in-depth
3309
3483
  // against that descriptor wiring ever changing.
3310
- if (runtime === 'claude' && isGlobal) {
3484
+ if (hostBehaviorsFor(runtime).specRootSkillPass && isGlobal) {
3311
3485
  applySpecRootReferenceToStagedSkills(stagedDir);
3312
3486
  }
3313
3487
  }
@@ -3339,7 +3513,9 @@ function rewriteStagedCommandBodies(stagedDir, opts) {
3339
3513
  const isGlobal = (0, install_scope_cjs_1.isGlobalScope)(scope);
3340
3514
  const isOpencode = false; // #2087: opencode installs via the combined-family engine path, never through the generic rewrite
3341
3515
  const isWindowsHost = platform === 'win32';
3342
- const pathPrefix = computePathPrefix({ isGlobal, isOpencode, isWindowsHost, resolvedTarget, homeDir });
3516
+ // #4377: localDirName lets a local install emit a project-relative prefix
3517
+ // when opted in; ignored for a global install and when the opt-in is off.
3518
+ const pathPrefix = computePathPrefix({ isGlobal, isOpencode, isWindowsHost, resolvedTarget, homeDir, localDirName: localIncludeDirName(runtime) });
3343
3519
  const attribution = resolveAttribution ? resolveAttribution(runtime) : undefined;
3344
3520
  return applyRuntimeContentRewritesForCommandsInPlace(stagedDir, runtime, pathPrefix, isGlobal, attribution);
3345
3521
  }
@@ -3357,7 +3533,7 @@ function rewriteStagedCommandBodies(stagedDir, opts) {
3357
3533
  * @param cmdNames gsd command names from readGsdCommandNames()
3358
3534
  */
3359
3535
  function normalizeAgentBodyForRuntime(content, runtime, cmdNames) {
3360
- if (_hostBehaviors(runtime).hyphenNameAgentBody !== true)
3536
+ if (hostBehaviorsFor(runtime).hyphenNameAgentBody !== true)
3361
3537
  return content;
3362
3538
  return transformContentToHyphen(content, cmdNames);
3363
3539
  }
@@ -3387,8 +3563,20 @@ function normalizeAgentBodyForRuntime(content, runtime, cmdNames) {
3387
3563
  * @returns content with path-prefix rewrites applied (or unchanged for noPathRewrite runtimes, e.g. copilot)
3388
3564
  */
3389
3565
  function applyAgentPathRewrites(content, runtime, pathPrefix) {
3390
- if (_hostBehaviors(runtime).noPathRewrite === true)
3566
+ if (hostBehaviorsFor(runtime).noPathRewrite === true)
3391
3567
  return content;
3568
+ // #4377: the agents pipeline is the third emit path that substitutes this
3569
+ // prefix (skills/commands via _applyRuntimeRewrites, the gsd-core spec tree
3570
+ // via copyWithPathReplacement, and here). All three carry the same guard:
3571
+ // with a project-relative prefix, `${VAR:-default}` shell defaults are
3572
+ // masked out so the runtime launcher keeps its absolute fallbacks. A no-op
3573
+ // for the absolute prefix. See withShellDefaultsPreserved for why.
3574
+ if (isRelativePathPrefix(pathPrefix)) {
3575
+ return withShellDefaultsPreserved(content, (masked) => applyAgentPathRewritesInner(masked, runtime, pathPrefix));
3576
+ }
3577
+ return applyAgentPathRewritesInner(content, runtime, pathPrefix);
3578
+ }
3579
+ function applyAgentPathRewritesInner(content, runtime, pathPrefix) {
3392
3580
  const normalizedPathPrefix = pathPrefix.replace(/\/$/, '');
3393
3581
  content = content.replace(/~\/\.claude\//g, pathPrefix);
3394
3582
  content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
@@ -3417,7 +3605,7 @@ function applyAgentPathRewrites(content, runtime, pathPrefix) {
3417
3605
  // anchored to the exact prefix string it is handed — so `restore(pathPrefix)`
3418
3606
  // fixes `@$HOME/.claude/x` and leaves a bare `@$HOME/.claude` broken. The
3419
3607
  // normalized form is a PREFIX of both, so one call covers both.
3420
- if (runtime === 'claude') {
3608
+ if (hostBehaviorsFor(runtime).restoreAtRefTildeInAgents) {
3421
3609
  content = restoreClaudeGlobalAtRefTilde(content, normalizedPathPrefix);
3422
3610
  }
3423
3611
  return content;
@@ -3441,8 +3629,8 @@ function deriveAgentName(fileName) {
3441
3629
  * `applyAgentFrontmatterExtensions` below for the orchestration that calls it.
3442
3630
  *
3443
3631
  * The function:
3444
- * - Detects the file's EOL (CRLF if the first `---` line ends with \r\n,
3445
- * otherwise LF).
3632
+ * - Reads the block the one fence owner (`locateFrontmatterFence`) finds, and
3633
+ * its EOL (CRLF if the opening `---` line ends with \r\n, otherwise LF).
3446
3634
  * - Skips injection if an `effort:` key already exists in the frontmatter
3447
3635
  * (idempotent).
3448
3636
  * - Inserts `effort: <value>` immediately before the closing `---` delimiter,
@@ -3451,19 +3639,15 @@ function deriveAgentName(fileName) {
3451
3639
  * - Returns the original content unchanged when no YAML frontmatter is found.
3452
3640
  */
3453
3641
  function injectEffortFrontmatter(content, effortValue) {
3454
- const eol = /^---\r\n/.test(content) ? '\r\n' : '\n';
3455
- const fmRe = /^---\r?\n([\s\S]*?)^---\r?$/m;
3456
- const match = fmRe.exec(content);
3457
- if (!match)
3642
+ const fence = (0, frontmatter_fence_cjs_1.locateFrontmatterFence)(content);
3643
+ if (!fence?.closed)
3458
3644
  return content; // no YAML frontmatter — leave unchanged
3459
- const fmBody = match[1]; // content between the two `---` lines
3645
+ const fmBody = content.slice(fence.openEnd, fence.closingStart); // content between the two fences
3460
3646
  if (/^effort:/m.test(fmBody))
3461
3647
  return content;
3462
- const openLen = 3 + eol.length; // "---" + eol
3463
- const closingStart = match.index + openLen + fmBody.length;
3464
- const before = content.slice(0, closingStart);
3465
- const after = content.slice(closingStart);
3466
- return `${before}effort: ${effortValue}${eol}${after}`;
3648
+ const before = content.slice(0, fence.closingStart);
3649
+ const after = content.slice(fence.closingStart);
3650
+ return `${before}effort: ${effortValue}${fence.eol}${after}`;
3467
3651
  }
3468
3652
  /**
3469
3653
  * #767 — Inject `disallowedTools: <value>` into the YAML frontmatter of a
@@ -3473,19 +3657,15 @@ function injectEffortFrontmatter(content, effortValue) {
3473
3657
  * frontmatter keys. Relocated verbatim from bin/install.js (#2875 Part 2).
3474
3658
  */
3475
3659
  function injectDisallowedToolsFrontmatter(content, disallowedValue) {
3476
- const eol = /^---\r\n/.test(content) ? '\r\n' : '\n';
3477
- const fmRe = /^---\r?\n([\s\S]*?)^---\r?$/m;
3478
- const match = fmRe.exec(content);
3479
- if (!match)
3660
+ const fence = (0, frontmatter_fence_cjs_1.locateFrontmatterFence)(content);
3661
+ if (!fence?.closed)
3480
3662
  return content; // no YAML frontmatter — leave unchanged
3481
- const fmBody = match[1]; // content between the two `---` lines
3663
+ const fmBody = content.slice(fence.openEnd, fence.closingStart); // content between the two fences
3482
3664
  if (/^disallowedTools:/m.test(fmBody))
3483
3665
  return content;
3484
- const openLen = 3 + eol.length; // "---" + eol
3485
- const closingStart = match.index + openLen + fmBody.length;
3486
- const before = content.slice(0, closingStart);
3487
- const after = content.slice(closingStart);
3488
- return `${before}disallowedTools: ${disallowedValue}${eol}${after}`;
3666
+ const before = content.slice(0, fence.closingStart);
3667
+ const after = content.slice(fence.closingStart);
3668
+ return `${before}disallowedTools: ${disallowedValue}${fence.eol}${after}`;
3489
3669
  }
3490
3670
  // #767 — Read-only verifier/auditor agents get a Claude-Code disallowedTools deny-list.
3491
3671
  // Group A (pure read-only) deny Write,Edit,MultiEdit. Group B report-writers Write one
@@ -3511,7 +3691,7 @@ const READONLY_AGENT_DISALLOWED_TOOLS = {
3511
3691
  * OpenCode/Qwen/Hermes reject unknown frontmatter keys.
3512
3692
  *
3513
3693
  * Byte-identical to the inline agent loop's
3514
- * `if ((_hostBehaviors(runtime).agentFrontmatterExtensions || []).includes('effort'))`
3694
+ * `if ((hostBehaviorsFor(runtime).agentFrontmatterExtensions || []).includes('effort'))`
3515
3695
  * block (bin/install.js): both the effort injection AND the disallowedTools
3516
3696
  * injection are gated behind the SAME `'effort'` extension flag — there is no
3517
3697
  * separate `'disallowedTools'` extension key, mirroring the loop exactly.
@@ -3526,7 +3706,7 @@ const READONLY_AGENT_DISALLOWED_TOOLS = {
3526
3706
  * @param targetDir install root — resolves .planning/config.json + ~/.gsd/defaults.json
3527
3707
  */
3528
3708
  function applyAgentFrontmatterExtensions(content, { runtime, agentName, targetDir }) {
3529
- const extensions = _hostBehaviors(runtime).agentFrontmatterExtensions || [];
3709
+ const extensions = hostBehaviorsFor(runtime).agentFrontmatterExtensions || [];
3530
3710
  if (!extensions.includes('effort'))
3531
3711
  return content;
3532
3712
  let result = content;
@@ -3645,8 +3825,8 @@ module.exports = {
3645
3825
  // #1182: agent converters + tool-name table dependency closure
3646
3826
  claudeToCopilotTools,
3647
3827
  convertCopilotToolName,
3648
- claudeToGeminiTools,
3649
- convertGeminiToolName,
3828
+ claudeToAntigravityTools,
3829
+ convertAntigravityToolName,
3650
3830
  convertClaudeAgentToCopilotAgent,
3651
3831
  convertClaudeAgentToAntigravityAgent,
3652
3832
  convertClaudeAgentToCursorAgent,
@@ -3690,6 +3870,12 @@ module.exports = {
3690
3870
  READONLY_AGENT_DISALLOWED_TOOLS,
3691
3871
  applyAgentFrontmatterExtensions,
3692
3872
  _computePathPrefix: computePathPrefix,
3873
+ _withShellDefaultsPreserved: withShellDefaultsPreserved,
3874
+ _isRelativePathPrefix: isRelativePathPrefix,
3875
+ _relativeIncludesEnabled: relativeIncludesEnabled,
3876
+ _projectRelativePrefix: projectRelativePrefix,
3877
+ _projectRelativePrefixFromProjectRoot: projectRelativePrefixFromProjectRoot,
3878
+ _localIncludeDirName: localIncludeDirName,
3693
3879
  _restoreClaudeGlobalAtRefTilde: restoreClaudeGlobalAtRefTilde,
3694
3880
  _applyRuntimeRewrites,
3695
3881
  _stampNonClaudeRuntimeDefaults,