@opengsd/gsd-core 1.15.0 → 1.17.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 (505) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -2
  3. package/.opencode/plugins/gsd-core.js +336 -6
  4. package/.opencode/plugins/gsd-statusline/tui.tsx +55 -0
  5. package/GEMINI.md +1 -1
  6. package/agents/gsd-code-fixer.compact.md +1 -1
  7. package/agents/gsd-code-fixer.md +1 -1
  8. package/agents/gsd-code-reviewer.compact.md +5 -3
  9. package/agents/gsd-code-reviewer.md +8 -6
  10. package/agents/gsd-debug-session-manager.compact.md +7 -1
  11. package/agents/gsd-debug-session-manager.md +7 -1
  12. package/agents/gsd-debugger.md +2 -2
  13. package/agents/gsd-eval-auditor.compact.md +1 -1
  14. package/agents/gsd-eval-auditor.md +1 -1
  15. package/agents/gsd-executor.md +4 -4
  16. package/agents/gsd-intel-updater.compact.md +1 -1
  17. package/agents/gsd-intel-updater.md +1 -1
  18. package/agents/gsd-mempalace-curator.md +2 -2
  19. package/agents/gsd-phase-researcher.md +1 -1
  20. package/agents/gsd-plan-checker.md +7 -2
  21. package/agents/gsd-planner.md +3 -3
  22. package/agents/gsd-project-researcher.compact.md +1 -1
  23. package/agents/gsd-project-researcher.md +1 -1
  24. package/agents/gsd-research-synthesizer.compact.md +1 -1
  25. package/agents/gsd-research-synthesizer.md +1 -1
  26. package/agents/gsd-ui-auditor.compact.md +21 -30
  27. package/agents/gsd-ui-auditor.md +42 -51
  28. package/agents/gsd-ui-researcher.compact.md +1 -1
  29. package/agents/gsd-ui-researcher.md +1 -1
  30. package/agents/gsd-verifier.md +31 -9
  31. package/bin/gsd-mcp-server.js +3 -1
  32. package/bin/install.js +487 -290
  33. package/commands/gsd/add-tests.md +6 -1
  34. package/commands/gsd/ai-integration-phase.md +6 -1
  35. package/commands/gsd/audit-fix.md +5 -0
  36. package/commands/gsd/audit-milestone.md +6 -1
  37. package/commands/gsd/autonomous.md +5 -0
  38. package/commands/gsd/capture.md +8 -4
  39. package/commands/gsd/code-review.md +7 -2
  40. package/commands/gsd/complete-milestone.md +4 -0
  41. package/commands/gsd/config.md +7 -3
  42. package/commands/gsd/debug.md +11 -7
  43. package/commands/gsd/discuss-phase.md +7 -3
  44. package/commands/gsd/docs-update.md +12 -7
  45. package/commands/gsd/eval-review.md +6 -1
  46. package/commands/gsd/execute-phase.md +12 -7
  47. package/commands/gsd/extract-learnings.md +5 -0
  48. package/commands/gsd/fast.md +4 -0
  49. package/commands/gsd/forensics.md +5 -1
  50. package/commands/gsd/graphify.md +10 -6
  51. package/commands/gsd/health.md +5 -0
  52. package/commands/gsd/help.md +7 -2
  53. package/commands/gsd/import.md +7 -3
  54. package/commands/gsd/inbox.md +5 -0
  55. package/commands/gsd/ingest-docs.md +5 -1
  56. package/commands/gsd/manager.md +6 -1
  57. package/commands/gsd/map-codebase.md +7 -3
  58. package/commands/gsd/mempalace-capture.md +5 -1
  59. package/commands/gsd/mempalace-recall.md +5 -1
  60. package/commands/gsd/milestone-summary.md +5 -1
  61. package/commands/gsd/mvp-phase.md +8 -3
  62. package/commands/gsd/new-milestone.md +6 -1
  63. package/commands/gsd/new-project.md +5 -0
  64. package/commands/gsd/next.md +6 -1
  65. package/commands/gsd/ns-context.md +4 -0
  66. package/commands/gsd/ns-ideate.md +4 -0
  67. package/commands/gsd/ns-manage.md +4 -0
  68. package/commands/gsd/ns-project.md +4 -0
  69. package/commands/gsd/ns-review.md +4 -0
  70. package/commands/gsd/ns-workflow.md +4 -0
  71. package/commands/gsd/onboard.md +6 -1
  72. package/commands/gsd/pause-work.md +5 -1
  73. package/commands/gsd/phase.md +8 -4
  74. package/commands/gsd/plan-phase.md +6 -1
  75. package/commands/gsd/plan-review-convergence.md +5 -1
  76. package/commands/gsd/pr-branch.md +4 -0
  77. package/commands/gsd/profile-user.md +5 -1
  78. package/commands/gsd/progress.md +6 -1
  79. package/commands/gsd/quick-batch.md +20 -8
  80. package/commands/gsd/quick.md +12 -7
  81. package/commands/gsd/review.md +5 -1
  82. package/commands/gsd/secure-phase.md +6 -1
  83. package/commands/gsd/ship.md +5 -0
  84. package/commands/gsd/sketch.md +7 -2
  85. package/commands/gsd/spec-phase.md +5 -1
  86. package/commands/gsd/spike.md +8 -3
  87. package/commands/gsd/surface.md +5 -1
  88. package/commands/gsd/thread.md +4 -0
  89. package/commands/gsd/ui-phase.md +6 -1
  90. package/commands/gsd/ui-review.md +6 -1
  91. package/commands/gsd/ultraplan-phase.md +5 -1
  92. package/commands/gsd/undo.md +5 -1
  93. package/commands/gsd/update.md +6 -2
  94. package/commands/gsd/validate-phase.md +6 -1
  95. package/commands/gsd/verify-work.md +6 -1
  96. package/commands/gsd/workspace.md +7 -3
  97. package/gsd-core/bin/gsd-tools.cjs +159 -58
  98. package/gsd-core/bin/lib/active-workstream-store.cjs +15 -0
  99. package/gsd-core/bin/lib/adr-parser.cjs +37 -5
  100. package/gsd-core/bin/lib/agent-install-check.cjs +4 -1
  101. package/gsd-core/bin/lib/audit.cjs +78 -44
  102. package/gsd-core/bin/lib/broken-windows.cjs +13 -13
  103. package/gsd-core/bin/lib/capability-activation.cjs +9 -4
  104. package/gsd-core/bin/lib/capability-registry.cjs +249 -117
  105. package/gsd-core/bin/lib/capability-validator.cjs +18 -1
  106. package/gsd-core/bin/lib/capability-writer.cjs +2 -1
  107. package/gsd-core/bin/lib/check-auto-mode.cjs +35 -0
  108. package/gsd-core/bin/lib/check-command-router.cjs +164 -1712
  109. package/gsd-core/bin/lib/cjs-command-router-adapter.cjs +13 -2
  110. package/gsd-core/bin/lib/cli-exit.cjs +12 -0
  111. package/gsd-core/bin/lib/codex-agent-toml.cjs +11 -8
  112. package/gsd-core/bin/lib/command-aliases.cjs +7 -0
  113. package/gsd-core/bin/lib/command-routing-hub.cjs +48 -1
  114. package/gsd-core/bin/lib/commands.cjs +201 -178
  115. package/gsd-core/bin/lib/complexity-trigger.cjs +8 -7
  116. package/gsd-core/bin/lib/config.cjs +43 -12
  117. package/gsd-core/bin/lib/core-utils.cjs +6 -1
  118. package/gsd-core/bin/lib/coverage.cjs +4 -8
  119. package/gsd-core/bin/lib/decision-coverage-support.cjs +259 -0
  120. package/gsd-core/bin/lib/drift.cjs +177 -42
  121. package/gsd-core/bin/lib/frontmatter-fence.cjs +90 -0
  122. package/gsd-core/bin/lib/frontmatter-splice.cjs +494 -0
  123. package/gsd-core/bin/lib/frontmatter.cjs +413 -234
  124. package/gsd-core/bin/lib/gap-checker.cjs +72 -29
  125. package/gsd-core/bin/lib/gate-api-coverage-verify-pre.cjs +381 -0
  126. package/gsd-core/bin/lib/gate-args.cjs +53 -0
  127. package/gsd-core/bin/lib/gate-codebase-drift.cjs +285 -0
  128. package/gsd-core/bin/lib/gate-config.cjs +46 -0
  129. package/gsd-core/bin/lib/gate-context-drift.cjs +142 -0
  130. package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +169 -0
  131. package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +126 -0
  132. package/gsd-core/bin/lib/gate-evaluation-scope.cjs +555 -0
  133. package/gsd-core/bin/lib/gate-evidence.cjs +138 -0
  134. package/gsd-core/bin/lib/gate-exit.cjs +27 -0
  135. package/gsd-core/bin/lib/gate-gap-analysis-plan-post.cjs +61 -0
  136. package/gsd-core/bin/lib/gate-phase-context.cjs +170 -0
  137. package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +1 -1
  138. package/gsd-core/bin/lib/gate-predicate.cjs +165 -0
  139. package/gsd-core/bin/lib/gate-prohibition-enforcement.cjs +94 -0
  140. package/gsd-core/bin/lib/gate-schema-drift.cjs +165 -0
  141. package/gsd-core/bin/lib/gate-tdd-red-evidence.cjs +100 -0
  142. package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +182 -0
  143. package/gsd-core/bin/lib/gate-ui-plan.cjs +86 -0
  144. package/gsd-core/bin/lib/gate-ui-safety.cjs +81 -0
  145. package/gsd-core/bin/lib/gate-verdict.cjs +64 -0
  146. package/gsd-core/bin/lib/gate-verify-command-paths.cjs +78 -0
  147. package/gsd-core/bin/lib/gate-verify-failure-directions.cjs +41 -0
  148. package/gsd-core/bin/lib/graphify.cjs +3 -2
  149. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +43 -47
  150. package/gsd-core/bin/lib/health-diagnostic.cjs +45 -8
  151. package/gsd-core/bin/lib/init.cjs +194 -108
  152. package/gsd-core/bin/lib/install-engine.cjs +30 -35
  153. package/gsd-core/bin/lib/install-profiles.cjs +7 -4
  154. package/gsd-core/bin/lib/install-shadow-report.cjs +12 -0
  155. package/gsd-core/bin/lib/installer-migrations/011-opencode-mcp-no-prompts.cjs +99 -0
  156. package/gsd-core/bin/lib/installer-migrations.cjs +54 -1
  157. package/gsd-core/bin/lib/io.cjs +122 -3
  158. package/gsd-core/bin/lib/jsonc-edit.cjs +418 -0
  159. package/gsd-core/bin/lib/loop-host-contract.cjs +2 -1
  160. package/gsd-core/bin/lib/loop-resolver.cjs +95 -0
  161. package/gsd-core/bin/lib/markdown-sectionizer.cjs +75 -1
  162. package/gsd-core/bin/lib/mcp-server.cjs +5 -1
  163. package/gsd-core/bin/lib/milestone.cjs +19 -1
  164. package/gsd-core/bin/lib/model-resolver.cjs +18 -18
  165. package/gsd-core/bin/lib/observability/event.cjs +1 -1
  166. package/gsd-core/bin/lib/observability/logger.cjs +46 -1
  167. package/gsd-core/bin/lib/opencode-mcp-entry.cjs +39 -0
  168. package/gsd-core/bin/lib/opencode-version.cjs +486 -0
  169. package/gsd-core/bin/lib/pattern.cjs +10 -0
  170. package/gsd-core/bin/lib/phase-command-router.cjs +11 -4
  171. package/gsd-core/bin/lib/phase-estimation.cjs +5 -4
  172. package/gsd-core/bin/lib/phase-id.cjs +1 -1
  173. package/gsd-core/bin/lib/phase-lifecycle.cjs +9 -2
  174. package/gsd-core/bin/lib/phase-status.cjs +360 -0
  175. package/gsd-core/bin/lib/phase.cjs +301 -80
  176. package/gsd-core/bin/lib/plan-document.cjs +93 -19
  177. package/gsd-core/bin/lib/plan-drift-guard.cjs +5 -0
  178. package/gsd-core/bin/lib/planning-document.cjs +273 -40
  179. package/gsd-core/bin/lib/planning-inspect.cjs +42 -8
  180. package/gsd-core/bin/lib/planning-snapshot.cjs +18 -0
  181. package/gsd-core/bin/lib/planning-workspace.cjs +76 -53
  182. package/gsd-core/bin/lib/pristine-baseline.cjs +10 -0
  183. package/gsd-core/bin/lib/profile-output.cjs +6 -3
  184. package/gsd-core/bin/lib/prohibition-enforcement.cjs +0 -55
  185. package/gsd-core/bin/lib/quick-batch-command-router.cjs +35 -9
  186. package/gsd-core/bin/lib/quick-batch-dispatch.cjs +13 -9
  187. package/gsd-core/bin/lib/real-home-guard.cjs +9 -1
  188. package/gsd-core/bin/lib/report-parser.cjs +269 -0
  189. package/gsd-core/bin/lib/roadmap-command-router.cjs +13 -15
  190. package/gsd-core/bin/lib/roadmap-parser.cjs +82 -15
  191. package/gsd-core/bin/lib/roadmap-upgrade.cjs +127 -65
  192. package/gsd-core/bin/lib/roadmap.cjs +169 -72
  193. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +186 -161
  194. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +2 -1
  195. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +57 -1
  196. package/gsd-core/bin/lib/runtime-homes.cjs +13 -10
  197. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +365 -594
  198. package/gsd-core/bin/lib/runtime-name-policy.cjs +142 -27
  199. package/gsd-core/bin/lib/runtime-slash.cjs +60 -30
  200. package/gsd-core/bin/lib/shell-command-projection.cjs +37 -2
  201. package/gsd-core/bin/lib/smart-entry.cjs +111 -56
  202. package/gsd-core/bin/lib/stale-bake-guard.cjs +32 -48
  203. package/gsd-core/bin/lib/state-contract.cjs +15 -18
  204. package/gsd-core/bin/lib/state-document.cjs +100 -22
  205. package/gsd-core/bin/lib/state-transition.cjs +67 -9
  206. package/gsd-core/bin/lib/state.cjs +668 -171
  207. package/gsd-core/bin/lib/surface.cjs +2 -1
  208. package/gsd-core/bin/lib/tdd-red-evidence.cjs +48 -152
  209. package/gsd-core/bin/lib/uat-predicate.cjs +313 -35
  210. package/gsd-core/bin/lib/uat.cjs +424 -7
  211. package/gsd-core/bin/lib/ui-frontend-evidence.cjs +94 -58
  212. package/gsd-core/bin/lib/update-context.cjs +18 -1
  213. package/gsd-core/bin/lib/vendor/README.md +31 -9
  214. package/gsd-core/bin/lib/vendor/saxes.cjs +1934 -0
  215. package/gsd-core/bin/lib/vendor/saxes.cjs.LICENSE.txt +92 -0
  216. package/gsd-core/bin/lib/vendor/tap-parser.cjs +8927 -0
  217. package/gsd-core/bin/lib/vendor/tap-parser.cjs.LICENSE.txt +152 -0
  218. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  219. package/gsd-core/bin/lib/verification.cjs +1265 -324
  220. package/gsd-core/bin/lib/verify-command-router.cjs +18 -7
  221. package/gsd-core/bin/lib/verify.cjs +286 -567
  222. package/gsd-core/bin/lib/workstream-inventory-builder.cjs +14 -6
  223. package/gsd-core/bin/lib/workstream-inventory.cjs +31 -21
  224. package/gsd-core/bin/lib/workstream-name-policy.cjs +31 -1
  225. package/gsd-core/bin/lib/workstream.cjs +11 -2
  226. package/gsd-core/bin/shared/config-defaults.manifest.json +5 -0
  227. package/gsd-core/bin/shared/config-schema.manifest.json +4 -0
  228. package/gsd-core/references/autonomous-smart-discuss.md +2 -1
  229. package/gsd-core/references/autonomous-ui-design-contract.md +3 -3
  230. package/gsd-core/references/execute-mvp-tdd.md +5 -10
  231. package/gsd-core/references/execute-phase-between-wave-reset.md +3 -0
  232. package/gsd-core/references/execute-phase-response-language.md +1 -1
  233. package/gsd-core/references/gsd-run-resolver.md +1 -1
  234. package/gsd-core/references/loop-hook-dispatch.md +7 -1
  235. package/gsd-core/references/offer-next.md +1 -1
  236. package/gsd-core/references/planning-config.md +1 -1
  237. package/gsd-core/references/spidr-splitting.md +1 -1
  238. package/gsd-core/references/tdd.md +10 -4
  239. package/gsd-core/references/verifier-phase-gates.md +5 -2
  240. package/gsd-core/references/verify-mvp-mode.md +2 -2
  241. package/gsd-core/references/workstream-flag.md +33 -3
  242. package/gsd-core/templates/README.md +1 -1
  243. package/gsd-core/templates/UAT.md +17 -1
  244. package/gsd-core/templates/config.json +2 -11
  245. package/gsd-core/templates/verification-report.md +1 -1
  246. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  247. package/gsd-core/workflows/add-backlog.md +1 -1
  248. package/gsd-core/workflows/add-phase.md +8 -7
  249. package/gsd-core/workflows/add-tests.md +3 -2
  250. package/gsd-core/workflows/add-todo.md +4 -3
  251. package/gsd-core/workflows/ai-integration-phase.md +3 -2
  252. package/gsd-core/workflows/audit-fix.md +1 -1
  253. package/gsd-core/workflows/audit-milestone.md +4 -3
  254. package/gsd-core/workflows/audit-uat.md +1 -1
  255. package/gsd-core/workflows/autonomous.md +28 -18
  256. package/gsd-core/workflows/check-todos.md +6 -5
  257. package/gsd-core/workflows/cleanup.md +1 -1
  258. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +20 -13
  259. package/gsd-core/workflows/code-review-fix.md +5 -4
  260. package/gsd-core/workflows/code-review.md +100 -91
  261. package/gsd-core/workflows/complete-milestone/detail/elaboration.md +4 -3
  262. package/gsd-core/workflows/complete-milestone.md +12 -7
  263. package/gsd-core/workflows/debug.md +30 -5
  264. package/gsd-core/workflows/diagnose-issues.md +3 -2
  265. package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
  266. package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
  267. package/gsd-core/workflows/discuss-phase-assumptions.md +4 -3
  268. package/gsd-core/workflows/discuss-phase.md +4 -3
  269. package/gsd-core/workflows/do.md +1 -1
  270. package/gsd-core/workflows/docs-update.md +4 -3
  271. package/gsd-core/workflows/edit-phase.md +4 -3
  272. package/gsd-core/workflows/eval-review.md +5 -3
  273. package/gsd-core/workflows/execute-phase/detail/elaboration.md +1 -1
  274. package/gsd-core/workflows/execute-phase/steps/code-review-disposition.md +4 -2
  275. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +15 -4
  276. package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +10 -7
  277. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +2 -2
  278. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +3 -1
  279. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +2 -2
  280. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
  281. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +1 -1
  282. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +1 -1
  283. package/gsd-core/workflows/execute-phase/steps/protected-branch.md +1 -1
  284. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +1 -1
  285. package/gsd-core/workflows/execute-phase/steps/regression-gate.md +2 -0
  286. package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +1 -1
  287. package/gsd-core/workflows/execute-phase/steps/verify-phase-goal.md +187 -0
  288. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +2 -3
  289. package/gsd-core/workflows/execute-phase.md +81 -171
  290. package/gsd-core/workflows/execute-plan.md +13 -19
  291. package/gsd-core/workflows/explore.md +2 -2
  292. package/gsd-core/workflows/extract-learnings.md +3 -2
  293. package/gsd-core/workflows/fast.md +1 -1
  294. package/gsd-core/workflows/forensics.md +1 -1
  295. package/gsd-core/workflows/graduation.md +1 -1
  296. package/gsd-core/workflows/health.md +2 -1
  297. package/gsd-core/workflows/import.md +3 -2
  298. package/gsd-core/workflows/inbox.md +1 -1
  299. package/gsd-core/workflows/ingest-docs.md +1 -1
  300. package/gsd-core/workflows/insert-phase.md +4 -3
  301. package/gsd-core/workflows/list-seeds.md +1 -1
  302. package/gsd-core/workflows/list-workspaces.md +1 -1
  303. package/gsd-core/workflows/manager.md +5 -3
  304. package/gsd-core/workflows/map-codebase.md +4 -3
  305. package/gsd-core/workflows/milestone-summary.md +3 -2
  306. package/gsd-core/workflows/mvp-phase.md +14 -14
  307. package/gsd-core/workflows/new-milestone.md +10 -10
  308. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
  309. package/gsd-core/workflows/new-project.md +1 -1
  310. package/gsd-core/workflows/new-workspace.md +1 -1
  311. package/gsd-core/workflows/next.md +1 -1
  312. package/gsd-core/workflows/pause-work.md +2 -2
  313. package/gsd-core/workflows/plan-phase/detail/elaboration.md +1 -1
  314. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +1 -1
  315. package/gsd-core/workflows/plan-phase/steps/closed-phase-gate.md +1 -1
  316. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +1 -1
  317. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +1 -1
  318. package/gsd-core/workflows/plan-phase.md +27 -18
  319. package/gsd-core/workflows/plan-review-convergence.md +1 -1
  320. package/gsd-core/workflows/plant-seed.md +1 -1
  321. package/gsd-core/workflows/pr-branch.md +1 -1
  322. package/gsd-core/workflows/profile-user.md +1 -1
  323. package/gsd-core/workflows/progress.md +19 -49
  324. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +4 -2
  325. package/gsd-core/workflows/quick/steps/quick-verification.md +4 -4
  326. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +30 -11
  327. package/gsd-core/workflows/quick-batch/steps/batch-init.md +1 -1
  328. package/gsd-core/workflows/quick-batch/steps/completion.md +1 -1
  329. package/gsd-core/workflows/quick-batch/steps/merge-wave.md +1 -1
  330. package/gsd-core/workflows/quick-batch/steps/planner-wave.md +1 -1
  331. package/gsd-core/workflows/quick-batch/steps/research-phase.md +1 -1
  332. package/gsd-core/workflows/quick-batch/steps/resume-mode.md +1 -1
  333. package/gsd-core/workflows/quick-batch/steps/verification-wave.md +9 -3
  334. package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +1 -1
  335. package/gsd-core/workflows/quick-batch.md +15 -10
  336. package/gsd-core/workflows/quick.md +42 -31
  337. package/gsd-core/workflows/remove-phase.md +3 -2
  338. package/gsd-core/workflows/remove-workspace.md +1 -1
  339. package/gsd-core/workflows/resume-project.md +17 -12
  340. package/gsd-core/workflows/review.md +3 -2
  341. package/gsd-core/workflows/scan.md +3 -2
  342. package/gsd-core/workflows/secure-phase.md +11 -12
  343. package/gsd-core/workflows/settings-advanced.md +1 -1
  344. package/gsd-core/workflows/settings-integrations.md +1 -1
  345. package/gsd-core/workflows/settings.md +1 -1
  346. package/gsd-core/workflows/ship.md +12 -12
  347. package/gsd-core/workflows/sketch-wrap-up.md +1 -1
  348. package/gsd-core/workflows/sketch.md +1 -1
  349. package/gsd-core/workflows/smart-entry.md +1 -1
  350. package/gsd-core/workflows/spec-phase.md +1 -1
  351. package/gsd-core/workflows/spike-wrap-up.md +1 -1
  352. package/gsd-core/workflows/spike.md +1 -1
  353. package/gsd-core/workflows/stats.md +1 -1
  354. package/gsd-core/workflows/sync-skills.md +1 -1
  355. package/gsd-core/workflows/thread.md +2 -2
  356. package/gsd-core/workflows/transition.md +13 -23
  357. package/gsd-core/workflows/ui-phase.md +5 -4
  358. package/gsd-core/workflows/ui-review.md +4 -3
  359. package/gsd-core/workflows/ultraplan-phase.md +3 -2
  360. package/gsd-core/workflows/undo.md +1 -1
  361. package/gsd-core/workflows/validate-phase.md +10 -12
  362. package/gsd-core/workflows/verify-work/detail/elaboration.md +43 -3
  363. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +1 -1
  364. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +5 -3
  365. package/gsd-core/workflows/verify-work.md +68 -59
  366. package/hooks/dist/gsd-agent-isolation-guard.js +8 -0
  367. package/hooks/dist/gsd-check-update-worker.js +8 -0
  368. package/hooks/dist/gsd-check-update.js +34 -20
  369. package/hooks/dist/gsd-context-monitor.js +45 -13
  370. package/hooks/dist/gsd-cursor-subagent-start.js +8 -0
  371. package/hooks/dist/gsd-secret-read-guard.js +263 -16
  372. package/hooks/dist/gsd-statusline.js +336 -29
  373. package/hooks/dist/gsd-update-banner.js +8 -0
  374. package/hooks/dist/gsd-windsurf-pre-write.js +11 -2
  375. package/hooks/dist/gsd-workflow-guard.js +5 -4
  376. package/hooks/dist/gsd-worktree-path-guard.js +6 -2
  377. package/hooks/dist/lib/cli-exit.js +12 -0
  378. package/hooks/dist/lib/git-probe.js +17 -1
  379. package/hooks/dist/lib/isolation-sentinel.js +2 -2
  380. package/hooks/gsd-agent-isolation-guard.js +8 -0
  381. package/hooks/gsd-check-update-worker.js +8 -0
  382. package/hooks/gsd-check-update.js +34 -20
  383. package/hooks/gsd-context-monitor.js +45 -13
  384. package/hooks/gsd-cursor-subagent-start.js +8 -0
  385. package/hooks/gsd-secret-read-guard.js +263 -16
  386. package/hooks/gsd-statusline.js +336 -29
  387. package/hooks/gsd-update-banner.js +8 -0
  388. package/hooks/gsd-windsurf-pre-write.js +11 -2
  389. package/hooks/gsd-workflow-guard.js +5 -4
  390. package/hooks/gsd-worktree-path-guard.js +6 -2
  391. package/hooks/hooks.json +5 -5
  392. package/hooks/lib/cli-exit.js +12 -0
  393. package/hooks/lib/git-probe.js +17 -1
  394. package/hooks/lib/isolation-sentinel.js +2 -2
  395. package/package.json +21 -4
  396. package/scripts/changeset/parse.cjs +52 -4
  397. package/scripts/ci-timeout-report.cjs +770 -4
  398. package/scripts/command-contract-helpers.cjs +12 -8
  399. package/scripts/docs-guard-registry.cjs +6 -0
  400. package/scripts/gen-capability-registry.cjs +7 -1
  401. package/scripts/gen-features.cjs +13 -8
  402. package/scripts/gen-hooks-cli-exit.cjs +12 -28
  403. package/scripts/gen-loop-host-contract.cjs +14 -2
  404. package/scripts/gen-platform-conformance-tier.cjs +187 -1
  405. package/scripts/gen-plugin-skills.cjs +87 -1
  406. package/scripts/gen-research-agents.cjs +24 -31
  407. package/scripts/gen-scripts-cli-exit.cjs +30 -3
  408. package/scripts/gen-test-timings.cjs +32 -7
  409. package/scripts/lib/cli-exit.cjs +12 -0
  410. package/scripts/lib/macos-conformance-tier.generated.cjs +24 -2
  411. package/scripts/lib/ndjson-reporter.cjs +28 -3
  412. package/scripts/lib/platform-conformance-tier.generated.cjs +44 -5
  413. package/scripts/lib/registration-ledger-preload.cjs +155 -0
  414. package/scripts/lib/vendor-bundle.cjs +59 -0
  415. package/scripts/lib/vendor-licenses/saxes-6.0.0.txt +64 -0
  416. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
  417. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
  418. package/scripts/lint-completion-predicate-drift.cjs +18 -19
  419. package/scripts/lint-descriptions.cjs +7 -3
  420. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +10 -0
  421. package/scripts/lint-eslint-glob-coverage.allowlist.json +20 -0
  422. package/scripts/lint-frontmatter-fence-drift.cjs +313 -0
  423. package/scripts/lint-frontmatter-scalar-broad-grep.cjs +122 -16
  424. package/scripts/lint-phase-enumeration-drift.cjs +12 -8
  425. package/scripts/lint-phase-id-drift.cjs +29 -0
  426. package/scripts/lint-planning-document-positive-control.cjs +329 -0
  427. package/scripts/lint-response-language-coverage.cjs +3 -0
  428. package/scripts/lint-skill-deps.cjs +7 -3
  429. package/scripts/lint-test-file-count.allowlist.json +11 -1
  430. package/scripts/lint-test-file-count.cjs +34 -1
  431. package/scripts/lint-vendored-deps.cjs +41 -5
  432. package/scripts/lint-workflow-shellcheck-baseline.json +10 -10
  433. package/scripts/mutation-matrix.cjs +53 -6
  434. package/scripts/release-tarball-smoke.cjs +14 -7
  435. package/scripts/require-issue-link-policy.cjs +6 -2
  436. package/scripts/sync-runtime-launcher.cjs +184 -2
  437. package/scripts/verify-npm-publish.cjs +76 -20
  438. package/skills/gsd-add-tests/SKILL.md +6 -1
  439. package/skills/gsd-ai-integration-phase/SKILL.md +6 -1
  440. package/skills/gsd-audit-fix/SKILL.md +5 -0
  441. package/skills/gsd-audit-milestone/SKILL.md +6 -1
  442. package/skills/gsd-autonomous/SKILL.md +5 -0
  443. package/skills/gsd-capture/SKILL.md +8 -4
  444. package/skills/gsd-code-review/SKILL.md +7 -2
  445. package/skills/gsd-complete-milestone/SKILL.md +4 -0
  446. package/skills/gsd-config/SKILL.md +8 -4
  447. package/skills/gsd-debug/SKILL.md +11 -7
  448. package/skills/gsd-discuss-phase/SKILL.md +7 -3
  449. package/skills/gsd-docs-update/SKILL.md +12 -7
  450. package/skills/gsd-eval-review/SKILL.md +6 -1
  451. package/skills/gsd-execute-phase/SKILL.md +12 -7
  452. package/skills/gsd-extract-learnings/SKILL.md +5 -0
  453. package/skills/gsd-fast/SKILL.md +4 -0
  454. package/skills/gsd-forensics/SKILL.md +5 -1
  455. package/skills/gsd-graphify/SKILL.md +10 -6
  456. package/skills/gsd-health/SKILL.md +5 -0
  457. package/skills/gsd-help/SKILL.md +7 -2
  458. package/skills/gsd-import/SKILL.md +7 -3
  459. package/skills/gsd-inbox/SKILL.md +5 -0
  460. package/skills/gsd-ingest-docs/SKILL.md +5 -1
  461. package/skills/gsd-manager/SKILL.md +6 -1
  462. package/skills/gsd-map-codebase/SKILL.md +7 -3
  463. package/skills/gsd-mempalace-capture/SKILL.md +5 -1
  464. package/skills/gsd-mempalace-recall/SKILL.md +5 -1
  465. package/skills/gsd-milestone-summary/SKILL.md +5 -1
  466. package/skills/gsd-mvp-phase/SKILL.md +8 -3
  467. package/skills/gsd-new-milestone/SKILL.md +6 -1
  468. package/skills/gsd-new-project/SKILL.md +5 -0
  469. package/skills/gsd-next/SKILL.md +6 -1
  470. package/skills/gsd-ns-context/SKILL.md +4 -0
  471. package/skills/gsd-ns-ideate/SKILL.md +4 -0
  472. package/skills/gsd-ns-manage/SKILL.md +4 -0
  473. package/skills/gsd-ns-project/SKILL.md +4 -0
  474. package/skills/gsd-ns-review/SKILL.md +4 -0
  475. package/skills/gsd-ns-workflow/SKILL.md +4 -0
  476. package/skills/gsd-onboard/SKILL.md +6 -1
  477. package/skills/gsd-pause-work/SKILL.md +5 -1
  478. package/skills/gsd-phase/SKILL.md +8 -4
  479. package/skills/gsd-plan-phase/SKILL.md +6 -1
  480. package/skills/gsd-plan-review-convergence/SKILL.md +5 -1
  481. package/skills/gsd-pr-branch/SKILL.md +4 -0
  482. package/skills/gsd-profile-user/SKILL.md +5 -1
  483. package/skills/gsd-progress/SKILL.md +6 -1
  484. package/skills/gsd-quick/SKILL.md +16 -10
  485. package/skills/gsd-quick-batch/SKILL.md +20 -8
  486. package/skills/gsd-review/SKILL.md +5 -1
  487. package/skills/gsd-review-backlog/SKILL.md +3 -2
  488. package/skills/gsd-secure-phase/SKILL.md +6 -1
  489. package/skills/gsd-ship/SKILL.md +5 -0
  490. package/skills/gsd-sketch/SKILL.md +7 -2
  491. package/skills/gsd-spec-phase/SKILL.md +5 -1
  492. package/skills/gsd-spike/SKILL.md +8 -3
  493. package/skills/gsd-surface/SKILL.md +5 -1
  494. package/skills/gsd-thread/SKILL.md +4 -0
  495. package/skills/gsd-ui-phase/SKILL.md +6 -1
  496. package/skills/gsd-ui-review/SKILL.md +6 -1
  497. package/skills/gsd-ultraplan-phase/SKILL.md +5 -1
  498. package/skills/gsd-undo/SKILL.md +5 -1
  499. package/skills/gsd-update/SKILL.md +6 -2
  500. package/skills/gsd-validate-phase/SKILL.md +6 -1
  501. package/skills/gsd-verify-work/SKILL.md +6 -1
  502. package/skills/gsd-workspace/SKILL.md +7 -3
  503. package/skills/gsd-workstreams/SKILL.md +6 -6
  504. package/vscode/package.json +1 -1
  505. package/gsd-core/workflows/execute-phase/steps/stale-reverification.md +0 -24
