@opengsd/gsd-core 1.9.1 → 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 (426) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +2 -3
  3. package/.opencode/plugins/gsd-core.js +8 -1
  4. package/agents/gsd-code-fixer.md +27 -3
  5. package/agents/gsd-debug-session-manager.md +11 -0
  6. package/agents/gsd-debugger.md +12 -246
  7. package/agents/gsd-doc-synthesizer.md +2 -4
  8. package/agents/gsd-executor.md +12 -10
  9. package/agents/gsd-integration-checker.md +3 -0
  10. package/agents/gsd-mempalace-curator.md +5 -2
  11. package/agents/gsd-phase-researcher.md +20 -1
  12. package/agents/gsd-plan-checker.md +46 -0
  13. package/agents/gsd-planner.md +49 -54
  14. package/agents/gsd-roadmapper.md +21 -3
  15. package/agents/gsd-user-profiler.md +3 -0
  16. package/agents/gsd-verifier.md +26 -73
  17. package/bin/install.js +1272 -1238
  18. package/bin/lib/ui-safety-gate.cjs +2 -0
  19. package/commands/gsd/code-review.md +1 -1
  20. package/commands/gsd/execute-phase.md +1 -1
  21. package/commands/gsd/map-codebase.md +1 -1
  22. package/commands/gsd/mempalace-capture.md +2 -2
  23. package/commands/gsd/mempalace-recall.md +1 -1
  24. package/commands/gsd/new-milestone.md +2 -2
  25. package/commands/gsd/plan-phase.md +1 -1
  26. package/commands/gsd/quick.md +1 -1
  27. package/commands/gsd/review-backlog.md +2 -1
  28. package/commands/gsd/verify-work.md +1 -1
  29. package/gsd-core/bin/gsd-tools.cjs +1009 -115
  30. package/gsd-core/bin/lib/active-workstream-store.cjs +153 -12
  31. package/gsd-core/bin/lib/agent-install-check.cjs +268 -38
  32. package/gsd-core/bin/lib/api-coverage.cjs +123 -5
  33. package/gsd-core/bin/lib/artifacts.cjs +3 -0
  34. package/gsd-core/bin/lib/assumption-delta.cjs +2 -4
  35. package/gsd-core/bin/lib/audit-command-router.cjs +9 -2
  36. package/gsd-core/bin/lib/audit.cjs +926 -202
  37. package/gsd-core/bin/lib/broken-windows.cjs +36 -6
  38. package/gsd-core/bin/lib/capability-consent.cjs +149 -15
  39. package/gsd-core/bin/lib/capability-lifecycle.cjs +45 -0
  40. package/gsd-core/bin/lib/capability-registry.cjs +608 -148
  41. package/gsd-core/bin/lib/capability-source.cjs +92 -0
  42. package/gsd-core/bin/lib/capability-trust.cjs +444 -25
  43. package/gsd-core/bin/lib/capability-validator.cjs +507 -24
  44. package/gsd-core/bin/lib/capability-writer.cjs +3 -2
  45. package/gsd-core/bin/lib/check-command-router.cjs +114 -38
  46. package/gsd-core/bin/lib/claude-orchestration.cjs +56 -3
  47. package/gsd-core/bin/lib/codex-agent-toml.cjs +329 -0
  48. package/gsd-core/bin/lib/command-aliases.cjs +94 -0
  49. package/gsd-core/bin/lib/command-roster.cjs +44 -1
  50. package/gsd-core/bin/lib/commands.cjs +665 -99
  51. package/gsd-core/bin/lib/commonjs-marker.cjs +142 -0
  52. package/gsd-core/bin/lib/complexity-trigger.cjs +1172 -0
  53. package/gsd-core/bin/lib/config-loader.cjs +76 -0
  54. package/gsd-core/bin/lib/config.cjs +22 -2
  55. package/gsd-core/bin/lib/context-composer.cjs +278 -0
  56. package/gsd-core/bin/lib/context-predicates.cjs +506 -0
  57. package/gsd-core/bin/lib/core-utils.cjs +217 -40
  58. package/gsd-core/bin/lib/decisions.cjs +23 -0
  59. package/gsd-core/bin/lib/docs.cjs +3 -2
  60. package/gsd-core/bin/lib/external-job.cjs +19 -4
  61. package/gsd-core/bin/lib/fallow-runner.cjs +20 -44
  62. package/gsd-core/bin/lib/frontmatter.cjs +239 -32
  63. package/gsd-core/bin/lib/gap-checker.cjs +68 -7
  64. package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +57 -6
  65. package/gsd-core/bin/lib/git-base-branch.cjs +160 -15
  66. package/gsd-core/bin/lib/graphify.cjs +142 -27
  67. package/gsd-core/bin/lib/gsd2-import.cjs +37 -5
  68. package/gsd-core/bin/lib/health-diagnostic-rules/agent-install.cjs +101 -0
  69. package/gsd-core/bin/lib/health-diagnostic-rules/config-validation.cjs +348 -0
  70. package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +145 -0
  71. package/gsd-core/bin/lib/health-diagnostic-rules/install-surface-shadowing.cjs +98 -0
  72. package/gsd-core/bin/lib/health-diagnostic-rules/milestone-archive-hygiene.cjs +100 -0
  73. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +222 -0
  74. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +265 -0
  75. package/gsd-core/bin/lib/health-diagnostic-rules/root-existence.cjs +161 -0
  76. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +303 -0
  77. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +173 -0
  78. package/gsd-core/bin/lib/health-diagnostic-types.cjs +68 -0
  79. package/gsd-core/bin/lib/health-diagnostic.cjs +431 -0
  80. package/gsd-core/bin/lib/host-integration.cjs +13 -1
  81. package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
  82. package/gsd-core/bin/lib/init-command-router.cjs +83 -8
  83. package/gsd-core/bin/lib/init.cjs +1325 -169
  84. package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
  85. package/gsd-core/bin/lib/install-engine.cjs +805 -264
  86. package/gsd-core/bin/lib/install-fs-adapter.cjs +262 -0
  87. package/gsd-core/bin/lib/install-model-override-resolver.cjs +203 -0
  88. package/gsd-core/bin/lib/install-profiles.cjs +160 -57
  89. package/gsd-core/bin/lib/install-scope.cjs +270 -0
  90. package/gsd-core/bin/lib/install-shadow-report.cjs +385 -0
  91. package/gsd-core/bin/lib/installed-surface-resolver.cjs +381 -0
  92. package/gsd-core/bin/lib/installer-migration-authoring.cjs +3 -1
  93. package/gsd-core/bin/lib/installer-migration-report.cjs +4 -0
  94. package/gsd-core/bin/lib/installer-migrations/007-retire-config-root-commonjs-marker.cjs +149 -0
  95. package/gsd-core/bin/lib/installer-migrations/008-cursor-retire-commands-surface.cjs +55 -0
  96. package/gsd-core/bin/lib/installer-migrations/009-pi-retire-reserved-hooks-dir.cjs +199 -0
  97. package/gsd-core/bin/lib/installer-migrations.cjs +206 -13
  98. package/gsd-core/bin/lib/io.cjs +38 -3
  99. package/gsd-core/bin/lib/markdown-sectionizer.cjs +8 -1
  100. package/gsd-core/bin/lib/markdown-table.cjs +133 -20
  101. package/gsd-core/bin/lib/mcp-catalog.cjs +518 -0
  102. package/gsd-core/bin/lib/mcp-server.cjs +135 -3
  103. package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
  104. package/gsd-core/bin/lib/milestone.cjs +821 -109
  105. package/gsd-core/bin/lib/model-catalog.cjs +59 -1
  106. package/gsd-core/bin/lib/model-resolver.cjs +183 -40
  107. package/gsd-core/bin/lib/normalize-test-command.cjs +1 -1
  108. package/gsd-core/bin/lib/pattern.cjs +122 -0
  109. package/gsd-core/bin/lib/phase-estimation.cjs +1 -1
  110. package/gsd-core/bin/lib/phase-id.cjs +507 -36
  111. package/gsd-core/bin/lib/phase-lifecycle.cjs +28 -3
  112. package/gsd-core/bin/lib/phase-locator.cjs +258 -58
  113. package/gsd-core/bin/lib/phase.cjs +891 -156
  114. package/gsd-core/bin/lib/plan-dependency-graph.cjs +303 -0
  115. package/gsd-core/bin/lib/plan-drift-guard.cjs +120 -0
  116. package/gsd-core/bin/lib/plan-scan.cjs +86 -2
  117. package/gsd-core/bin/lib/planning-scope.cjs +31 -0
  118. package/gsd-core/bin/lib/planning-snapshot.cjs +890 -0
  119. package/gsd-core/bin/lib/planning-workspace.cjs +60 -6
  120. package/gsd-core/bin/lib/probe-core.cjs +1 -1
  121. package/gsd-core/bin/lib/profile-output.cjs +1 -1
  122. package/gsd-core/bin/lib/prompt-budget.cjs +128 -165
  123. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +740 -0
  124. package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +85 -0
  125. package/gsd-core/bin/lib/review-lane-descriptor.cjs +108 -0
  126. package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
  127. package/gsd-core/bin/lib/review-lane-runner.cjs +447 -68
  128. package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
  129. package/gsd-core/bin/lib/roadmap-command-router.cjs +76 -9
  130. package/gsd-core/bin/lib/roadmap-parser.cjs +1035 -194
  131. package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
  132. package/gsd-core/bin/lib/roadmap.cjs +405 -84
  133. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +795 -100
  134. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
  135. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +440 -57
  136. package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
  137. package/gsd-core/bin/lib/runtime-homes.cjs +220 -41
  138. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +220 -44
  139. package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
  140. package/gsd-core/bin/lib/runtime-slash.cjs +27 -9
  141. package/gsd-core/bin/lib/section-manifest.cjs +209 -0
  142. package/gsd-core/bin/lib/security.cjs +104 -5
  143. package/gsd-core/bin/lib/shell-command-projection.cjs +388 -30
  144. package/gsd-core/bin/lib/smart-entry.cjs +154 -22
  145. package/gsd-core/bin/lib/state-command-router.cjs +5 -1
  146. package/gsd-core/bin/lib/state-document.cjs +152 -8
  147. package/gsd-core/bin/lib/state-transition.cjs +424 -105
  148. package/gsd-core/bin/lib/state.cjs +1927 -401
  149. package/gsd-core/bin/lib/surface.cjs +35 -10
  150. package/gsd-core/bin/lib/text-lines.cjs +80 -0
  151. package/gsd-core/bin/lib/token-scanner.cjs +76 -0
  152. package/gsd-core/bin/lib/uat-predicate.cjs +20 -4
  153. package/gsd-core/bin/lib/uat.cjs +706 -64
  154. package/gsd-core/bin/lib/ui-frontend-evidence.cjs +157 -0
  155. package/gsd-core/bin/lib/ui-safety-gate.cjs +14 -5
  156. package/gsd-core/bin/lib/unusable-input.cjs +33 -0
  157. package/gsd-core/bin/lib/update-context.cjs +8 -2
  158. package/gsd-core/bin/lib/user-artifact-staging.cjs +705 -0
  159. package/gsd-core/bin/lib/validate.cjs +20 -6
  160. package/gsd-core/bin/lib/vendor/README.md +37 -0
  161. package/gsd-core/bin/lib/vendor/re2js.cjs +6480 -0
  162. package/gsd-core/bin/lib/vendor/re2js.d.cts +938 -0
  163. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  164. package/gsd-core/bin/lib/verification.cjs +287 -20
  165. package/gsd-core/bin/lib/verify.cjs +368 -880
  166. package/gsd-core/bin/lib/workflow-fragments.cjs +557 -0
  167. package/gsd-core/bin/lib/workstream-inventory-builder.cjs +203 -19
  168. package/gsd-core/bin/lib/workstream-inventory.cjs +576 -31
  169. package/gsd-core/bin/lib/workstream.cjs +8 -2
  170. package/gsd-core/bin/lib/worktree-base-ref.cjs +50 -6
  171. package/gsd-core/bin/lib/worktree-safety.cjs +450 -125
  172. package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
  173. package/gsd-core/bin/shared/config-schema.manifest.json +9 -1
  174. package/gsd-core/references/agent-contracts.md +43 -26
  175. package/gsd-core/references/artifact-types.md +10 -3
  176. package/gsd-core/references/autonomous-ui-design-contract.md +42 -0
  177. package/gsd-core/references/checkpoints.md +2 -2
  178. package/gsd-core/references/context-budget.md +1 -1
  179. package/gsd-core/references/debugger-techniques.md +255 -0
  180. package/gsd-core/references/dispatch-isolation-gate.md +138 -0
  181. package/gsd-core/references/doc-conflict-engine.md +1 -1
  182. package/gsd-core/references/execute-mvp-tdd.md +3 -3
  183. package/gsd-core/references/execute-phase-between-wave-reset.md +6 -2
  184. package/gsd-core/references/execute-phase-context-guard.md +1 -1
  185. package/gsd-core/references/execute-phase-response-language.md +1 -1
  186. package/gsd-core/references/execute-phase-wave-guard.md +6 -2
  187. package/gsd-core/references/gate-prompts.md +1 -1
  188. package/gsd-core/references/git-planning-commit.md +2 -1
  189. package/gsd-core/references/loop-hook-dispatch.md +39 -2
  190. package/gsd-core/references/model-profiles.md +12 -4
  191. package/gsd-core/references/mvp-concepts.md +9 -9
  192. package/gsd-core/references/planner-guidance.md +3 -9
  193. package/gsd-core/references/planner-preconditions.md +1 -1
  194. package/gsd-core/references/planner-reviews.md +1 -1
  195. package/gsd-core/references/planning-config.md +8 -6
  196. package/gsd-core/references/research-documentation-lookup.md +5 -3
  197. package/gsd-core/references/revision-loop.md +1 -1
  198. package/gsd-core/references/specless-probe-fallback.md +8 -7
  199. package/gsd-core/references/universal-anti-patterns.md +3 -3
  200. package/gsd-core/references/verifier-phase-gates.md +192 -0
  201. package/gsd-core/references/verifier-wiring-patterns.md +100 -0
  202. package/gsd-core/references/verify-mvp-mode.md +1 -1
  203. package/gsd-core/references/workstream-flag.md +22 -6
  204. package/gsd-core/references/worktree-branch-check.md +2 -2
  205. package/gsd-core/templates/discussion-log.md +1 -1
  206. package/gsd-core/templates/phase-prompt.md +2 -4
  207. package/gsd-core/templates/state.md +4 -4
  208. package/gsd-core/templates/summary-complex.md +2 -0
  209. package/gsd-core/templates/summary-minimal.md +2 -0
  210. package/gsd-core/templates/summary-standard.md +2 -0
  211. package/gsd-core/templates/summary.md +2 -0
  212. package/gsd-core/templates/verification-report.md +9 -1
  213. package/gsd-core/workflows/ai-integration-phase.md +9 -11
  214. package/gsd-core/workflows/audit-milestone.md +3 -0
  215. package/gsd-core/workflows/autonomous/steps/converge-banner.md +1 -0
  216. package/gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md +11 -0
  217. package/gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md +7 -0
  218. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +21 -0
  219. package/gsd-core/workflows/autonomous/steps/converge-loop.md +7 -0
  220. package/gsd-core/workflows/autonomous.md +33 -70
  221. package/gsd-core/workflows/cleanup.md +62 -3
  222. package/gsd-core/workflows/code-review/steps/dispatch-fix.md +39 -0
  223. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +93 -0
  224. package/gsd-core/workflows/code-review-fix.md +37 -10
  225. package/gsd-core/workflows/code-review.md +74 -166
  226. package/gsd-core/workflows/complete-milestone/steps/git-tag.md +29 -0
  227. package/gsd-core/workflows/complete-milestone.md +160 -95
  228. package/gsd-core/workflows/debug.md +16 -17
  229. package/gsd-core/workflows/diagnose-issues.md +56 -8
  230. package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -1
  231. package/gsd-core/workflows/discuss-phase/modes/default.md +1 -1
  232. package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +15 -0
  233. package/gsd-core/workflows/discuss-phase-assumptions.md +7 -17
  234. package/gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md +51 -0
  235. package/gsd-core/workflows/docs-update.md +8 -51
  236. package/gsd-core/workflows/edit-phase.md +26 -1
  237. package/gsd-core/workflows/eval-review.md +3 -5
  238. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +64 -7
  239. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +50 -0
  240. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +31 -0
  241. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +77 -0
  242. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +21 -0
  243. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +42 -0
  244. package/gsd-core/workflows/execute-phase/steps/regression-gate.md +43 -37
  245. package/gsd-core/workflows/execute-phase.md +103 -187
  246. package/gsd-core/workflows/execute-plan.md +36 -4
  247. package/gsd-core/workflows/explore.md +131 -4
  248. package/gsd-core/workflows/fast.md +10 -2
  249. package/gsd-core/workflows/health.md +73 -4
  250. package/gsd-core/workflows/help/modes/full.md +6 -1
  251. package/gsd-core/workflows/import.md +4 -4
  252. package/gsd-core/workflows/ingest-docs.md +7 -6
  253. package/gsd-core/workflows/mvp-phase.md +6 -3
  254. package/gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md +16 -0
  255. package/gsd-core/workflows/new-milestone/steps/reset-phase-safety.md +19 -0
  256. package/gsd-core/workflows/new-milestone.md +35 -47
  257. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +176 -0
  258. package/gsd-core/workflows/new-project/steps/auto-mode-detection.md +32 -0
  259. package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +18 -0
  260. package/gsd-core/workflows/new-project.md +27 -240
  261. package/gsd-core/workflows/next.md +12 -0
  262. package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +15 -0
  263. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +110 -0
  264. package/gsd-core/workflows/plan-phase/steps/prd-express-gate.md +8 -0
  265. package/gsd-core/workflows/plan-phase/steps/research-only-early-exit.md +17 -0
  266. package/gsd-core/workflows/plan-phase/steps/research-only-modifiers.md +16 -0
  267. package/gsd-core/workflows/plan-phase/steps/reviews-prerequisite.md +17 -0
  268. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +149 -0
  269. package/gsd-core/workflows/plan-phase.md +89 -209
  270. package/gsd-core/workflows/plan-review-convergence.md +50 -2
  271. package/gsd-core/workflows/progress/steps/forensic-audit.md +125 -0
  272. package/gsd-core/workflows/progress/steps/mvp-display.md +18 -0
  273. package/gsd-core/workflows/progress.md +45 -159
  274. package/gsd-core/workflows/quick/steps/discussion-phase.md +124 -0
  275. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +111 -0
  276. package/gsd-core/workflows/quick/steps/quick-verification.md +67 -0
  277. package/gsd-core/workflows/quick/steps/research-phase.md +72 -0
  278. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +37 -0
  279. package/gsd-core/workflows/quick.md +55 -405
  280. package/gsd-core/workflows/resume-project.md +3 -0
  281. package/gsd-core/workflows/review/steps/reviewer-instances-note-1.md +4 -0
  282. package/gsd-core/workflows/review/steps/reviewer-instances-note-2.md +3 -0
  283. package/gsd-core/workflows/review.md +41 -13
  284. package/gsd-core/workflows/section-manifest.json +219 -0
  285. package/gsd-core/workflows/secure-phase.md +1 -1
  286. package/gsd-core/workflows/session-report.md +2 -1
  287. package/gsd-core/workflows/settings.md +66 -2
  288. package/gsd-core/workflows/ship.md +104 -44
  289. package/gsd-core/workflows/sketch.md +1 -1
  290. package/gsd-core/workflows/spec-phase.md +41 -20
  291. package/gsd-core/workflows/spike-wrap-up.md +20 -5
  292. package/gsd-core/workflows/spike.md +50 -16
  293. package/gsd-core/workflows/sync-skills.md +106 -13
  294. package/gsd-core/workflows/transition/steps/workstream-collision-check.md +17 -0
  295. package/gsd-core/workflows/transition.md +53 -31
  296. package/gsd-core/workflows/ui-phase.md +13 -12
  297. package/gsd-core/workflows/ui-review.md +2 -2
  298. package/gsd-core/workflows/update/steps/channel-banner.md +7 -0
  299. package/gsd-core/workflows/update.md +19 -8
  300. package/gsd-core/workflows/validate-phase.md +1 -1
  301. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +36 -0
  302. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +21 -0
  303. package/gsd-core/workflows/verify-work.md +17 -65
  304. package/hooks/dist/gsd-agent-isolation-guard.js +517 -0
  305. package/hooks/dist/gsd-check-update-worker.js +64 -12
  306. package/hooks/dist/gsd-check-update.js +19 -1
  307. package/hooks/dist/gsd-cursor-pre-tool.js +0 -3
  308. package/hooks/dist/gsd-cursor-subagent-start.js +607 -26
  309. package/hooks/dist/gsd-cursor-subagent-stop.js +3 -2
  310. package/hooks/dist/gsd-prompt-guard.js +21 -20
  311. package/hooks/dist/gsd-read-injection-scanner.js +45 -24
  312. package/hooks/dist/gsd-statusline.js +90 -6
  313. package/hooks/dist/gsd-update-banner.js +22 -1
  314. package/hooks/dist/gsd-workflow-guard.js +134 -36
  315. package/hooks/dist/gsd-worktree-path-guard.js +2 -1
  316. package/hooks/dist/gsd-write-guard.js +359 -0
  317. package/hooks/dist/lib/git-cmd.js +92 -59
  318. package/hooks/dist/lib/injection-patterns.js +45 -0
  319. package/hooks/dist/lib/isolation-deny-reason.js +39 -0
  320. package/hooks/dist/lib/isolation-sentinel.js +277 -0
  321. package/hooks/dist/managed-hooks-registry.cjs +2 -0
  322. package/hooks/gsd-agent-isolation-guard.js +517 -0
  323. package/hooks/gsd-check-update-worker.js +64 -12
  324. package/hooks/gsd-check-update.js +19 -1
  325. package/hooks/gsd-cursor-pre-tool.js +0 -3
  326. package/hooks/gsd-cursor-subagent-start.js +607 -26
  327. package/hooks/gsd-cursor-subagent-stop.js +3 -2
  328. package/hooks/gsd-prompt-guard.js +21 -20
  329. package/hooks/gsd-read-injection-scanner.js +45 -24
  330. package/hooks/gsd-statusline.js +90 -6
  331. package/hooks/gsd-update-banner.js +22 -1
  332. package/hooks/gsd-workflow-guard.js +134 -36
  333. package/hooks/gsd-worktree-path-guard.js +2 -1
  334. package/hooks/gsd-write-guard.js +359 -0
  335. package/hooks/hooks.json +12 -0
  336. package/hooks/lib/git-cmd.js +92 -59
  337. package/hooks/lib/injection-patterns.js +45 -0
  338. package/hooks/lib/isolation-deny-reason.js +39 -0
  339. package/hooks/lib/isolation-sentinel.js +277 -0
  340. package/hooks/managed-hooks-registry.cjs +2 -0
  341. package/package.json +31 -10
  342. package/pi/gsd.cjs +71 -12
  343. package/scripts/baselines/planning-prompt-drift-baseline.json +4 -0
  344. package/scripts/baselines/planning-snapshot-bypass-baseline.json +12 -0
  345. package/scripts/baselines/unreachable-guard-drift-baseline.json +4 -0
  346. package/scripts/build-hooks.js +9 -0
  347. package/scripts/changeset/lint.cjs +68 -6
  348. package/scripts/changeset/serialize.cjs +5 -1
  349. package/scripts/check-alias-drift.cjs +7 -43
  350. package/scripts/check-contract-drift.cjs +297 -0
  351. package/scripts/ci-test-scope.cjs +19 -2
  352. package/scripts/command-contract-helpers.cjs +903 -1
  353. package/scripts/gen-adr-index.cjs +728 -38
  354. package/scripts/gen-capability-matrix.cjs +1 -1
  355. package/scripts/gen-capability-registry.cjs +3 -15
  356. package/scripts/gen-context-index.cjs +439 -0
  357. package/scripts/gen-health-docs.cjs +390 -0
  358. package/scripts/gen-inventory-manifest.cjs +150 -4
  359. package/scripts/gen-loop-host-contract.cjs +4 -24
  360. package/scripts/gen-prompt-budget-parity-corpus.cjs +645 -0
  361. package/scripts/gen-registry.cjs +3 -14
  362. package/scripts/gen-section-manifest.cjs +638 -0
  363. package/scripts/generate-package-identity.cjs +4 -2
  364. package/scripts/lib/alias-drift-families.cjs +46 -0
  365. package/scripts/lib/drift-scan.cjs +278 -0
  366. package/scripts/lint-allow-test-rule-refs.allowlist.json +15 -54
  367. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +4 -0
  368. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +3 -0
  369. package/scripts/lint-canary-version-leak.cjs +73 -0
  370. package/scripts/lint-command-contract.cjs +96 -13
  371. package/scripts/lint-compiled-artifact-sync.cjs +6 -1
  372. package/scripts/lint-completion-predicate-drift.cjs +933 -0
  373. package/scripts/lint-completion-ratio-drift.cjs +214 -0
  374. package/scripts/lint-default-flip-documentation.cjs +193 -0
  375. package/scripts/lint-docs-command-form.cjs +195 -0
  376. package/scripts/lint-docs-required.cjs +9 -1
  377. package/scripts/lint-emitted-drift-ack.cjs +215 -20
  378. package/scripts/lint-eslint-glob-coverage.allowlist.json +34 -0
  379. package/scripts/lint-eslint-glob-coverage.cjs +340 -0
  380. package/scripts/lint-example-parser-parity.cjs +395 -0
  381. package/scripts/lint-frontmatter-scalar-broad-grep.cjs +237 -0
  382. package/scripts/lint-health-diagnostic-rule-table.cjs +404 -0
  383. package/scripts/lint-hooks-runtime-build-seam.cjs +262 -0
  384. package/scripts/lint-milestone-window-drift.cjs +468 -0
  385. package/scripts/lint-phase-enumeration-drift.cjs +479 -0
  386. package/scripts/lint-plan-count-drift.cjs +318 -0
  387. package/scripts/lint-planning-artifact-writer-drift.cjs +398 -0
  388. package/scripts/lint-planning-prompt-drift.cjs +434 -0
  389. package/scripts/lint-planning-snapshot-bypass-drift.cjs +544 -0
  390. package/scripts/lint-regression-test-names.cjs +15 -13
  391. package/scripts/lint-removed-but-needed.cjs +320 -0
  392. package/scripts/lint-state-field-drift.cjs +805 -0
  393. package/scripts/lint-state-write-path-drift.cjs +1045 -0
  394. package/scripts/lint-test-file-count.allowlist.json +40 -3
  395. package/scripts/lint-unreachable-guard-drift.cjs +843 -0
  396. package/scripts/lint-vendored-deps.cjs +124 -0
  397. package/scripts/mutation-matrix.cjs +13 -0
  398. package/scripts/pr-changed-files.cjs +63 -0
  399. package/scripts/pr-template-policy.cjs +14 -4
  400. package/scripts/prompt-injection-scan.sh +52 -6
  401. package/scripts/require-issue-link-policy.cjs +192 -0
  402. package/scripts/state-write-path-drift-baseline.json +19 -0
  403. package/scripts/sync-runtime-launcher.cjs +2 -4
  404. package/skills/gsd-autonomous/SKILL.md +0 -1
  405. package/skills/gsd-code-review/SKILL.md +1 -1
  406. package/skills/gsd-execute-phase/SKILL.md +1 -2
  407. package/skills/gsd-map-codebase/SKILL.md +1 -1
  408. package/skills/gsd-mempalace-capture/SKILL.md +2 -2
  409. package/skills/gsd-mempalace-recall/SKILL.md +1 -1
  410. package/skills/gsd-new-milestone/SKILL.md +2 -2
  411. package/skills/gsd-next/SKILL.md +0 -1
  412. package/skills/gsd-plan-phase/SKILL.md +1 -2
  413. package/skills/gsd-progress/SKILL.md +0 -1
  414. package/skills/gsd-quick/SKILL.md +1 -1
  415. package/skills/gsd-review-backlog/SKILL.md +2 -1
  416. package/skills/gsd-stats/SKILL.md +0 -1
  417. package/skills/gsd-verify-work/SKILL.md +1 -1
  418. package/vscode/package.json +1 -1
  419. package/gsd-core/workflows/discovery-phase.md +0 -298
  420. package/gsd-core/workflows/plan-milestone-gaps.md +0 -281
  421. package/gsd-core/workflows/verify-phase.md +0 -577
  422. package/scripts/affected-tests-lib.cjs +0 -554
  423. package/scripts/gen-emitted-baseline.cjs +0 -145
  424. package/scripts/lint-allow-test-rule-refs.cjs +0 -162
  425. package/scripts/run-affected-tests.cjs +0 -7
  426. package/scripts/run-tests.cjs +0 -1050
