@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
@@ -13,13 +13,26 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
13
13
  const node_fs_1 = __importDefault(require("node:fs"));
14
14
  const node_path_1 = __importDefault(require("node:path"));
15
15
  const node_os_1 = __importDefault(require("node:os"));
16
+ // #2874 (ADR-58 cleanup phase): route the staging functions' fs calls
17
+ // through the installRuntimeArtifacts call tree's injectable seam — see
18
+ // install-fs-adapter.cts's module doc. Resolves to real `node:fs` unless the
19
+ // top-level installRuntimeArtifacts call injected a `deps.fs`. Only the
20
+ // staging functions reachable FROM that call tree are routed
21
+ // (stageSkillsForProfile / stageAgentsForProfile /
22
+ // stageAgentsForRuntimeWithConverter / stageSkillsForRuntimeAsSkills /
23
+ // stageCommandsForRuntimeFlat / buildNamespaceBundleMap) — the profile-marker
24
+ // and manifest-loading helpers below are not on that call tree and keep
25
+ // using real `fs` directly.
26
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
27
+ const installFsAdapter = require("./install-fs-adapter.cjs");
28
+ const { installFs, mkInstallTempDir } = installFsAdapter;
16
29
  const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
17
30
  // #2322: reuse the existing pure path-containment seam (ADR-1239 Phase C-2)
18
31
  // instead of hand-rolling a new traversal check for capability skill stems.
19
32
  const external_descriptor_trust_cjs_1 = require("./external-descriptor-trust.cjs");
20
33
  // eslint-disable-next-line @typescript-eslint/no-require-imports
21
34
  const conversionModule = require("./runtime-artifact-conversion.cjs");
22
- const { applyAgentPathRewrites: _applyAgentPathRewrites, processAttribution: _processAttribution, normalizeAgentBodyForRuntime: _normalizeAgentBodyForRuntime, readGsdCommandNames: _readGsdCommandNames, } = conversionModule;
35
+ const { applyAgentPathRewrites: _applyAgentPathRewrites, processAttribution: _processAttribution, normalizeAgentBodyForRuntime: _normalizeAgentBodyForRuntime, readGsdCommandNames: _readGsdCommandNames, deriveAgentName: _deriveAgentName, applyAgentFrontmatterExtensions: _applyAgentFrontmatterExtensions, } = conversionModule;
23
36
  // #2995 (epic #1671 Phase 6.4): agent bodies join the fragment model. Markers are
24
37
  // stripped at emit BEFORE any path rewrite or converter runs, so a `.claude/` ->
25
38
  // `.windsurf/` regex can never reach inside a marker attribute and corrupt it —