@@ -33,11 +33,12 @@ const { installFs, mkInstallTempDir } = installFsAdapter;
33
33
  const commandRoster = require("./command-roster.cjs");
34
34
  const { readGsdCommandNames, transformContentToHyphen } = commandRoster;
35
35
  const runtimeNamePolicy = require("./runtime-name-policy.cjs");
36
- const { getDirName } = runtimeNamePolicy;
36
+ const { getDirName, hostBehaviorsFor } = runtimeNamePolicy;
37
37
  const capabilityRegistry = require("./capability-registry.cjs");
38
38
  const hostIntegration = require("./host-integration.cjs");
39
39
  const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
40
40
  const frontmatterModule = require("./frontmatter.cjs");
41
+ const frontmatter_fence_cjs_1 = require("./frontmatter-fence.cjs");
41
42
  const pattern_cjs_1 = require("./pattern.cjs");
42
43
  const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
43
44
  // #2870: install-scope.cts is a leaf-tier sibling (imports only
@@ -93,24 +94,6 @@ function gsdVersion() {
93
94
  cachedVersion = resolveVersionFrom(__dirname);
94
95
  return cachedVersion;
95
96
  }
96
- /**
97
- * Host-specific install behaviors declared on the runtime descriptor
98
- * (capabilities/<runtime>/capability.json -> runtime.hostBehaviors). Mirrors
99
- * bin/install.js's / install-engine.cts's `_hostBehaviors` (ADR-1239 / #2086
100
- * / #2092). Returns {} for runtimes that declare none, so every behavior
101
- * branch degrades to the generic path by default. Unlike the bin/install.js
102
- * and install-engine.cts variants, this module already imports
103
- * `capabilityRegistry` statically (see NON_CLAUDE_RUNTIMES below), so this
104
- * reads it directly rather than re-require()-ing inside a try/catch.
105
- */
106
- function _hostBehaviors(runtime) {
107
- return ((capabilityRegistry &&
108
- capabilityRegistry.runtimes &&
109
- capabilityRegistry.runtimes[runtime] &&
110
- capabilityRegistry.runtimes[runtime].runtime &&
111
- capabilityRegistry.runtimes[runtime].runtime.hostBehaviors) ||
112
- {});
113
- }
114
97
  /**
115
98
  * Public accessor for the `hostBehaviors.agentFileExtension` descriptor field
116
99
  * (ADR-1239 / #2099 / #2103). Returns the runtime's declared agent-file
@@ -122,7 +105,7 @@ function _hostBehaviors(runtime) {
122
105
  * duplicating a hardcoded `runtime === 'copilot'` check (#2103 fold).
123
106
  */