@@ -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
- const { stageSkillsForProfile, stageAgentsForProfile, stageAgentsForRuntimeWithConverter, stageSkillsForRuntimeAsSkills, stageCommandsForRuntimeFlat, } = installProfiles;
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;
@@ -117,9 +169,63 @@ function agentsKind(destSubpath, prefix, configDir) {
117
169
  kind: 'agents',
118
170
  destSubpath,
119
171
  prefix,
120
- stage: (resolved) => stageAgentsForProfile(findAgentsSourceRoot(configDir), resolved),
172
+ // #2995: a `converter: null` agents entry (claude local, zcode) previously
173
+ // staged via stageAgentsForProfile — a RAW byte copy that never reads content
174
+ // into JS, so gsd:section markers shipped verbatim. Route through the
175
+ // composing stager with an identity converter instead: same output as the raw
176
+ // copy for an unmarked agent, markers stripped for a marked one. Routing both
177
+ // agent kinds through the stager collapses what were five independent agent
178
+ // read points down to three compose call sites: this stager, bin/install.js's
179
+ // inline agent loop, and installCodexConfig's per-agent .toml writer. The
180
+ // exhaustive per-runtime sweep in tests/agent-fragments-emission.install.test.cjs
181
+ // is what keeps a fourth from appearing uncomposed.
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),
121
191
  };
