@opengsd/gsd-core 1.10.0 → 1.11.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 (328) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/agents/gsd-debug-session-manager.md +11 -0
  4. package/agents/gsd-doc-synthesizer.md +2 -4
  5. package/agents/gsd-executor.md +5 -5
  6. package/agents/gsd-mempalace-curator.md +5 -2
  7. package/agents/gsd-phase-researcher.md +20 -1
  8. package/agents/gsd-plan-checker.md +37 -0
  9. package/agents/gsd-planner.md +44 -46
  10. package/agents/gsd-user-profiler.md +3 -0
  11. package/agents/gsd-verifier.md +12 -3
  12. package/bin/install.js +841 -971
  13. package/bin/lib/ui-safety-gate.cjs +2 -0
  14. package/commands/gsd/code-review.md +1 -1
  15. package/commands/gsd/execute-phase.md +1 -1
  16. package/commands/gsd/map-codebase.md +1 -1
  17. package/commands/gsd/mempalace-capture.md +1 -1
  18. package/commands/gsd/mempalace-recall.md +1 -1
  19. package/commands/gsd/new-milestone.md +1 -1
  20. package/commands/gsd/quick.md +1 -1
  21. package/commands/gsd/review-backlog.md +2 -1
  22. package/commands/gsd/verify-work.md +1 -1
  23. package/gsd-core/bin/gsd-tools.cjs +469 -88
  24. package/gsd-core/bin/lib/active-workstream-store.cjs +138 -22
  25. package/gsd-core/bin/lib/agent-install-check.cjs +230 -32
  26. package/gsd-core/bin/lib/api-coverage.cjs +3 -5
  27. package/gsd-core/bin/lib/artifacts.cjs +3 -0
  28. package/gsd-core/bin/lib/assumption-delta.cjs +2 -4
  29. package/gsd-core/bin/lib/audit-command-router.cjs +9 -2
  30. package/gsd-core/bin/lib/audit.cjs +876 -240
  31. package/gsd-core/bin/lib/broken-windows.cjs +1 -1
  32. package/gsd-core/bin/lib/capability-consent.cjs +149 -15
  33. package/gsd-core/bin/lib/capability-lifecycle.cjs +45 -0
  34. package/gsd-core/bin/lib/capability-registry.cjs +575 -101
  35. package/gsd-core/bin/lib/capability-source.cjs +92 -0
  36. package/gsd-core/bin/lib/capability-trust.cjs +444 -25
  37. package/gsd-core/bin/lib/capability-validator.cjs +495 -22
  38. package/gsd-core/bin/lib/capability-writer.cjs +3 -2
  39. package/gsd-core/bin/lib/check-command-router.cjs +71 -37
  40. package/gsd-core/bin/lib/claude-orchestration.cjs +56 -3
  41. package/gsd-core/bin/lib/codex-agent-toml.cjs +329 -0
  42. package/gsd-core/bin/lib/command-aliases.cjs +22 -0
  43. package/gsd-core/bin/lib/command-roster.cjs +44 -1
  44. package/gsd-core/bin/lib/commands.cjs +651 -86
  45. package/gsd-core/bin/lib/commonjs-marker.cjs +12 -6
  46. package/gsd-core/bin/lib/complexity-trigger.cjs +1172 -0
  47. package/gsd-core/bin/lib/config-loader.cjs +75 -0
  48. package/gsd-core/bin/lib/config.cjs +10 -1
  49. package/gsd-core/bin/lib/core-utils.cjs +127 -29
  50. package/gsd-core/bin/lib/decisions.cjs +23 -0
  51. package/gsd-core/bin/lib/fallow-runner.cjs +20 -44
  52. package/gsd-core/bin/lib/frontmatter.cjs +155 -20
  53. package/gsd-core/bin/lib/gap-checker.cjs +68 -7
  54. package/gsd-core/bin/lib/git-base-branch.cjs +102 -0
  55. package/gsd-core/bin/lib/gsd2-import.cjs +10 -1
  56. package/gsd-core/bin/lib/health-diagnostic-rules/agent-install.cjs +101 -0
  57. package/gsd-core/bin/lib/health-diagnostic-rules/config-validation.cjs +348 -0
  58. package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +145 -0
  59. package/gsd-core/bin/lib/health-diagnostic-rules/install-surface-shadowing.cjs +98 -0
  60. package/gsd-core/bin/lib/health-diagnostic-rules/milestone-archive-hygiene.cjs +100 -0
  61. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +222 -0
  62. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +265 -0
  63. package/gsd-core/bin/lib/health-diagnostic-rules/root-existence.cjs +161 -0
  64. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +303 -0
  65. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +173 -0
  66. package/gsd-core/bin/lib/health-diagnostic-types.cjs +68 -0
  67. package/gsd-core/bin/lib/health-diagnostic.cjs +431 -0
  68. package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
  69. package/gsd-core/bin/lib/init.cjs +321 -129
  70. package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
  71. package/gsd-core/bin/lib/install-engine.cjs +745 -258
  72. package/gsd-core/bin/lib/install-fs-adapter.cjs +262 -0
  73. package/gsd-core/bin/lib/install-model-override-resolver.cjs +203 -0
  74. package/gsd-core/bin/lib/install-profiles.cjs +134 -57
  75. package/gsd-core/bin/lib/install-scope.cjs +270 -0
  76. package/gsd-core/bin/lib/install-shadow-report.cjs +385 -0
  77. package/gsd-core/bin/lib/installed-surface-resolver.cjs +381 -0
  78. package/gsd-core/bin/lib/installer-migrations.cjs +138 -31
  79. package/gsd-core/bin/lib/io.cjs +10 -0
  80. package/gsd-core/bin/lib/markdown-sectionizer.cjs +2 -1
  81. package/gsd-core/bin/lib/markdown-table.cjs +133 -20
  82. package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
  83. package/gsd-core/bin/lib/milestone.cjs +754 -70
  84. package/gsd-core/bin/lib/model-catalog.cjs +59 -1
  85. package/gsd-core/bin/lib/model-resolver.cjs +183 -40
  86. package/gsd-core/bin/lib/normalize-test-command.cjs +1 -1
  87. package/gsd-core/bin/lib/pattern.cjs +122 -0
  88. package/gsd-core/bin/lib/phase-estimation.cjs +1 -1
  89. package/gsd-core/bin/lib/phase-id.cjs +444 -36
  90. package/gsd-core/bin/lib/phase-lifecycle.cjs +28 -3
  91. package/gsd-core/bin/lib/phase-locator.cjs +125 -18
  92. package/gsd-core/bin/lib/phase.cjs +646 -143
  93. package/gsd-core/bin/lib/plan-dependency-graph.cjs +72 -1
  94. package/gsd-core/bin/lib/plan-drift-guard.cjs +120 -0
  95. package/gsd-core/bin/lib/plan-scan.cjs +86 -2
  96. package/gsd-core/bin/lib/planning-scope.cjs +31 -0
  97. package/gsd-core/bin/lib/planning-snapshot.cjs +890 -0
  98. package/gsd-core/bin/lib/planning-workspace.cjs +56 -6
  99. package/gsd-core/bin/lib/probe-core.cjs +1 -1
  100. package/gsd-core/bin/lib/profile-output.cjs +1 -1
  101. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +740 -0
  102. package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +11 -6
  103. package/gsd-core/bin/lib/review-lane-descriptor.cjs +13 -4
  104. package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
  105. package/gsd-core/bin/lib/review-lane-runner.cjs +421 -66
  106. package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
  107. package/gsd-core/bin/lib/roadmap-command-router.cjs +34 -0
  108. package/gsd-core/bin/lib/roadmap-parser.cjs +943 -184
  109. package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
  110. package/gsd-core/bin/lib/roadmap.cjs +385 -94
  111. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +608 -46
  112. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
  113. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +426 -55
  114. package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
  115. package/gsd-core/bin/lib/runtime-homes.cjs +69 -3
  116. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +115 -3
  117. package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
  118. package/gsd-core/bin/lib/runtime-slash.cjs +27 -9
  119. package/gsd-core/bin/lib/security.cjs +104 -5
  120. package/gsd-core/bin/lib/shell-command-projection.cjs +275 -3
  121. package/gsd-core/bin/lib/smart-entry.cjs +142 -22
  122. package/gsd-core/bin/lib/state-command-router.cjs +5 -1
  123. package/gsd-core/bin/lib/state-document.cjs +152 -8
  124. package/gsd-core/bin/lib/state-transition.cjs +371 -117
  125. package/gsd-core/bin/lib/state.cjs +1794 -357
  126. package/gsd-core/bin/lib/surface.cjs +23 -9
  127. package/gsd-core/bin/lib/text-lines.cjs +80 -0
  128. package/gsd-core/bin/lib/token-scanner.cjs +76 -0
  129. package/gsd-core/bin/lib/uat-predicate.cjs +9 -3
  130. package/gsd-core/bin/lib/uat.cjs +399 -56
  131. package/gsd-core/bin/lib/ui-frontend-evidence.cjs +157 -0
  132. package/gsd-core/bin/lib/ui-safety-gate.cjs +14 -5
  133. package/gsd-core/bin/lib/unusable-input.cjs +24 -0
  134. package/gsd-core/bin/lib/update-context.cjs +8 -2
  135. package/gsd-core/bin/lib/user-artifact-staging.cjs +705 -0
  136. package/gsd-core/bin/lib/validate.cjs +20 -6
  137. package/gsd-core/bin/lib/vendor/README.md +37 -0
  138. package/gsd-core/bin/lib/vendor/re2js.cjs +6480 -0
  139. package/gsd-core/bin/lib/vendor/re2js.d.cts +938 -0
  140. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  141. package/gsd-core/bin/lib/verification.cjs +258 -8
  142. package/gsd-core/bin/lib/verify.cjs +368 -888
  143. package/gsd-core/bin/lib/workstream-inventory-builder.cjs +53 -32
  144. package/gsd-core/bin/lib/workstream-inventory.cjs +63 -10
  145. package/gsd-core/bin/lib/workstream.cjs +2 -2
  146. package/gsd-core/bin/lib/worktree-safety.cjs +176 -9
  147. package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
  148. package/gsd-core/bin/shared/config-schema.manifest.json +7 -1
  149. package/gsd-core/references/agent-contracts.md +43 -26
  150. package/gsd-core/references/checkpoints.md +2 -2
  151. package/gsd-core/references/context-budget.md +1 -1
  152. package/gsd-core/references/dispatch-isolation-gate.md +138 -0
  153. package/gsd-core/references/doc-conflict-engine.md +1 -1
  154. package/gsd-core/references/execute-mvp-tdd.md +3 -3
  155. package/gsd-core/references/execute-phase-between-wave-reset.md +6 -2
  156. package/gsd-core/references/execute-phase-context-guard.md +1 -1
  157. package/gsd-core/references/execute-phase-response-language.md +1 -1
  158. package/gsd-core/references/execute-phase-wave-guard.md +6 -2
  159. package/gsd-core/references/gate-prompts.md +1 -1
  160. package/gsd-core/references/git-planning-commit.md +2 -1
  161. package/gsd-core/references/loop-hook-dispatch.md +39 -2
  162. package/gsd-core/references/model-profiles.md +12 -4
  163. package/gsd-core/references/mvp-concepts.md +9 -9
  164. package/gsd-core/references/planner-guidance.md +3 -9
  165. package/gsd-core/references/planner-preconditions.md +1 -1
  166. package/gsd-core/references/planner-reviews.md +1 -1
  167. package/gsd-core/references/planning-config.md +8 -6
  168. package/gsd-core/references/revision-loop.md +1 -1
  169. package/gsd-core/references/specless-probe-fallback.md +1 -1
  170. package/gsd-core/references/universal-anti-patterns.md +3 -3
  171. package/gsd-core/references/verifier-phase-gates.md +192 -0
  172. package/gsd-core/references/verify-mvp-mode.md +1 -1
  173. package/gsd-core/references/workstream-flag.md +22 -6
  174. package/gsd-core/templates/discussion-log.md +1 -1
  175. package/gsd-core/templates/phase-prompt.md +2 -4
  176. package/gsd-core/templates/state.md +4 -4
  177. package/gsd-core/templates/verification-report.md +9 -1
  178. package/gsd-core/workflows/ai-integration-phase.md +9 -11
  179. package/gsd-core/workflows/autonomous.md +1 -1
  180. package/gsd-core/workflows/cleanup.md +62 -3
  181. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +13 -3
  182. package/gsd-core/workflows/code-review-fix.md +37 -10
  183. package/gsd-core/workflows/code-review.md +38 -12
  184. package/gsd-core/workflows/complete-milestone.md +141 -18
  185. package/gsd-core/workflows/debug.md +7 -5
  186. package/gsd-core/workflows/diagnose-issues.md +35 -9
  187. package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -1
  188. package/gsd-core/workflows/discuss-phase/modes/default.md +1 -1
  189. package/gsd-core/workflows/discuss-phase-assumptions.md +2 -1
  190. package/gsd-core/workflows/edit-phase.md +26 -1
  191. package/gsd-core/workflows/eval-review.md +3 -5
  192. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +31 -6
  193. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +77 -0
  194. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +2 -0
  195. package/gsd-core/workflows/execute-phase.md +38 -50
  196. package/gsd-core/workflows/execute-plan.md +36 -4
  197. package/gsd-core/workflows/explore.md +131 -4
  198. package/gsd-core/workflows/fast.md +10 -2
  199. package/gsd-core/workflows/health.md +73 -4
  200. package/gsd-core/workflows/import.md +4 -4
  201. package/gsd-core/workflows/ingest-docs.md +5 -5
  202. package/gsd-core/workflows/mvp-phase.md +6 -3
  203. package/gsd-core/workflows/new-milestone.md +14 -9
  204. package/gsd-core/workflows/new-project.md +14 -14
  205. package/gsd-core/workflows/next.md +12 -0
  206. package/gsd-core/workflows/plan-phase.md +41 -17
  207. package/gsd-core/workflows/plan-review-convergence.md +50 -2
  208. package/gsd-core/workflows/progress.md +34 -6
  209. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +4 -4
  210. package/gsd-core/workflows/quick/steps/quick-verification.md +27 -6
  211. package/gsd-core/workflows/quick/steps/research-phase.md +2 -2
  212. package/gsd-core/workflows/quick.md +35 -15
  213. package/gsd-core/workflows/review.md +26 -5
  214. package/gsd-core/workflows/secure-phase.md +1 -1
  215. package/gsd-core/workflows/session-report.md +2 -1
  216. package/gsd-core/workflows/settings.md +66 -2
  217. package/gsd-core/workflows/ship.md +104 -44
  218. package/gsd-core/workflows/spec-phase.md +30 -12
  219. package/gsd-core/workflows/sync-skills.md +63 -8
  220. package/gsd-core/workflows/transition.md +46 -11
  221. package/gsd-core/workflows/ui-phase.md +5 -5
  222. package/gsd-core/workflows/ui-review.md +2 -2
  223. package/gsd-core/workflows/update.md +1 -1
  224. package/gsd-core/workflows/validate-phase.md +1 -1
  225. package/gsd-core/workflows/verify-work.md +9 -7
  226. package/hooks/dist/gsd-agent-isolation-guard.js +103 -14
  227. package/hooks/dist/gsd-check-update-worker.js +56 -13
  228. package/hooks/dist/gsd-check-update.js +19 -1
  229. package/hooks/dist/gsd-cursor-pre-tool.js +0 -3
  230. package/hooks/dist/gsd-cursor-subagent-start.js +77 -2
  231. package/hooks/dist/gsd-cursor-subagent-stop.js +3 -2
  232. package/hooks/dist/gsd-prompt-guard.js +21 -20
  233. package/hooks/dist/gsd-read-injection-scanner.js +38 -24
  234. package/hooks/dist/gsd-statusline.js +18 -0
  235. package/hooks/dist/gsd-update-banner.js +22 -1
  236. package/hooks/dist/gsd-workflow-guard.js +134 -36
  237. package/hooks/dist/lib/git-cmd.js +92 -59
  238. package/hooks/dist/lib/injection-patterns.js +45 -0
  239. package/hooks/dist/lib/isolation-deny-reason.js +39 -0
  240. package/hooks/dist/lib/isolation-sentinel.js +9 -0
  241. package/hooks/gsd-agent-isolation-guard.js +103 -14
  242. package/hooks/gsd-check-update-worker.js +56 -13
  243. package/hooks/gsd-check-update.js +19 -1
  244. package/hooks/gsd-cursor-pre-tool.js +0 -3
  245. package/hooks/gsd-cursor-subagent-start.js +77 -2
  246. package/hooks/gsd-cursor-subagent-stop.js +3 -2
  247. package/hooks/gsd-prompt-guard.js +21 -20
  248. package/hooks/gsd-read-injection-scanner.js +38 -24
  249. package/hooks/gsd-statusline.js +18 -0
  250. package/hooks/gsd-update-banner.js +22 -1
  251. package/hooks/gsd-workflow-guard.js +134 -36
  252. package/hooks/lib/git-cmd.js +92 -59
  253. package/hooks/lib/injection-patterns.js +45 -0
  254. package/hooks/lib/isolation-deny-reason.js +39 -0
  255. package/hooks/lib/isolation-sentinel.js +9 -0
  256. package/package.json +21 -9
  257. package/pi/gsd.cjs +19 -5
  258. package/scripts/baselines/planning-prompt-drift-baseline.json +4 -0
  259. package/scripts/baselines/planning-snapshot-bypass-baseline.json +12 -0
  260. package/scripts/baselines/unreachable-guard-drift-baseline.json +4 -0
  261. package/scripts/changeset/lint.cjs +60 -5
  262. package/scripts/check-alias-drift.cjs +7 -43
  263. package/scripts/check-contract-drift.cjs +297 -0
  264. package/scripts/ci-test-scope.cjs +19 -2
  265. package/scripts/command-contract-helpers.cjs +903 -1
  266. package/scripts/gen-adr-index.cjs +728 -38
  267. package/scripts/gen-capability-registry.cjs +3 -15
  268. package/scripts/gen-context-index.cjs +2 -11
  269. package/scripts/gen-health-docs.cjs +390 -0
  270. package/scripts/gen-inventory-manifest.cjs +50 -4
  271. package/scripts/gen-loop-host-contract.cjs +4 -24
  272. package/scripts/gen-registry.cjs +3 -14
  273. package/scripts/lib/alias-drift-families.cjs +46 -0
  274. package/scripts/lib/drift-scan.cjs +278 -0
  275. package/scripts/lint-allow-test-rule-refs.allowlist.json +1 -26
  276. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +4 -0
  277. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +3 -0
  278. package/scripts/lint-canary-version-leak.cjs +73 -0
  279. package/scripts/lint-command-contract.cjs +96 -13
  280. package/scripts/lint-completion-predicate-drift.cjs +933 -0
  281. package/scripts/lint-completion-ratio-drift.cjs +214 -0
  282. package/scripts/lint-default-flip-documentation.cjs +193 -0
  283. package/scripts/lint-eslint-glob-coverage.allowlist.json +34 -0
  284. package/scripts/lint-eslint-glob-coverage.cjs +340 -0
  285. package/scripts/lint-frontmatter-scalar-broad-grep.cjs +237 -0
  286. package/scripts/lint-health-diagnostic-rule-table.cjs +404 -0
  287. package/scripts/lint-hooks-runtime-build-seam.cjs +262 -0
  288. package/scripts/lint-milestone-window-drift.cjs +468 -0
  289. package/scripts/lint-phase-enumeration-drift.cjs +479 -0
  290. package/scripts/lint-plan-count-drift.cjs +318 -0
  291. package/scripts/lint-planning-artifact-writer-drift.cjs +398 -0
  292. package/scripts/lint-planning-prompt-drift.cjs +434 -0
  293. package/scripts/lint-planning-snapshot-bypass-drift.cjs +544 -0
  294. package/scripts/lint-regression-test-names.cjs +15 -13
  295. package/scripts/lint-removed-but-needed.cjs +320 -0
  296. package/scripts/lint-state-field-drift.cjs +805 -0
  297. package/scripts/lint-state-write-path-drift.cjs +1045 -0
  298. package/scripts/lint-test-file-count.allowlist.json +21 -10
  299. package/scripts/lint-unreachable-guard-drift.cjs +843 -0
  300. package/scripts/lint-vendored-deps.cjs +124 -0
  301. package/scripts/pr-changed-files.cjs +63 -0
  302. package/scripts/pr-template-policy.cjs +14 -4
  303. package/scripts/prompt-injection-scan.sh +25 -0
  304. package/scripts/require-issue-link-policy.cjs +192 -0
  305. package/scripts/state-write-path-drift-baseline.json +19 -0
  306. package/scripts/sync-runtime-launcher.cjs +2 -4
  307. package/skills/gsd-autonomous/SKILL.md +0 -1
  308. package/skills/gsd-code-review/SKILL.md +1 -1
  309. package/skills/gsd-execute-phase/SKILL.md +1 -2
  310. package/skills/gsd-map-codebase/SKILL.md +1 -1
  311. package/skills/gsd-mempalace-capture/SKILL.md +1 -1
  312. package/skills/gsd-mempalace-recall/SKILL.md +1 -1
  313. package/skills/gsd-new-milestone/SKILL.md +1 -1
  314. package/skills/gsd-next/SKILL.md +0 -1
  315. package/skills/gsd-plan-phase/SKILL.md +0 -1
  316. package/skills/gsd-progress/SKILL.md +0 -1
  317. package/skills/gsd-quick/SKILL.md +1 -1
  318. package/skills/gsd-review-backlog/SKILL.md +2 -1
  319. package/skills/gsd-stats/SKILL.md +0 -1
  320. package/skills/gsd-verify-work/SKILL.md +1 -1
  321. package/vscode/package.json +1 -1
  322. package/gsd-core/workflows/discovery-phase.md +0 -298
  323. package/gsd-core/workflows/plan-milestone-gaps.md +0 -281
  324. package/gsd-core/workflows/verify-phase.md +0 -574
  325. package/scripts/affected-tests-lib.cjs +0 -554
  326. package/scripts/lint-allow-test-rule-refs.cjs +0 -162
  327. package/scripts/run-affected-tests.cjs +0 -7
  328. package/scripts/run-tests.cjs +0 -1051
