@opengsd/gsd-core 1.10.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 (544) 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 +12 -1
  5. package/agents/gsd-debugger.md +1 -1
  6. package/agents/gsd-doc-synthesizer.md +2 -4
  7. package/agents/gsd-dom-verifier.md +169 -0
  8. package/agents/gsd-eval-auditor.md +1 -1
  9. package/agents/gsd-executor.md +22 -14
  10. package/agents/gsd-framework-selector.md +1 -3
  11. package/agents/gsd-intel-updater.md +1 -1
  12. package/agents/gsd-mempalace-curator.md +5 -3
  13. package/agents/gsd-pattern-mapper.md +11 -0
  14. package/agents/gsd-phase-researcher.md +23 -2
  15. package/agents/gsd-plan-checker.md +50 -53
  16. package/agents/gsd-planner.md +50 -50
  17. package/agents/gsd-project-researcher.md +1 -1
  18. package/agents/gsd-research-synthesizer.md +2 -2
  19. package/agents/gsd-roadmapper.md +15 -11
  20. package/agents/gsd-ui-checker.md +63 -4
  21. package/agents/gsd-ui-researcher.md +41 -3
  22. package/agents/gsd-user-profiler.md +3 -0
  23. package/agents/gsd-verifier.md +13 -4
  24. package/bin/install.js +1448 -1103
  25. package/commands/gsd/code-review.md +1 -1
  26. package/commands/gsd/discuss-phase.md +1 -1
  27. package/commands/gsd/execute-phase.md +1 -1
  28. package/commands/gsd/import.md +1 -1
  29. package/commands/gsd/map-codebase.md +1 -1
  30. package/commands/gsd/mempalace-capture.md +1 -1
  31. package/commands/gsd/mempalace-recall.md +1 -1
  32. package/commands/gsd/new-milestone.md +1 -1
  33. package/commands/gsd/quick.md +9 -5
  34. package/commands/gsd/review-backlog.md +2 -1
  35. package/commands/gsd/verify-work.md +1 -1
  36. package/gsd-core/bin/gsd-tools.cjs +1035 -138
  37. package/gsd-core/bin/lib/active-workstream-store.cjs +146 -22
  38. package/gsd-core/bin/lib/adr-parser.cjs +13 -7
  39. package/gsd-core/bin/lib/agent-install-check.cjs +392 -32
  40. package/gsd-core/bin/lib/api-coverage.cjs +33 -14
  41. package/gsd-core/bin/lib/artifacts.cjs +5 -0
  42. package/gsd-core/bin/lib/assumption-delta.cjs +32 -15
  43. package/gsd-core/bin/lib/audit-command-router.cjs +9 -2
  44. package/gsd-core/bin/lib/audit.cjs +1026 -268
  45. package/gsd-core/bin/lib/broken-windows.cjs +306 -28
  46. package/gsd-core/bin/lib/capability-consent.cjs +149 -15
  47. package/gsd-core/bin/lib/capability-lifecycle.cjs +45 -0
  48. package/gsd-core/bin/lib/capability-lock.cjs +10 -4
  49. package/gsd-core/bin/lib/capability-registry.cjs +845 -130
  50. package/gsd-core/bin/lib/capability-source.cjs +92 -0
  51. package/gsd-core/bin/lib/capability-state.cjs +18 -3
  52. package/gsd-core/bin/lib/capability-trust.cjs +444 -25
  53. package/gsd-core/bin/lib/capability-validator.cjs +700 -40
  54. package/gsd-core/bin/lib/capability-writer.cjs +3 -2
  55. package/gsd-core/bin/lib/check-command-router.cjs +216 -42
  56. package/gsd-core/bin/lib/claude-orchestration.cjs +56 -3
  57. package/gsd-core/bin/lib/cli-exit.cjs +496 -10
  58. package/gsd-core/bin/lib/code-review-depth.cjs +288 -0
  59. package/gsd-core/bin/lib/codex-agent-toml.cjs +735 -0
  60. package/gsd-core/bin/lib/command-aliases.cjs +22 -0
  61. package/gsd-core/bin/lib/command-arg-projection.cjs +144 -14
  62. package/gsd-core/bin/lib/command-roster.cjs +44 -1
  63. package/gsd-core/bin/lib/command-routing-hub.cjs +31 -2
  64. package/gsd-core/bin/lib/commands.cjs +1172 -108
  65. package/gsd-core/bin/lib/commonjs-marker.cjs +12 -6
  66. package/gsd-core/bin/lib/complexity-trigger.cjs +1192 -0
  67. package/gsd-core/bin/lib/config-loader.cjs +187 -23
  68. package/gsd-core/bin/lib/config.cjs +102 -3
  69. package/gsd-core/bin/lib/configuration.cjs +129 -37
  70. package/gsd-core/bin/lib/core-utils.cjs +208 -33
  71. package/gsd-core/bin/lib/decisions.cjs +23 -0
  72. package/gsd-core/bin/lib/edge-probe.cjs +9 -1
  73. package/gsd-core/bin/lib/estimate-cli.cjs +55 -11
  74. package/gsd-core/bin/lib/exit-code-registry.cjs +98 -0
  75. package/gsd-core/bin/lib/fallow-runner.cjs +20 -44
  76. package/gsd-core/bin/lib/frontmatter.cjs +899 -229
  77. package/gsd-core/bin/lib/gap-checker.cjs +95 -10
  78. package/gsd-core/bin/lib/git-base-branch.cjs +276 -39
  79. package/gsd-core/bin/lib/gsd2-import.cjs +10 -1
  80. package/gsd-core/bin/lib/health-diagnostic-rules/agent-install.cjs +101 -0
  81. package/gsd-core/bin/lib/health-diagnostic-rules/config-validation.cjs +348 -0
  82. package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +149 -0
  83. package/gsd-core/bin/lib/health-diagnostic-rules/install-surface-shadowing.cjs +98 -0
  84. package/gsd-core/bin/lib/health-diagnostic-rules/milestone-archive-hygiene.cjs +100 -0
  85. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +222 -0
  86. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +268 -0
  87. package/gsd-core/bin/lib/health-diagnostic-rules/root-existence.cjs +161 -0
  88. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +303 -0
  89. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +187 -0
  90. package/gsd-core/bin/lib/health-diagnostic-types.cjs +68 -0
  91. package/gsd-core/bin/lib/health-diagnostic.cjs +451 -0
  92. package/gsd-core/bin/lib/host-integration.cjs +39 -6
  93. package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
  94. package/gsd-core/bin/lib/init-command-router.cjs +118 -21
  95. package/gsd-core/bin/lib/init.cjs +439 -168
  96. package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
  97. package/gsd-core/bin/lib/install-engine.cjs +811 -259
  98. package/gsd-core/bin/lib/install-fs-adapter.cjs +262 -0
  99. package/gsd-core/bin/lib/install-model-override-resolver.cjs +235 -0
  100. package/gsd-core/bin/lib/install-profiles.cjs +212 -61
  101. package/gsd-core/bin/lib/install-scope.cjs +270 -0
  102. package/gsd-core/bin/lib/install-shadow-report.cjs +385 -0
  103. package/gsd-core/bin/lib/installed-surface-resolver.cjs +381 -0
  104. package/gsd-core/bin/lib/installer-migration-report.cjs +3 -0
  105. package/gsd-core/bin/lib/installer-migrations/010-antigravity-retire-confighome-artifacts.cjs +169 -0
  106. package/gsd-core/bin/lib/installer-migrations.cjs +148 -38
  107. package/gsd-core/bin/lib/intel.cjs +101 -26
  108. package/gsd-core/bin/lib/io.cjs +170 -15
  109. package/gsd-core/bin/lib/learnings.cjs +85 -14
  110. package/gsd-core/bin/lib/legacy-cleanup.cjs +8 -2
  111. package/gsd-core/bin/lib/markdown-sectionizer.cjs +2 -1
  112. package/gsd-core/bin/lib/markdown-table.cjs +183 -22
  113. package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
  114. package/gsd-core/bin/lib/milestone.cjs +842 -73
  115. package/gsd-core/bin/lib/model-catalog.cjs +232 -16
  116. package/gsd-core/bin/lib/model-resolver.cjs +193 -68
  117. package/gsd-core/bin/lib/normalize-test-command.cjs +1 -1
  118. package/gsd-core/bin/lib/onboard-projection.cjs +5 -1
  119. package/gsd-core/bin/lib/pattern.cjs +122 -0
  120. package/gsd-core/bin/lib/phase-estimation.cjs +18 -9
  121. package/gsd-core/bin/lib/phase-id.cjs +514 -40
  122. package/gsd-core/bin/lib/phase-lifecycle.cjs +52 -19
  123. package/gsd-core/bin/lib/phase-locator.cjs +262 -34
  124. package/gsd-core/bin/lib/phase.cjs +1038 -214
  125. package/gsd-core/bin/lib/plan-dependency-graph.cjs +72 -1
  126. package/gsd-core/bin/lib/plan-document.cjs +263 -0
  127. package/gsd-core/bin/lib/plan-drift-guard.cjs +120 -0
  128. package/gsd-core/bin/lib/plan-scan.cjs +98 -3
  129. package/gsd-core/bin/lib/planning-command-router.cjs +61 -0
  130. package/gsd-core/bin/lib/planning-inspect.cjs +1168 -0
  131. package/gsd-core/bin/lib/planning-scope.cjs +31 -0
  132. package/gsd-core/bin/lib/planning-snapshot.cjs +894 -0
  133. package/gsd-core/bin/lib/planning-workspace.cjs +112 -6
  134. package/gsd-core/bin/lib/probe-core.cjs +5 -2
  135. package/gsd-core/bin/lib/profile-output.cjs +1 -1
  136. package/gsd-core/bin/lib/profile-pipeline-command-router.cjs +50 -7
  137. package/gsd-core/bin/lib/profile-pipeline.cjs +6 -3
  138. package/gsd-core/bin/lib/real-home-guard.cjs +419 -0
  139. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +766 -0
  140. package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +11 -6
  141. package/gsd-core/bin/lib/review-lane-descriptor.cjs +22 -13
  142. package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
  143. package/gsd-core/bin/lib/review-lane-runner.cjs +421 -66
  144. package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
  145. package/gsd-core/bin/lib/roadmap-command-router.cjs +59 -11
  146. package/gsd-core/bin/lib/roadmap-parser.cjs +1006 -184
  147. package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
  148. package/gsd-core/bin/lib/roadmap.cjs +442 -96
  149. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +702 -52
  150. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
  151. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +459 -55
  152. package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
  153. package/gsd-core/bin/lib/runtime-homes.cjs +69 -3
  154. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +402 -58
  155. package/gsd-core/bin/lib/runtime-identity.cjs +234 -0
  156. package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
  157. package/gsd-core/bin/lib/runtime-slash.cjs +96 -8
  158. package/gsd-core/bin/lib/security.cjs +104 -5
  159. package/gsd-core/bin/lib/shell-command-projection.cjs +342 -7
  160. package/gsd-core/bin/lib/smart-entry.cjs +133 -23
  161. package/gsd-core/bin/lib/spec-section.cjs +12 -7
  162. package/gsd-core/bin/lib/state-command-router.cjs +52 -19
  163. package/gsd-core/bin/lib/state-contract.cjs +359 -0
  164. package/gsd-core/bin/lib/state-document.cjs +338 -8
  165. package/gsd-core/bin/lib/state-md-schema.cjs +221 -0
  166. package/gsd-core/bin/lib/state-transition.cjs +846 -176
  167. package/gsd-core/bin/lib/state.cjs +2589 -369
  168. package/gsd-core/bin/lib/surface.cjs +33 -11
  169. package/gsd-core/bin/lib/task-command-router.cjs +111 -1
  170. package/gsd-core/bin/lib/task-content-resolution.cjs +368 -0
  171. package/gsd-core/bin/lib/teams-status.cjs +4 -1
  172. package/gsd-core/bin/lib/text-lines.cjs +80 -0
  173. package/gsd-core/bin/lib/token-scanner.cjs +76 -0
  174. package/gsd-core/bin/lib/uat-predicate.cjs +67 -23
  175. package/gsd-core/bin/lib/uat.cjs +1761 -167
  176. package/gsd-core/bin/lib/ui-consideration-probe.cjs +9 -1
  177. package/gsd-core/bin/lib/ui-frontend-evidence.cjs +157 -0
  178. package/gsd-core/bin/lib/ui-safety-gate.cjs +51 -12
  179. package/gsd-core/bin/lib/unusable-input.cjs +37 -0
  180. package/gsd-core/bin/lib/update-context.cjs +8 -2
  181. package/gsd-core/bin/lib/user-artifact-staging.cjs +705 -0
  182. package/gsd-core/bin/lib/validate-command-router.cjs +2 -2
  183. package/gsd-core/bin/lib/validate.cjs +20 -6
  184. package/gsd-core/bin/lib/vendor/README.md +75 -0
  185. package/gsd-core/bin/lib/vendor/js-yaml.cjs +3014 -0
  186. package/gsd-core/bin/lib/vendor/re2js.cjs +6480 -0
  187. package/gsd-core/bin/lib/vendor/re2js.d.cts +938 -0
  188. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  189. package/gsd-core/bin/lib/verification.cjs +272 -9
  190. package/gsd-core/bin/lib/verify-command-grounding.cjs +846 -0
  191. package/gsd-core/bin/lib/verify.cjs +453 -918
  192. package/gsd-core/bin/lib/workstream-inventory-builder.cjs +53 -32
  193. package/gsd-core/bin/lib/workstream-inventory.cjs +63 -10
  194. package/gsd-core/bin/lib/workstream-name-policy.cjs +25 -4
  195. package/gsd-core/bin/lib/workstream.cjs +2 -2
  196. package/gsd-core/bin/lib/worktree-base-ref.cjs +66 -12
  197. package/gsd-core/bin/lib/worktree-safety.cjs +341 -18
  198. package/gsd-core/bin/shared/config-defaults.manifest.json +8 -1
  199. package/gsd-core/bin/shared/config-schema.manifest.json +12 -1
  200. package/gsd-core/bin/shared/exit-codes.json +8 -0
  201. package/gsd-core/bin/shared/exit-codes.sh +20 -0
  202. package/gsd-core/bin/shared/model-catalog.json +8 -1
  203. package/gsd-core/references/agent-contracts.md +44 -26
  204. package/gsd-core/references/api-coverage.md +24 -2
  205. package/gsd-core/references/autonomous-smart-discuss.md +3 -3
  206. package/gsd-core/references/checkpoints.md +39 -21
  207. package/gsd-core/references/context-budget.md +1 -1
  208. package/gsd-core/references/decimal-phase-calculation.md +5 -5
  209. package/gsd-core/references/dispatch-isolation-gate.md +138 -0
  210. package/gsd-core/references/doc-conflict-engine.md +1 -1
  211. package/gsd-core/references/edge-probe.md +8 -0
  212. package/gsd-core/references/execute-mvp-tdd.md +4 -6
  213. package/gsd-core/references/execute-phase-between-wave-reset.md +15 -14
  214. package/gsd-core/references/execute-phase-context-guard.md +1 -1
  215. package/gsd-core/references/execute-phase-response-language.md +1 -1
  216. package/gsd-core/references/execute-phase-wave-guard.md +17 -11
  217. package/gsd-core/references/failing-direction.md +78 -0
  218. package/gsd-core/references/gate-prompts.md +1 -1
  219. package/gsd-core/references/git-integration.md +5 -5
  220. package/gsd-core/references/git-planning-commit.md +5 -4
  221. package/gsd-core/references/gsd-run-resolver.md +1 -1
  222. package/gsd-core/references/loop-hook-dispatch.md +61 -2
  223. package/gsd-core/references/model-profiles.md +12 -4
  224. package/gsd-core/references/mvp-concepts.md +9 -9
  225. package/gsd-core/references/nyquist-compliance.md +74 -0
  226. package/gsd-core/references/offer-next.md +3 -5
  227. package/gsd-core/references/phase-argument-parsing.md +3 -3
  228. package/gsd-core/references/planner-failing-direction.md +53 -0
  229. package/gsd-core/references/planner-guidance.md +3 -9
  230. package/gsd-core/references/planner-human-verify-mode.md +15 -1
  231. package/gsd-core/references/planner-preconditions.md +1 -1
  232. package/gsd-core/references/planner-reviews.md +1 -1
  233. package/gsd-core/references/planner-revision.md +1 -1
  234. package/gsd-core/references/planner-verify-command-grounding.md +17 -0
  235. package/gsd-core/references/planning-config.md +44 -13
  236. package/gsd-core/references/reviewer-instances.md +31 -0
  237. package/gsd-core/references/revision-loop.md +1 -1
  238. package/gsd-core/references/runtime-aware-dispatch.md +1 -1
  239. package/gsd-core/references/specless-probe-fallback.md +1 -1
  240. package/gsd-core/references/tdd.md +1 -3
  241. package/gsd-core/references/ui-brand.md +65 -21
  242. package/gsd-core/references/ui-consideration-probe.md +1 -1
  243. package/gsd-core/references/universal-anti-patterns.md +5 -5
  244. package/gsd-core/references/verifier-phase-gates.md +192 -0
  245. package/gsd-core/references/verify-command-path-resolvability.md +42 -0
  246. package/gsd-core/references/verify-mvp-mode.md +2 -2
  247. package/gsd-core/references/workstream-flag.md +33 -17
  248. package/gsd-core/templates/README.md +1 -1
  249. package/gsd-core/templates/SECURITY.md +3 -3
  250. package/gsd-core/templates/UI-SPEC.md +25 -3
  251. package/gsd-core/templates/VALIDATION.md +3 -3
  252. package/gsd-core/templates/discussion-log.md +1 -1
  253. package/gsd-core/templates/phase-prompt.md +5 -4
  254. package/gsd-core/templates/state.md +11 -4
  255. package/gsd-core/templates/verification-report.md +9 -1
  256. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  257. package/gsd-core/workflows/add-backlog.md +1 -1
  258. package/gsd-core/workflows/add-phase.md +3 -3
  259. package/gsd-core/workflows/add-tests.md +3 -8
  260. package/gsd-core/workflows/add-todo.md +1 -1
  261. package/gsd-core/workflows/ai-integration-phase.md +13 -20
  262. package/gsd-core/workflows/audit-fix.md +12 -3
  263. package/gsd-core/workflows/audit-milestone.md +9 -9
  264. package/gsd-core/workflows/audit-uat.md +17 -2
  265. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +2 -2
  266. package/gsd-core/workflows/autonomous.md +11 -27
  267. package/gsd-core/workflows/check-todos.md +1 -1
  268. package/gsd-core/workflows/cleanup.md +64 -5
  269. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +14 -4
  270. package/gsd-core/workflows/code-review-fix.md +38 -11
  271. package/gsd-core/workflows/code-review.md +159 -52
  272. package/gsd-core/workflows/complete-milestone.md +151 -23
  273. package/gsd-core/workflows/debug.md +12 -8
  274. package/gsd-core/workflows/diagnose-issues.md +47 -15
  275. package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
  276. package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -8
  277. package/gsd-core/workflows/discuss-phase/modes/default.md +1 -1
  278. package/gsd-core/workflows/discuss-phase/modes/text.md +1 -1
  279. package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +1 -3
  280. package/gsd-core/workflows/discuss-phase-assumptions.md +4 -3
  281. package/gsd-core/workflows/discuss-phase.md +1 -1
  282. package/gsd-core/workflows/do.md +3 -6
  283. package/gsd-core/workflows/docs-update.md +5 -4
  284. package/gsd-core/workflows/edit-phase.md +27 -2
  285. package/gsd-core/workflows/eval-review.md +7 -14
  286. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
  287. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +142 -15
  288. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
  289. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
  290. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +77 -0
  291. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +24 -4
  292. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +2 -2
  293. package/gsd-core/workflows/execute-phase/steps/protected-branch.md +21 -0
  294. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -2
  295. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +39 -0
  296. package/gsd-core/workflows/execute-phase.md +72 -100
  297. package/gsd-core/workflows/execute-plan.md +52 -15
  298. package/gsd-core/workflows/explore.md +131 -4
  299. package/gsd-core/workflows/extract-learnings.md +1 -1
  300. package/gsd-core/workflows/fast.md +10 -2
  301. package/gsd-core/workflows/forensics.md +1 -1
  302. package/gsd-core/workflows/graduation.md +5 -5
  303. package/gsd-core/workflows/health.md +76 -10
  304. package/gsd-core/workflows/import.md +18 -15
  305. package/gsd-core/workflows/inbox.md +4 -5
  306. package/gsd-core/workflows/ingest-docs.md +49 -16
  307. package/gsd-core/workflows/insert-phase.md +5 -5
  308. package/gsd-core/workflows/list-seeds.md +5 -3
  309. package/gsd-core/workflows/list-workspaces.md +1 -1
  310. package/gsd-core/workflows/manager.md +12 -23
  311. package/gsd-core/workflows/map-codebase.md +1 -1
  312. package/gsd-core/workflows/milestone-summary.md +1 -1
  313. package/gsd-core/workflows/mvp-phase.md +8 -5
  314. package/gsd-core/workflows/new-milestone.md +22 -29
  315. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
  316. package/gsd-core/workflows/new-project.md +26 -40
  317. package/gsd-core/workflows/new-workspace.md +1 -1
  318. package/gsd-core/workflows/next.md +14 -2
  319. package/gsd-core/workflows/pause-work.md +1 -1
  320. package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +1 -1
  321. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +1 -1
  322. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -4
  323. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +3 -3
  324. package/gsd-core/workflows/plan-phase.md +162 -59
  325. package/gsd-core/workflows/plan-review-convergence.md +96 -11
  326. package/gsd-core/workflows/plant-seed.md +2 -2
  327. package/gsd-core/workflows/pr-branch.md +187 -51
  328. package/gsd-core/workflows/profile-user.md +16 -14
  329. package/gsd-core/workflows/progress.md +61 -18
  330. package/gsd-core/workflows/quick/steps/discussion-phase.md +1 -3
  331. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +5 -7
  332. package/gsd-core/workflows/quick/steps/quick-verification.md +28 -9
  333. package/gsd-core/workflows/quick/steps/research-phase.md +4 -6
  334. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +3 -3
  335. package/gsd-core/workflows/quick.md +55 -44
  336. package/gsd-core/workflows/remove-phase.md +4 -4
  337. package/gsd-core/workflows/remove-workspace.md +2 -2
  338. package/gsd-core/workflows/resume-project.md +8 -12
  339. package/gsd-core/workflows/review.md +219 -20
  340. package/gsd-core/workflows/scan.md +1 -1
  341. package/gsd-core/workflows/secure-phase.md +3 -3
  342. package/gsd-core/workflows/session-report.md +2 -1
  343. package/gsd-core/workflows/settings-advanced.md +7 -9
  344. package/gsd-core/workflows/settings-integrations.md +64 -31
  345. package/gsd-core/workflows/settings.md +69 -7
  346. package/gsd-core/workflows/ship.md +116 -50
  347. package/gsd-core/workflows/sketch-wrap-up.md +11 -17
  348. package/gsd-core/workflows/sketch.md +12 -18
  349. package/gsd-core/workflows/smart-entry.md +3 -5
  350. package/gsd-core/workflows/spec-phase.md +53 -13
  351. package/gsd-core/workflows/spike-wrap-up.md +7 -11
  352. package/gsd-core/workflows/spike.md +20 -31
  353. package/gsd-core/workflows/stats.md +2 -2
  354. package/gsd-core/workflows/sync-skills.md +64 -9
  355. package/gsd-core/workflows/thread.md +11 -7
  356. package/gsd-core/workflows/transition.md +49 -14
  357. package/gsd-core/workflows/ui-phase.md +15 -21
  358. package/gsd-core/workflows/ui-review.md +8 -12
  359. package/gsd-core/workflows/ultraplan-phase.md +5 -13
  360. package/gsd-core/workflows/undo.md +8 -16
  361. package/gsd-core/workflows/update.md +7 -11
  362. package/gsd-core/workflows/validate-phase.md +3 -3
  363. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +25 -1
  364. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
  365. package/gsd-core/workflows/verify-work.md +66 -25
  366. package/hooks/dist/gsd-agent-isolation-guard.js +158 -30
  367. package/hooks/dist/gsd-check-update-worker.js +56 -13
  368. package/hooks/dist/gsd-check-update.js +19 -1
  369. package/hooks/dist/gsd-config-reload.js +18 -12
  370. package/hooks/dist/gsd-context-monitor.js +19 -10
  371. package/hooks/dist/gsd-cursor-post-tool.js +3 -1
  372. package/hooks/dist/gsd-cursor-pre-tool.js +2 -3
  373. package/hooks/dist/gsd-cursor-session-start.js +2 -1
  374. package/hooks/dist/gsd-cursor-stop.js +2 -1
  375. package/hooks/dist/gsd-cursor-subagent-start.js +83 -3
  376. package/hooks/dist/gsd-cursor-subagent-stop.js +6 -3
  377. package/hooks/dist/gsd-ensure-canonical-path.js +2 -1
  378. package/hooks/dist/gsd-graphify-update.sh +22 -18
  379. package/hooks/dist/gsd-node-runner.sh +76 -0
  380. package/hooks/dist/gsd-phase-boundary.sh +1 -0
  381. package/hooks/dist/gsd-prompt-guard.js +37 -27
  382. package/hooks/dist/gsd-read-guard.js +16 -7
  383. package/hooks/dist/gsd-read-injection-scanner.js +55 -32
  384. package/hooks/dist/gsd-session-state.sh +1 -0
  385. package/hooks/dist/gsd-statusline.js +231 -24
  386. package/hooks/dist/gsd-update-banner.js +22 -1
  387. package/hooks/dist/gsd-validate-commit.sh +80 -6
  388. package/hooks/dist/gsd-windsurf-pre-command.js +16 -11
  389. package/hooks/dist/gsd-windsurf-pre-write.js +22 -13
  390. package/hooks/dist/gsd-workflow-guard.js +162 -46
  391. package/hooks/dist/gsd-worktree-path-guard.js +36 -21
  392. package/hooks/dist/gsd-write-guard.js +35 -25
  393. package/hooks/dist/lib/cli-exit.js +560 -0
  394. package/hooks/dist/lib/exit-code-registry.js +98 -0
  395. package/hooks/dist/lib/git-cmd.js +92 -59
  396. package/hooks/dist/lib/git-probe.js +84 -0
  397. package/hooks/dist/lib/hook-exit.js +81 -0
  398. package/hooks/dist/lib/injection-patterns.js +45 -0
  399. package/hooks/dist/lib/isolation-deny-reason.js +39 -0
  400. package/hooks/dist/lib/isolation-sentinel.js +9 -0
  401. package/hooks/dist/managed-hooks-registry.cjs +3 -0
  402. package/hooks/gsd-agent-isolation-guard.js +158 -30
  403. package/hooks/gsd-check-update-worker.js +56 -13
  404. package/hooks/gsd-check-update.js +19 -1
  405. package/hooks/gsd-config-reload.js +18 -12
  406. package/hooks/gsd-context-monitor.js +19 -10
  407. package/hooks/gsd-cursor-post-tool.js +3 -1
  408. package/hooks/gsd-cursor-pre-tool.js +2 -3
  409. package/hooks/gsd-cursor-session-start.js +2 -1
  410. package/hooks/gsd-cursor-stop.js +2 -1
  411. package/hooks/gsd-cursor-subagent-start.js +83 -3
  412. package/hooks/gsd-cursor-subagent-stop.js +6 -3
  413. package/hooks/gsd-ensure-canonical-path.js +2 -1
  414. package/hooks/gsd-graphify-update.sh +22 -18
  415. package/hooks/gsd-node-runner.sh +76 -0
  416. package/hooks/gsd-phase-boundary.sh +1 -0
  417. package/hooks/gsd-prompt-guard.js +37 -27
  418. package/hooks/gsd-read-guard.js +16 -7
  419. package/hooks/gsd-read-injection-scanner.js +55 -32
  420. package/hooks/gsd-session-state.sh +1 -0
  421. package/hooks/gsd-statusline.js +231 -24
  422. package/hooks/gsd-update-banner.js +22 -1
  423. package/hooks/gsd-validate-commit.sh +80 -6
  424. package/hooks/gsd-windsurf-pre-command.js +16 -11
  425. package/hooks/gsd-windsurf-pre-write.js +22 -13
  426. package/hooks/gsd-workflow-guard.js +162 -46
  427. package/hooks/gsd-worktree-path-guard.js +36 -21
  428. package/hooks/gsd-write-guard.js +35 -25
  429. package/hooks/lib/cli-exit.js +560 -0
  430. package/hooks/lib/exit-code-registry.js +98 -0
  431. package/hooks/lib/git-cmd.js +92 -59
  432. package/hooks/lib/git-probe.js +84 -0
  433. package/hooks/lib/hook-exit.js +81 -0
  434. package/hooks/lib/injection-patterns.js +45 -0
  435. package/hooks/lib/isolation-deny-reason.js +39 -0
  436. package/hooks/lib/isolation-sentinel.js +9 -0
  437. package/hooks/managed-hooks-registry.cjs +3 -0
  438. package/package.json +28 -11
  439. package/pi/gsd.cjs +19 -5
  440. package/scripts/base64-scan.sh +74 -12
  441. package/scripts/baselines/planning-prompt-drift-baseline.json +4 -0
  442. package/scripts/baselines/planning-snapshot-bypass-baseline.json +12 -0
  443. package/scripts/baselines/unreachable-guard-drift-baseline.json +4 -0
  444. package/scripts/build-hooks.js +5 -0
  445. package/scripts/changeset/lint.cjs +60 -5
  446. package/scripts/check-alias-drift.cjs +7 -43
  447. package/scripts/check-contract-drift.cjs +297 -0
  448. package/scripts/check-glossary-refs.cjs +77 -15
  449. package/scripts/check-mutation-score-ratchet.cjs +156 -0
  450. package/scripts/ci-check-job-near-cap.cjs +49 -0
  451. package/scripts/ci-pr-mergeability.cjs +262 -0
  452. package/scripts/ci-test-scope.cjs +64 -14
  453. package/scripts/ci-timeout-report.cjs +230 -0
  454. package/scripts/command-contract-helpers.cjs +903 -1
  455. package/scripts/docs-guard-registry.cjs +396 -0
  456. package/scripts/gen-adr-index.cjs +728 -38
  457. package/scripts/gen-capability-registry.cjs +11 -21
  458. package/scripts/gen-context-index.cjs +2 -11
  459. package/scripts/gen-exit-code-docs.cjs +318 -0
  460. package/scripts/gen-exit-code-registry.cjs +891 -0
  461. package/scripts/gen-features.cjs +836 -0
  462. package/scripts/gen-health-docs.cjs +390 -0
  463. package/scripts/gen-hooks-cli-exit.cjs +239 -0
  464. package/scripts/gen-install-tree-fixtures.cjs +2 -2
  465. package/scripts/gen-inventory-manifest.cjs +50 -4
  466. package/scripts/gen-loop-host-contract.cjs +138 -25
  467. package/scripts/gen-registry.cjs +3 -14
  468. package/scripts/gen-scripts-cli-exit.cjs +185 -0
  469. package/scripts/gen-state-md-docs.cjs +727 -0
  470. package/scripts/{test-failure-reasons.cjs → gsd-test-gate-reasons.cjs} +6 -0
  471. package/scripts/lib/alias-drift-families.cjs +46 -0
  472. package/scripts/lib/ci-job-timing.cjs +72 -0
  473. package/scripts/lib/cli-exit.cjs +546 -44
  474. package/scripts/lib/drift-scan.cjs +308 -0
  475. package/scripts/lib/exit-code-registry.cjs +98 -0
  476. package/scripts/lib/ndjson-reporter.cjs +119 -0
  477. package/scripts/lint-allow-test-rule-refs.allowlist.json +1 -26
  478. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +4 -0
  479. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +3 -0
  480. package/scripts/lint-canary-version-leak.cjs +73 -0
  481. package/scripts/lint-command-contract.cjs +96 -13
  482. package/scripts/lint-completion-predicate-drift.cjs +933 -0
  483. package/scripts/lint-completion-ratio-drift.cjs +214 -0
  484. package/scripts/lint-default-flip-documentation.cjs +193 -0
  485. package/scripts/lint-docs-guard-registration.cjs +495 -0
  486. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +193 -0
  487. package/scripts/lint-eslint-glob-coverage.allowlist.json +38 -0
  488. package/scripts/lint-eslint-glob-coverage.cjs +340 -0
  489. package/scripts/{lint-fix-has-regression-test.cjs → lint-fix-has-regression-tests.cjs} +12 -6
  490. package/scripts/lint-frontmatter-scalar-broad-grep.cjs +237 -0
  491. package/scripts/lint-health-diagnostic-rule-table.cjs +461 -0
  492. package/scripts/lint-hooks-runtime-build-seam.cjs +262 -0
  493. package/scripts/lint-milestone-window-drift.cjs +468 -0
  494. package/scripts/lint-mutation-test-derivation-drift.cjs +86 -0
  495. package/scripts/lint-phase-enumeration-drift.cjs +492 -0
  496. package/scripts/lint-plan-count-drift.cjs +318 -0
  497. package/scripts/lint-planning-artifact-writer-drift.cjs +398 -0
  498. package/scripts/lint-planning-prompt-drift.cjs +471 -0
  499. package/scripts/lint-planning-snapshot-bypass-drift.cjs +544 -0
  500. package/scripts/lint-regression-test-names.cjs +15 -13
  501. package/scripts/lint-removed-but-needed.cjs +488 -0
  502. package/scripts/lint-seam-enforcement.cjs +182 -0
  503. package/scripts/lint-slug-derivation-drift.cjs +921 -0
  504. package/scripts/lint-source-test-name-collision.cjs +241 -0
  505. package/scripts/lint-state-field-drift.cjs +805 -0
  506. package/scripts/lint-state-write-path-drift.cjs +950 -0
  507. package/scripts/lint-test-file-count.allowlist.json +137 -8
  508. package/scripts/lint-test-file-count.cjs +25 -3
  509. package/scripts/lint-unreachable-guard-drift.cjs +830 -0
  510. package/scripts/lint-vendored-deps.cjs +297 -0
  511. package/scripts/mutation-matrix.cjs +599 -50
  512. package/scripts/pr-changed-files.cjs +63 -0
  513. package/scripts/pr-template-policy.cjs +14 -4
  514. package/scripts/prompt-injection-scan.sh +100 -14
  515. package/scripts/require-issue-link-policy.cjs +192 -0
  516. package/scripts/secret-scan.sh +75 -13
  517. package/scripts/select-docs-guards.cjs +56 -0
  518. package/scripts/sync-runtime-launcher.cjs +24 -7
  519. package/skills/gsd-autonomous/SKILL.md +0 -1
  520. package/skills/gsd-code-review/SKILL.md +1 -1
  521. package/skills/gsd-discuss-phase/SKILL.md +1 -1
  522. package/skills/gsd-execute-phase/SKILL.md +1 -2
  523. package/skills/gsd-import/SKILL.md +1 -1
  524. package/skills/gsd-map-codebase/SKILL.md +1 -1
  525. package/skills/gsd-mempalace-capture/SKILL.md +1 -1
  526. package/skills/gsd-mempalace-recall/SKILL.md +1 -1
  527. package/skills/gsd-new-milestone/SKILL.md +1 -1
  528. package/skills/gsd-next/SKILL.md +0 -1
  529. package/skills/gsd-plan-phase/SKILL.md +0 -1
  530. package/skills/gsd-progress/SKILL.md +0 -1
  531. package/skills/gsd-quick/SKILL.md +9 -5
  532. package/skills/gsd-review-backlog/SKILL.md +2 -1
  533. package/skills/gsd-stats/SKILL.md +0 -1
  534. package/skills/gsd-verify-work/SKILL.md +1 -1
  535. package/vscode/package.json +1 -1
  536. package/bin/lib/ui-safety-gate.cjs +0 -107
  537. package/gsd-core/workflows/discovery-phase.md +0 -298
  538. package/gsd-core/workflows/plan-milestone-gaps.md +0 -281
  539. package/gsd-core/workflows/verify-phase.md +0 -574
  540. package/scripts/affected-tests-lib.cjs +0 -554
  541. package/scripts/lint-allow-test-rule-refs.cjs +0 -162
  542. package/scripts/lint-emitted-drift-ack.cjs +0 -344
  543. package/scripts/run-affected-tests.cjs +0 -7
  544. package/scripts/run-tests.cjs +0 -1051