122
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
+ }
123
229
  /**
124
230
  * Build a converted-agents kind descriptor for runtimes whose agent `.md` files
125
231
  * need runtime-specific frontmatter/body conversion (e.g. Copilot, Cursor, Codex).
@@ -131,27 +237,80 @@ function agentsKind(destSubpath, prefix, configDir) {
131
237
  * Agent filenames are preserved verbatim (the prefix is already embedded in the
132
238
  * agent stem — e.g. `gsd-planner.md`).
133
239
  *
134
- * #1173 SCOPE — plumbing only (real install still elsewhere): this provides
135
- * the converter dispatch + `isGlobal` scope threading for the descriptor's
136
- * `agents` kind. As of #2092, 8 non-Claude runtimes DO declare a converted
137
- * `agents` kind in their `capability.json` — qwen (`convertClaudeAgentToQwenAgent`)
138
- * plus the 7 that already declared one before it (antigravity, augment,
139
- * codebuddy, copilot, cursor, trae, windsurf) — so the descriptor-level
140
- * declaration is no longer deferred. What IS still deferred is wiring
141
- * `resolveRuntimeArtifactLayout`'s `agents` kind into the REAL install:
142
- * `bin/install.js`'s agent-staging loop does not consume this module's
143
- * `convertedAgentsKind` resolution at all — it dispatches the very same
144
- * converter functions directly via `_hostBehaviors(runtime)` checks
145
- * (`frontmatterDialect`, `brandingRewrites`, `isCopilot`/`isAntigravity`/…),
146
- * duplicating the mapping declared here. That duplication is deliberate until
147
- * the second `layout.kinds` consumer — `applySurface` / `/gsd:surface` /
148
- * `--materialize` (`src/surface.cts`) — mirrors the legacy agent pipeline
149
- * (Copilot's `.agent.md` filename rename, the cross-cutting path-prefix
150
- * rewrite + attribution, stale-file cleanup, config-reading steps); declaring
151
- * `bin/install.js` itself against this resolver before then would risk
152
- * regressing the surface path. Until that follow-up lands, `bin/install.js`
153
- * remains authoritative for the real install, and this `convertedAgentsKind`
154
- * 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.
155
314
  *
156
315
  * Mirrors the `convertedCommandsKind` pattern (#785).
157
316
  *
@@ -166,15 +325,45 @@ function convertedAgentsKind(destSubpath, prefix, converterName, configDir, scop
166
325
  destSubpath,
167
326
  prefix,
168
327
  stage: (resolved, agentCtx) => {
169
- // isGlobal is threaded so scope-aware agent converters (copilot, antigravity)
170
- // choose global-home vs workspace-relative paths; converters that only take
171
- // (content) ignore the extra positional arg. Mirrors skillsKind's scope
172
- // threading (#1173).
173
- 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
+ }
174
363
  // ADR-1235 §1: when agentCtx is provided (by createRuntimeArtifactInstallPlan
175
364
  // for descriptor-driven runtimes), thread it through so stageAgentsForRuntimeWithConverter
176
365
  // can apply the full pre-converter + post-converter sequence in the correct order.
177
- return stageAgentsForRuntimeWithConverter(findAgentsSourceRoot(configDir), resolved, converter, scope === 'global', agentCtx);
366
+ return stageAgentsForRuntimeWithConverter(findAgentsSourceRoot(configDir), resolved, converter, (0, install_scope_cjs_1.isGlobalScope)(scope), agentCtx);
178
367
  },
179
368
  };
180
369
  }
@@ -185,30 +374,32 @@ function kimiAgentsKind(destSubpath, prefix, configDir) {
185
374
  prefix,
186
375
  stage: (resolved) => {
187
376
  const buildKimiAgentArtifacts = conversionExports['buildKimiAgentArtifacts'];
188
- const stagedAgents = stageAgentsForProfile(findAgentsSourceRoot(configDir), resolved);
377
+ // #2995: compose at staging (identity converter) so the readFileSync below
378
+ // sees marker-free content — same single composing stager as agentsKind.
379
+ const stagedAgents = stageAgentsForRuntimeWithConverter(findAgentsSourceRoot(configDir), resolved, (content) => content);
189
380
  const subagents = [];
190
- if (node_fs_1.default.existsSync(stagedAgents)) {
191
- 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 })) {
192
383
  if (!entry.isFile() || !entry.name.endsWith('.md'))
193
384
  continue;
194
385
  const agentPath = node_path_1.default.join(stagedAgents, entry.name);
195
386
  subagents.push({
196
387
  path: (0, shell_command_projection_cjs_1.posixNormalize)(node_path_1.default.join('agents', entry.name)),
197
- content: node_fs_1.default.readFileSync(agentPath, 'utf8'),
388
+ content: installFs().readFileSync(agentPath, 'utf8'),
198
389
  });
199
390
  }
200
391
  }
201
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`;
202
393
  const artifacts = buildKimiAgentArtifacts({ rootAgent, subagents });
203
- 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-');
204
395
  installProfiles.STAGED_DIRS.add(stageDir);
205
- node_fs_1.default.writeFileSync(node_path_1.default.join(stageDir, 'gsd.yaml'), artifacts.root.yaml);
206
- 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);
207
398
  const subagentsDir = node_path_1.default.join(stageDir, 'subagents');
208
- node_fs_1.default.mkdirSync(subagentsDir, { recursive: true });
399
+ installFs().mkdirSync(subagentsDir, { recursive: true });
209
400
  for (const artifact of artifacts.subagents) {
210
- node_fs_1.default.writeFileSync(node_path_1.default.join(subagentsDir, `${artifact.name}.yaml`), artifact.yaml);
211
- 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);
212
403
  }
213
404
  return stageDir;
214
405
  },
@@ -239,7 +430,7 @@ function skillsKind(destSubpath, prefix, converterName, runtime, configDir, nest
239
430
  prefix,
240
431
  converter: converterName,
241
432
  stage: (resolved) => {
242
- const realConverter = conversionExports[converterName];
433
+ const realConverter = _resolveNamedConverter(converterName, 'skills');
243
434
  // Compute cmdNames once per stage call for performance (#3583).
244
435
  // Extra trailing args are ignored by converters that don't need them. The
245
436
  // isGlobal flag is the 5th positional (NOT the 3rd): the 3rd positional is
@@ -249,7 +440,21 @@ function skillsKind(destSubpath, prefix, converterName, runtime, configDir, nest
249
440
  const cmdNames = conversionExports.readGsdCommandNames
250
441
  ? conversionExports.readGsdCommandNames()
251
442
  : [];
252
- 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.
253
458
  const wrappedConverter = (content, skillName) => realConverter(content, skillName, runtime, cmdNames, isGlobal);
254
459
  return stageSkillsForRuntimeAsSkills(findInstallSourceRoot(configDir), resolved, wrappedConverter, prefix, nested, capabilityRegistry);
255
460
  },
@@ -277,7 +482,7 @@ function convertedCommandsKind(destSubpath, prefix, converterName, configDir) {
277
482
  destSubpath,
278
483
  prefix,
279
484
  stage: (resolved) => {
280
- const converter = conversionExports[converterName];
485
+ const converter = _resolveNamedConverter(converterName, 'commands');
281
486
  return stageCommandsForRuntimeFlat(findInstallSourceRoot(configDir), resolved, converter, prefix);
282
487
  },
283
488
  };
@@ -316,7 +521,11 @@ function dispatchKindEntry(entry, runtime, configDir, scope, capabilityRegistry)
316
521
  default:
317
522
  throw new TypeError(`resolveRuntimeArtifactLayout: unknown kind '${kind}' in descriptor for runtime '${runtime}'`);
318
523
  }
319
- 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 !== '') {
320
529
  result.home = node_path_1.default.join(node_os_1.default.homedir(), entry.home);
321
530
  }
322
531
  return result;
@@ -355,4 +564,178 @@ function resolveRuntimeArtifactLayoutFromRegistry(registry, runtime, configDir,
355
564
  const kinds = entries.map((entry) => dispatchKindEntry(entry, runtime, configDir, scope, capabilityRegistry));
356
565
  return { runtime, configDir, scope, kinds };
357
566
  }
358
- 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')