@@ -264,10 +277,41 @@ function resolveProfile({ modes, manifest, _profilesOverride, registry } = {}) {
264
277
  // site to track its own cleanup handle.
265
278
  const STAGED_DIRS = new Set();
266
279
  let exitHandlerRegistered = false;
280
+ // #2874 leak-fix: `cleanupStagedSkills` runs at process exit — AFTER
281
+ // `withInstallFs` has already restored `current` back to the real adapter
282
+ // (install-fs-adapter.cts's module doc, "SYNCHRONOUS-ONLY / RE-ENTRANCY
283
+ // ASSUMPTION" — extended there to reference this map). A dir staged during a
284
+ // fake-adapter call must be cleaned up with THAT SAME fake adapter, not with
285
+ // whatever is ambiently active later, or an exit handler would perform real
286
+ // filesystem IO on a path that only ever existed in the fake's in-memory
287
+ // store. Capturing the adapter OBJECT `installFs()` returns at registration
288
+ // time (not the ambient `current` variable, which changes) means cleanup
289
+ // always replays the exact adapter that created the path. Dirs added to
290
+ // STAGED_DIRS without going through `registerStagedDir` (the deprecated
291
+ // `stageSkillsForMode`, which creates its stage dir via raw `fs.mkdtempSync`
292
+ // and was never on the injectable seam) have no entry here and fall back to
293
+ // real `fs.rmSync` below — the same real fs it always used to create them.
294
+ const STAGED_DIR_ADAPTERS = new Map();
295
+ /**
296
+ * Register a dir just staged through the injectable seam (`installFs()`) for
297
+ * exit-time cleanup, capturing the adapter that staged it alongside the path.
298
+ * See `STAGED_DIR_ADAPTERS`'s comment for why the capture matters.
299
+ */
300
+ function registerStagedDir(dir) {
301
+ STAGED_DIRS.add(dir);
302
+ STAGED_DIR_ADAPTERS.set(dir, installFs());
303
+ ensureExitCleanup();
304
+ }
267
305
  function cleanupStagedSkills() {
268
306
  for (const dir of STAGED_DIRS) {
307
+ const adapter = STAGED_DIR_ADAPTERS.get(dir);
269
308
  try {
270
- node_fs_1.default.rmSync(dir, { recursive: true, force: true });
309
+ if (adapter) {
310
+ adapter.rmSync(dir, { recursive: true, force: true });
311
+ }
312
+ else {
313
+ node_fs_1.default.rmSync(dir, { recursive: true, force: true });
314
+ }
271
315
  }
272
316
  catch {
273
317
  // Best-effort: missing dir or permission error shouldn't crash a
@@ -275,6 +319,7 @@ function cleanupStagedSkills() {
275
319
  }
276
320
  }
277
321
  STAGED_DIRS.clear();
322
+ STAGED_DIR_ADAPTERS.clear();
278
323
  }
279
324
  // Signals we register a cleanup handler for in addition to the natural
280
325
  // 'exit' event. `process.on('exit')` does NOT fire on these — an installer
@@ -303,11 +348,11 @@ function ensureExitCleanup() {
303
348
  function stageSkillsForProfile(srcDir, resolvedProfile) {
304
349
  if (resolvedProfile.skills === '*')
305
350
  return srcDir;
306
- if (!node_fs_1.default.existsSync(srcDir))
351
+ if (!installFs().existsSync(srcDir))
307
352
  return srcDir;
308
- const stageDir = node_fs_1.default.mkdtempSync(node_path_1.default.join(node_os_1.default.tmpdir(), 'gsd-profile-skills-'));
353
+ const stageDir = mkInstallTempDir('gsd-profile-skills-');
309
354
  try {
310
- const entries = node_fs_1.default.readdirSync(srcDir, { withFileTypes: true });
355
+ const entries = installFs().readdirSync(srcDir, { withFileTypes: true });
311
356
  for (const entry of entries) {
312
357
  if (!entry.isFile())
313
358
  continue;
@@ -316,18 +361,17 @@ function stageSkillsForProfile(srcDir, resolvedProfile) {
316
361
  const stem = entry.name.slice(0, -3);
317
362
  if (!(resolvedProfile.skills).has(stem))
318
363
  continue;
319
- node_fs_1.default.copyFileSync(node_path_1.default.join(srcDir, entry.name), node_path_1.default.join(stageDir, entry.name));
364
+ installFs().copyFileSync(node_path_1.default.join(srcDir, entry.name), node_path_1.default.join(stageDir, entry.name));
320
365
  }
321
366
  }
322
367
  catch (err) {
323
368
  try {
324
- node_fs_1.default.rmSync(stageDir, { recursive: true, force: true });
369
+ installFs().rmSync(stageDir, { recursive: true, force: true });
325
370
  }
326
371
  catch { /* best-effort */ }
327
372
  throw err;
328
373
  }
329
- STAGED_DIRS.add(stageDir);
330
- ensureExitCleanup();
374
+ registerStagedDir(stageDir);
331
375
  return stageDir;
332
376
  }
333
377
  /**
@@ -353,12 +397,12 @@ function stageSkillsForProfile(srcDir, resolvedProfile) {
353
397
  function stageAgentsForProfile(srcAgentsDir, resolvedProfile) {
354
398
  if (resolvedProfile.skills === '*')
355
399
  return srcAgentsDir;
356
- if (!node_fs_1.default.existsSync(srcAgentsDir))
400
+ if (!installFs().existsSync(srcAgentsDir))
357
401
  return srcAgentsDir;
358
- const stageDir = node_fs_1.default.mkdtempSync(node_path_1.default.join(node_os_1.default.tmpdir(), 'gsd-profile-agents-'));
402
+ const stageDir = mkInstallTempDir('gsd-profile-agents-');
359
403
  try {
360
404
  if (resolvedProfile.agents instanceof Set && resolvedProfile.agents.size > 0) {
361
- const entries = node_fs_1.default.readdirSync(srcAgentsDir, { withFileTypes: true });
405
+ const entries = installFs().readdirSync(srcAgentsDir, { withFileTypes: true });
362
406
  for (const entry of entries) {
363
407
  if (!entry.isFile())
364
408
  continue;
@@ -368,20 +412,19 @@ function stageAgentsForProfile(srcAgentsDir, resolvedProfile) {
368
412
  const stem = entry.name.slice(0, -3);
369
413
  if (!resolvedProfile.agents.has(stem))
370
414
  continue;
371
- node_fs_1.default.copyFileSync(node_path_1.default.join(srcAgentsDir, entry.name), node_path_1.default.join(stageDir, entry.name));
415
+ installFs().copyFileSync(node_path_1.default.join(srcAgentsDir, entry.name), node_path_1.default.join(stageDir, entry.name));
372
416
  }
373
417
  }
374
418
  // If agents is empty Set, we produce an empty stageDir (no agents for this profile)
375
419
  }
376
420
  catch (err) {
377
421
  try {
378
- node_fs_1.default.rmSync(stageDir, { recursive: true, force: true });
422
+ installFs().rmSync(stageDir, { recursive: true, force: true });
379
423
  }
380
424
  catch { /* best-effort */ }
381
425
  throw err;
382
426
  }
383
- STAGED_DIRS.add(stageDir);
384
- ensureExitCleanup();
427
+ registerStagedDir(stageDir);
385
428
  return stageDir;
386
429
  }
387
430
  /**
@@ -395,10 +438,10 @@ function buildNamespaceBundleMap(srcCommandsDir) {
395
438
  const routerStems = new Set();
396
439
  const routerChildren = new Map();
397
440
  const childToRouters = new Map();
398
- if (!node_fs_1.default.existsSync(srcCommandsDir)) {
441
+ if (!installFs().existsSync(srcCommandsDir)) {
399
442
  return { routerStems, routerChildren, childToRouters };
400
443
  }
401
- for (const entry of node_fs_1.default.readdirSync(srcCommandsDir, { withFileTypes: true })) {
444
+ for (const entry of installFs().readdirSync(srcCommandsDir, { withFileTypes: true })) {
402
445
  if (!entry.isFile() || !entry.name.endsWith('.md'))
403
446
  continue;
404
447
  if (!entry.name.startsWith('ns-'))
@@ -406,7 +449,7 @@ function buildNamespaceBundleMap(srcCommandsDir) {
406
449
  const stem = entry.name.slice(0, -3);
407
450
  let children = [];
408
451
  try {
409
- children = parseRequires(node_fs_1.default.readFileSync(node_path_1.default.join(srcCommandsDir, entry.name), 'utf8'));
452
+ children = parseRequires(installFs().readFileSync(node_path_1.default.join(srcCommandsDir, entry.name), 'utf8'));
410
453
  }
411
454
  catch {
412
455
  children = [];
@@ -637,7 +680,7 @@ function readInstalledCapabilitySkill(stem, registry) {
637
680
  * When absent, NOTHING third-party is staged (fail closed).
638
681
  */
639
682
  function stageSkillsForRuntimeAsSkills(srcCommandsDir, resolvedProfile, converter, prefix, nested = false, registry) {
640
- if (!node_fs_1.default.existsSync(srcCommandsDir))
683
+ if (!installFs().existsSync(srcCommandsDir))
641
684
  return srcCommandsDir;
642
685
  // Nesting applies to the `full` install AND to any surface whose skill set
643
686
  // still contains every namespace router (a full/reset surface). It must NOT
@@ -660,9 +703,9 @@ function stageSkillsForRuntimeAsSkills(srcCommandsDir, resolvedProfile, converte
660
703
  // this call, so the third-party fill-in pass below can enforce "first-party
661
704
  // ALWAYS wins on collision" without re-deriving membership.
662
705
  const firstPartyStems = new Set();
663
- const stageDir = node_fs_1.default.mkdtempSync(node_path_1.default.join(node_os_1.default.tmpdir(), 'gsd-profile-runtime-skills-'));
706
+ const stageDir = mkInstallTempDir('gsd-profile-runtime-skills-');
664
707
  try {
665
- const entries = node_fs_1.default.readdirSync(srcCommandsDir, { withFileTypes: true });
708
+ const entries = installFs().readdirSync(srcCommandsDir, { withFileTypes: true });
666
709
  for (const entry of entries) {
667
710
  if (!entry.isFile())
668
711
  continue;
@@ -672,15 +715,15 @@ function stageSkillsForRuntimeAsSkills(srcCommandsDir, resolvedProfile, converte
672
715
  if (resolvedProfile.skills !== '*' && !(resolvedProfile.skills).has(stem))
673
716
  continue;
674
717
  firstPartyStems.add(stem);
675
- const content = node_fs_1.default.readFileSync(node_path_1.default.join(srcCommandsDir, entry.name), 'utf8');
718
+ const content = installFs().readFileSync(node_path_1.default.join(srcCommandsDir, entry.name), 'utf8');
676
719
  const skillName = `${prefix}${stem}`;
677
720
  const converted = converter(content, skillName);
678
721
  if (doNest && bundles.routerStems.has(stem)) {
679
722
  // Router skill: rewrite its routing table to the nested Read pattern and
680
723
  // emit it as the single top-level bundle entry.
681
724
  const destDir = node_path_1.default.join(stageDir, skillName);
682
- node_fs_1.default.mkdirSync(destDir, { recursive: true });
683
- node_fs_1.default.writeFileSync(node_path_1.default.join(destDir, 'SKILL.md'), transformRouterBodyToNested(converted));
725
+ installFs().mkdirSync(destDir, { recursive: true });
726
+ installFs().writeFileSync(node_path_1.default.join(destDir, 'SKILL.md'), transformRouterBodyToNested(converted));
684
727
  continue;
685
728
  }
686
729
  if (doNest && bundles.childToRouters.has(stem)) {
@@ -689,16 +732,16 @@ function stageSkillsForRuntimeAsSkills(srcCommandsDir, resolvedProfile, converte
689
732
  // top-level eager listing while staying readable by file path (#69).
690
733
  for (const routerStem of bundles.childToRouters.get(stem)) {
691
734
  const destDir = node_path_1.default.join(stageDir, `${prefix}${routerStem}`, 'skills', stem);
692
- node_fs_1.default.mkdirSync(destDir, { recursive: true });
693
- node_fs_1.default.writeFileSync(node_path_1.default.join(destDir, 'SKILL.md'), converted);
735
+ installFs().mkdirSync(destDir, { recursive: true });
736
+ installFs().writeFileSync(node_path_1.default.join(destDir, 'SKILL.md'), converted);
694
737
  }
695
738
  continue;
696
739
  }
697
740
  // Flat top-level skill (default behaviour; also the unrouted fallback when
698
741
  // nesting is active).
699
742
  const destDir = node_path_1.default.join(stageDir, skillName);
700
- node_fs_1.default.mkdirSync(destDir, { recursive: true });
701
- node_fs_1.default.writeFileSync(node_path_1.default.join(destDir, 'SKILL.md'), converted);
743
+ installFs().mkdirSync(destDir, { recursive: true });
744
+ installFs().writeFileSync(node_path_1.default.join(destDir, 'SKILL.md'), converted);
702
745
  }
703
746
  // #2322: materialize installed THIRD-PARTY capability skills, bound to
704
747
  // their DECLARING capability via the registry's capabilityClusters view
@@ -740,25 +783,24 @@ function stageSkillsForRuntimeAsSkills(srcCommandsDir, resolvedProfile, converte
740
783
  if (!(0, external_descriptor_trust_cjs_1.isPathConfined)(skillName, stageDir))
741
784
  continue; // defense-in-depth
742
785
  const destDir = node_path_1.default.join(stageDir, skillName);
743
- node_fs_1.default.mkdirSync(destDir, { recursive: true });
744
- node_fs_1.default.writeFileSync(node_path_1.default.join(destDir, 'SKILL.md'), found.content);
786
+ installFs().mkdirSync(destDir, { recursive: true });
787
+ installFs().writeFileSync(node_path_1.default.join(destDir, 'SKILL.md'), found.content);
745
788
  // #2322 HIGH-3: persist the capability-owned marker so a later prune
746
789
  // pass (surface.cts pruneSkillDirs) can identify — and remove — this
747
790
  // directory even once the owning capability is uninstalled/unsurfaced
748
791
  // and no longer appears in any registry view.
749
- node_fs_1.default.writeFileSync(node_path_1.default.join(destDir, CAPABILITY_SKILL_MARKER), found.capId + '\n', 'utf8');
792
+ installFs().writeFileSync(node_path_1.default.join(destDir, CAPABILITY_SKILL_MARKER), found.capId + '\n', 'utf8');
750
793
  }
751
794
  }
752
795
  }
753
796
  catch (err) {
754
797
  try {
755
- node_fs_1.default.rmSync(stageDir, { recursive: true, force: true });
798
+ installFs().rmSync(stageDir, { recursive: true, force: true });
756
799
  }
757
800
  catch { /* best-effort */ }
758
801
  throw err;
759
802
  }
760
- STAGED_DIRS.add(stageDir);
761
- ensureExitCleanup();
803
+ registerStagedDir(stageDir);
762
804
  return stageDir;
763
805
  }
764
806
  /**
@@ -782,26 +824,57 @@ function stageSkillsForRuntimeAsSkills(srcCommandsDir, resolvedProfile, converte
782
824
  * 1. applyAgentPathRewrites (4 base ~/.claude/ regexes; skipped for copilot/antigravity)
783
825
  * 2. processAttribution (Co-Authored-By policy)
784
826
  * 3. converter (runtime-specific frontmatter/body transform)
785
- * 4. normalizeAgentBodyForRuntime (colon→hyphen refs; no-op for trivial group)
827
+ * 4. applyAgentFrontmatterExtensions (#2875 Part 2: effort/disallowedTools,
828
+ * gated by hostBehaviors.agentFrontmatterExtensions — no-op for a runtime
829
+ * that declares nothing, e.g. every non-Claude runtime today)
830
+ * 5. normalizeAgentBodyForRuntime (colon→hyphen refs; no-op for trivial group)
786
831
  * When `agentCtx` is absent, only the converter is applied (backward-compat for
787
832
  * the feat-1173 synthetic-descriptor tests and the copilot/antigravity paths
788
833
  * that handle cross-cutting inside their converters).
789
834
  *
790
835
  * @param srcAgentsDir source agents directory (e.g. agents/)
791
836
  * @param resolvedProfile profile filter from resolveProfile()
792
- * @param converter (content: string, isGlobal?: boolean) → string per-file
793
- * converter; scope-aware converters (copilot/antigravity)
794
- * read isGlobal, single-arg converters ignore it (#1173)
837
+ * @param converter (content: string, isGlobal?: boolean, meta?: {agentName: string}) → string
838
+ * per-file converter; scope-aware converters (copilot/antigravity)
839
+ * read isGlobal, single-arg converters ignore both extra args (#1173).
840
+ * `meta.agentName` (#2875 Part 2) is passed ONLY when `agentCtx` is
841
+ * present, letting a converter close over per-agent config
842
+ * (e.g. kilo/opencode model-override resolution in
843
+ * runtime-artifact-layout.cts's convertedAgentsKind) without widening
844
+ * every OTHER converter's contract — converters that don't declare a
845
+ * 3rd parameter simply never read it.
795
846
  * @param isGlobal install scope passed through to the converter
796
847
  * @param agentCtx optional cross-cutting context (ADR-1235 §1); when absent,
797
848
  * only the converter is applied (backward compat)
798
849
  */
799
850
  function stageAgentsForRuntimeWithConverter(srcAgentsDir, resolvedProfile, converter, isGlobal = false, agentCtx) {
800
- if (!node_fs_1.default.existsSync(srcAgentsDir))
851
+ if (!installFs().existsSync(srcAgentsDir))
801
852
  return srcAgentsDir;
802
- const stageDir = node_fs_1.default.mkdtempSync(node_path_1.default.join(node_os_1.default.tmpdir(), 'gsd-profile-runtime-agents-'));
853
+ const stageDir = mkInstallTempDir('gsd-profile-runtime-agents-');
854
+ let entries;
855
+ try {
856
+ // #2284/#2875: readdirSync-ing the shipped agents/ source is isolated in
857
+ // its own try/catch so an unreadable source directory (permissions, a
858
+ // corrupted install, or — as the #2284 test suite demonstrates — the
859
+ // fail-closed injection harness) produces the SAME "refusing to install"
860
+ // fail-closed wording every other agents-dir-unreadable path in this
861
+ // codebase uses (bin/install.js's `_resolveAvailableGsdRoles` /
862
+ // `_assertRoleResolvable`), instead of an unrelated raw fs error message
863
+ // escaping uncaught. Hermes's role-dispatch validation depends on the
864
+ // SAME shipped agents/ directory being readable; before this runtime also
865
+ // declared an `agents` kind, this function was never reached on a Hermes
866
+ // install, so an unreadable source here silently surfaced as a raw error
867
+ // rather than the deliberate fail-closed contract #2284 established.
868
+ entries = installFs().readdirSync(srcAgentsDir, { withFileTypes: true });
869
+ }
870
+ catch (err) {
871
+ try {
872
+ installFs().rmSync(stageDir, { recursive: true, force: true });
873
+ }
874
+ catch { /* best-effort */ }
875
+ throw new Error(`stageAgentsForRuntimeWithConverter: could not resolve the shipped agents/ directory "${srcAgentsDir}" to stage — refusing to install (fail-closed, #2284/#2875): ${err.message}`);
876
+ }
803
877
  try {
804
- const entries = node_fs_1.default.readdirSync(srcAgentsDir, { withFileTypes: true });
805
878
  // Resolve cmdNames once per staging call (not per file) for performance.
806
879
  const cmdNames = agentCtx ? _readGsdCommandNames() : [];
807
880
  for (const entry of entries) {
@@ -817,39 +890,44 @@ function stageAgentsForRuntimeWithConverter(srcAgentsDir, resolvedProfile, conve
817
890
  }
818
891
  }
819
892
  const agentSourcePath = node_path_1.default.join(srcAgentsDir, entry.name);
820
- let content = node_fs_1.default.readFileSync(agentSourcePath, 'utf8');
893
+ let content = installFs().readFileSync(agentSourcePath, 'utf8');
821
894
  // #2995: strip gsd:section markers FIRST — before path rewrites, attribution,
822
895
  // and the per-runtime converter. Byte-identical (no-op) for an unmarked agent;
823
896
  // throws loudly naming the file for a malformed marker, never emitting a
824
897
  // half-composed agent.
825
898
  content = _composeWorkflow(content, { sourcePath: agentSourcePath });
826
899
  if (agentCtx) {
900
+ // #2875 Part 2 / row I3: derived exactly as the inline loop does —
901
+ // single-sourced via deriveAgentName (runtime-artifact-conversion.cts).
902
+ const agentName = _deriveAgentName(entry.name);
827
903
  // ADR-1235 §1: pre-converter cross-cutting (matches inline loop order exactly)
828
904
  // Step 1: path rewrites (4 base ~/.claude/ regexes; skipped for copilot/antigravity)
829
905
  content = _applyAgentPathRewrites(content, agentCtx.runtime, agentCtx.pathPrefix);
830
906
  // Step 2: attribution
831
907
  content = _processAttribution(content, agentCtx.attribution);
832
908
  // Step 3: converter (runtime-specific frontmatter/body transform)
833
- content = converter(content, isGlobal);
834
- // Step 4: normalize colon→hyphen refs (no-op for trivial group)
909
+ content = converter(content, isGlobal, { agentName });
910
+ // Step 4: frontmatter extensions (effort/disallowedTools; no-op unless
911
+ // the runtime declares hostBehaviors.agentFrontmatterExtensions)
912
+ content = _applyAgentFrontmatterExtensions(content, { runtime: agentCtx.runtime, agentName, targetDir: agentCtx.targetDir });
913
+ // Step 5: normalize colon→hyphen refs (no-op for trivial group)
835
914
  content = _normalizeAgentBodyForRuntime(content, agentCtx.runtime, cmdNames);
836
915
  }
837
916
  else {
838
917
  // Backward-compat: only apply the converter (no cross-cutting)
839
918
  content = converter(content, isGlobal);
840
919
  }
841
- node_fs_1.default.writeFileSync(node_path_1.default.join(stageDir, entry.name), content, 'utf8');
920
+ installFs().writeFileSync(node_path_1.default.join(stageDir, entry.name), content, 'utf8');
842
921
  }
843
922
  }
844
923
  catch (err) {
845
924
  try {
846
- node_fs_1.default.rmSync(stageDir, { recursive: true, force: true });
925
+ installFs().rmSync(stageDir, { recursive: true, force: true });
847
926
  }
848
927
  catch { /* best-effort */ }
849
928
  throw err;
850
929
  }
851
- STAGED_DIRS.add(stageDir);
852
- ensureExitCleanup();
930
+ registerStagedDir(stageDir);
853
931
  return stageDir;
854
932
  }
855
933
  /**
@@ -876,11 +954,11 @@ function stageAgentsForRuntimeWithConverter(srcAgentsDir, resolvedProfile, conve
876
954
  * @param prefix command name prefix (for converter arg), e.g. 'gsd-'
877
955
  */
878
956
  function stageCommandsForRuntimeFlat(srcCommandsDir, resolvedProfile, converter, prefix) {
879
- if (!node_fs_1.default.existsSync(srcCommandsDir))
957
+ if (!installFs().existsSync(srcCommandsDir))
880
958
  return srcCommandsDir;
881
- const stageDir = node_fs_1.default.mkdtempSync(node_path_1.default.join(node_os_1.default.tmpdir(), 'gsd-profile-runtime-commands-'));
959
+ const stageDir = mkInstallTempDir('gsd-profile-runtime-commands-');
882
960
  try {
883
- const entries = node_fs_1.default.readdirSync(srcCommandsDir, { withFileTypes: true });
961
+ const entries = installFs().readdirSync(srcCommandsDir, { withFileTypes: true });
884
962
  for (const entry of entries) {
885
963
  if (!entry.isFile())
886
964
  continue;
@@ -889,24 +967,23 @@ function stageCommandsForRuntimeFlat(srcCommandsDir, resolvedProfile, converter,
889
967
  const stem = entry.name.slice(0, -3);
890
968
  if (resolvedProfile.skills !== '*' && !(resolvedProfile.skills).has(stem))
891
969
  continue;
892
- const content = node_fs_1.default.readFileSync(node_path_1.default.join(srcCommandsDir, entry.name), 'utf8');
970
+ const content = installFs().readFileSync(node_path_1.default.join(srcCommandsDir, entry.name), 'utf8');
893
971
  // Pass the full command name (with prefix) to the converter so it can
894
972
  // reference the installed command name in the body (e.g. for descriptions).
895
973
  // The staged file itself is named without the prefix; _copyStaged adds it.
896
974
  const commandName = `${prefix}${stem}`;
897
975
  const converted = converter(content, commandName);
898
- node_fs_1.default.writeFileSync(node_path_1.default.join(stageDir, `${stem}.md`), converted);
976
+ installFs().writeFileSync(node_path_1.default.join(stageDir, `${stem}.md`), converted);
899
977
  }
900
978
  }
901
979
  catch (err) {
902
980
  try {
903
- node_fs_1.default.rmSync(stageDir, { recursive: true, force: true });
981
+ installFs().rmSync(stageDir, { recursive: true, force: true });
904
982
  }
905
983
  catch { /* best-effort */ }
906
984
  throw err;
907
985
  }
908
- STAGED_DIRS.add(stageDir);
909
- ensureExitCleanup();
986
+ registerStagedDir(stageDir);
910
987
  return stageDir;
911
988
  }
912
989
  // ---------------------------------------------------------------------------