@@ -21,12 +21,35 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
21
21
  const node_path_1 = __importDefault(require("node:path"));
22
22
  const node_os_1 = __importDefault(require("node:os"));
23
23
  const node_fs_1 = __importDefault(require("node:fs"));
24
+ // #2874 (ADR-58 cleanup phase): route this module's content-rewrite-pass fs
25
+ // calls through the installRuntimeArtifacts call tree's injectable seam —
26
+ // see install-fs-adapter.cts's module doc. Resolves to real `node:fs` unless
27
+ // the top-level installRuntimeArtifacts call injected a `deps.fs`. These
28
+ // walkers operate on already-staged temp directories (never the real GSD
29
+ // source tree or the real install destination directly), so routing them is
30
+ // unconditionally safe.
31
+ const installFsAdapter = require("./install-fs-adapter.cjs");
32
+ const { installFs, mkInstallTempDir } = installFsAdapter;
24
33
  const commandRoster = require("./command-roster.cjs");
25
34
  const { readGsdCommandNames, transformContentToHyphen } = commandRoster;
26
35
  const runtimeNamePolicy = require("./runtime-name-policy.cjs");
27
36
  const { getDirName } = runtimeNamePolicy;
28
37
  const capabilityRegistry = require("./capability-registry.cjs");
38
+ const hostIntegration = require("./host-integration.cjs");
29
39
  const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