124
107
  function agentFileExtensionFor(runtime) {
125
- const ext = _hostBehaviors(runtime).agentFileExtension;
108
+ const ext = hostBehaviorsFor(runtime).agentFileExtension;
126
109
  return typeof ext === 'string' ? ext : undefined;
127
110
  }
128
111
  const colorNameToHex = {
@@ -312,6 +295,61 @@ function buildKiloAgentPermissionBlock(claudeTools) {
312
295
  }
313
296
  return lines;
314
297
  }
298
+ // OpenCode agent tool restrictions (#5338, ADR-5331 D4). Claude's `tools:` allowlist and
299
+ // `disallowedTools:` denylist become OpenCode `permission:` entries. The V1 `permission` form is the one
300
+ // written because OpenCode 2.x translates it automatically ("legacy agent frontmatter … is translated"),
301
+ // so one form serves both generations — verified: a `permission: {edit: deny, bash: deny}` agent drops the
302
+ // edit/write/shell tools from the model's tool list on 1.18.29 and 2.0.27. Only denials are emitted: an
303
+ // allowed tool keeps the user's own configured default instead of being forced to `allow`, and MCP tool
304
+ // entries are not restricted.
305
+ const claudeToOpencodePermission = {
306
+ Read: 'read',
307
+ Write: 'edit',
308
+ Edit: 'edit',
309
+ MultiEdit: 'edit',
310
+ NotebookEdit: 'edit',
311
+ Bash: 'bash',
312
+ Grep: 'grep',
313
+ Glob: 'glob',
314
+ WebFetch: 'webfetch',
315
+ WebSearch: 'websearch',
316
+ Task: 'task',
317
+ Agent: 'task',
318
+ Skill: 'skill',
319
+ SlashCommand: 'skill',
320
+ TodoWrite: 'todowrite',
321
+ };
322
+ const opencodeAgentPermissionOrder = ['read', 'edit', 'bash', 'grep', 'glob', 'webfetch', 'websearch', 'task', 'skill', 'todowrite'];
323
+ /**
324
+ * `permission:` frontmatter lines for an OpenCode agent, or `[]` when the agent declares no tool
325
+ * restriction (an agent with neither `tools` nor `disallowedTools` inherits everything, as in Claude).
326
+ */
327
+ function buildOpencodeAgentPermissionBlock(allowedTools, disallowedTools) {
328
+ const allowed = new Set();
329
+ for (const tool of allowedTools) {
330
+ const mapped = claudeToOpencodePermission[tool];
331
+ if (mapped)
332
+ allowed.add(mapped);
333
+ }
334
+ const denied = new Set();
335
+ if (allowedTools.length > 0) {
336
+ for (const permission of opencodeAgentPermissionOrder) {
337
+ if (!allowed.has(permission))
338
+ denied.add(permission);
339
+ }
340
+ }
341
+ for (const tool of disallowedTools) {
342
+ const mapped = claudeToOpencodePermission[tool];
343
+ if (mapped)
344
+ denied.add(mapped);
345
+ }
346
+ const lines = [];
347
+ for (const permission of opencodeAgentPermissionOrder) {
348
+ if (denied.has(permission))
349
+ lines.push(` ${permission}: deny`);
350
+ }
351
+ return lines.length > 0 ? ['permission:', ...lines] : [];
352
+ }
315
353
  function replaceRelativePathReference(content, fromPath, toPath) {
316
354
  const escapedPath = (0, pattern_cjs_1.escapeRegex)(fromPath);
317
355
  return content.replace(new RegExp(`(^|[^A-Za-z0-9_./-])${escapedPath}`, 'g'), (_, prefix) => `${prefix}${toPath}`);
@@ -485,7 +523,7 @@ function convertClaudeCommandToClaudeSkill(content, skillName, runtime = null, c
485
523
  // Hermes' SKILL.md spec lists `version` as a required frontmatter field.
486
524
  // Track GSD's package version so Hermes' skill_view() reports a stable
487
525
  // identifier per install.
488
- if (runtime === 'hermes') {
526
+ if (hostBehaviorsFor(runtime).skillFrontmatterVersion) {
489
527
  const version = gsdVersion();
490
528
  if (version)
491
529
  fm += `version: ${yamlQuote(version)}\n`;
@@ -495,7 +533,7 @@ function convertClaudeCommandToClaudeSkill(content, skillName, runtime = null, c
495
533
  // runtimes that declare the flag so Claude/Hermes skill frontmatter is
496
534
  // unchanged (they ignore the field, but we keep their output byte-stable).
497
535
  // skillName is the `gsd-<stem>` dir name. (ADR-1239 / #2092)
498
- if (_hostBehaviors(runtime).skillPriorityFrontmatter) {
536
+ if (hostBehaviorsFor(runtime).skillPriorityFrontmatter) {
499
537
  const stem = typeof skillName === 'string' && skillName.startsWith('gsd-')
500
538
  ? skillName.slice(4)
501
539
  : skillName;
@@ -730,10 +768,13 @@ function appendAgentTools(content, grants) {
730
768
  return content;
731
769
  const eol = content.includes('\r\n') ? '\r\n' : '\n';
732
770
  const lines = content.split(eol);
733
- if (lines[0] !== '---')
771
+ // The block is the one the one fence owner finds; `frontmatterEnd` is its closing fence's
772
+ // line index in `lines`.
773
+ const fence = (0, frontmatter_fence_cjs_1.locateFrontmatterFence)(content);
774
+ if (!fence?.closed)
734
775
  return content;
735
- const frontmatterEnd = lines.indexOf('---', 1);
736
- if (frontmatterEnd === -1)
776
+ const frontmatterEnd = content.slice(0, fence.closingStart).split(eol).length - 1;
777
+ if (frontmatterEnd < 1)
737
778
  return content;
738
779
  const toolsIndex = lines.findIndex((line, index) => index < frontmatterEnd && /^tools:[ \t]*(.*)$/.test(line));
739
780
  if (toolsIndex === -1)
@@ -1080,17 +1121,19 @@ function yamlIdentifier(value) {
1080
1121
  }
1081
1122
  return yamlQuote(text);
1082
1123
  }
1124
+ /**
1125
+ * The frontmatter YAML (trimmed) and everything after the closing fence line's text, for the
1126
+ * block the one fence owner (`locateFrontmatterFence`) finds — so a `---` inside a value (a
1127
+ * description that mentions `a---b`) cannot end the block early.
1128
+ */
1083
1129
  function extractFrontmatterAndBody(content) {
1084
- if (!content.startsWith('---')) {
1085
- return { frontmatter: null, body: content };
1086
- }
1087
- const endIndex = content.indexOf('---', 3);
1088
- if (endIndex === -1) {
1130
+ const fence = (0, frontmatter_fence_cjs_1.locateFrontmatterFence)(content);
1131
+ if (!fence?.closed) {
1089
1132
  return { frontmatter: null, body: content };
1090
1133
  }
1091
1134
  return {
1092
- frontmatter: content.substring(3, endIndex).trim(),
1093
- body: content.substring(endIndex + 3),
1135
+ frontmatter: content.slice(fence.openEnd, fence.bodyEnd).trim(),
1136
+ body: content.slice(fence.closingFenceEnd),
1094
1137
  };
1095
1138
  }
1096
1139
  function extractFrontmatterField(frontmatter, fieldName) {
@@ -1539,7 +1582,7 @@ function convertClaudeCommandToTraeSkill(content, skillName) {
1539
1582
  // is not formally documented (thin SPA docs) — descriptor-driven, single
1540
1583
  // fixed GSD-side value (runtime.hostBehaviors.soloStageMetadata), inferred/
1541
1584
  // best-effort.
1542
- const soloStage = _hostBehaviors('trae').soloStageMetadata;
1585
+ const soloStage = hostBehaviorsFor('trae').soloStageMetadata;
1543
1586
  if (soloStage)
1544
1587
  fm += `stage: ${soloStage}\n`;
1545
1588
  fm += '---';
@@ -1955,23 +1998,21 @@ function convertClaudeToOpencodeFrontmatter(content, { isAgent = false, modelOve
1955
1998
  convertedContent = convertedContent.replace(/subagent_type="general-purpose"/g, 'subagent_type="general"');
1956
1999
  // Runtime-neutral agent name replacement (#766)
1957
2000
  convertedContent = neutralizeAgentReferences(convertedContent, 'AGENTS.md');
1958
- // Check if content has frontmatter
1959
- if (!convertedContent.startsWith('---')) {
2001
+ // The frontmatter block, as the one fence owner finds it (none → nothing to convert).
2002
+ const { frontmatter, body } = extractFrontmatterAndBody(convertedContent);
2003
+ if (frontmatter === null) {
1960
2004
  return convertedContent;
1961
2005
  }
1962
- // Find the end of frontmatter
1963
- const endIndex = convertedContent.indexOf('---', 3);
1964
- if (endIndex === -1) {
1965
- return convertedContent;
1966
- }
1967
- const frontmatter = convertedContent.substring(3, endIndex).trim();
1968
- const body = convertedContent.substring(endIndex + 3);
1969
2006
  // Parse frontmatter line by line (simple YAML parsing)
1970
2007
  const lines = frontmatter.split('\n');
1971
2008
  const newLines = [];
1972
2009
  let inAllowedTools = false;
1973
2010
  let inSkippedArray = false;
2011
+ let inAgentTools = false;
2012
+ let inDisallowedTools = false;
1974
2013
  const allowedTools = [];
2014
+ const agentTools = [];
2015
+ const agentDisallowed = [];
1975
2016
  for (const line of lines) {
1976
2017
  const trimmed = line.trim();
1977
2018
  // For agents: skip commented-out lines (e.g. hooks blocks)
@@ -1983,11 +2024,31 @@ function convertClaudeToOpencodeFrontmatter(content, { isAgent = false, modelOve
1983
2024
  inAllowedTools = true;
1984
2025
  continue;
1985
2026
  }
2027
+ // Agents: collect the `tools:` allowlist and `disallowedTools:` denylist (block-list continuation
2028
+ // lines) so they can be rendered as `permission:` entries below.
2029
+ if (isAgent && (inAgentTools || inDisallowedTools)) {
2030
+ if (trimmed.startsWith('- ')) {
2031
+ const tool = decodeToolScalar(trimmed.substring(2));
2032
+ if (tool !== null)
2033
+ (inAgentTools ? agentTools : agentDisallowed).push(tool);
2034
+ continue;
2035
+ }
2036
+ if (trimmed && !trimmed.startsWith('-')) {
2037
+ inAgentTools = false;
2038
+ inDisallowedTools = false;
2039
+ }
2040
+ }
1986
2041
  // Detect inline tools: field (comma-separated string)
1987
2042
  if (trimmed.startsWith('tools:')) {
1988
2043
  if (isAgent) {
1989
- // Agents: strip tools entirely (not supported in OpenCode agent frontmatter)
1990
- inSkippedArray = true;
2044
+ const toolsValue = trimmed.substring(6).trim();
2045
+ // A comment-only value (`tools: # note`) is not inline content: read the block list instead.
2046
+ if (toolsValue && !toolsValue.startsWith('#')) {
2047
+ agentTools.push(...splitToolScalars(toolsValue).map(decodeToolScalar).filter((tool) => tool !== null));
2048
+ }
2049
+ else {
2050
+ inAgentTools = true;
2051
+ }
1991
2052
  continue;
1992
2053
  }
1993
2054
  const toolsValue = trimmed.substring(6).trim();
@@ -1998,8 +2059,18 @@ function convertClaudeToOpencodeFrontmatter(content, { isAgent = false, modelOve
1998
2059
  }
1999
2060
  continue;
2000
2061
  }
2001
- // For agents: strip skills:, color:, memory:, maxTurns:, permissionMode:, disallowedTools:
2002
- if (isAgent && /^(skills|color|memory|maxTurns|permissionMode|disallowedTools):/.test(trimmed)) {
2062
+ if (isAgent && trimmed.startsWith('disallowedTools:')) {
2063
+ const value = trimmed.substring(16).trim();
2064
+ if (value && !value.startsWith('#')) {
2065
+ agentDisallowed.push(...splitToolScalars(value).map(decodeToolScalar).filter((tool) => tool !== null));
2066
+ }
2067
+ else {
2068
+ inDisallowedTools = true;
2069
+ }
2070
+ continue;
2071
+ }
2072
+ // For agents: strip skills:, color:, memory:, maxTurns:, permissionMode:
2073
+ if (isAgent && /^(skills|color|memory|maxTurns|permissionMode):/.test(trimmed)) {
2003
2074
  inSkippedArray = true;
2004
2075
  continue;
2005
2076
  }
@@ -2083,6 +2154,7 @@ function convertClaudeToOpencodeFrontmatter(content, { isAgent = false, modelOve
2083
2154
  if (variant) {
2084
2155
  newLines.push(frontmatterScalar('variant:', variant));
2085
2156
  }
2157
+ newLines.push(...buildOpencodeAgentPermissionBlock(agentTools, agentDisallowed));
2086
2158
  }
2087
2159
  // For commands: add tools object if we had allowed-tools or tools
2088
2160
  if (!isAgent && allowedTools.length > 0) {
@@ -2121,17 +2193,11 @@ function convertClaudeToKiloFrontmatter(content, { isAgent = false, modelOverrid
2121
2193
  convertedContent = convertedContent.replace(/subagent_type="general-purpose"/g, 'subagent_type="general"');
2122
2194
  // Runtime-neutral agent name replacement (#766)
2123
2195
  convertedContent = neutralizeAgentReferences(convertedContent, 'AGENTS.md');
2124
- // Check if content has frontmatter
2125
- if (!convertedContent.startsWith('---')) {
2126
- return convertedContent;
2127
- }
2128
- // Find the end of frontmatter
2129
- const endIndex = convertedContent.indexOf('---', 3);
2130
- if (endIndex === -1) {
2196
+ // The frontmatter block, as the one fence owner finds it (none → nothing to convert).
2197
+ const { frontmatter, body } = extractFrontmatterAndBody(convertedContent);
2198
+ if (frontmatter === null) {
2131
2199
  return convertedContent;
2132
2200
  }
2133
- const frontmatter = convertedContent.substring(3, endIndex).trim();
2134
- const body = convertedContent.substring(endIndex + 3);
2135
2201
  // Parse frontmatter line by line (simple YAML parsing)
2136
2202
  const lines = frontmatter.split('\n');
2137
2203
  const newLines = [];
@@ -2526,7 +2592,7 @@ function convertClaudeAgentToTraeAgent(content) {
2526
2592
  * forms to avoid double-rewriting the same substring.
2527
2593
  */
2528
2594
  function convertClaudeAgentToQwenAgent(content) {
2529
- const _b = _hostBehaviors('qwen').brandingRewrites || {};
2595
+ const _b = hostBehaviorsFor('qwen').brandingRewrites || {};
2530
2596
  let converted = content;
2531
2597
  if (_b['CLAUDE.md'])
2532
2598
  converted = converted.replace(/CLAUDE\.md/g, _b['CLAUDE.md']);
@@ -2591,18 +2657,14 @@ function convertClaudeAgentToZcodeAgent(content) {
2591
2657
  // present. The unchanged scan below still preserves byte-identical content.
2592
2658
  if (!content.includes('mcp__') && !content.includes('\\'))
2593
2659
  return content;
2594
- const lines = content.split('\n');
2595
- if (lines[0] !== '---')
2660
+ // The block is the one the one fence owner finds; unterminated (or absent) → leave verbatim.
2661
+ // The rewrite below re-joins lines on '\n', so a CRLF block is left verbatim too rather than
2662
+ // given mixed line endings.
2663
+ const fence = (0, frontmatter_fence_cjs_1.locateFrontmatterFence)(content);
2664
+ if (!fence?.closed || fence.eol !== '\n')
2596
2665
  return content;
2597
- let fmEnd = -1;
2598
- for (let i = 1; i < lines.length; i++) {
2599
- if (lines[i] === '---') {
2600
- fmEnd = i;
2601
- break;
2602
- }
2603
- }
2604
- if (fmEnd === -1)
2605
- return content; // unterminated frontmatter — leave verbatim
2666
+ const lines = content.split('\n');
2667
+ const fmEnd = content.slice(0, fence.closingStart).split('\n').length - 1;
2606
2668
  const out = [];
2607
2669
  let changed = false;
2608
2670
  let i = 1;
@@ -2699,12 +2761,12 @@ function convertClaudeAgentToClineAgent(content) {
2699
2761
  * rewrites applied automatically. A runtime with no `brandingRewrites`
2700
2762
  * declared returns `content` unchanged (no rewrite table to apply).
2701
2763
  *
2702
- * Byte-identical to the inline loop's `else if (_hostBehaviors(runtime).brandingRewrites)`
2764
+ * Byte-identical to the inline loop's `else if (hostBehaviorsFor(runtime).brandingRewrites)`
2703
2765
  * branch, including plain (non-word-boundary) `.replace(/\bClaude Code\b/g, ...)`
2704
2766
  * semantics — J9.
2705
2767
  */
2706
2768
  function applyAgentBrandingRewrites(content, runtime) {
2707
- const _b = _hostBehaviors(runtime).brandingRewrites;
2769
+ const _b = hostBehaviorsFor(runtime).brandingRewrites;
2708
2770
  if (!_b)
2709
2771
  return content;
2710
2772
  let converted = content;
@@ -2890,7 +2952,7 @@ function projectRelativePrefixFromProjectRoot(projectRoot, resolvedTarget) {
2890
2952
  * directory in a project-relative include: that directory was never created.
2891
2953
  */
2892
2954
  function localIncludeDirName(runtime) {
2893
- return _hostBehaviors(runtime).localTargetIsProjectRoot === true ? undefined : getDirName(runtime);
2955
+ return hostBehaviorsFor(runtime).localTargetIsProjectRoot === true ? undefined : getDirName(runtime);
2894
2956
  }
2895
2957
  /**
2896
2958
  * #4377: the project-relative prefix for a local install, or `''` when the
@@ -3194,11 +3256,16 @@ function _applyRuntimeRewritesInner(content, runtime, pathPrefix, isGlobal = fal
3194
3256
  // #1521: stamp runtime identity + use_worktrees=false for every non-Claude runtime
3195
3257
  // before brand-specific path rewrites, so the replace operates on the pristine
3196
3258
  // source line and is idempotent regardless of subsequent path substitutions.
3197
- if (runtime !== 'claude') {
3259
+ // #5169: the descriptor declares which hosts skip the stamp
3260
+ // (`hostBehaviors.skipRuntimeDefaultsStamp`) and which rewrite profile applies
3261
+ // (`hostBehaviors.contentRewriteProfile`). The switch below is keyed by PROFILE
3262
+ // name, not runtime id, so two hosts that share a body share one case.
3263
+ const rewriteBehaviors = hostBehaviorsFor(runtime);
3264
+ if (!rewriteBehaviors.skipRuntimeDefaultsStamp) {
3198
3265
  content = _stampNonClaudeRuntimeDefaults(content, runtime);
3199
3266
  }
3200
- switch (runtime) {
3201
- case 'codex':
3267
+ switch (rewriteBehaviors.contentRewriteProfile) {
3268
+ case 'slash-own-tilde':
3202
3269
  content = content.replace(/~\/\.claude\//g, pathPrefix);
3203
3270
  content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
3204
3271
  content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
@@ -3206,7 +3273,7 @@ function _applyRuntimeRewritesInner(content, runtime, pathPrefix, isGlobal = fal
3206
3273
  // #1515 stamp moved to _stampNonClaudeRuntimeDefaults (#1521 generalisation).
3207
3274
  content = processAttribution(content, attribution);
3208
3275
  break;
3209
- case 'cline':
3276
+ case 'slash-bare-own-tilde':
3210
3277
  content = content.replace(/~\/\.claude\//g, pathPrefix);
3211
3278
  content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
3212
3279
  content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
@@ -3218,7 +3285,7 @@ function _applyRuntimeRewritesInner(content, runtime, pathPrefix, isGlobal = fal
3218
3285
  content = content.replace(/\$HOME\/\.cline\b/g, normalizedPathPrefix);
3219
3286
  content = processAttribution(content, attribution);
3220
3287
  break;
3221
- case 'cursor':
3288
+ case 'slash-lookahead-own-tilde':
3222
3289
  content = content.replace(/~\/\.claude\//g, pathPrefix);
3223
3290
  content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
3224
3291
  content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
@@ -3228,7 +3295,7 @@ function _applyRuntimeRewritesInner(content, runtime, pathPrefix, isGlobal = fal
3228
3295
  content = content.replace(/~\/\.cursor\//g, pathPrefix);
3229
3296
  content = processAttribution(content, attribution);
3230
3297
  break;
3231
- case 'windsurf': {
3298
+ case 'slash-lookahead-nested-own-devin': {
3232
3299
  content = content.replace(/~\/\.claude\//g, pathPrefix);
3233
3300
  content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
3234
3301
  content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
@@ -3244,7 +3311,7 @@ function _applyRuntimeRewritesInner(content, runtime, pathPrefix, isGlobal = fal
3244
3311
  content = processAttribution(content, attribution);
3245
3312
  break;
3246
3313
  }
3247
- case 'augment': {
3314
+ case 'slash-lookahead-own-derived': {
3248
3315
  content = content.replace(/~\/\.claude\//g, pathPrefix);
3249
3316
  content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
3250
3317
  content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
@@ -3262,7 +3329,7 @@ function _applyRuntimeRewritesInner(content, runtime, pathPrefix, isGlobal = fal
3262
3329
  content = processAttribution(content, attribution);
3263
3330
  break;
3264
3331
  }
3265
- case 'trae':
3332
+ case 'slash-bare-own-derived':
3266
3333
  content = content.replace(/~\/\.claude\//g, pathPrefix);
3267
3334
  content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
3268
3335
  content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
@@ -3275,7 +3342,7 @@ function _applyRuntimeRewritesInner(content, runtime, pathPrefix, isGlobal = fal
3275
3342
  content = content.replace(new RegExp('~/' + (0, pattern_cjs_1.escapeRegex)(dirName) + '/', 'g'), pathPrefix);
3276
3343
  content = processAttribution(content, attribution);
3277
3344
  break;
3278
- case 'codebuddy':
3345
+ case 'slash-bare-own-tilde-bare':
3279
3346
  content = content.replace(/~\/\.claude\//g, pathPrefix);
3280
3347
  content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
3281
3348
  content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
@@ -3288,13 +3355,14 @@ function _applyRuntimeRewritesInner(content, runtime, pathPrefix, isGlobal = fal
3288
3355
  content = content.replace(/\$HOME\/\.codebuddy\b/g, normalizedPathPrefix);
3289
3356
  content = processAttribution(content, attribution);
3290
3357
  break;
3291
- case 'copilot':
3358
+ case 'attribution-only':
3292
3359
  content = processAttribution(content, attribution);
3293
3360
  break;
3294
- case 'antigravity':
3295
- content = processAttribution(content, attribution);
3296
- break;
3297
- case 'claude':
3361
+ // claude and zcode share this profile. #4002: ZCode is a Claude-Code-shaped
3362
+ // host (dot-home `.zcode`, `@~`-ref expansion, `~/.zcode/...` documented
3363
+ // paths) whose commands install with `converter: null` — this pass is their
3364
+ // only chance to receive runtime-correct paths, including the tilde restore.
3365
+ case 'slash-tilde-restore':
3298
3366
  content = content.replace(/~\/\.claude\//g, pathPrefix);
3299
3367
  content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
3300
3368
  content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
@@ -3308,12 +3376,15 @@ function _applyRuntimeRewritesInner(content, runtime, pathPrefix, isGlobal = fal
3308
3376
  // brand VALUES (CLAUDE.md/Claude Code/.claude/ replacements) now read from
3309
3377
  // runtime.hostBehaviors.brandingRewrites instead of hardcoded literals.
3310
3378
  // EXACT regexes/order preserved — only the replacement values changed.
3311
- case 'qwen': {
3379
+ // qwen and hermes share this profile; their own dot-dir is derived from the
3380
+ // descriptor's `localConfigDir` (`getDirName`) rather than spelled per host.
3381
+ case 'branded-lookahead-own-derived': {
3312
3382
  // Guarded (post-review #2092): brandingRewrites is undefined if the
3313
3383
  // capability registry fails to load — degrade closed (skip the
3314
3384
  // brand-literal replacements, still apply the non-branding path
3315
3385
  // rewrites below) instead of throwing on `_b['CLAUDE.md']`.
3316
- const _b = _hostBehaviors(runtime).brandingRewrites;
3386
+ const _b = rewriteBehaviors.brandingRewrites;
3387
+ const _own = (0, pattern_cjs_1.escapeRegex)(dirName);
3317
3388
  if (_b) {
3318
3389
  content = content.replace(/CLAUDE\.md/g, _b['CLAUDE.md']);
3319
3390
  // #2284(b): skips <runtime_compatibility> comparison-table content (protected region).
@@ -3321,46 +3392,21 @@ function _applyRuntimeRewritesInner(content, runtime, pathPrefix, isGlobal = fal
3321
3392
  }
3322
3393
  content = content.replace(/~\/\.claude\//g, pathPrefix);
3323
3394
  content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
3324
- content = content.replace(/~\/\.qwen\//g, pathPrefix);
3325
- content = content.replace(/\$HOME\/\.qwen\//g, pathPrefix);
3395
+ content = content.replace(new RegExp('~/' + _own + '/', 'g'), pathPrefix);
3396
+ content = content.replace(new RegExp('\\$HOME/' + _own + '/', 'g'), pathPrefix);
3326
3397
  content = content.replace(/~\/\.claude(?![\w-])/g, normalizedPathPrefix);
3327
3398
  content = content.replace(/\$HOME\/\.claude(?![\w-])/g, normalizedPathPrefix);
3328
- content = content.replace(/~\/\.qwen(?![\w-])/g, normalizedPathPrefix);
3329
- content = content.replace(/\$HOME\/\.qwen(?![\w-])/g, normalizedPathPrefix);
3399
+ content = content.replace(new RegExp('~/' + _own + '(?![\\w-])', 'g'), normalizedPathPrefix);
3400
+ content = content.replace(new RegExp('\\$HOME/' + _own + '(?![\\w-])', 'g'), normalizedPathPrefix);
3330
3401
  if (_b) {
3331
3402
  content = content.replace(/\.claude\//g, _b['.claude/']);
3332
3403
  }
3333
3404
  content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
3334
- content = content.replace(/\.\/\.qwen\//g, `./${dirName}/`);
3405
+ content = content.replace(new RegExp('\\./' + _own + '/', 'g'), `./${dirName}/`);
3335
3406
  content = processAttribution(content, attribution);
3336
3407
  break;
3337
3408
  }
3338
- case 'hermes': {
3339
- // Guarded (post-review #2092): see qwen case above — same degrade-closed
3340
- // rationale.
3341
- const _b = _hostBehaviors(runtime).brandingRewrites;
3342
- if (_b) {
3343
- content = content.replace(/CLAUDE\.md/g, _b['CLAUDE.md']);
3344
- // #2284(b): skips <runtime_compatibility> comparison-table content (protected region).
3345
- content = applyClaudeCodeBrandSwap(content, _b['Claude Code']);
3346
- }
3347
- content = content.replace(/~\/\.claude\//g, pathPrefix);
3348
- content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
3349
- content = content.replace(/~\/\.hermes\//g, pathPrefix);
3350
- content = content.replace(/\$HOME\/\.hermes\//g, pathPrefix);
3351
- content = content.replace(/~\/\.claude(?![\w-])/g, normalizedPathPrefix);
3352
- content = content.replace(/\$HOME\/\.claude(?![\w-])/g, normalizedPathPrefix);
3353
- content = content.replace(/~\/\.hermes(?![\w-])/g, normalizedPathPrefix);
3354
- content = content.replace(/\$HOME\/\.hermes(?![\w-])/g, normalizedPathPrefix);
3355
- if (_b) {
3356
- content = content.replace(/\.claude\//g, _b['.claude/']);
3357
- }
3358
- content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
3359
- content = content.replace(/\.\/\.hermes\//g, `./${dirName}/`);
3360
- content = processAttribution(content, attribution);
3361
- break;
3362
- }
3363
- case 'kimi':
3409
+ case 'slash-bare':
3364
3410
  content = content.replace(/~\/\.claude\//g, pathPrefix);
3365
3411
  content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
3366
3412
  content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
@@ -3369,22 +3415,8 @@ function _applyRuntimeRewritesInner(content, runtime, pathPrefix, isGlobal = fal
3369
3415
  content = content.replace(/\.\/\.claude\b/g, `./${dirName}`);
3370
3416
  content = processAttribution(content, attribution);
3371
3417
  break;
3372
- case 'zcode':
3373
- // #4002: ZCode is a Claude-Code-shaped host (dot-home `.zcode`, `@~`-ref
3374
- // expansion, `~/.zcode/...` documented paths) whose commands install with
3375
- // `converter: null` — this pass is their only chance to receive
3376
- // runtime-correct paths. Same shape as `claude`, including the tilde
3377
- // restore: the tilde form is what ZCode expands and what its docs use.
3378
- // `${_GSD_RUNTIME_ROOT}/.claude/…` matches none of these regexes and
3379
- // survives as the project-local fallback, exactly as on every sibling.
3380
- content = content.replace(/~\/\.claude\//g, pathPrefix);
3381
- content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
3382
- content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
3383
- content = restoreClaudeGlobalAtRefTilde(content, pathPrefix);
3384
- content = processAttribution(content, attribution);
3385
- break;
3386
3418
  default:
3387
- // Unknown runtime — no rewrites (OpenCode/Kilo handled by their own install path).
3419
+ // No profile declared — no rewrites (OpenCode/Kilo handled by their own install path).
3388
3420
  break;
3389
3421
  }
3390
3422
  return content;
@@ -3450,7 +3482,7 @@ function applyRuntimeContentRewritesForCommandsInPlace(stagedDir, runtime, pathP
3450
3482
  content = _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal, attribution);
3451
3483
  // #2097 (ADR-1239): descriptor-driven — commandBodyConverter name comes
3452
3484
  // from runtime.hostBehaviors instead of a hardcoded runtime-name branch.
3453
- const _cmdConv = _hostBehaviors(runtime).commandBodyConverter;
3485
+ const _cmdConv = hostBehaviorsFor(runtime).commandBodyConverter;
3454
3486
  if (_cmdConv && COMMAND_BODY_CONVERTERS[_cmdConv]) {
3455
3487
  content = COMMAND_BODY_CONVERTERS[_cmdConv](content);
3456
3488
  }
@@ -3539,7 +3571,7 @@ function rewriteStagedSkillBodies(stagedDir, opts) {
3539
3571
  // no skills-kind entry at local scope, so this is already structurally
3540
3572
  // scoped to global (row 23) — the explicit isGlobal check is defense-in-depth
3541
3573
  // against that descriptor wiring ever changing.
3542
- if (runtime === 'claude' && isGlobal) {
3574
+ if (hostBehaviorsFor(runtime).specRootSkillPass && isGlobal) {
3543
3575
  applySpecRootReferenceToStagedSkills(stagedDir);
3544
3576
  }
3545
3577
  }
@@ -3591,7 +3623,7 @@ function rewriteStagedCommandBodies(stagedDir, opts) {
3591
3623
  * @param cmdNames gsd command names from readGsdCommandNames()
3592
3624
  */
3593
3625
  function normalizeAgentBodyForRuntime(content, runtime, cmdNames) {
3594
- if (_hostBehaviors(runtime).hyphenNameAgentBody !== true)
3626
+ if (hostBehaviorsFor(runtime).hyphenNameAgentBody !== true)
3595
3627
  return content;
3596
3628
  return transformContentToHyphen(content, cmdNames);
3597
3629
  }
@@ -3621,7 +3653,7 @@ function normalizeAgentBodyForRuntime(content, runtime, cmdNames) {
3621
3653
  * @returns content with path-prefix rewrites applied (or unchanged for noPathRewrite runtimes, e.g. copilot)
3622
3654
  */
3623
3655
  function applyAgentPathRewrites(content, runtime, pathPrefix) {
3624
- if (_hostBehaviors(runtime).noPathRewrite === true)
3656
+ if (hostBehaviorsFor(runtime).noPathRewrite === true)
3625
3657
  return content;
3626
3658
  // #4377: the agents pipeline is the third emit path that substitutes this
3627
3659
  // prefix (skills/commands via _applyRuntimeRewrites, the gsd-core spec tree
@@ -3663,7 +3695,7 @@ function applyAgentPathRewritesInner(content, runtime, pathPrefix) {
3663
3695
  // anchored to the exact prefix string it is handed — so `restore(pathPrefix)`
3664
3696
  // fixes `@$HOME/.claude/x` and leaves a bare `@$HOME/.claude` broken. The
3665
3697
  // normalized form is a PREFIX of both, so one call covers both.
3666
- if (runtime === 'claude') {
3698
+ if (hostBehaviorsFor(runtime).restoreAtRefTildeInAgents) {
3667
3699
  content = restoreClaudeGlobalAtRefTilde(content, normalizedPathPrefix);
3668
3700
  }
3669
3701
  return content;
@@ -3687,8 +3719,8 @@ function deriveAgentName(fileName) {
3687
3719
  * `applyAgentFrontmatterExtensions` below for the orchestration that calls it.
3688
3720
  *
3689
3721
  * The function:
3690
- * - Detects the file's EOL (CRLF if the first `---` line ends with \r\n,
3691
- * otherwise LF).
3722
+ * - Reads the block the one fence owner (`locateFrontmatterFence`) finds, and
3723
+ * its EOL (CRLF if the opening `---` line ends with \r\n, otherwise LF).
3692
3724
  * - Skips injection if an `effort:` key already exists in the frontmatter
3693
3725
  * (idempotent).
3694
3726
  * - Inserts `effort: <value>` immediately before the closing `---` delimiter,
@@ -3697,19 +3729,15 @@ function deriveAgentName(fileName) {
3697
3729
  * - Returns the original content unchanged when no YAML frontmatter is found.
3698
3730
  */
3699
3731
  function injectEffortFrontmatter(content, effortValue) {
3700
- const eol = /^---\r\n/.test(content) ? '\r\n' : '\n';
3701
- const fmRe = /^---\r?\n([\s\S]*?)^---\r?$/m;
3702
- const match = fmRe.exec(content);
3703
- if (!match)
3732
+ const fence = (0, frontmatter_fence_cjs_1.locateFrontmatterFence)(content);
3733
+ if (!fence?.closed)
3704
3734
  return content; // no YAML frontmatter — leave unchanged
3705
- const fmBody = match[1]; // content between the two `---` lines
3735
+ const fmBody = content.slice(fence.openEnd, fence.closingStart); // content between the two fences
3706
3736
  if (/^effort:/m.test(fmBody))
3707
3737
  return content;
3708
- const openLen = 3 + eol.length; // "---" + eol
3709
- const closingStart = match.index + openLen + fmBody.length;
3710
- const before = content.slice(0, closingStart);
3711
- const after = content.slice(closingStart);
3712
- return `${before}effort: ${effortValue}${eol}${after}`;
3738
+ const before = content.slice(0, fence.closingStart);
3739
+ const after = content.slice(fence.closingStart);
3740
+ return `${before}effort: ${effortValue}${fence.eol}${after}`;
3713
3741
  }
3714
3742
  /**
3715
3743
  * #767 — Inject `disallowedTools: <value>` into the YAML frontmatter of a
@@ -3719,19 +3747,15 @@ function injectEffortFrontmatter(content, effortValue) {
3719
3747
  * frontmatter keys. Relocated verbatim from bin/install.js (#2875 Part 2).
3720
3748
  */
3721
3749
  function injectDisallowedToolsFrontmatter(content, disallowedValue) {
3722
- const eol = /^---\r\n/.test(content) ? '\r\n' : '\n';
3723
- const fmRe = /^---\r?\n([\s\S]*?)^---\r?$/m;
3724
- const match = fmRe.exec(content);
3725
- if (!match)
3750
+ const fence = (0, frontmatter_fence_cjs_1.locateFrontmatterFence)(content);
3751
+ if (!fence?.closed)
3726
3752
  return content; // no YAML frontmatter — leave unchanged
3727
- const fmBody = match[1]; // content between the two `---` lines
3753
+ const fmBody = content.slice(fence.openEnd, fence.closingStart); // content between the two fences
3728
3754
  if (/^disallowedTools:/m.test(fmBody))
3729
3755
  return content;
3730
- const openLen = 3 + eol.length; // "---" + eol
3731
- const closingStart = match.index + openLen + fmBody.length;
3732
- const before = content.slice(0, closingStart);
3733
- const after = content.slice(closingStart);
3734
- return `${before}disallowedTools: ${disallowedValue}${eol}${after}`;
3756
+ const before = content.slice(0, fence.closingStart);
3757
+ const after = content.slice(fence.closingStart);
3758
+ return `${before}disallowedTools: ${disallowedValue}${fence.eol}${after}`;
3735
3759
  }
3736
3760
  // #767 — Read-only verifier/auditor agents get a Claude-Code disallowedTools deny-list.
3737
3761
  // Group A (pure read-only) deny Write,Edit,MultiEdit. Group B report-writers Write one
@@ -3757,7 +3781,7 @@ const READONLY_AGENT_DISALLOWED_TOOLS = {
3757
3781
  * OpenCode/Qwen/Hermes reject unknown frontmatter keys.
3758
3782
  *
3759
3783
  * Byte-identical to the inline agent loop's
3760
- * `if ((_hostBehaviors(runtime).agentFrontmatterExtensions || []).includes('effort'))`
3784
+ * `if ((hostBehaviorsFor(runtime).agentFrontmatterExtensions || []).includes('effort'))`
3761
3785
  * block (bin/install.js): both the effort injection AND the disallowedTools
3762
3786
  * injection are gated behind the SAME `'effort'` extension flag — there is no
3763
3787
  * separate `'disallowedTools'` extension key, mirroring the loop exactly.
@@ -3772,7 +3796,7 @@ const READONLY_AGENT_DISALLOWED_TOOLS = {
3772
3796
  * @param targetDir install root — resolves .planning/config.json + ~/.gsd/defaults.json
3773
3797
  */
3774
3798
  function applyAgentFrontmatterExtensions(content, { runtime, agentName, targetDir }) {
3775
- const extensions = _hostBehaviors(runtime).agentFrontmatterExtensions || [];
3799
+ const extensions = hostBehaviorsFor(runtime).agentFrontmatterExtensions || [];
3776
3800
  if (!extensions.includes('effort'))
3777
3801
  return content;
3778
3802
  let result = content;
@@ -3882,6 +3906,7 @@ module.exports = {
3882
3906
  convertClaudeToOpencodeFrontmatter,
3883
3907
  convertClaudeToKiloFrontmatter,
3884
3908
  _decodeToolScalar: decodeToolScalar,
3909
+ _buildOpencodeAgentPermissionBlock: buildOpencodeAgentPermissionBlock,
3885
3910
  _splitToolScalars: splitToolScalars,
3886
3911
  readGsdCommandNames,
3887
3912
  transformContentToHyphen,