@@ -17,13 +17,31 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
17
17
  const node_path_1 = __importDefault(require("node:path"));
18
18
  const node_fs_1 = __importDefault(require("node:fs"));
19
19
  const node_os_1 = __importDefault(require("node:os"));
20
+ // #2874 (ADR-58 cleanup phase): route this module's fs calls through the
21
+ // installRuntimeArtifacts call tree's injectable seam — see
22
+ // install-fs-adapter.cts's module doc. Resolves to real `node:fs` (the
23
+ // `fs` import above stays for type-only references, e.g. `fs.Dirent`)
24
+ // unless the top-level installRuntimeArtifacts call injected a `deps.fs`.
25
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
26
+ const installFsAdapter = require("./install-fs-adapter.cjs");
27
+ const { installFs, mkInstallTempDir } = installFsAdapter;
20
28
  // eslint-disable-next-line @typescript-eslint/no-require-imports
21
29
  const installProfiles = require("./install-profiles.cjs");
22
30
  const { stageSkillsForProfile, stageAgentsForRuntimeWithConverter, stageSkillsForRuntimeAsSkills, stageCommandsForRuntimeFlat, } = installProfiles;
23
31
  // eslint-disable-next-line @typescript-eslint/no-require-imports
24
32
  const runtimeArtifactConversion = require("./runtime-artifact-conversion.cjs");