40
+ const frontmatterModule = require("./frontmatter.cjs");
41
+ const pattern_cjs_1 = require("./pattern.cjs");
42
+ const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
43
+ // #2870: install-scope.cts is a leaf-tier sibling (imports only
44
+ // runtime-homes.cjs + node builtins, never this module) — no cycle. See the
45
+ // isGlobal sites below for why the boolean projection is centralized here too.
46
+ const install_scope_cjs_1 = require("./install-scope.cjs");
47
+ // #2875 Part 2: install-effort-resolver.cjs is a leaf-tier sibling (#2071) —
48
+ // used by applyAgentFrontmatterExtensions below to read the SAME merged
49
+ // effort config the install-time Claude .md injection has always read,
50
+ // without this module reaching upward into bin/install.js (ADR-1508).
51
+ const installEffortResolver = require("./install-effort-resolver.cjs");
52
+ const { readGsdEffectiveEffortConfig, resolveInstallTimeEffort, _getGsdEffortCatalog } = installEffortResolver;
30
53
  // #1383: resolve GSD's version WITHOUT a top-level
31
54
  // `require('../../../package.json')`. That require ran at module load on every
32
55
  // gsd-tools invocation (this module sits in the gsd-tools loader chain) and
@@ -270,11 +293,8 @@ function buildKiloAgentPermissionBlock(claudeTools) {
270
293
  }
271
294
  return lines;
272
295
  }
273
- function escapeRegExp(value) {
274
- return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
275
- }
276
296
  function replaceRelativePathReference(content, fromPath, toPath) {
277
- const escapedPath = escapeRegExp(fromPath);
297
+ const escapedPath = (0, pattern_cjs_1.escapeRegex)(fromPath);
278
298
  return content.replace(new RegExp(`(^|[^A-Za-z0-9_./-])${escapedPath}`, 'g'), (_, prefix) => `${prefix}${toPath}`);
279
299
  }
280
300
  /**
@@ -367,16 +387,6 @@ function skillFrontmatterName(skillDirName) {
367
387
  // Return the hyphen form as-is (gsd-<cmd>) — canonical since #2808.
368
388
  return skillDirName;
369
389
  }
370
- function normalizeClaudeSkillEffort(effort) {
371
- // #3039: `max` is rejected by Anthropic models when extended thinking is
372
- // disabled (400: output_config.effort 'max' is not supported when thinking
373
- // is disabled). The frontmatter is static at install time and the installer
374
- // cannot know whether thinking will be on or off at invocation. `high` is the
375
- // maximum value that works in both states on all supported models.
376
- if (effort === 'xhigh' || effort === 'max')
377
- return 'high';
378
- return effort;
379
- }
380
390
  /**
381
391
  * Qwen Code skills accept an optional numeric `priority` frontmatter field.
382
392
  * Per the Qwen skills spec (qwen-code/docs/users/features/skills.md, verified
@@ -431,10 +441,13 @@ function convertClaudeCommandToClaudeSkill(content, skillName, runtime = null, c
431
441
  const description = extractFrontmatterField(frontmatter, 'description') || '';
432
442
  const argumentHint = extractFrontmatterField(frontmatter, 'argument-hint');
433
443
  const agent = extractFrontmatterField(frontmatter, 'agent');
434
- // #769: preserve context: and effort: from source command files so they
435
- // are emitted into the installed SKILL.md frontmatter unchanged.
444
+ // #769: preserve context: from source command files so it is emitted into
445
+ // the installed SKILL.md frontmatter unchanged. (#3151: effort: is no longer
446
+ // emitted into skill frontmatter — a static effort value changes
447
+ // output_config.effort on invocation and invalidates the caller's prompt
448
+ // cache at both scope boundaries; the reporter's owned measurement confirms
449
+ // the mechanism. The separate agent-effort surface is tracked by #3160.)
436
450
  const context = extractFrontmatterField(frontmatter, 'context');
437
- const effort = extractFrontmatterField(frontmatter, 'effort');
438
451
  // Preserve allowed-tools as YAML multiline list (Claude native format)
439
452
  const toolsMatch = frontmatter.match(/^allowed-tools:\s*\n((?:\s+-\s+.+\n?)*)/m);
440
453
  let toolsBlock = '';
@@ -474,19 +487,100 @@ function convertClaudeCommandToClaudeSkill(content, skillName, runtime = null, c
474
487
  fm += `argument-hint: ${yamlQuote(argumentHint)}\n`;
475
488
  if (agent)
476
489
  fm += `agent: ${agent}\n`;
477
- // #769: emit context: and effort: when present so the runtime can honour
478
- // them natively (context: fork = isolated subagent window; effort: =
479
- // token-budget tier). Fields are Claude-specific; unknown frontmatter
480
- // fields are silently ignored by other runtimes (backward-compatible).
490
+ // #769: emit context: when present so the runtime can honour it natively
491
+ // (context: fork = isolated subagent window). Claude-specific; unknown
492
+ // frontmatter fields are silently ignored by other runtimes (backward-compatible).
493
+ // (#3151: effort: is intentionally NOT emitted into skill frontmatter — a
494
+ // static effort value changes output_config.effort on invocation and
495
+ // invalidates the caller's prompt cache at both scope boundaries.)
481
496
  if (context)
482
497
  fm += `context: ${context}\n`;
483
- if (effort)
484
- fm += `effort: ${normalizeClaudeSkillEffort(effort)}\n`;
485
498
  if (toolsBlock)
486
499
  fm += toolsBlock;
487
500
  fm += '---';
488
501
  return `${fm}\n${normalizedBody}`;
489
502
  }
503
+ // #2873 (4b) — spec-root reachability. Matches ONLY a line that is a real
504
+ // `@~/.claude/gsd-core/workflows/<stem>.md` include: line-start `@`, exact
505
+ // spec-root shape, nothing else on the line. This is deliberately narrower
506
+ // than "any line mentioning gsd-core/workflows" so prose mentions and
507
+ // `references/`/`templates/`/`@.planning/...` includes are never touched
508
+ // (rows 24/25). CRLF-safe: an optional trailing `\r` is captured and
509
+ // preserved rather than dropped.
510
+ const WORKFLOW_SPEC_ROOT_INCLUDE_RE = /^@~\/\.claude\/gsd-core\/workflows\/([A-Za-z0-9._-]+)\.md[ \t]*(\r?)$/gm;
511
+ /**
512
+ * Rewrite a static global-scope Claude skill `@`-include of the command's own
513
+ * workflow spec into an imperative two-step resolution the agent performs at
514
+ * runtime: prefer the project-local spec (cwd-relative), fall back to the
515
+ * global spec, and treat "neither exists" as a visible failure rather than a
516
+ * silent no-spec proceed.
517
+ *
518
+ * WHY this can't stay a static `@`-include (even a relative one): Claude Code
519
+ * documents relative `@`-paths as resolving against the file *containing* the
520
+ * import, which for a global skill is `~/.claude/skills/gsd-<stem>/` — not
521
+ * the project's working directory. `@./.claude/...` would therefore always
522
+ * resolve inside the skill's own install directory, never the project, so
523
+ * there is no static include syntax that can express "prefer local, fall
524
+ * back to global". This function exists precisely so that resolution can be
525
+ * performed by the agent, not the host's pre-expansion.
526
+ *
527
+ * Scope-free by design: this function does not know or care whether it is
528
+ * being applied to a global or local artifact, or which runtime — that
529
+ * judgment belongs to the caller (`skillsKind` in
530
+ * `runtime-artifact-layout.cts`, the one site that knows install scope).
531
+ * Applying it to a body with no workflow include is a no-op (row 26); a body
532
+ * with two independent workflow includes has each rewritten independently
533
+ * (row 27); an include inside a fenced code block or wrapped in inline
534
+ * backticks is left untouched (the backtick case is already excluded by the
535
+ * line-start anchor, since a backtick-wrapped line does not begin with `@`).
536
+ * Idempotent: the replacement text never begins with `@` and never matches
537
+ * `WORKFLOW_SPEC_ROOT_INCLUDE_RE`, so re-applying this function to its own
538
+ * output is a no-op.
539
+ *
540
+ * Fence detection reuses `scanFencedBlocks` (markdown-sectionizer.cts) — the
541
+ * same CommonMark-correct state machine `stripFencedCode`/`extractFencedBlock`
542
+ * are built on — instead of a hand-rolled "any delimiter line toggles
543
+ * open/closed" tracker. A naive toggle is wrong under CommonMark: a fence
544
+ * opened with ``` is NOT closed by a ~~~ line (closer must share the
545
+ * opener's delimiter character and have run length >= the opener's), so a
546
+ * mismatched delimiter is fence CONTENT, not a boundary. #2873 review.
547
+ */
548
+ function resolveSpecRootReference(body) {
549
+ if (typeof body !== 'string' || body.length === 0)
550
+ return body;
551
+ if (!body.includes('@~/.claude/gsd-core/workflows/'))
552
+ return body;
553
+ // Collect [start, end) character-offset ranges covered by fenced code
554
+ // blocks so matches inside them are skipped. An unterminated trailing
555
+ // fence covers to the end of the string (still "inside a fence").
556
+ const lines = body.split('\n');
557
+ const lineStartOffsets = [];
558
+ {
559
+ let offset = 0;
560
+ for (const line of lines) {
561
+ lineStartOffsets.push(offset);
562
+ offset += line.length + 1; // +1 for the '\n' separator
563
+ }
564
+ }
565
+ const fenceRanges = (0, markdown_sectionizer_cjs_1.scanFencedBlocks)(lines).map(({ openLineIdx, closeLineIdx }) => {
566
+ const start = lineStartOffsets[openLineIdx];
567
+ const end = closeLineIdx === -1
568
+ ? body.length
569
+ : lineStartOffsets[closeLineIdx] + lines[closeLineIdx].length;
570
+ return [start, end];
571
+ });
572
+ const isInsideFence = (offset) => fenceRanges.some(([start, end]) => offset >= start && offset < end);
573
+ return body.replace(WORKFLOW_SPEC_ROOT_INCLUDE_RE, (match, stem, cr, offset) => {
574
+ if (isInsideFence(offset))
575
+ return match;
576
+ return (`To load this command's workflow spec: check for ` +
577
+ `\`.claude/gsd-core/workflows/${stem}.md\` relative to the current working ` +
578
+ `directory first (project-local); if it is not there, fall back to ` +
579
+ `\`~/.claude/gsd-core/workflows/${stem}.md\` (the global install). If ` +
580
+ `neither file exists, stop — a workflow spec is required and none was found.` +
581
+ cr);
582
+ });
583
+ }
490
584
  function normalizeKimiSkillName(skillName) {
491
585
  let text = String(skillName || '').trim().toLowerCase();
492
586
  if (text.startsWith('/'))
@@ -502,7 +596,7 @@ function normalizeKimiSkillName(skillName) {
502
596
  function convertGsdCommandReferencesToKimiSkillInvocations(content, cmdNames) {
503
597
  if (!Array.isArray(cmdNames) || cmdNames.length === 0)
504
598
  return content;
505
- const commands = [...cmdNames].sort((a, b) => b.length - a.length).map(escapeRegExp);
599
+ const commands = [...cmdNames].sort((a, b) => b.length - a.length).map(pattern_cjs_1.escapeRegex);
506
600
  const commandGroup = commands.join('|');
507
601
  const colonPattern = new RegExp(`(?<![A-Za-z0-9_/:.-])/?gsd:(${commandGroup})(?=[^A-Za-z0-9_-]|$)`, 'g');
508
602
  const hyphenPattern = new RegExp(`(?:/|\\$)gsd-(${commandGroup})(?=[^A-Za-z0-9_-]|$)`, 'g');
@@ -754,7 +848,8 @@ function buildKimiAgentArtifacts({ rootAgent = '', subagents = [], requestedSuba
754
848
  /**
755
849
  * Apply Antigravity-specific content conversion — path replacement + command name conversion.
756
850
  * Path mappings depend on install mode:
757
- * Global: ~/.claude/ → ~/.gemini/antigravity/, ./.claude/ → ./.agents/
851
+ * Global: ~/.claude/skills/ → ~/.gemini/config/skills/ (#3738),
852
+ * ~/.claude/ → ~/.gemini/antigravity/, ./.claude/ → ./.agents/
758
853
  * Local: ~/.claude/ → .agents/, ./.claude/ → ./.agents/
759
854
  * Applied to ALL Antigravity content (skills, agents, engine files).
760
855
  * @param {string} content - Source content to convert
@@ -763,6 +858,18 @@ function buildKimiAgentArtifacts({ rootAgent = '', subagents = [], requestedSuba
763
858
  function convertClaudeToAntigravityContent(content, isGlobal = false) {
764
859
  let c = content;
765
860
  if (isGlobal) {
861
+ // #3738: global skills install under ~/.gemini/config/skills (the dir AGY
862
+ // scans for global discovery), so skills-path references must divert there
863
+ // — BEFORE the configHome rewrite below, which is correct for gsd-core
864
+ // runtime-file references (settings, workflows, VERSION) but wrong for the
865
+ // skills dir itself.
866
+ c = c.replace(/\$HOME\/\.claude\/skills\//g, '$HOME/.gemini/config/skills/');
867
+ c = c.replace(/~\/\.claude\/skills\//g, '~/.gemini/config/skills/');
868
+ // Bare skills form (no trailing slash) — must also precede the generic
869
+ // slash rule, which would otherwise divert it to the retired configHome
870
+ // path ($HOME/.gemini/antigravity/skills).
871
+ c = c.replace(/\$HOME\/\.claude\/skills\b/g, '$HOME/.gemini/config/skills');
872
+ c = c.replace(/~\/\.claude\/skills\b/g, '~/.gemini/config/skills');
766
873
  c = c.replace(/\$HOME\/\.claude\//g, '$HOME/.gemini/antigravity/');
767
874
  c = c.replace(/~\/\.claude\//g, '~/.gemini/antigravity/');
768
875
  // Bare form (no trailing slash) — must come after slash form to avoid double-replace
@@ -1533,7 +1640,7 @@ Typed mapping (agent_type-capable schema only):
1533
1640
  to \`spawn_agent\` when the runtime/tool supports it. Omit missing, empty,
1534
1641
  inherited, or unsupported values; do not invent one-off effort literals in
1535
1642
  workflow prose.
1536
- - \`fork_context: false\` by default — GSD agents load their own context via \`<files_to_read>\` blocks
1643
+ - \`fork_context: false\` by default — GSD agents load their own context via \`<required_reading>\` blocks
1537
1644
  - \`task_name\` — required by the collaboration schema; provide a descriptive name for each spawned task
1538
1645
  - \`fork_turns\` — optional parameter controlling turn-forking depth; coexists with \`fork_context\` (not a replacement)
1539
1646
  - \`Task(isolation="worktree")\` / \`Agent(isolation="worktree")\` → no direct \`spawn_agent\` mapping,
@@ -1609,7 +1716,26 @@ function neutralizeAgentReferences(content, instructionFile) {
1609
1716
  c = c.replace(/Do NOT load full `AGENTS\.md` files[^\n]*/g, '');
1610
1717
  return c;
1611
1718
  }
1612
- function convertClaudeToOpencodeFrontmatter(content, { isAgent = false, modelOverride = null } = {}) {
1719
+ /**
1720
+ * Render one frontmatter `key: value` line whose value came from user config.
1721
+ *
1722
+ * #3706: both `model:` and `variant:` interpolate a value read from
1723
+ * `.planning/config.json` / `~/.gsd/defaults.json`. Raw interpolation lets a value
1724
+ * containing a newline inject additional TOP-LEVEL frontmatter keys into the
1725
+ * generated agent file — proven by execution during the #3705 security review
1726
+ * (`"sonnet\ntools: [\"*\"]\npermission: bypass"` produced two extra keys).
1727
+ *
1728
+ * That sink predates #3706, but this change adds a SECOND write to it, so it is
1729
+ * closed here rather than doubled. Both the decision and the escaping live in
1730
+ * frontmatter.cts so there is exactly one set of YAML scalar rules; a value that
1731
+ * round-trips bare is still emitted bare, so generated files do not churn.
1732
+ */
1733
+ function frontmatterScalar(key, value) {
1734
+ return frontmatterModule.agentScalarNeedsDoubleQuoting(value)
1735
+ ? `${key} "${frontmatterModule.escapeDoubleQuotedScalar(value)}"`
1736
+ : `${key} ${value}`;
1737
+ }
1738
+ function convertClaudeToOpencodeFrontmatter(content, { isAgent = false, modelOverride = null, variant = null } = {}) {
1613
1739
  // Replace tool name references in content (applies to all files)
1614
1740
  let convertedContent = content;
1615
1741
  convertedContent = convertedContent.replace(/\bAskUserQuestion\b/g, 'question');
@@ -1734,7 +1860,23 @@ function convertClaudeToOpencodeFrontmatter(content, { isAgent = false, modelOve
1734
1860
  // respected on OpenCode (which uses static agent frontmatter, not inline
1735
1861
  // Task() model parameters). See #2256.
1736
1862
  if (modelOverride) {
1737
- newLines.push(['model:', modelOverride].join(' '));
1863
+ newLines.push(frontmatterScalar('model:', modelOverride));
1864
+ }
1865
+ // #3706: deliver the RESOLVED reasoning effort to the spawned subagent.
1866
+ // `query resolve-model` reported an effort that never reached OpenCode, so
1867
+ // every subagent ran at whatever opencode.jsonc defaults the model to — for
1868
+ // a custom provider commonly the most expensive setting.
1869
+ //
1870
+ // OpenCode ONLY: `EFFORT_ARGV` declares surfaces for claude/opencode/codex
1871
+ // and NO kilo entry, so the Kilo converter below deliberately does not emit
1872
+ // this. Unlike the model side — where #2794 J8 requires kilo and opencode to
1873
+ // resolve identically — there is no kilo effort surface to render into, and
1874
+ // inventing one would emit a key that runtime never documented.
1875
+ //
1876
+ // Omitted entirely when absent, per #1156's rule for `model: inherit`:
1877
+ // never an empty or sentinel value, let the runtime use its own default.
1878
+ if (variant) {
1879
+ newLines.push(frontmatterScalar('variant:', variant));
1738
1880
  }
1739
1881
  }
1740
1882
  // For commands: add tools object if we had allowed-tools or tools
@@ -1906,7 +2048,7 @@ function convertClaudeToKiloFrontmatter(content, { isAgent = false, modelOverrid
1906
2048
  // model emission exactly (#2093 UPGRADE 2 / ADR-1239; Kilo is an OpenCode
1907
2049
  // fork with the same static-frontmatter model constraint). See #2256.
1908
2050
  if (modelOverride) {
1909
- newLines.push(['model:', modelOverride].join(' '));
2051
+ newLines.push(frontmatterScalar('model:', modelOverride));
1910
2052
  }
1911
2053
  newLines.push(...buildKiloAgentPermissionBlock(agentTools));
1912
2054
  }
@@ -2188,6 +2330,106 @@ function convertClaudeAgentToQwenAgent(content) {
2188
2330
  fm += '---';
2189
2331
  return `${fm}\n${body}`;
2190
2332
  }
2333
+ /**
2334
+ * Convert a Claude Code agent .md for ZCode (#3384).
2335
+ *
2336
+ * ZCode is Claude-shaped (same frontmatter, same named-dispatch subagents), so
2337
+ * the file is preserved verbatim EXCEPT the `tools:` grant list: ZCode's
2338
+ * dispatcher treats every `mcp__<server>__*` entry as a REQUIRED MCP server and
2339
+ * hard-fails the subagent spawn (CONFIGURATION_ERROR: "Required MCP server is
2340
+ * not connected") whenever it is not connected, whereas Claude Code treats the
2341
+ * same entries as an optional allowlist. The `mcp__*` entries are stripped at
2342
+ * install time — the same exclusion Kimi's converter applies via
2343
+ * convertKimiToolName — so subagent spawns succeed with zero MCP servers
2344
+ * configured; connected servers' tools remain reachable (auto-discovered by the
2345
+ * host, not granted by frontmatter).
2346
+ *
2347
+ * Line-surgical by design: ONLY `tools:` lines inside the frontmatter are
2348
+ * touched, so every other byte (description, color, commented-out blocks, the
2349
+ * body) survives identically. Handles both shapes GSD emits — the inline comma
2350
+ * list (`tools: A, B, C`) and the YAML block list (`tools:` + `- A` items).
2351
+ * An agent whose filtered grant list becomes empty (every grant was `mcp__*`)
2352
+ * drops the `tools:` key entirely: an absent key inherits the full toolkit,
2353
+ * which is the degrade-gracefully outcome, never a toolless subagent.
2354
+ *
2355
+ * Byte-identical for an agent with no `mcp__*` grants (the common case) and
2356
+ * for an agent with no frontmatter at all.
2357
+ */
2358
+ function convertClaudeAgentToZcodeAgent(content) {
2359
+ // Fast path: no MCP grant token anywhere means nothing to strip. (A body
2360
+ // mention alone is not a grant — the line scan below finds no tools-line
2361
+ // change and returns `content` unchanged anyway; this just skips the scan.)
2362
+ if (!content.includes('mcp__'))
2363
+ return content;
2364
+ const lines = content.split('\n');
2365
+ if (lines[0] !== '---')
2366
+ return content;
2367
+ let fmEnd = -1;
2368
+ for (let i = 1; i < lines.length; i++) {
2369
+ if (lines[i] === '---') {
2370
+ fmEnd = i;
2371
+ break;
2372
+ }
2373
+ }
2374
+ if (fmEnd === -1)
2375
+ return content; // unterminated frontmatter — leave verbatim
2376
+ const out = [];
2377
+ let changed = false;
2378
+ let i = 1;
2379
+ while (i < fmEnd) {
2380
+ const line = lines[i];
2381
+ const inlineTools = /^tools:[ \t]*(.+)$/.exec(line);
2382
+ if (inlineTools) {
2383
+ const grants = inlineTools[1].split(',').map((tool) => tool.trim()).filter((tool) => tool !== '');
2384
+ const kept = grants.filter((tool) => !tool.startsWith('mcp__'));
2385
+ if (kept.length === grants.length) {
2386
+ out.push(line); // no mcp__* grants — keep the line byte-identical
2387
+ }
2388
+ else if (kept.length > 0) {
2389
+ out.push(`tools: ${kept.join(', ')}`);
2390
+ changed = true;
2391
+ }
2392
+ else {
2393
+ changed = true; // every grant was mcp__*: drop the tools key entirely
2394
+ }
2395
+ i++;
2396
+ continue;
2397
+ }
2398
+ if (/^tools:[ \t]*$/.test(line)) {
2399
+ // Block-list form: collect the following `- item` lines.
2400
+ const items = [];
2401
+ let j = i + 1;
2402
+ while (j < fmEnd && /^([ \t]*)-[ \t]*(\S.*)$/.test(lines[j])) {
2403
+ items.push(lines[j]);
2404
+ j++;
2405
+ }
2406
+ const kept = items.filter((item) => {
2407
+ const name = /^([ \t]*)-[ \t]*(\S.*)$/.exec(item)[2].trim();
2408
+ return !name.startsWith('mcp__');
2409
+ });
2410
+ if (kept.length !== items.length) {
2411
+ changed = true;
2412
+ if (kept.length > 0) {
2413
+ out.push(line);
2414
+ out.push(...kept);
2415
+ } // else: drop the tools key and all its items
2416
+ }
2417
+ else {
2418
+ out.push(line, ...items);
2419
+ }
2420
+ i = j;
2421
+ continue;
2422
+ }
2423
+ out.push(line);
2424
+ i++;
2425
+ }
2426
+ if (!changed)
2427
+ return content;
2428
+ // Opening delimiter + transformed frontmatter + closing delimiter + body.
2429
+ out.unshift(lines[0]);
2430
+ out.push(...lines.slice(fmEnd));
2431
+ return out.join('\n');
2432
+ }
2191
2433
  function convertClaudeAgentToCodebuddyAgent(content) {
2192
2434
  const converted = convertClaudeToCodebuddyMarkdown(content);
2193
2435
  const { frontmatter, body } = extractFrontmatterAndBody(converted);
@@ -2208,6 +2450,48 @@ function convertClaudeAgentToClineAgent(content) {
2208
2450
  const cleanFrontmatter = `---\nname: ${yamlIdentifier(name)}\ndescription: ${yamlQuote(toSingleLine(description))}\n---`;
2209
2451
  return `${cleanFrontmatter}\n${body}`;
2210
2452
  }
2453
+ /**
2454
+ * Apply a runtime's descriptor-declared `hostBehaviors.brandingRewrites` to an
2455
+ * agent body — the three literal-substring replaces the inline agent loop
2456
+ * (bin/install.js) previously hardcoded per-branding-runtime (qwen/hermes):
2457
+ * CLAUDE.md -> brandingRewrites['CLAUDE.md']
2458
+ * Claude Code -> brandingRewrites['Claude Code'] (word-boundary, \bClaude Code\b)
2459
+ * .claude/ -> brandingRewrites['.claude/']
2460
+ *
2461
+ * Data-driven (#2875 Part 2 / J10): reads the rewrite table from the
2462
+ * runtime's OWN descriptor rather than hardcoding any runtime's strings, so a
2463
+ * runtime declaring a different `brandingRewrites` table gets its own
2464
+ * rewrites applied automatically. A runtime with no `brandingRewrites`
2465
+ * declared returns `content` unchanged (no rewrite table to apply).
2466
+ *
2467
+ * Byte-identical to the inline loop's `else if (_hostBehaviors(runtime).brandingRewrites)`
2468
+ * branch, including plain (non-word-boundary) `.replace(/\bClaude Code\b/g, ...)`
2469
+ * semantics — J9.
2470
+ */
2471
+ function applyAgentBrandingRewrites(content, runtime) {
2472
+ const _b = _hostBehaviors(runtime).brandingRewrites;
2473
+ if (!_b)
2474
+ return content;
2475
+ let converted = content;
2476
+ if (_b['CLAUDE.md'])
2477
+ converted = converted.replace(/CLAUDE\.md/g, _b['CLAUDE.md']);
2478
+ if (_b['Claude Code'])
2479
+ converted = converted.replace(/\bClaude Code\b/g, _b['Claude Code']);
2480
+ if (_b['.claude/'])
2481
+ converted = converted.replace(/\.claude\//g, _b['.claude/']);
2482
+ return converted;
2483
+ }
2484
+ /**
2485
+ * Named branding converter for Hermes agents (#2875 Part 2 / J9-J10).
2486
+ * `convertedAgentsKind` dispatches converters by exported name, so a named
2487
+ * export is required even though the transform itself is fully generic
2488
+ * (`applyAgentBrandingRewrites`) — resolved from
2489
+ * `capabilities/hermes/capability.json`'s `hostBehaviors.brandingRewrites`,
2490
+ * never hardcoded here.
2491
+ */
2492
+ function convertClaudeAgentToHermesAgent(content) {
2493
+ return applyAgentBrandingRewrites(content, 'hermes');
2494
+ }
2211
2495
  /**
2212
2496
  * Convert Claude Code agent markdown to Codex agent format.
2213
2497
  * Applies base markdown conversions, then adds a <codex_agent_role> header
@@ -2320,20 +2604,151 @@ function computePathPrefix({ isGlobal, isOpencode, isWindowsHost: _isWindowsHost
2320
2604
  const NON_CLAUDE_RUNTIMES = Object.keys(capabilityRegistry.runtimes)
2321
2605
  .filter((id) => id !== 'claude')
2322
2606
  .sort();
2607
+ /**
2608
+ * #2652: The isolation a runtime can actually negotiate at dispatch time,
2609
+ * resolved from the registry exactly as `gsd_run query dispatch-isolation`
2610
+ * resolves it at runtime (`routeDispatchIsolation`, gsd-core/bin/gsd-tools.cjs):
2611
+ * the declared value must be in the closed vocabulary, a `harness-worktree`
2612
+ * host must also declare the flag the scheduler passes, and an
2613
+ * `orchestrator-worktree` host must carry a descriptor that resolves. Anything
2614
+ * else — unknown runtime, `undocumented`, out-of-vocabulary, a throw — is
2615
+ * `none` (ADR-1239, "Fail-closed").
2616
+ *
2617
+ * Install time cannot know the worktree path a future dispatch will target, so
2618
+ * the descriptor is probed with a placeholder; `resolveOrchestratorExec` fails
2619
+ * only on descriptor shape, never on a well-formed target's value.
2620
+ *
2621
+ * @private — exported as `_negotiatedDispatchIsolation` for tests.
2622
+ */
2623
+ function _negotiatedDispatchIsolation(runtime) {
2624
+ try {
2625
+ const runtimeEntry = capabilityRegistry?.runtimes?.[runtime] ?? null;
2626
+ const declared = runtimeEntry?.runtime?.hostIntegration?.dispatch?.isolation ?? null;
2627
+ if (declared === 'harness-worktree') {
2628
+ const declaredFlag = runtimeEntry?.runtime?.harnessIsolationFlag ?? null;
2629
+ return typeof declaredFlag === 'string' && declaredFlag.length > 0
2630
+ ? 'harness-worktree'
2631
+ : 'none';
2632
+ }
2633
+ if (declared === 'orchestrator-worktree') {
2634
+ return hostIntegration.resolveOrchestratorExec(runtimeEntry?.runtime?.orchestratorExec, '/gsd-orchestrator-worktree-probe').ok
2635
+ ? 'orchestrator-worktree'
2636
+ : 'none';
2637
+ }
2638
+ return 'none';
2639
+ }
2640
+ catch {
2641
+ return 'none';
2642
+ }
2643
+ }
2323
2644
  /**
2324
2645
  * #1521: Every non-Claude runtime resolves its own runtime identity from a
2325
- * runtime-neutral config, and defaults workflow.use_worktrees to false —
2326
- * GSD's worktree isolation uses Claude Code's isolation="worktree" spawn
2327
- * parameter, which no other runtime honors. Stamped into the emitted
2328
- * workflow runtime-resolution blocks. (Generalizes the Codex-only #1515 fix.)
2646
+ * runtime-neutral config. Stamped into the emitted workflow runtime-resolution
2647
+ * blocks. (Generalizes the Codex-only #1515 fix.)
2648
+ *
2649
+ * #1521 also stamped `workflow.use_worktrees` to default false for every
2650
+ * non-Claude runtime, because GSD's worktree isolation was Claude Code's
2651
+ * `isolation="worktree"` spawn parameter and no other runtime honored it.
2652
+ * #2584 removed that premise: isolation is now a negotiated capability
2653
+ * (`dispatch.isolation`), and Cursor declares `harness-worktree` while Codex,
2654
+ * OpenCode, Kimi and Kimi Code declare `orchestrator-worktree`. Stamping the
2655
+ * false default for those hosts resolved `USE_WORKTREES=false` before
2656
+ * `dispatch.isolation` was ever consulted, so a runtime that declares worktree
2657
+ * support still got `ISOLATION=none` — judged by its name after all, which is
2658
+ * the defect #2652 exists to remove. The stamp is therefore scoped to the
2659
+ * runtimes whose negotiated isolation really is `none`, where the default it
2660
+ * writes is the outcome the resolver would reach anyway.
2329
2661
  *
2330
2662
  * @private — exported as `_stampNonClaudeRuntimeDefaults` for tests.
2331
2663
  */
2332
2664
  function _stampNonClaudeRuntimeDefaults(content, runtime) {
2333
- content = content.replace(/config-get workflow\.use_worktrees --raw 2>\/dev\/null \|\| echo "true"/g, 'config-get workflow.use_worktrees --default false --raw 2>/dev/null || echo "false"');
2665
+ if (_negotiatedDispatchIsolation(runtime) === 'none') {
2666
+ content = content.replace(/config-get workflow\.use_worktrees --raw 2>\/dev\/null \|\| echo "true"/g, 'config-get workflow.use_worktrees --default false --raw 2>/dev/null || echo "false"');
2667
+ }
2334
2668
  content = content.replace(/config-get runtime --default claude --raw 2>\/dev\/null \|\| echo "claude"/g, `config-get runtime --default ${runtime} --raw 2>/dev/null || echo "${runtime}"`);
2335
2669
  return content;
2336
2670
  }
2671
+ /**
2672
+ * #3544 (extending #3133's fix): restore `@$HOME<suffix>` `@`-file-reference
2673
+ * lines back to their tilde equivalent (`@~<suffix>`) in Claude-emitted
2674
+ * content whose pathPrefix is the `$HOME` form. This is a NARROW,
2675
+ * context-sensitive correction layered on top of the blanket `~/.claude/` /
2676
+ * `$HOME/.claude/` -> pathPrefix substitution every Claude emit path
2677
+ * applies: that blanket substitution MUST keep emitting `$HOME` for global
2678
+ * installs — shell commands embedded in workflow/command bodies (e.g.
2679
+ * `node "$HOME/.claude/gsd-core/bin/gsd-tools.cjs"`) need it, since `~` does
2680
+ * not expand inside double-quoted shell strings (#1284). But Claude Code's
2681
+ * own `@`-import resolver does the opposite: it documents `~` expansion and
2682
+ * does NOT expand `$HOME`. That is not merely undocumented — a controlled
2683
+ * `/context` measurement showed an `@$HOME/…` import loading nothing (see
2684
+ * .gsd/bug/fix-3544-home-expansion-spec-tree/10-diagnosis.md's ADDENDUM). No
2685
+ * automated test can verify *resolution* inside a live Claude Code session
2686
+ * (nothing in CI can spawn one and read `/context`); every test here — unit
2687
+ * and spawned-installer alike — verifies only the emitted STRING takes the
2688
+ * `~` form Claude Code documents as expanding. A single pathPrefix string
2689
+ * cannot satisfy both the shell and the `@`-import consumer, so this runs as
2690
+ * a second, `@`-anchored pass AFTER the blanket substitution.
2691
+ *
2692
+ * #3133 first applied this restore inline in `_applyRuntimeRewrites`'s
2693
+ * `case 'claude'` below (the skill/command staging pipeline). #3544 found
2694
+ * the identical defect in bin/install.js's `copyWithPathReplacement` — the
2695
+ * `gsd-core/` spec-tree emit path, which never had the restore step, so
2696
+ * every `@~/.claude/gsd-core/…` include in a global install's workflows/
2697
+ * references tree silently resolved to nothing (54 includes across 22 files
2698
+ * on a live install, per the diagnosis). Both call sites now share this one
2699
+ * implementation instead of drifting independently (DEFECT.GENERATIVE-FIX).
2700
+ *
2701
+ * No-op unless `pathPrefix` is the `$HOME` form — local installs already
2702
+ * bake an absolute, `@`-resolvable pathPrefix and are unaffected, as are
2703
+ * every non-Claude runtime (never called for them).
2704
+ *
2705
+ * #3544 review (2nd pass): the first cut of this function hardcoded the
2706
+ * literal `.claude/` segment, so it silently no-opped for any global install
2707
+ * under a non-default `--config-dir` (e.g. `~/.claude-work`) — reproducing
2708
+ * the exact defect #3544 fixes, just one directory name later. This ALSO
2709
+ * corrects the same latent gap in #3133's original path, since both call
2710
+ * sites share this one implementation. Fixed by deriving the rewrite from
2711
+ * `pathPrefix` itself rather than a hardcoded directory name: the tilde
2712
+ * equivalent of any `$HOME`-form prefix is `'~' + pathPrefix.slice(5)`
2713
+ * (`'$HOME'.length === 5`), so the transform generalizes to any config-dir
2714
+ * name with no runtime-specific literal.
2715
+ *
2716
+ * #3544 review (2nd pass), quote-awareness: the anchor is a negative
2717
+ * lookbehind for a preceding quote character, NOT a line-start anchor —
2718
+ * Claude Code documents `@`-references as valid "anywhere in your
2719
+ * CLAUDE.md" (e.g. `See @README for project overview`), so anchoring to
2720
+ * line-start would miss a legitimate mid-line reference. The lookbehind
2721
+ * instead guards the one demonstrated false-positive: a quoted shell string
2722
+ * like `echo "@$HOME/.claude/x"`, where rewriting `$HOME` to `~` inside
2723
+ * double quotes reintroduces the #1284 failure mode (`~` does not expand in
2724
+ * double-quoted shell). Deliberately NOT fenced-code-block aware (unlike
2725
+ * `resolveSpecRootReference`'s `scanFencedBlocks` use above): this pass
2726
+ * targets genuine `@`-import lines and inline shell references across the
2727
+ * whole emitted corpus, and today there are zero occurrences anywhere in the
2728
+ * tree of an `@$HOME<suffix>` sequence inside a fenced code block (the
2729
+ * quote-guard already closes the one reachable false-positive class).
2730
+ * Layering `scanFencedBlocks` on top would roughly double this function's
2731
+ * size to guard an undemonstrated case — the opposite of the brief's
2732
+ * "simpler, not more complex" direction. If a fenced example ever needs this
2733
+ * literal sequence, add fence-awareness then, with a regression test proving
2734
+ * the fence is real.
2735
+ *
2736
+ * @private — exported as `_restoreClaudeGlobalAtRefTilde` for tests and for
2737
+ * bin/install.js's `copyWithPathReplacement`.
2738
+ */
2739
+ function restoreClaudeGlobalAtRefTilde(content, pathPrefix) {
2740
+ if (typeof pathPrefix !== 'string' || !pathPrefix.startsWith('$HOME'))
2741
+ return content;
2742
+ const tildeEquivalent = '~' + pathPrefix.slice('$HOME'.length);
2743
+ const atRefRe = new RegExp(`(?<!["'])@${(0, pattern_cjs_1.escapeRegex)(pathPrefix)}`, 'g');
2744
+ // Function replacement, not a string. A string replacement treats `$&` and the
2745
+ // backtick-dollar form in the SUBSTITUTION as special patterns, so a --config-dir
2746
+ // containing either corrupts output: `$HOME/.cl$&ude/` yielded
2747
+ // `@~/.cl@$HOME/.cl$&ude/ude/x`, and the backtick form silently DROPPED text.
2748
+ // Pre-existing, and #3719 adds a THIRD call site to this sink — which is how the
2749
+ // previous two came to share the defect in the first place.
2750
+ return content.replace(atRefRe, () => `@${tildeEquivalent}`);
2751
+ }
2337
2752
  /**
2338
2753
  * Apply the per-runtime rewrite table to a single content string.
2339
2754
  * Relocated from bin/install.js `_applyRuntimeRewrites`.
@@ -2410,7 +2825,7 @@ function _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal = false, a
2410
2825
  // #2097: dot-dir self-references (~/.augment/…) → resolved prefix,
2411
2826
  // dirName-derived (no runtime literal). getDirName('augment') resolves
2412
2827
  // to '.augment', so this is byte-identical to the prior hardcoded regexes.
2413
- const _dd = escapeRegExp(dirName);
2828
+ const _dd = (0, pattern_cjs_1.escapeRegex)(dirName);
2414
2829
  content = content.replace(new RegExp('~/' + _dd + '/', 'g'), pathPrefix);
2415
2830
  content = content.replace(new RegExp('\\$HOME/' + _dd + '/', 'g'), pathPrefix);
2416
2831
  content = content.replace(new RegExp('~/' + _dd + '(?![\\w-])', 'g'), normalizedPathPrefix);
@@ -2428,7 +2843,7 @@ function _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal = false, a
2428
2843
  // #2094: descriptor-driven — dirName resolves to '.trae' via
2429
2844
  // getDirName()/localConfigDir, so this regex is built rather than
2430
2845
  // hardcoded as `/~\/\.trae\//g` (byte-identical output for trae).
2431
- content = content.replace(new RegExp('~/' + escapeRegExp(dirName) + '/', 'g'), pathPrefix);
2846
+ content = content.replace(new RegExp('~/' + (0, pattern_cjs_1.escapeRegex)(dirName) + '/', 'g'), pathPrefix);
2432
2847
  content = processAttribution(content, attribution);
2433
2848
  break;
2434
2849
  case 'codebuddy':
@@ -2454,6 +2869,10 @@ function _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal = false, a
2454
2869
  content = content.replace(/~\/\.claude\//g, pathPrefix);
2455
2870
  content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
2456
2871
  content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
2872
+ // #3133 / #3544: restore @-file-reference lines to the tilde form
2873
+ // Claude actually expands — see restoreClaudeGlobalAtRefTilde's doc
2874
+ // comment above for why this must be a separate, @-anchored pass.
2875
+ content = restoreClaudeGlobalAtRefTilde(content, pathPrefix);
2457
2876
  content = processAttribution(content, attribution);
2458
2877
  break;
2459
2878
  // Descriptor-driven brand literals (ADR-1239 / #2092): the qwen/hermes
@@ -2541,18 +2960,18 @@ function _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal = false, a
2541
2960
  * @param attribution Co-Authored-By value (string | null | undefined)
2542
2961
  */
2543
2962
  function applyRuntimeContentRewritesInPlace(stagedDir, runtime, pathPrefix, isGlobal = false, attribution = undefined) {
2544
- if (!node_fs_1.default.existsSync(stagedDir))
2963
+ if (!installFs().existsSync(stagedDir))
2545
2964
  return;
2546
2965
  const walkAndRewrite = (dir) => {
2547
- for (const entry of node_fs_1.default.readdirSync(dir, { withFileTypes: true })) {
2966
+ for (const entry of installFs().readdirSync(dir, { withFileTypes: true })) {
2548
2967
  const fullPath = node_path_1.default.join(dir, entry.name);
2549
2968
  if (entry.isDirectory()) {
2550
2969
  walkAndRewrite(fullPath);
2551
2970
  }
2552
2971
  else if (entry.name.endsWith('.md')) {
2553
- let content = node_fs_1.default.readFileSync(fullPath, 'utf8');
2972
+ let content = installFs().readFileSync(fullPath, 'utf8');
2554
2973
  content = _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal, attribution);
2555
- node_fs_1.default.writeFileSync(fullPath, content);
2974
+ installFs().writeFileSync(fullPath, content);
2556
2975
  }
2557
2976
  }
2558
2977
  };
@@ -2577,14 +2996,14 @@ function applyRuntimeContentRewritesInPlace(stagedDir, runtime, pathPrefix, isGl
2577
2996
  * @returns {string} path to the temp dir (caller is responsible for cleanup)
2578
2997
  */
2579
2998
  function applyRuntimeContentRewritesForCommandsInPlace(stagedDir, runtime, pathPrefix, isGlobal = false, attribution = undefined) {
2580
- if (!node_fs_1.default.existsSync(stagedDir))
2999
+ if (!installFs().existsSync(stagedDir))
2581
3000
  return stagedDir;
2582
- const tempDir = node_fs_1.default.mkdtempSync(node_path_1.default.join(node_os_1.default.tmpdir(), 'gsd-cmd-rewrites-'));
3001
+ const tempDir = mkInstallTempDir('gsd-cmd-rewrites-');
2583
3002
  try {
2584
- for (const entry of node_fs_1.default.readdirSync(stagedDir, { withFileTypes: true })) {
3003
+ for (const entry of installFs().readdirSync(stagedDir, { withFileTypes: true })) {
2585
3004
  if (!entry.isFile() || !entry.name.endsWith('.md'))
2586
3005
  continue;
2587
- let content = node_fs_1.default.readFileSync(node_path_1.default.join(stagedDir, entry.name), 'utf8');
3006
+ let content = installFs().readFileSync(node_path_1.default.join(stagedDir, entry.name), 'utf8');
2588
3007
  content = _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal, attribution);
2589
3008
  // #2097 (ADR-1239): descriptor-driven — commandBodyConverter name comes
2590
3009
  // from runtime.hostBehaviors instead of a hardcoded runtime-name branch.
@@ -2592,18 +3011,52 @@ function applyRuntimeContentRewritesForCommandsInPlace(stagedDir, runtime, pathP
2592
3011
  if (_cmdConv && COMMAND_BODY_CONVERTERS[_cmdConv]) {
2593
3012
  content = COMMAND_BODY_CONVERTERS[_cmdConv](content);
2594
3013
  }
2595
- node_fs_1.default.writeFileSync(node_path_1.default.join(tempDir, entry.name), content);
3014
+ installFs().writeFileSync(node_path_1.default.join(tempDir, entry.name), content);
2596
3015
  }
2597
3016
  }
2598
3017
  catch (err) {
2599
3018
  try {
2600
- node_fs_1.default.rmSync(tempDir, { recursive: true, force: true });
3019
+ installFs().rmSync(tempDir, { recursive: true, force: true });
2601
3020
  }
2602
3021
  catch { /* best-effort */ }
2603
3022
  throw err;
2604
3023
  }
2605
3024
  return tempDir;
2606
3025
  }
3026
+ /**
3027
+ * #2873 (4b) — second pass over a staged skills directory, run strictly AFTER
3028
+ * `applyRuntimeContentRewritesInPlace`. That pass's `case 'claude':` branch
3029
+ * unconditionally rewrites any bare (non-`@`-prefixed) `~/.claude/` substring
3030
+ * in the body to the computed pathPrefix (`$HOME/.claude/` for a global
3031
+ * install) and restores ONLY the `@`-prefixed form back to `~`
3032
+ * (`@$HOME/.claude/` → `@~/.claude/`). `resolveSpecRootReference`'s
3033
+ * replacement text is deliberately imperative prose containing a literal,
3034
+ * non-`@`-prefixed `~/.claude/gsd-core/workflows/<stem>.md` — running it
3035
+ * BEFORE the pass above would let that literal tilde text get silently
3036
+ * mangled into the undocumented `$HOME/` form the design explicitly rejects.
3037
+ * Running it here, after, means it only ever sees the FINAL
3038
+ * `@~/.claude/gsd-core/workflows/<stem>.md` include line (which survives the
3039
+ * pass above intact via its own `@`-guarded restore).
3040
+ */
3041
+ function applySpecRootReferenceToStagedSkills(stagedDir) {
3042
+ if (!installFs().existsSync(stagedDir))
3043
+ return;
3044
+ const walk = (dir) => {
3045
+ for (const entry of installFs().readdirSync(dir, { withFileTypes: true })) {
3046
+ const fullPath = node_path_1.default.join(dir, entry.name);
3047
+ if (entry.isDirectory()) {
3048
+ walk(fullPath);
3049
+ }
3050
+ else if (entry.name === 'SKILL.md') {
3051
+ const content = installFs().readFileSync(fullPath, 'utf8');
3052
+ const rewritten = resolveSpecRootReference(content);
3053
+ if (rewritten !== content)
3054
+ installFs().writeFileSync(fullPath, rewritten);
3055
+ }
3056
+ }
3057
+ };
3058
+ walk(stagedDir);
3059
+ }
2607
3060
  /**
2608
3061
  * HIGH-LEVEL: In-place fs walk: rewrite all .md files under stagedDir for the given runtime.
2609
3062
  *
@@ -2620,16 +3073,30 @@ function applyRuntimeContentRewritesForCommandsInPlace(stagedDir, runtime, pathP
2620
3073
  */
2621
3074
  function rewriteStagedSkillBodies(stagedDir, opts) {
2622
3075
  const { runtime, configDir, scope = 'global', homedir = () => node_os_1.default.homedir(), platform = process.platform, resolveAttribution, } = opts;
2623
- if (!node_fs_1.default.existsSync(stagedDir))
3076
+ if (!installFs().existsSync(stagedDir))
2624
3077
  return;
2625
3078
  const resolvedTarget = (0, shell_command_projection_cjs_1.posixNormalize)(node_path_1.default.resolve(configDir));
2626
3079
  const homeDir = (0, shell_command_projection_cjs_1.posixNormalize)(homedir());
2627
- const isGlobal = scope === 'global';
3080
+ // #2870: `scope` is defaulted to 'global' above, so it is never undefined
3081
+ // here, and every reachable caller passes 'global' | 'local' | undefined —
3082
+ // isGlobalScope's throw-on-out-of-union case is unreachable at this site.
3083
+ const isGlobal = (0, install_scope_cjs_1.isGlobalScope)(scope);
2628
3084
  const isOpencode = false; // #2087: opencode installs via the combined-family engine path, never through the generic rewrite
2629
3085
  const isWindowsHost = platform === 'win32';
2630
3086
  const pathPrefix = computePathPrefix({ isGlobal, isOpencode, isWindowsHost, resolvedTarget, homeDir });
2631
3087
  const attribution = resolveAttribution ? resolveAttribution(runtime) : undefined;
2632
3088
  applyRuntimeContentRewritesInPlace(stagedDir, runtime, pathPrefix, isGlobal, attribution);
3089
+ // #2873 (4b): claude, global scope only — see
3090
+ // applySpecRootReferenceToStagedSkills's doc comment for why this MUST run
3091
+ // after the rewrite pass above, not before. `rewriteStagedSkillBodies` is
3092
+ // the skills-kind seam (`kind.kind === 'skills'`), so this never touches a
3093
+ // 'commands' or 'agents' kind body (rows 24/25 unaffected), and claude has
3094
+ // no skills-kind entry at local scope, so this is already structurally
3095
+ // scoped to global (row 23) — the explicit isGlobal check is defense-in-depth
3096
+ // against that descriptor wiring ever changing.
3097
+ if (runtime === 'claude' && isGlobal) {
3098
+ applySpecRootReferenceToStagedSkills(stagedDir);
3099
+ }
2633
3100
  }
2634
3101
  /**
2635
3102
  * HIGH-LEVEL: Copy-to-temp then rewrite all .md files for the given runtime.
@@ -2649,11 +3116,14 @@ function rewriteStagedSkillBodies(stagedDir, opts) {
2649
3116
  */
2650
3117
  function rewriteStagedCommandBodies(stagedDir, opts) {
2651
3118
  const { runtime, configDir, scope = 'global', homedir = () => node_os_1.default.homedir(), platform = process.platform, resolveAttribution, } = opts;
2652
- if (!node_fs_1.default.existsSync(stagedDir))
3119
+ if (!installFs().existsSync(stagedDir))
2653
3120
  return stagedDir;
2654
3121
  const resolvedTarget = (0, shell_command_projection_cjs_1.posixNormalize)(node_path_1.default.resolve(configDir));
2655
3122
  const homeDir = (0, shell_command_projection_cjs_1.posixNormalize)(homedir());
2656
- const isGlobal = scope === 'global';
3123
+ // #2870: `scope` is defaulted to 'global' above, so it is never undefined
3124
+ // here, and every reachable caller passes 'global' | 'local' | undefined —
3125
+ // isGlobalScope's throw-on-out-of-union case is unreachable at this site.
3126
+ const isGlobal = (0, install_scope_cjs_1.isGlobalScope)(scope);
2657
3127
  const isOpencode = false; // #2087: opencode installs via the combined-family engine path, never through the generic rewrite
2658
3128
  const isWindowsHost = platform === 'win32';
2659
3129
  const pathPrefix = computePathPrefix({ isGlobal, isOpencode, isWindowsHost, resolvedTarget, homeDir });
@@ -2709,11 +3179,165 @@ function applyAgentPathRewrites(content, runtime, pathPrefix) {
2709
3179
  const normalizedPathPrefix = pathPrefix.replace(/\/$/, '');
2710
3180
  content = content.replace(/~\/\.claude\//g, pathPrefix);
2711
3181
  content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
2712
- content = content.replace(/~\/\.claude\b/g, normalizedPathPrefix);
2713
- content = content.replace(/\$HOME\/\.claude\b/g, normalizedPathPrefix);
3182
+ // #3719 review (MAJOR): a bare `\b` is satisfied by ANY non-word character,
3183
+ // including '-' — for a --config-dir whose name EXTENDS '.claude' (e.g.
3184
+ // '.claude-work'), this re-matched the '.claude' PREFIX of the emitted
3185
+ // '.claude-work' path and corrupted it to '.claude-work-work'. Guard with
3186
+ // the SAME negative-lookahead convention already used at
3187
+ // copyWithPathReplacement's call site (bin/install.js:7729-7730).
3188
+ content = content.replace(/~\/\.claude(?![\w-])/g, normalizedPathPrefix);
3189
+ content = content.replace(/\$HOME\/\.claude(?![\w-])/g, normalizedPathPrefix);
3190
+ // #3719: the THIRD emit path that needed this restore. #3133 added it to the
3191
+ // skill/command pipeline (`_applyRuntimeRewrites` case 'claude') and #3544 to
3192
+ // bin/install.js's spec-tree copy; the agents pipeline never got it, so every
3193
+ // `@~/.claude/...` include in a global Claude install shipped as `@$HOME/...`
3194
+ // and resolved to NOTHING (27 of 34 emitted agents, 103 lines).
3195
+ //
3196
+ // Guarded on `claude` for the same reason the sibling call site lives inside
3197
+ // `case 'claude'`: the helper self-guards only on the `$HOME` PREFIX, and every
3198
+ // runtime's global prefix is a `$HOME` form (`$HOME/.cursor/`, ...), so an
3199
+ // unguarded call would rewrite `@`-refs for runtimes whose resolver has no
3200
+ // documented `~` expansion at all.
3201
+ //
3202
+ // Passed the NORMALIZED prefix, not `pathPrefix`. The two word-boundary
3203
+ // replaces above emit the trailing-slash-free form, and the helper's regex is
3204
+ // anchored to the exact prefix string it is handed — so `restore(pathPrefix)`
3205
+ // fixes `@$HOME/.claude/x` and leaves a bare `@$HOME/.claude` broken. The
3206
+ // normalized form is a PREFIX of both, so one call covers both.
3207
+ if (runtime === 'claude') {
3208
+ content = restoreClaudeGlobalAtRefTilde(content, normalizedPathPrefix);
3209
+ }
2714
3210
  return content;
2715
3211
  }
2716
3212
  // ── End rewrite engine ────────────────────────────────────────────────────────
3213
+ /**
3214
+ * Derive an agent's stem name from its source `.md` filename. Byte-identical
3215
+ * to the inline agent loop's `entry.name.replace(/\.md$/, '')` (bin/install.js)
3216
+ * — single-sourced here so the descriptor pipeline's per-agent resolution
3217
+ * context (`agentCtx.agentName`, ADR-1235 §1 / #2875 Part 2 row I3) can never
3218
+ * diverge from it. A filename with no trailing `.md` is returned unchanged
3219
+ * (the regex has nothing to match) — I3's boundary row.
3220
+ */
3221
+ function deriveAgentName(fileName) {
3222
+ return fileName.replace(/\.md$/, '');
3223
+ }
3224
+ /**
3225
+ * #443 — Inject `effort: <value>` into YAML frontmatter of a Claude .md agent
3226
+ * file in a newline-agnostic way (LF and CRLF source files are both handled).
3227
+ * Relocated verbatim from bin/install.js (#2875 Part 2) — see
3228
+ * `applyAgentFrontmatterExtensions` below for the orchestration that calls it.
3229
+ *
3230
+ * The function:
3231
+ * - Detects the file's EOL (CRLF if the first `---` line ends with \r\n,
3232
+ * otherwise LF).
3233
+ * - Skips injection if an `effort:` key already exists in the frontmatter
3234
+ * (idempotent).
3235
+ * - Inserts `effort: <value>` immediately before the closing `---` delimiter,
3236
+ * using the same EOL as the surrounding frontmatter so the output file
3237
+ * stays EOL-consistent.
3238
+ * - Returns the original content unchanged when no YAML frontmatter is found.
3239
+ */
3240
+ function injectEffortFrontmatter(content, effortValue) {
3241
+ const eol = /^---\r\n/.test(content) ? '\r\n' : '\n';
3242
+ const fmRe = /^---\r?\n([\s\S]*?)^---\r?$/m;
3243
+ const match = fmRe.exec(content);
3244
+ if (!match)
3245
+ return content; // no YAML frontmatter — leave unchanged
3246
+ const fmBody = match[1]; // content between the two `---` lines
3247
+ if (/^effort:/m.test(fmBody))
3248
+ return content;
3249
+ const openLen = 3 + eol.length; // "---" + eol
3250
+ const closingStart = match.index + openLen + fmBody.length;
3251
+ const before = content.slice(0, closingStart);
3252
+ const after = content.slice(closingStart);
3253
+ return `${before}effort: ${effortValue}${eol}${after}`;
3254
+ }
3255
+ /**
3256
+ * #767 — Inject `disallowedTools: <value>` into the YAML frontmatter of a
3257
+ * Claude .md agent. Mirrors injectEffortFrontmatter: idempotent (skips if
3258
+ * disallowedTools: already present), inserts immediately before the closing
3259
+ * `---`. Claude-only — never call for other runtimes, which break on unknown
3260
+ * frontmatter keys. Relocated verbatim from bin/install.js (#2875 Part 2).
3261
+ */
3262
+ function injectDisallowedToolsFrontmatter(content, disallowedValue) {
3263
+ const eol = /^---\r\n/.test(content) ? '\r\n' : '\n';
3264
+ const fmRe = /^---\r?\n([\s\S]*?)^---\r?$/m;
3265
+ const match = fmRe.exec(content);
3266
+ if (!match)
3267
+ return content; // no YAML frontmatter — leave unchanged
3268
+ const fmBody = match[1]; // content between the two `---` lines
3269
+ if (/^disallowedTools:/m.test(fmBody))
3270
+ return content;
3271
+ const openLen = 3 + eol.length; // "---" + eol
3272
+ const closingStart = match.index + openLen + fmBody.length;
3273
+ const before = content.slice(0, closingStart);
3274
+ const after = content.slice(closingStart);
3275
+ return `${before}disallowedTools: ${disallowedValue}${eol}${after}`;
3276
+ }
3277
+ // #767 — Read-only verifier/auditor agents get a Claude-Code disallowedTools deny-list.
3278
+ // Group A (pure read-only) deny Write,Edit,MultiEdit. Group B report-writers Write one
3279
+ // output file so they deny only Edit,MultiEdit. gsd-nyquist-auditor is intentionally
3280
+ // excluded (it legitimately uses Write AND Edit to create/patch test files). Relocated
3281
+ // verbatim from bin/install.js (#2875 Part 2) — single source of truth for both the
3282
+ // inline loop (which now requires this export) and the descriptor pipeline.
3283
+ const READONLY_AGENT_DISALLOWED_TOOLS = {
3284
+ 'gsd-plan-checker': 'Write, Edit, MultiEdit',
3285
+ 'gsd-integration-checker': 'Write, Edit, MultiEdit',
3286
+ 'gsd-ui-checker': 'Write, Edit, MultiEdit',
3287
+ 'gsd-verifier': 'Edit, MultiEdit',
3288
+ 'gsd-doc-verifier': 'Edit, MultiEdit',
3289
+ 'gsd-eval-auditor': 'Edit, MultiEdit',
3290
+ 'gsd-ui-auditor': 'Edit, MultiEdit',
3291
+ };
3292
+ /**
3293
+ * Post-converter frontmatter-extensions step (#2875 Part 2 / ADR-1235 §1
3294
+ * follow-up). Driven by the runtime descriptor's
3295
+ * `hostBehaviors.agentFrontmatterExtensions` allow-list — Claude is its only
3296
+ * declared consumer today (`agentFrontmatterExtensions: ["effort"]`).
3297
+ * A runtime that does NOT declare the extension gets nothing injected (J3):
3298
+ * OpenCode/Qwen/Hermes reject unknown frontmatter keys.
3299
+ *
3300
+ * Byte-identical to the inline agent loop's
3301
+ * `if ((_hostBehaviors(runtime).agentFrontmatterExtensions || []).includes('effort'))`
3302
+ * block (bin/install.js): both the effort injection AND the disallowedTools
3303
+ * injection are gated behind the SAME `'effort'` extension flag — there is no
3304
+ * separate `'disallowedTools'` extension key, mirroring the loop exactly.
3305
+ *
3306
+ * J2 (the trap row): when the resolved effort is `'inherit'`, NO `effort:`
3307
+ * key is written at all — the absence of the key IS the behavior (#3533).
3308
+ * Writing `effort: inherit` would be a regression that looks like success.
3309
+ *
3310
+ * @param content agent .md content, already converter-transformed
3311
+ * @param runtime canonical runtime ID
3312
+ * @param agentName agent stem (from deriveAgentName), e.g. 'gsd-planner'
3313
+ * @param targetDir install root — resolves .planning/config.json + ~/.gsd/defaults.json
3314
+ */
3315
+ function applyAgentFrontmatterExtensions(content, { runtime, agentName, targetDir }) {
3316
+ const extensions = _hostBehaviors(runtime).agentFrontmatterExtensions || [];
3317
+ if (!extensions.includes('effort'))
3318
+ return content;
3319
+ let result = content;
3320
+ const effortCfg = readGsdEffectiveEffortConfig(targetDir ?? null);
3321
+ const universalEffort = resolveInstallTimeEffort(effortCfg, agentName);
3322
+ // #3533 (10d): 'inherit' means the effort: key must NOT exist — Claude Code
3323
+ // then follows the session effort. The canonical source agents carry no
3324
+ // effort key, so skipping injection is the whole job.
3325
+ if (universalEffort !== 'inherit') {
3326
+ const renderedEffort = _getGsdEffortCatalog().renderEffortForRuntime(runtime, universalEffort).value;
3327
+ // #3007: `value` is `string | null` — a rejected/unrenderable level (e.g.
3328
+ // 'ultra', or a catalog with no advertised level at or above the request)
3329
+ // renders null. Same posture as the 'inherit' case above: omit the key
3330
+ // entirely rather than writing a literal `effort: null`, so the host
3331
+ // falls back to its own default instead of failing to parse.
3332
+ if (renderedEffort !== null) {
3333
+ result = injectEffortFrontmatter(result, renderedEffort);
3334
+ }
3335
+ }
3336
+ const disallowedTools = READONLY_AGENT_DISALLOWED_TOOLS[agentName];
3337
+ if (disallowedTools)
3338
+ result = injectDisallowedToolsFrontmatter(result, disallowedTools);
3339
+ return result;
3340
+ }
2717
3341
  /**
2718
3342
  * Apply Co-Authored-By attribution policy to file content.
2719
3343
  * - null -> remove the Co-Authored-By line and its preceding blank line
@@ -2756,6 +3380,11 @@ module.exports = {
2756
3380
  convertClaudeToAntigravityContent,
2757
3381
  convertClaudeCommandToAntigravitySkill,
2758
3382
  convertClaudeCommandToClaudeSkill,
3383
+ // #2873 (4b): pure, scope-free transform — applied by the one call site
3384
+ // that knows install scope (skillsKind's stage() in
3385
+ // runtime-artifact-layout.cts), never inside convertClaudeCommandToClaudeSkill
3386
+ // itself.
3387
+ resolveSpecRootReference,
2759
3388
  convertClaudeCommandToKimiSkill,
2760
3389
  convertClaudeCommandToKimiCodeSkill,
2761
3390
  buildKimiAgentArtifacts,
@@ -2810,11 +3439,20 @@ module.exports = {
2810
3439
  convertClaudeAgentToCodebuddyAgent,
2811
3440
  convertClaudeAgentToClineAgent,
2812
3441
  convertClaudeAgentToCodexAgent,
3442
+ // #2875 Part 2 (J10): Hermes named branding converter, generic underlying
3443
+ // transform exported alongside it for direct reuse/testing.
3444
+ convertClaudeAgentToHermesAgent,
3445
+ applyAgentBrandingRewrites,
2813
3446
  // ADR-1239 / #2092 Phase B Upgrade 1: native .qwen/agents/*.md subagent
2814
3447
  // projection — registered by name so convertedAgentsKind's
2815
3448
  // conversionExports[converterName] dispatch (runtime-artifact-layout.cts)
2816
3449
  // can resolve it from capabilities/qwen/capability.json's agents kind.
2817
3450
  convertClaudeAgentToQwenAgent,
3451
+ // #3384: ZCode agents are Claude-shaped but its dispatcher treats mcp__*
3452
+ // tools grants as required MCP servers — registered by name for the same
3453
+ // conversionExports[converterName] dispatch, resolved from
3454
+ // capabilities/zcode/capability.json's agents kind.
3455
+ convertClaudeAgentToZcodeAgent,
2818
3456
  // #1511 ADR-1508 Phase 2: rewrite engine deep seam
2819
3457
  // Low-level walkers (pathPrefix + attribution pre-resolved by caller):
2820
3458
  applyRuntimeContentRewritesInPlace,
@@ -2825,9 +3463,21 @@ module.exports = {
2825
3463
  // ADR-1235 §1: descriptor-driven agent cross-cutting
2826
3464
  applyAgentPathRewrites,
2827
3465
  normalizeAgentBodyForRuntime,
3466
+ // #2875 Part 2: descriptor-driven agent frontmatter-extensions step + its
3467
+ // single-sourced building blocks (also required back by bin/install.js so
3468
+ // the inline loop and the descriptor pipeline resolve through the SAME
3469
+ // code — no drift between the two byte-parity-gated pipelines).
3470
+ deriveAgentName,
3471
+ injectEffortFrontmatter,
3472
+ injectDisallowedToolsFrontmatter,
3473
+ READONLY_AGENT_DISALLOWED_TOOLS,
3474
+ applyAgentFrontmatterExtensions,
2828
3475
  _computePathPrefix: computePathPrefix,
3476
+ _restoreClaudeGlobalAtRefTilde: restoreClaudeGlobalAtRefTilde,
2829
3477
  _applyRuntimeRewrites,
2830
3478
  _stampNonClaudeRuntimeDefaults,
3479
+ // #2652: registry-resolved dispatch isolation, mirroring routeDispatchIsolation
3480
+ _negotiatedDispatchIsolation,
2831
3481
  // #1521: canonical non-Claude runtime list for test files and tooling
2832
3482
  NON_CLAUDE_RUNTIMES,
2833
3483
  };