25
33
  const conversionExports = runtimeArtifactConversion;
34
+ // #2875 Part 2 (J8): shared model-override precedence resolver — see its
35
+ // module doc for why kilo/opencode MUST resolve through this ONE function
36
+ // rather than re-deriving the chain per runtime.
37
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
38
+ const installModelOverrideResolver = require("./install-model-override-resolver.cjs");
26
39
  const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
40
+ // #2870: `isGlobalScope` centralizes the `scope === 'global'` boolean
41
+ // projection both kind-builder closures below need at the converters'
42
+ // positional `isGlobal` boundary (see its doc comment in install-scope.cts
43
+ // for why the projection is centralized rather than eliminated).
44
+ const install_scope_cjs_1 = require("./install-scope.cjs");
27
45
  // In .cts (CommonJS output) files, `require` is available as a global.
28
46
  const _require = require;
29
47
  // ---------------------------------------------------------------------------
@@ -38,24 +56,51 @@ const _require = require;
38
56
  * 3. Throw a descriptive error if neither succeeds.
39
57
  */
40
58
  function findInstallSourceRoot(runtimeConfigDir) {
41
- // Step 1: marker check
59
+ // Step 1: marker check — reads `<runtimeConfigDir>/.gsd-source`, a path
60
+ // under the INSTALL DESTINATION, so this probe goes through the injected
61
+ // adapter (installFs()).
42
62
  if (runtimeConfigDir) {
43
63
  const markerPath = node_path_1.default.join(runtimeConfigDir, '.gsd-source');
44
- if (node_fs_1.default.existsSync(markerPath)) {
64
+ if (installFs().existsSync(markerPath)) {
45
65
  try {
46
- const src = node_fs_1.default.readFileSync(markerPath, 'utf8').trim();
47
- if (src && node_fs_1.default.existsSync(src))
66
+ const src = installFs().readFileSync(markerPath, 'utf8').trim();
67
+ if (src && installFs().existsSync(src))
48
68
  return src;
49
69
  }
50
70
  catch { /* fall through */ }
51
71
  }
52
72
  }
53
- // Step 2: walk up from __dirname
73
+ // Step 2: walk up from __dirname to locate the GSD PACKAGE'S OWN source
74
+ // tree (commands/gsd/) — this resolves where the installer's own code is
75
+ // running FROM, not anything under the install destination, so it is
76
+ // deliberately NOT routed through the injected fs adapter (#2874): a fake
77
+ // "destination" adapter has no reason to know about the real package's own
78
+ // on-disk layout (an injected adapter's store starts empty and is never
79
+ // seeded with real repo paths), and routing it through would make this
80
+ // resolution unconditionally throw rather than gracefully staging nothing.
81
+ //
82
+ // Uses `fs.statSync` in a try/catch rather than `fs.existsSync` — this is
83
+ // LOAD-BEARING, not a style choice: tests/executed-plan.test.cjs's F2 cases
84
+ // poison every method on the ROUTED fs surface (including `existsSync`,
85
+ // since installFs()'s REAL_ADAPTER also calls it) to prove nothing on the
86
+ // installRuntimeArtifacts call tree reaches real fs. The F2 "nativePlugin
87
+ // runtime: pi" test calls this function (via findInstallSourceRoot()) AFTER
88
+ // installing that poison, specifically to resolve the pi nativePlugin
89
+ // source path against this repo's own real layout — an operation this
90
+ // function must still be able to perform even while `existsSync` is
91
+ // poisoned, because this Step 2 walk is real-fs-only by design and was
92
+ // never meant to be covered by that poison list. `statSync` is not on the
93
+ // poisoned surface, so this probe survives; switching back to `existsSync`
94
+ // makes that F2 test throw (verified: reverting this to `existsSync` trips
95
+ // the poison and breaks the pi nativePlugin case).
54
96
  let dir = __dirname;
55
97
  for (let i = 0; i < 6; i++) {
56
98
  const candidate = node_path_1.default.join(dir, 'commands', 'gsd');
57
- if (node_fs_1.default.existsSync(candidate))
99
+ try {
100
+ node_fs_1.default.statSync(candidate);
58
101
  return candidate;
102
+ }
103
+ catch { /* not here — keep walking up */ }
59
104
  const parent = node_path_1.default.dirname(dir);
60
105
  if (parent === dir)
61
106
  break;
@@ -72,28 +117,35 @@ function findInstallSourceRoot(runtimeConfigDir) {
72
117
  * 3. Throw a descriptive error if neither succeeds.
73
118
  */
74
119
  function findAgentsSourceRoot(runtimeConfigDir) {
75
- // Step 1: marker check
120
+ // Step 1: marker check (destination-relative — routed through installFs()).
76
121
  if (runtimeConfigDir) {
77
122
  const markerPath = node_path_1.default.join(runtimeConfigDir, '.gsd-source');
78
- if (node_fs_1.default.existsSync(markerPath)) {
123
+ if (installFs().existsSync(markerPath)) {
79
124
  try {
80
- const src = node_fs_1.default.readFileSync(markerPath, 'utf8').trim();
81
- if (src && node_fs_1.default.existsSync(src)) {
125
+ const src = installFs().readFileSync(markerPath, 'utf8').trim();
126
+ if (src && installFs().existsSync(src)) {
82
127
  // Marker points to commands/gsd; agents/ is a sibling of commands/
83
128
  const agentsCandidate = node_path_1.default.resolve(node_path_1.default.dirname(src), '..', 'agents');
84
- if (node_fs_1.default.existsSync(agentsCandidate))
129
+ if (installFs().existsSync(agentsCandidate))
85
130
  return agentsCandidate;
86
131
  }
87
132
  }
88
133
  catch { /* fall through */ }
89
134
  }
90
135
  }
91
- // Step 2: walk up from __dirname
136
+ // Step 2: walk up from __dirname — locates THIS package's own agents/
137
+ // source tree, not the install destination. See findInstallSourceRoot's
138
+ // Step 2 comment (#2874) for why this stays unrouted, real-fs-only, and why
139
+ // it uses `statSync` rather than `existsSync` (load-bearing against F2's
140
+ // poison of the routed fs surface, not a style choice).
92
141
  let dir = __dirname;
93
142
  for (let i = 0; i < 6; i++) {
94
143
  const candidate = node_path_1.default.join(dir, 'agents');
95
- if (node_fs_1.default.existsSync(candidate))
144
+ try {
145
+ node_fs_1.default.statSync(candidate);
96
146
  return candidate;
147
+ }
148
+ catch { /* not here — keep walking up */ }
97
149
  const parent = node_path_1.default.dirname(dir);
98
150
  if (parent === dir)
99
151
  break;
@@ -127,9 +179,53 @@ function agentsKind(destSubpath, prefix, configDir) {
127
179
  // inline agent loop, and installCodexConfig's per-agent .toml writer. The
128
180
  // exhaustive per-runtime sweep in tests/agent-fragments-emission.install.test.cjs
129
181
  // is what keeps a fourth from appearing uncomposed.
130
- stage: (resolved) => stageAgentsForRuntimeWithConverter(findAgentsSourceRoot(configDir), resolved, (content) => content),
182
+ // #2875 Part 2 (row I2): agentCtx threaded through so a runtime using this
183
+ // converter:null builder (claude, plus any future identity-copy runtime)
184
+ // ALSO gets path-rewrites/attribution/frontmatter-extensions/normalize
185
+ // when a caller supplies agentCtx (createRuntimeArtifactInstallPlan /
186
+ // applySurface's agentCtx build). Previously this closure's `(resolved) =>`
187
+ // signature silently dropped the second arg every caller already passed —
188
+ // a caller with NO agentCtx in scope is unaffected (row I2: converter-only,
189
+ // as today), matching stageAgentsForRuntimeWithConverter's own contract.
190
+ stage: (resolved, agentCtx) => stageAgentsForRuntimeWithConverter(findAgentsSourceRoot(configDir), resolved, (content) => content, false, agentCtx),
131
191
  };
132
192
  }
193
+ /**
194
+ * Runtime allowlist check for a descriptor-declared `converter` name, applied
195
+ * at DISPATCH time (security fix). `VALID_CONVERTER_NAMES` (capability-
196
+ * validator.cjs) is otherwise enforced ONLY at lint/build time
197
+ * (`check:contract-drift`) — every `conversionExports[converterName]`
198
+ * dynamic-property read below trusted that a `capability.json` reaching this
199
+ * far had already passed that check. It had not, in general: a hand-edited
200
+ * or malformed descriptor naming an Object-prototype member (`"constructor"`,
201
+ * `"toString"`, `"hasOwnProperty"`, ...) resolves to that member instead of
202
+ * throwing, producing garbage staged content rather than a loud failure —
203
+ * pre-existing, but promoted from the `/gsd-surface`-only path to the real
204
+ * install path for seven runtimes by #2875 Part 2's agents-bypass closure.
205
+ * Required lazily (call-time, not module-top) to avoid a load-time circular
206
+ * require, the same pattern install-engine.cts's `_hostBehaviors` already
207
+ * uses for `capability-registry.cjs`. Fails CLOSED: any error loading the
208
+ * allowlist itself (missing module, exotic bundling) is treated as "nothing
209
+ * is allowed", never as "skip the check".
210
+ */
211
+ function _resolveNamedConverter(converterName, kindLabel) {
212
+ let validNames;
213
+ try {
214
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
215
+ validNames = require('./capability-validator.cjs').VALID_CONVERTER_NAMES;
216
+ }
217
+ catch {
218
+ validNames = undefined;
219
+ }
220
+ if (!validNames || !validNames.has(converterName)) {
221
+ throw new Error(`Unknown converter "${converterName}" declared for a ${kindLabel} kind — refusing to dispatch (not in capability-validator.cjs's VALID_CONVERTER_NAMES allowlist).`);
222
+ }
223
+ const fn = conversionExports[converterName];
224
+ if (typeof fn !== 'function') {
225
+ throw new Error(`Converter "${converterName}" is allowlisted but is not an exported function of runtime-artifact-conversion.cjs.`);
226
+ }
227
+ return fn;
228
+ }
133
229
  /**
134
230
  * Build a converted-agents kind descriptor for runtimes whose agent `.md` files
135
231
  * need runtime-specific frontmatter/body conversion (e.g. Copilot, Cursor, Codex).
@@ -141,27 +237,80 @@ function agentsKind(destSubpath, prefix, configDir) {
141
237
  * Agent filenames are preserved verbatim (the prefix is already embedded in the
142
238
  * agent stem — e.g. `gsd-planner.md`).
143
239
  *
144
- * #1173 SCOPE — plumbing only (real install still elsewhere): this provides
145
- * the converter dispatch + `isGlobal` scope threading for the descriptor's
146
- * `agents` kind. As of #2092, 8 non-Claude runtimes DO declare a converted
147
- * `agents` kind in their `capability.json` — qwen (`convertClaudeAgentToQwenAgent`)
148
- * plus the 7 that already declared one before it (antigravity, augment,
149
- * codebuddy, copilot, cursor, trae, windsurf) — so the descriptor-level
150
- * declaration is no longer deferred. What IS still deferred is wiring
151
- * `resolveRuntimeArtifactLayout`'s `agents` kind into the REAL install:
152
- * `bin/install.js`'s agent-staging loop does not consume this module's
153
- * `convertedAgentsKind` resolution at all — it dispatches the very same
154
- * converter functions directly via `_hostBehaviors(runtime)` checks
155
- * (`frontmatterDialect`, `brandingRewrites`, `isCopilot`/`isAntigravity`/…),
156
- * duplicating the mapping declared here. That duplication is deliberate until
157
- * the second `layout.kinds` consumer — `applySurface` / `/gsd:surface` /
158
- * `--materialize` (`src/surface.cts`) — mirrors the legacy agent pipeline
159
- * (Copilot's `.agent.md` filename rename, the cross-cutting path-prefix
160
- * rewrite + attribution, stale-file cleanup, config-reading steps); declaring
161
- * `bin/install.js` itself against this resolver before then would risk
162
- * regressing the surface path. Until that follow-up lands, `bin/install.js`
163
- * remains authoritative for the real install, and this `convertedAgentsKind`
164
- * is exercised only by `/gsd:surface` and synthetic-descriptor seam tests.
240
+ * #1173 SCOPE, updated by #2875 Part 2 (the agents-bypass closure) — measured
241
+ * against the tree, not the ADR-3574 framing that preceded it:
242
+ *
243
+ * Of the four blockers this comment used to name for wiring `bin/install.js`'s
244
+ * inline agent loop against this resolver, THREE were already stale by the
245
+ * time #2875 measured them and are not re-litigated here: Copilot's
246
+ * `.agent.md` rename (the loop's own `destName = entry.name` comment records
247
+ * the ternary dropped in #2099; the descriptor fold applies it via
248
+ * `hostBehaviors.agentFileExtension`), the cross-cutting path-prefix rewrite +
249
+ * attribution (`stageAgentsForRuntimeWithConverter` already applies
250
+ * `applyAgentPathRewrites` -> `processAttribution` when `agentCtx` is
251
+ * present), and stale-file cleanup (`_removeGsdEntries` prunes every
252
+ * `gsd-`-prefixed entry in a kind's destSubpath, broader than the loop's own
253
+ * extension-gated check).
254
+ *
255
+ * The fourth — config-reading steps — was the real gap, and #2875 Part 2
256
+ * closed it: `stageAgentsForRuntimeWithConverter` now takes a per-file
257
+ * `agentName` (`agentCtx.agentName`, ADR-1235 §1) and a `targetDir`
258
+ * (`agentCtx.targetDir`), which together let it (a) run a post-converter
259
+ * frontmatter-extensions step (`applyAgentFrontmatterExtensions`, driven by
260
+ * `hostBehaviors.agentFrontmatterExtensions` — Claude's `effort` +
261
+ * `disallowedTools` injection) and (b) let THIS function resolve a per-agent
262
+ * model override (`installModelOverrideResolver.resolveAgentModelOverride`,
263
+ * `model_overrides[agent]` > `model_profile_overrides.<rt>.<tier>` > omit)
264
+ * before invoking a converter that needs it (kilo/opencode). Both pieces —
265
+ * plus a data-driven Hermes branding converter
266
+ * (`convertClaudeAgentToHermesAgent`, reading `hostBehaviors.brandingRewrites`
267
+ * rather than a hardcoded string table) — are single-sourced: `bin/install.js`
268
+ * requires the SAME functions this module does, so its inline loop and the
269
+ * descriptor path can no longer independently drift (the CLAUDE.md
270
+ * "Generative Fix Divergence" class the prior duplication risked).
271
+ *
272
+ * `tests/agent-descriptor-parity.test.cjs` proves byte-identical output
273
+ * between the inline loop and a SYNTHETIC descriptor registry (the same
274
+ * override seam `resolveRuntimeArtifactLayoutFromRegistry` exposes) for all
275
+ * six runtimes the inline loop still served: claude, cline, codex, hermes,
276
+ * kilo, opencode.
277
+ *
278
+ * Both findings the prior revision of this comment named as STILL deferred
279
+ * are now CLOSED (#2875 Part 2 Task A/B/C), measured against the real
280
+ * `capability.json` entries and the real production entry points, not
281
+ * argued from this module alone:
282
+ *
283
+ * 1. **kilo/opencode reaching `layout.kinds`.** `installEngine.
284
+ * installAgentsKindStandalone` (install-engine.cts) is called from inside
285
+ * `installOpencodeFamilyArtifacts` and resolves the agents kind through
286
+ * THIS SAME `resolveRuntimeArtifactLayout`/`convertedAgentsKind` path —
287
+ * `installOpencodeFamilyArtifacts` no longer stages only `commands` +
288
+ * `skills`. `bin/install.js`'s legacy-flat local path (claude-local,
289
+ * `hostBehaviors.localInstallStyle === 'legacy-flat'`) reaches the SAME
290
+ * generic loop only via `installRuntimeArtifacts`'s conditional
291
+ * `_isSkillsRuntime` branch; a call to `installAgentsKindStandalone` was
292
+ * added at claude-local's own call site to cover that scope too — the
293
+ * install-tree golden fixture (`tests/fixtures/install-tree/claude-local.json`)
294
+ * is what caught the gap when it was first missed.
295
+ * 2. **`/gsd:surface` / `applySurface` activation.** Confirmed convergent,
296
+ * not merely non-broken: for all six runtimes (claude, cline, codex,
297
+ * hermes, kilo, opencode), staging via `applySurface` into a freshly
298
+ * wiped `agents/` directory produces byte-identical output (including
299
+ * filenames) to `installRuntimeArtifacts`'s own write — verified directly
300
+ * against the built registry, not inferred.
301
+ *
302
+ * The inline loop (`_DESCRIPTOR_AGENTS_RUNTIMES` and the `bin/install.js`
303
+ * agent-staging block it gated) is DELETED — every runtime the registry
304
+ * declares an `agents` kind for is descriptor-driven now, including a
305
+ * seventh runtime (`kimi-code`) this comment's own prior measurement missed
306
+ * (it fell through the inline loop's generic `else if` branch, same as
307
+ * claude, with no dedicated dialect arm — caught by the same golden fixture).
308
+ *
309
+ * Codex's `config.toml [agents.gsd-*]` strip (`bin/install.js`, under
310
+ * `isMinimalMode` + `hostBehaviors.tomlConfigInstall`) remains the one
311
+ * genuinely out-of-scope constraint: it mutates a host config file, not the
312
+ * agents directory, and no descriptor kind models host-config mutation. It
313
+ * stays exactly where it is.
165
314
  *
166
315
  * Mirrors the `convertedCommandsKind` pattern (#785).
167
316
  *
@@ -176,15 +325,45 @@ function convertedAgentsKind(destSubpath, prefix, converterName, configDir, scop
176
325
  destSubpath,
177
326
  prefix,
178
327
  stage: (resolved, agentCtx) => {
179
- // isGlobal is threaded so scope-aware agent converters (copilot, antigravity)
180
- // choose global-home vs workspace-relative paths; converters that only take
181
- // (content) ignore the extra positional arg. Mirrors skillsKind's scope
182
- // threading (#1173).
183
- const converter = conversionExports[converterName];
328
+ // #2870: `scope` is this function's own parameter (default `'global'`,
329
+ // so it is never undefined here), sourced upstream from the Install
330
+ // Scope Module's resolved id. `isGlobalScope` projects it to the
331
+ // boolean `stageAgentsForRuntimeWithConverter`'s positional API
332
+ // requires — see its doc comment in install-scope.cts.
333
+ const rawConverter = _resolveNamedConverter(converterName, 'agents');
334
+ // #2875 Part 2 (J5-J8): kilo/opencode agent converters take an options
335
+ // bag (`{isAgent, modelOverride}`), not the `isGlobal` boolean every
336
+ // other agent converter's 2nd positional arg means — mirrors the
337
+ // inline loop's per-runtime `frontmatterDialect === 'opencode' | 'kilo'`
338
+ // branches (bin/install.js), which resolve model_overrides[agent] >
339
+ // model_profile_overrides.<runtime>.<tier> > omit BEFORE calling the
340
+ // converter. Resolved ONCE per stage() call (not per file — a pure
341
+ // function of configDir/targetDir) via the single shared precedence
342
+ // resolver so kilo and opencode can never diverge (J8).
343
+ const needsModelOverride = converterName === 'convertClaudeToOpencodeFrontmatter' || converterName === 'convertClaudeToKiloFrontmatter';
344
+ let converter;
345
+ if (needsModelOverride) {
346
+ const overrideTargetDir = agentCtx?.targetDir ?? configDir;
347
+ const modelOverrides = installModelOverrideResolver.readGsdEffectiveModelOverrides(overrideTargetDir);
348
+ const runtimeResolver = installModelOverrideResolver.readGsdRuntimeProfileResolver(overrideTargetDir);
349
+ converter = (content, _isGlobal, meta) => {
350
+ const modelOverride = meta
351
+ ? installModelOverrideResolver.resolveAgentModelOverride(meta.agentName, modelOverrides, runtimeResolver)
352
+ : null;
353
+ return rawConverter(content, { isAgent: true, modelOverride });
354
+ };
355
+ }
356
+ else {
357
+ // isGlobal is threaded so scope-aware agent converters (copilot, antigravity)
358
+ // choose global-home vs workspace-relative paths; converters that only take
359
+ // (content) ignore the extra positional arg. Mirrors skillsKind's scope
360
+ // threading (#1173).
361
+ converter = (content) => rawConverter(content, (0, install_scope_cjs_1.isGlobalScope)(scope));
362
+ }
184
363
  // ADR-1235 §1: when agentCtx is provided (by createRuntimeArtifactInstallPlan
185
364
  // for descriptor-driven runtimes), thread it through so stageAgentsForRuntimeWithConverter
186
365
  // can apply the full pre-converter + post-converter sequence in the correct order.
187
- return stageAgentsForRuntimeWithConverter(findAgentsSourceRoot(configDir), resolved, converter, scope === 'global', agentCtx);
366
+ return stageAgentsForRuntimeWithConverter(findAgentsSourceRoot(configDir), resolved, converter, (0, install_scope_cjs_1.isGlobalScope)(scope), agentCtx);
188
367
  },
189
368
  };
190
369
  }
@@ -199,28 +378,28 @@ function kimiAgentsKind(destSubpath, prefix, configDir) {
199
378
  // sees marker-free content — same single composing stager as agentsKind.
200
379
  const stagedAgents = stageAgentsForRuntimeWithConverter(findAgentsSourceRoot(configDir), resolved, (content) => content);
201
380
  const subagents = [];
202
- if (node_fs_1.default.existsSync(stagedAgents)) {
203
- for (const entry of node_fs_1.default.readdirSync(stagedAgents, { withFileTypes: true })) {
381
+ if (installFs().existsSync(stagedAgents)) {
382
+ for (const entry of installFs().readdirSync(stagedAgents, { withFileTypes: true })) {
204
383
  if (!entry.isFile() || !entry.name.endsWith('.md'))
205
384
  continue;
206
385
  const agentPath = node_path_1.default.join(stagedAgents, entry.name);
207
386
  subagents.push({
208
387
  path: (0, shell_command_projection_cjs_1.posixNormalize)(node_path_1.default.join('agents', entry.name)),
209
- content: node_fs_1.default.readFileSync(agentPath, 'utf8'),
388
+ content: installFs().readFileSync(agentPath, 'utf8'),
210
389
  });
211
390
  }
212
391
  }
213
392
  const rootAgent = `---\nname: gsd\ndescription: Run GSD workflows in Kimi CLI.\ntools: Agent\n---\n\n# GSD for Kimi CLI\n\nCoordinate installed /skill:gsd-* workflows and route work to generated GSD subagents when a workflow requires an agent handoff.\n`;
214
393
  const artifacts = buildKimiAgentArtifacts({ rootAgent, subagents });
215
- const stageDir = node_fs_1.default.mkdtempSync(node_path_1.default.join(node_os_1.default.tmpdir(), 'gsd-kimi-agents-'));
394
+ const stageDir = mkInstallTempDir('gsd-kimi-agents-');
216
395
  installProfiles.STAGED_DIRS.add(stageDir);
217
- node_fs_1.default.writeFileSync(node_path_1.default.join(stageDir, 'gsd.yaml'), artifacts.root.yaml);
218
- node_fs_1.default.writeFileSync(node_path_1.default.join(stageDir, 'gsd.md'), artifacts.root.prompt);
396
+ installFs().writeFileSync(node_path_1.default.join(stageDir, 'gsd.yaml'), artifacts.root.yaml);
397
+ installFs().writeFileSync(node_path_1.default.join(stageDir, 'gsd.md'), artifacts.root.prompt);
219
398
  const subagentsDir = node_path_1.default.join(stageDir, 'subagents');
220
- node_fs_1.default.mkdirSync(subagentsDir, { recursive: true });
399
+ installFs().mkdirSync(subagentsDir, { recursive: true });
221
400
  for (const artifact of artifacts.subagents) {
222
- node_fs_1.default.writeFileSync(node_path_1.default.join(subagentsDir, `${artifact.name}.yaml`), artifact.yaml);
223
- node_fs_1.default.writeFileSync(node_path_1.default.join(subagentsDir, `${artifact.name}.md`), artifact.prompt);
401
+ installFs().writeFileSync(node_path_1.default.join(subagentsDir, `${artifact.name}.yaml`), artifact.yaml);
402
+ installFs().writeFileSync(node_path_1.default.join(subagentsDir, `${artifact.name}.md`), artifact.prompt);
224
403
  }
225
404
  return stageDir;
226
405
  },
@@ -251,7 +430,7 @@ function skillsKind(destSubpath, prefix, converterName, runtime, configDir, nest
251
430
  prefix,
252
431
  converter: converterName,
253
432
  stage: (resolved) => {
254
- const realConverter = conversionExports[converterName];
433
+ const realConverter = _resolveNamedConverter(converterName, 'skills');
255
434
  // Compute cmdNames once per stage call for performance (#3583).
256
435
  // Extra trailing args are ignored by converters that don't need them. The
257
436
  // isGlobal flag is the 5th positional (NOT the 3rd): the 3rd positional is
@@ -261,7 +440,21 @@ function skillsKind(destSubpath, prefix, converterName, runtime, configDir, nest
261
440
  const cmdNames = conversionExports.readGsdCommandNames
262
441
  ? conversionExports.readGsdCommandNames()
263
442
  : [];
264
- const isGlobal = scope === 'global';
443
+ // #2870: same judgment as convertedAgentsKind above — `scope` is this
444
+ // function's own parameter (default `'global'`, so it is never
445
+ // undefined here); `isGlobalScope` projects it to the boolean
446
+ // `realConverter`'s positional `isGlobal` arg requires.
447
+ const isGlobal = (0, install_scope_cjs_1.isGlobalScope)(scope);
448
+ // #2873 (4b): spec-root reachability is applied LATER in the pipeline —
449
+ // see `rewriteStagedSkillBodies` in runtime-artifact-conversion.cts, not
450
+ // here. This stage() closure runs BEFORE the staged directory's generic
451
+ // path-prefix rewrite pass (`applyRuntimeContentRewritesInPlace`'s
452
+ // `case 'claude'`), which unconditionally rewrites any bare (non-`@`)
453
+ // `~/.claude/` substring to the undocumented `$HOME/.claude/` form and
454
+ // only restores the `@`-prefixed form. Emitting the imperative
455
+ // tilde-path prose here would get silently mangled by that later pass;
456
+ // it must run AFTER it instead, once the `@`-include is in its final
457
+ // rewritten shape.
265
458
  const wrappedConverter = (content, skillName) => realConverter(content, skillName, runtime, cmdNames, isGlobal);
266
459
  return stageSkillsForRuntimeAsSkills(findInstallSourceRoot(configDir), resolved, wrappedConverter, prefix, nested, capabilityRegistry);
267
460
  },
@@ -289,7 +482,7 @@ function convertedCommandsKind(destSubpath, prefix, converterName, configDir) {
289
482
  destSubpath,
290
483
  prefix,
291
484
  stage: (resolved) => {
292
- const converter = conversionExports[converterName];
485
+ const converter = _resolveNamedConverter(converterName, 'commands');
293
486
  return stageCommandsForRuntimeFlat(findInstallSourceRoot(configDir), resolved, converter, prefix);
294
487
  },
295
488
  };
@@ -328,7 +521,11 @@ function dispatchKindEntry(entry, runtime, configDir, scope, capabilityRegistry)
328
521
  default:
329
522
  throw new TypeError(`resolveRuntimeArtifactLayout: unknown kind '${kind}' in descriptor for runtime '${runtime}'`);
330
523
  }
331
- if (scope === 'global' && typeof entry.home === 'string' && entry.home !== '') {
524
+ // scope is guaranteed 'local' | 'global' here: resolveRuntimeArtifactLayoutFromRegistry
525
+ // (the only caller of dispatchKindEntry) throws TypeError before this point if scope is
526
+ // anything else (see the `scope !== 'local' && scope !== 'global'` guard above its
527
+ // dispatchKindEntry call), so isGlobalScope's throw-on-invalid-input never fires here.
528
+ if ((0, install_scope_cjs_1.isGlobalScope)(scope) && typeof entry.home === 'string' && entry.home !== '') {
332
529
  result.home = node_path_1.default.join(node_os_1.default.homedir(), entry.home);
333
530
  }
334
531
  return result;
@@ -367,4 +564,178 @@ function resolveRuntimeArtifactLayoutFromRegistry(registry, runtime, configDir,
367
564
  const kinds = entries.map((entry) => dispatchKindEntry(entry, runtime, configDir, scope, capabilityRegistry));
368
565
  return { runtime, configDir, scope, kinds };
369
566
  }
370
- module.exports = { resolveRuntimeArtifactLayout, resolveRuntimeArtifactLayoutFromRegistry, findInstallSourceRoot };
567
+ function getTriggerRegistry() {
568
+ return _require('./capability-registry.cjs');
569
+ }
570
+ /**
571
+ * capability-validator.cjs is a COMMITTED plain .cjs (not built from a .cts
572
+ * source — see its own header comment), so it is required the same way
573
+ * capability-registry.cjs is above: a lazy `_require` rather than a static
574
+ * ES import. DEFAULT_TRIGGER_PRECEDENCE is the single source of truth for
575
+ * "what applies when a descriptor omits triggerPrecedence"; this module
576
+ * reads it rather than re-declaring `['skills', 'commands']` as a second
577
+ * literal that could silently drift from the validator's own default.
578
+ */
579
+ function getDefaultTriggerPrecedence() {
580
+ const capValidator = _require('./capability-validator.cjs');
581
+ return capValidator.DEFAULT_TRIGGER_PRECEDENCE;
582
+ }
583
+ /**
584
+ * True when a `commands` kind entry is namespaced by its destination
585
+ * directory rather than by a filename prefix — i.e. `destSubpath`'s basename
586
+ * equals `prefix` with its trailing hyphen stripped (e.g. `commands/gsd` +
587
+ * `gsd-`). When true, `_copyStaged` (`install-engine.cts`) and `surface.cts`
588
+ * both write the bare stem filename (no prefix) because the directory itself
589
+ * already carries the namespace; `resolveTriggerSurface` mirrors that in its
590
+ * own `destPath` computation. Single source of truth for the three sites
591
+ * that used to compute this independently (#2871 Phase 2 review finding) —
592
+ * a new caller MUST reuse this rather than re-deriving the rule.
593
+ */
594
+ function isNamespacedByDir(kind, destSubpath, prefix) {
595
+ const destLast = node_path_1.default.posix.basename((0, shell_command_projection_cjs_1.posixNormalize)(destSubpath));
596
+ const prefixStem = prefix ? prefix.replace(/-$/, '') : '';
597
+ return kind === 'commands' && destLast === prefixStem;
598
+ }
599
+ /**
600
+ * Compose the destination filename `_copyStaged` (install-engine.cts) writes
601
+ * for a `commands` kind entry, given `isNamespacedByDir`'s result, the
602
+ * kind's prefix, and the file's `.md`-stripped stem. Single source of truth
603
+ * alongside `isNamespacedByDir` for the FILENAME COMPOSITION itself (#2871
604
+ * Phase 2 review finding — the boolean was single-sourced first, but the
605
+ * `${stem}.md` / `${prefix}${stem}.md` string-building around it stayed
606
+ * duplicated between `_copyStaged` and `resolveTriggerSurface`'s `destPath`
607
+ * prediction below, so a divergence in the write convention would not have
608
+ * failed anything).
609
+ *
610
+ * Byte-identical to `_copyStaged`'s prior separate branches: when
611
+ * `namespacedByDir` is true this returns `${stem}.md`. `_copyStaged` always
612
+ * derives `stem` as `entry.name.slice(0, -3)` for an `entry.name` that has
613
+ * already been filtered to end in `.md`, so `${stem}.md` is always exactly
614
+ * `entry.name` again — `_copyStaged` can pass this helper's result in place
615
+ * of the `entry.name` it used to write directly, with no behavior change.
616
+ */
617
+ function composeCommandFilename(namespacedByDir, prefix, stem) {
618
+ return namespacedByDir ? `${stem}.md` : `${prefix}${stem}.md`;
619
+ }
620
+ /**
621
+ * True when candidate `a` should win over the current best `b` for the same
622
+ * trigger. Scope rank first (Phase 1's `install-scope.cts#scopeRank` —
623
+ * global outranks local; NOT re-derived here), then the runtime's
624
+ * `triggerPrecedence` kind ordering (lower index = higher priority). A kind
625
+ * absent from `precedenceRank` (should not happen — every entry's kind is
626
+ * validated against the same closed vocabulary the precedence list draws
627
+ * from) sorts last rather than throwing, so a malformed precedence value
628
+ * degrades to "leaves the incumbent standing" instead of corrupting the
629
+ * whole resolution.
630
+ */
631
+ function isHigherPriority(a, b, precedenceRank) {
632
+ const rankA = (0, install_scope_cjs_1.scopeRank)(a.scope);
633
+ const rankB = (0, install_scope_cjs_1.scopeRank)(b.scope);
634
+ if (rankA !== rankB)
635
+ return rankA > rankB;
636
+ const pa = precedenceRank.get(a.kind) ?? Number.POSITIVE_INFINITY;
637
+ const pb = precedenceRank.get(b.kind) ?? Number.POSITIVE_INFINITY;
638
+ return pa < pb;
639
+ }
640
+ /**
641
+ * Resolve the `/gsd-<name>`-style trigger surface for a runtime: what the
642
+ * user types, at which scope, whether it wins or is shadowed, and (for
643
+ * nested-router runtimes) whether the host registers it directly or only
644
+ * reaches it through a router. Pure — no filesystem, no mutation of `scopes`
645
+ * or `opts`, and safe against a caller mutating the returned array/objects
646
+ * (a fresh array/objects are built on every call; nothing is cached or
647
+ * shared across calls beyond the read-only registry module).
648
+ *
649
+ * Only `commands` and `skills` kind entries are considered — see the
650
+ * module-level comment above. `resolveRuntimeArtifactLayout` is untouched by
651
+ * this function; they are independent readers of the same descriptor.
652
+ *
653
+ * @throws {TypeError} for an unknown runtime — same contract (and message
654
+ * shape) as `resolveRuntimeArtifactLayoutFromRegistry`.
655
+ * @throws {TypeError} for an unrecognized entry in `scopes` — reuses
656
+ * `install-scope.cts`'s shared `validateScopeId`, the same validator
657
+ * `scopeRank`/`resolveScope`/`isGlobalScope` already throw through, so this
658
+ * sibling of theirs cannot silently fail open on a bad scope (#2871 Phase 2
659
+ * review finding). `scopes: []` is untouched — an empty array has no
660
+ * entries to validate and still resolves to `[]`.
661
+ */
662
+ function resolveTriggerSurface(runtime, scopes, opts) {
663
+ const registry = opts.registry ?? getTriggerRegistry();
664
+ const runtimeDescriptor = registry.runtimes[runtime]?.runtime;
665
+ const layout = runtimeDescriptor?.artifactLayout;
666
+ if (!layout) {
667
+ throw new TypeError(`Unknown runtime: '${runtime}' — add to runtime-artifact-layout.cjs table`);
668
+ }
669
+ for (const scope of scopes) {
670
+ (0, install_scope_cjs_1.validateScopeId)(scope, 'resolveTriggerSurface');
671
+ }
672
+ const scopeSet = new Set(scopes);
673
+ const stems = opts.stems ?? [];
674
+ const routerStemSet = new Set(opts.routerStems ?? []);
675
+ const childToRouters = opts.childToRouters ?? {};
676
+ const precedence = runtimeDescriptor?.triggerPrecedence ?? getDefaultTriggerPrecedence();
677
+ const precedenceRank = new Map(precedence.map((kind, index) => [kind, index]));
678
+ const surfaces = [];
679
+ for (const scope of install_scope_cjs_1.SCOPE_ORDER) {
680
+ if (!scopeSet.has(scope))
681
+ continue;
682
+ const entries = layout[scope] ?? [];
683
+ for (const entry of entries) {
684
+ if (entry.kind !== 'commands' && entry.kind !== 'skills')
685
+ continue; // excludes agents/kimi-agents
686
+ const kind = entry.kind;
687
+ const destSubpath = (0, shell_command_projection_cjs_1.posixNormalize)(entry.destSubpath);
688
+ const namespacedByDir = isNamespacedByDir(kind, entry.destSubpath, entry.prefix);
689
+ const nested = entry.nesting === 'nested';
690
+ for (const stem of stems) {
691
+ const trigger = `${entry.prefix}${stem}`;
692
+ let destPath;
693
+ if (kind === 'skills') {
694
+ destPath = `${destSubpath}/${entry.prefix}${stem}`;
695
+ }
696
+ else {
697
+ destPath = `${destSubpath}/${composeCommandFilename(namespacedByDir, entry.prefix, stem)}`;
698
+ }
699
+ let registration = 'direct';
700
+ let routerTrigger = null;
701
+ if (nested && routerStemSet.size > 0 && !routerStemSet.has(stem)) {
702
+ const owningRouters = childToRouters[stem];
703
+ const routerStem = owningRouters && owningRouters.length > 0 ? owningRouters[0] : undefined;
704
+ if (routerStem !== undefined && routerStemSet.has(routerStem)) {
705
+ registration = 'via-router';
706
+ routerTrigger = `${entry.prefix}${routerStem}`;
707
+ }
708
+ }
709
+ surfaces.push({ trigger, kind, scope, destPath, registration, routerTrigger, shadowedBy: null });
710
+ }
711
+ }
712
+ }
713
+ // Winner computation, per trigger string, across every scope/kind candidate.
714
+ const groups = new Map();
715
+ for (const surface of surfaces) {
716
+ const group = groups.get(surface.trigger);
717
+ if (group) {
718
+ group.push(surface);
719
+ }
720
+ else {
721
+ groups.set(surface.trigger, [surface]);
722
+ }
723
+ }
724
+ for (const group of groups.values()) {
725
+ if (group.length <= 1)
726
+ continue; // sole candidate: unshadowed by construction
727
+ let winner = group[0];
728
+ for (let i = 1; i < group.length; i++) {
729
+ const candidate = group[i];
730
+ if (isHigherPriority(candidate, winner, precedenceRank))
731
+ winner = candidate;
732
+ }
733
+ for (const surface of group) {
734
+ if (surface !== winner) {
735
+ surface.shadowedBy = { kind: winner.kind, scope: winner.scope };
736
+ }
737
+ }
738
+ }
739
+ return surfaces;
740
+ }
741
+ module.exports = { resolveRuntimeArtifactLayout, resolveRuntimeArtifactLayoutFromRegistry, findInstallSourceRoot, resolveTriggerSurface, isNamespacedByDir, composeCommandFilename };
@@ -40,8 +40,9 @@ const VALID_SANDBOX_TIERS = new Set(['none', 'codex-agent-sandbox']);
40
40
  * (Marketplace/VSIX-distributed, never dispatched through
41
41
  * install()/finishInstall()), so it is not a "config-adapter runtime" by
42
42
  * definition. This keeps this set in lockstep with bin/install.js's
43
- * `allRuntimes` (see tests/issue-57-runtime-install-no-drift.test.cjs) without
44
- * needing a separate hand-kept exclusion list.
43
+ * `allRuntimes` (see the folded:issue-57-runtime-install-no-drift describe
44
+ * block in tests/runtime-config-adapter-registry.test.cjs) without needing a
45
+ * separate hand-kept exclusion list.
45
46
  */
46
47
  const ALLOWED_CONFIG_RUNTIMES = new Set(Object.entries(runtimes)
47
48
  .filter(([, cap]) => cap && cap.runtime && typeof cap.runtime['installSurface'] === 'string' && cap.runtime['installSurface'] !== 'none')