@opengsd/gsd-core 1.12.0 → 1.13.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 (286) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.opencode/plugins/gsd-core.js +12 -0
  4. package/agents/gsd-executor.md +63 -35
  5. package/agents/gsd-plan-checker.md +76 -57
  6. package/agents/gsd-planner.md +14 -0
  7. package/agents/gsd-ui-checker.md +19 -3
  8. package/agents/gsd-ui-researcher.md +29 -0
  9. package/agents/gsd-verifier.md +23 -1
  10. package/bin/install.js +239 -67
  11. package/commands/gsd/execute-phase.md +1 -1
  12. package/commands/gsd/ns-workflow.md +2 -1
  13. package/commands/gsd/phase.md +1 -1
  14. package/commands/gsd/quick-batch.md +105 -0
  15. package/commands/gsd/surface.md +18 -8
  16. package/gsd-core/bin/gsd-tools.cjs +195 -50
  17. package/gsd-core/bin/lib/capability-activation.cjs +27 -0
  18. package/gsd-core/bin/lib/capability-registry.cjs +514 -114
  19. package/gsd-core/bin/lib/capability-state.cjs +7 -1
  20. package/gsd-core/bin/lib/capability-validator.cjs +120 -4
  21. package/gsd-core/bin/lib/capability-writer.cjs +14 -4
  22. package/gsd-core/bin/lib/check-command-router.cjs +85 -2
  23. package/gsd-core/bin/lib/claude-orchestration.cjs +10 -25
  24. package/gsd-core/bin/lib/clusters.cjs +1 -0
  25. package/gsd-core/bin/lib/command-aliases.cjs +16 -0
  26. package/gsd-core/bin/lib/commands.cjs +337 -13
  27. package/gsd-core/bin/lib/config-loader.cjs +3 -0
  28. package/gsd-core/bin/lib/core-utils.cjs +34 -7
  29. package/gsd-core/bin/lib/decisions.cjs +213 -1
  30. package/gsd-core/bin/lib/edge-probe.cjs +14 -1
  31. package/gsd-core/bin/lib/file-overlap-partitioner.cjs +74 -0
  32. package/gsd-core/bin/lib/frontmatter.cjs +137 -23
  33. package/gsd-core/bin/lib/gap-checker.cjs +22 -13
  34. package/gsd-core/bin/lib/git-base-branch.cjs +10 -2
  35. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +8 -2
  36. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +54 -11
  37. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +75 -22
  38. package/gsd-core/bin/lib/host-integration.cjs +57 -5
  39. package/gsd-core/bin/lib/init-command-router.cjs +14 -0
  40. package/gsd-core/bin/lib/init.cjs +132 -15
  41. package/gsd-core/bin/lib/install-engine.cjs +184 -12
  42. package/gsd-core/bin/lib/install-model-override-resolver.cjs +45 -0
  43. package/gsd-core/bin/lib/install-profiles.cjs +22 -14
  44. package/gsd-core/bin/lib/installer-migration-report.cjs +1 -0
  45. package/gsd-core/bin/lib/io.cjs +35 -0
  46. package/gsd-core/bin/lib/loop-resolver.cjs +14 -8
  47. package/gsd-core/bin/lib/markdown-table.cjs +123 -0
  48. package/gsd-core/bin/lib/milestone.cjs +22 -2
  49. package/gsd-core/bin/lib/phase-command-router.cjs +13 -6
  50. package/gsd-core/bin/lib/phase-id.cjs +251 -9
  51. package/gsd-core/bin/lib/phase.cjs +774 -35
  52. package/gsd-core/bin/lib/plan-document.cjs +10 -0
  53. package/gsd-core/bin/lib/planning-snapshot.cjs +147 -20
  54. package/gsd-core/bin/lib/planning-workspace.cjs +103 -28
  55. package/gsd-core/bin/lib/quick-batch-command-router.cjs +285 -0
  56. package/gsd-core/bin/lib/quick-batch-dispatch.cjs +250 -0
  57. package/gsd-core/bin/lib/quick-batch.cjs +840 -0
  58. package/gsd-core/bin/lib/review-lane-descriptor.cjs +53 -5
  59. package/gsd-core/bin/lib/review-lane-invocation.cjs +73 -1
  60. package/gsd-core/bin/lib/review-lane-runner.cjs +136 -10
  61. package/gsd-core/bin/lib/roadmap-parser.cjs +499 -26
  62. package/gsd-core/bin/lib/roadmap.cjs +187 -58
  63. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +233 -33
  64. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +16 -17
  65. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +286 -108
  66. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +215 -43
  67. package/gsd-core/bin/lib/shell-command-projection.cjs +4 -0
  68. package/gsd-core/bin/lib/smart-entry.cjs +7 -9
  69. package/gsd-core/bin/lib/state-document.cjs +30 -5
  70. package/gsd-core/bin/lib/state-md-schema.cjs +23 -13
  71. package/gsd-core/bin/lib/state-transition.cjs +333 -44
  72. package/gsd-core/bin/lib/state.cjs +684 -125
  73. package/gsd-core/bin/lib/surface.cjs +23 -8
  74. package/gsd-core/bin/lib/tdd-red-evidence.cjs +133 -0
  75. package/gsd-core/bin/lib/uat.cjs +1419 -515
  76. package/gsd-core/bin/lib/update-context.cjs +6 -2
  77. package/gsd-core/bin/lib/validate.cjs +230 -12
  78. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  79. package/gsd-core/bin/lib/verification.cjs +273 -12
  80. package/gsd-core/bin/lib/verify-command-router.cjs +1 -0
  81. package/gsd-core/bin/lib/verify.cjs +346 -16
  82. package/gsd-core/bin/lib/workstream-inventory.cjs +20 -2
  83. package/gsd-core/bin/lib/worktree-safety.cjs +8 -0
  84. package/gsd-core/bin/shared/config-schema.manifest.json +8 -0
  85. package/gsd-core/bin/verify-reapply-patches.cjs +70 -3
  86. package/gsd-core/references/agent-contracts.md +3 -3
  87. package/gsd-core/references/edge-probe.md +17 -13
  88. package/gsd-core/references/execute-mvp-tdd.md +18 -16
  89. package/gsd-core/references/execute-phase-response-language.md +6 -0
  90. package/gsd-core/references/executor-examples.md +42 -0
  91. package/gsd-core/references/few-shot-examples/plan-checker.md +15 -15
  92. package/gsd-core/references/mvp-concepts.md +2 -2
  93. package/gsd-core/references/plan-checker-examples.md +41 -0
  94. package/gsd-core/references/planner-antipatterns.md +25 -0
  95. package/gsd-core/references/planner-chunked.md +5 -1
  96. package/gsd-core/references/planner-coupling.md +42 -0
  97. package/gsd-core/references/planner-quick-batch.md +71 -0
  98. package/gsd-core/references/planner-reviews.md +47 -0
  99. package/gsd-core/references/planner-revision.md +75 -2
  100. package/gsd-core/references/planning-config.md +2 -1
  101. package/gsd-core/references/response-language-directive.md +9 -0
  102. package/gsd-core/references/revision-loop.md +118 -11
  103. package/gsd-core/references/tdd.md +14 -9
  104. package/gsd-core/references/verifier-evidence-gate.md +160 -0
  105. package/gsd-core/templates/phase-prompt.md +4 -0
  106. package/gsd-core/templates/verification-report.md +5 -0
  107. package/gsd-core/workflows/add-backlog.md +2 -0
  108. package/gsd-core/workflows/add-phase.md +2 -0
  109. package/gsd-core/workflows/add-tests.md +1 -1
  110. package/gsd-core/workflows/add-todo.md +1 -1
  111. package/gsd-core/workflows/ai-integration-phase.md +1 -1
  112. package/gsd-core/workflows/analyze-dependencies.md +2 -0
  113. package/gsd-core/workflows/audit-fix.md +2 -0
  114. package/gsd-core/workflows/audit-milestone.md +2 -0
  115. package/gsd-core/workflows/audit-uat.md +2 -0
  116. package/gsd-core/workflows/autonomous.md +2 -0
  117. package/gsd-core/workflows/check-todos.md +1 -1
  118. package/gsd-core/workflows/cleanup.md +1 -1
  119. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +15 -13
  120. package/gsd-core/workflows/code-review-fix.md +2 -0
  121. package/gsd-core/workflows/code-review.md +73 -31
  122. package/gsd-core/workflows/complete-milestone.md +13 -4
  123. package/gsd-core/workflows/debug.md +1 -1
  124. package/gsd-core/workflows/diagnose-issues.md +5 -1
  125. package/gsd-core/workflows/discuss-phase/modes/advisor.md +2 -0
  126. package/gsd-core/workflows/discuss-phase/modes/all.md +2 -0
  127. package/gsd-core/workflows/discuss-phase/modes/analyze.md +2 -0
  128. package/gsd-core/workflows/discuss-phase/modes/auto.md +2 -0
  129. package/gsd-core/workflows/discuss-phase/modes/batch.md +2 -0
  130. package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -0
  131. package/gsd-core/workflows/discuss-phase/modes/default.md +2 -0
  132. package/gsd-core/workflows/discuss-phase/modes/power.md +2 -0
  133. package/gsd-core/workflows/discuss-phase/modes/text.md +2 -0
  134. package/gsd-core/workflows/discuss-phase/templates/context.md +2 -0
  135. package/gsd-core/workflows/discuss-phase/templates/discussion-log.md +2 -0
  136. package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
  137. package/gsd-core/workflows/discuss-phase-power.md +2 -0
  138. package/gsd-core/workflows/discuss-phase.md +1 -1
  139. package/gsd-core/workflows/do.md +43 -13
  140. package/gsd-core/workflows/docs-update.md +1 -1
  141. package/gsd-core/workflows/edit-phase.md +2 -0
  142. package/gsd-core/workflows/eval-review.md +1 -1
  143. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +2 -0
  144. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +17 -1
  145. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +8 -2
  146. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -0
  147. package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +25 -0
  148. package/gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md +2 -0
  149. package/gsd-core/workflows/execute-phase.md +32 -14
  150. package/gsd-core/workflows/execute-plan.md +8 -8
  151. package/gsd-core/workflows/explore.md +2 -0
  152. package/gsd-core/workflows/extract-learnings.md +2 -0
  153. package/gsd-core/workflows/fast.md +6 -0
  154. package/gsd-core/workflows/forensics.md +2 -0
  155. package/gsd-core/workflows/graduation.md +1 -1
  156. package/gsd-core/workflows/health.md +1 -1
  157. package/gsd-core/workflows/help/modes/brief.md +2 -0
  158. package/gsd-core/workflows/help/modes/default.md +2 -0
  159. package/gsd-core/workflows/help/modes/full.md +12 -0
  160. package/gsd-core/workflows/help/modes/topic.md +2 -0
  161. package/gsd-core/workflows/help.md +2 -0
  162. package/gsd-core/workflows/import.md +3 -3
  163. package/gsd-core/workflows/inbox.md +1 -1
  164. package/gsd-core/workflows/ingest-docs.md +1 -1
  165. package/gsd-core/workflows/insert-phase.md +2 -0
  166. package/gsd-core/workflows/list-phase-assumptions.md +2 -0
  167. package/gsd-core/workflows/list-seeds.md +2 -0
  168. package/gsd-core/workflows/list-workspaces.md +2 -0
  169. package/gsd-core/workflows/manager.md +3 -3
  170. package/gsd-core/workflows/map-codebase.md +2 -0
  171. package/gsd-core/workflows/milestone-summary.md +2 -0
  172. package/gsd-core/workflows/mvp-phase.md +1 -1
  173. package/gsd-core/workflows/new-milestone.md +1 -1
  174. package/gsd-core/workflows/new-project.md +5 -3
  175. package/gsd-core/workflows/new-workspace.md +1 -1
  176. package/gsd-core/workflows/next.md +2 -0
  177. package/gsd-core/workflows/node-repair.md +2 -0
  178. package/gsd-core/workflows/note.md +2 -0
  179. package/gsd-core/workflows/onboard.md +1 -1
  180. package/gsd-core/workflows/pause-work.md +19 -4
  181. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +100 -18
  182. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -0
  183. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +9 -0
  184. package/gsd-core/workflows/plan-phase.md +130 -12
  185. package/gsd-core/workflows/plan-review-convergence.md +102 -10
  186. package/gsd-core/workflows/plant-seed.md +1 -1
  187. package/gsd-core/workflows/pr-branch.md +11 -3
  188. package/gsd-core/workflows/profile-user.md +1 -1
  189. package/gsd-core/workflows/progress/steps/forensic-audit.md +1 -1
  190. package/gsd-core/workflows/progress.md +25 -3
  191. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +37 -2
  192. package/gsd-core/workflows/quick/steps/research-phase.md +3 -3
  193. package/gsd-core/workflows/quick-batch/steps/batch-init.md +55 -0
  194. package/gsd-core/workflows/quick-batch/steps/completion.md +65 -0
  195. package/gsd-core/workflows/quick-batch/steps/merge-wave.md +100 -0
  196. package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +147 -0
  197. package/gsd-core/workflows/quick-batch/steps/planner-wave.md +158 -0
  198. package/gsd-core/workflows/quick-batch/steps/research-phase.md +95 -0
  199. package/gsd-core/workflows/quick-batch/steps/resume-mode.md +49 -0
  200. package/gsd-core/workflows/quick-batch/steps/verification-wave.md +73 -0
  201. package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +169 -0
  202. package/gsd-core/workflows/quick-batch.md +203 -0
  203. package/gsd-core/workflows/quick.md +13 -3
  204. package/gsd-core/workflows/reapply-patches.md +2 -0
  205. package/gsd-core/workflows/remove-phase.md +2 -0
  206. package/gsd-core/workflows/remove-workspace.md +1 -1
  207. package/gsd-core/workflows/resume-project.md +6 -2
  208. package/gsd-core/workflows/review.md +215 -10
  209. package/gsd-core/workflows/scan.md +2 -0
  210. package/gsd-core/workflows/section-manifest.json +12 -0
  211. package/gsd-core/workflows/secure-phase.md +1 -1
  212. package/gsd-core/workflows/session-report.md +2 -0
  213. package/gsd-core/workflows/settings-advanced.md +2 -0
  214. package/gsd-core/workflows/settings-integrations.md +9 -8
  215. package/gsd-core/workflows/settings.md +1 -1
  216. package/gsd-core/workflows/ship.md +10 -10
  217. package/gsd-core/workflows/sketch-wrap-up.md +2 -0
  218. package/gsd-core/workflows/sketch.md +1 -1
  219. package/gsd-core/workflows/smart-entry.md +1 -1
  220. package/gsd-core/workflows/spec-phase.md +24 -19
  221. package/gsd-core/workflows/spike-wrap-up.md +2 -0
  222. package/gsd-core/workflows/spike.md +1 -1
  223. package/gsd-core/workflows/stats.md +2 -0
  224. package/gsd-core/workflows/sync-skills.md +12 -4
  225. package/gsd-core/workflows/thread.md +2 -0
  226. package/gsd-core/workflows/transition.md +2 -0
  227. package/gsd-core/workflows/ui-phase.md +26 -5
  228. package/gsd-core/workflows/ui-review.md +1 -1
  229. package/gsd-core/workflows/ultraplan-phase.md +2 -0
  230. package/gsd-core/workflows/undo.md +1 -1
  231. package/gsd-core/workflows/update.md +41 -38
  232. package/gsd-core/workflows/validate-phase.md +1 -1
  233. package/gsd-core/workflows/verify-work.md +49 -3
  234. package/hooks/dist/gsd-check-update-worker.js +19 -2
  235. package/hooks/dist/gsd-context-monitor.js +283 -12
  236. package/hooks/dist/gsd-node-runner.sh +1 -0
  237. package/hooks/dist/gsd-prompt-guard.js +30 -5
  238. package/hooks/dist/gsd-read-guard.js +2 -0
  239. package/hooks/dist/gsd-read-injection-scanner.js +5 -5
  240. package/hooks/dist/gsd-secret-read-guard.js +1079 -0
  241. package/hooks/dist/gsd-statusline.js +7 -3
  242. package/hooks/dist/gsd-validate-commit.sh +444 -7
  243. package/hooks/dist/gsd-workflow-guard.js +2 -1
  244. package/hooks/dist/lib/git-cmd.js +210 -1
  245. package/hooks/dist/lib/injection-patterns.js +36 -6
  246. package/hooks/dist/managed-hooks-registry.cjs +1 -0
  247. package/hooks/gsd-check-update-worker.js +19 -2
  248. package/hooks/gsd-context-monitor.js +283 -12
  249. package/hooks/gsd-node-runner.sh +1 -0
  250. package/hooks/gsd-prompt-guard.js +30 -5
  251. package/hooks/gsd-read-guard.js +2 -0
  252. package/hooks/gsd-read-injection-scanner.js +5 -5
  253. package/hooks/gsd-secret-read-guard.js +1079 -0
  254. package/hooks/gsd-statusline.js +7 -3
  255. package/hooks/gsd-validate-commit.sh +444 -7
  256. package/hooks/gsd-workflow-guard.js +2 -1
  257. package/hooks/hooks.json +6 -0
  258. package/hooks/lib/git-cmd.js +210 -1
  259. package/hooks/lib/injection-patterns.js +36 -6
  260. package/hooks/managed-hooks-registry.cjs +1 -0
  261. package/package.json +5 -5
  262. package/scripts/build-hooks.js +11 -4
  263. package/scripts/ci-test-scope.cjs +7 -0
  264. package/scripts/docs-guard-registry.cjs +10 -0
  265. package/scripts/gen-loop-host-contract.cjs +67 -15
  266. package/scripts/lib/shellcheck-fetch.cjs +247 -0
  267. package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -6
  268. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
  269. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
  270. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +5 -0
  271. package/scripts/lint-phase-enumeration-drift.cjs +24 -6
  272. package/scripts/lint-phase-id-drift.cjs +133 -8
  273. package/scripts/lint-portable-grep.cjs +176 -0
  274. package/scripts/lint-response-language-coverage.cjs +524 -0
  275. package/scripts/lint-test-file-count.allowlist.json +3 -1
  276. package/scripts/lint-workflow-shellcheck-baseline.json +1027 -0
  277. package/scripts/lint-workflow-shellcheck.cjs +614 -0
  278. package/scripts/npm-audit-baseline.cjs +376 -0
  279. package/scripts/prompt-injection-scan.sh +8 -0
  280. package/scripts/require-issue-link-policy.cjs +16 -1
  281. package/skills/gsd-execute-phase/SKILL.md +1 -1
  282. package/skills/gsd-ns-workflow/SKILL.md +1 -0
  283. package/skills/gsd-phase/SKILL.md +1 -1
  284. package/skills/gsd-quick-batch/SKILL.md +105 -0
  285. package/skills/gsd-surface/SKILL.md +18 -8
  286. package/vscode/package.json +1 -1
@@ -25,6 +25,10 @@ const node_os_1 = __importDefault(require("node:os"));
25
25
  // eslint-disable-next-line @typescript-eslint/no-require-imports
26
26
  const installFsAdapter = require("./install-fs-adapter.cjs");
27
27
  const { installFs, mkInstallTempDir } = installFsAdapter;
28
+ // Reuse the install manifest's existing parser and streamed SHA-256
29
+ // classification instead of deriving a second integrity implementation here.
30
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
31
+ const installerMigrations = require("./installer-migrations.cjs");
28
32
  // eslint-disable-next-line @typescript-eslint/no-require-imports
29
33
  const installProfiles = require("./install-profiles.cjs");
30
34
  const { stageSkillsForProfile, stageAgentsForRuntimeWithConverter, stageSkillsForRuntimeAsSkills, stageCommandsForRuntimeFlat, } = installProfiles;
@@ -48,127 +52,288 @@ const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs")
48
52
  const install_scope_cjs_1 = require("./install-scope.cjs");
49
53
  // In .cts (CommonJS output) files, `require` is available as a global.
50
54
  const _require = require;
55
+ function requiredRuntimeSurfaceSourceClasses(kinds) {
56
+ const required = new Set();
57
+ for (const kind of kinds) {
58
+ if (kind.kind === 'commands' || kind.kind === 'skills')
59
+ required.add('commands');
60
+ if (kind.kind === 'agents' || kind.kind === 'kimi-agents')
61
+ required.add('agents');
62
+ }
63
+ return required;
64
+ }
51
65
  // ---------------------------------------------------------------------------
52
66
  // Source root finders
53
67
  // ---------------------------------------------------------------------------
54
- /**
55
- * Locate the GSD commands/gsd source directory.
56
- *
57
- * Resolution order:
58
- * 1. If runtimeConfigDir provided, check <runtimeConfigDir>/.gsd-source marker.
59
- * 2. Walk up from __dirname using path.dirname (no literal .. segments).
60
- * 3. Throw a descriptive error if neither succeeds.
61
- */
62
- function findInstallSourceRoot(runtimeConfigDir) {
63
- // Step 1: marker check — reads `<runtimeConfigDir>/.gsd-source`, a path
64
- // under the INSTALL DESTINATION, so this probe goes through the injected
65
- // adapter (installFs()).
66
- if (runtimeConfigDir) {
67
- const markerPath = node_path_1.default.join(runtimeConfigDir, '.gsd-source');
68
- if (installFs().existsSync(markerPath)) {
69
- try {
70
- const src = installFs().readFileSync(markerPath, 'utf8').trim();
71
- if (src && installFs().existsSync(src))
72
- return src;
68
+ function isReadableDirectory(candidate, routed) {
69
+ try {
70
+ const io = routed ? installFs() : node_fs_1.default;
71
+ const stat = io.lstatSync(candidate);
72
+ if (!stat.isDirectory() || stat.isSymbolicLink())
73
+ return false;
74
+ const entries = io.readdirSync(candidate);
75
+ let readableFiles = 0;
76
+ for (const name of entries) {
77
+ const child = node_path_1.default.join(candidate, name);
78
+ const childStat = io.lstatSync(child);
79
+ if (childStat.isSymbolicLink())
80
+ return false;
81
+ if (childStat.isDirectory()) {
82
+ if (!isReadableDirectory(child, routed))
83
+ return false;
84
+ readableFiles += 1;
85
+ }
86
+ else if (childStat.isFile()) {
87
+ if (routed)
88
+ io.readFileSync(child);
89
+ else
90
+ node_fs_1.default.accessSync(child, node_fs_1.default.constants.R_OK);
91
+ readableFiles += 1;
92
+ }
93
+ else {
94
+ return false;
73
95
  }
74
- catch { /* fall through */ }
75
96
  }
97
+ return readableFiles > 0;
76
98
  }
77
- // Step 2: walk up from __dirname to locate the GSD PACKAGE'S OWN source
78
- // tree (commands/gsd/) — this resolves where the installer's own code is
79
- // running FROM, not anything under the install destination, so it is
80
- // deliberately NOT routed through the injected fs adapter (#2874): a fake
81
- // "destination" adapter has no reason to know about the real package's own
82
- // on-disk layout (an injected adapter's store starts empty and is never
83
- // seeded with real repo paths), and routing it through would make this
84
- // resolution unconditionally throw rather than gracefully staging nothing.
85
- //
86
- // Uses `fs.statSync` in a try/catch rather than `fs.existsSync` — this is
87
- // LOAD-BEARING, not a style choice: tests/executed-plan.test.cjs's F2 cases
88
- // poison every method on the ROUTED fs surface (including `existsSync`,
89
- // since installFs()'s REAL_ADAPTER also calls it) to prove nothing on the
90
- // installRuntimeArtifacts call tree reaches real fs. The F2 "nativePlugin
91
- // runtime: pi" test calls this function (via findInstallSourceRoot()) AFTER
92
- // installing that poison, specifically to resolve the pi nativePlugin
93
- // source path against this repo's own real layout — an operation this
94
- // function must still be able to perform even while `existsSync` is
95
- // poisoned, because this Step 2 walk is real-fs-only by design and was
96
- // never meant to be covered by that poison list. `statSync` is not on the
97
- // poisoned surface, so this probe survives; switching back to `existsSync`
98
- // makes that F2 test throw (verified: reverting this to `existsSync` trips
99
- // the poison and breaks the pi nativePlugin case).
100
- let dir = __dirname;
101
- for (let i = 0; i < 6; i++) {
102
- const candidate = node_path_1.default.join(dir, 'commands', 'gsd');
103
- try {
104
- node_fs_1.default.statSync(candidate);
105
- return candidate;
99
+ catch {
100
+ return false;
101
+ }
102
+ }
103
+ function isPhysicallyConfinedTo(root, candidate) {
104
+ try {
105
+ const physicalRoot = installFs().realpathSync(root);
106
+ const physicalCandidate = installFs().realpathSync(candidate);
107
+ return physicalCandidate === physicalRoot || physicalCandidate.startsWith(physicalRoot + node_path_1.default.sep);
108
+ }
109
+ catch {
110
+ return false;
111
+ }
112
+ }
113
+ function installedManifestIsComplete(runtimeConfigDir, required) {
114
+ // This synchronous admission check binds provider selection to the corpus
115
+ // observed here. Same-user mutation after resolution is outside #4132's
116
+ // threat model and would require a broader snapshot/transaction design.
117
+ const io = installFs();
118
+ const manifestPath = node_path_1.default.join(runtimeConfigDir, 'gsd-file-manifest.json');
119
+ if (!io.existsSync(manifestPath))
120
+ return false;
121
+ if (!isPhysicallyConfinedTo(runtimeConfigDir, manifestPath))
122
+ return false;
123
+ try {
124
+ const manifest = installerMigrations.readInstallManifest(runtimeConfigDir);
125
+ if (manifest.manifestVersion === null)
126
+ return false;
127
+ const keys = Object.keys(manifest.files);
128
+ const prefixes = [];
129
+ if (required.has('commands'))
130
+ prefixes.push('gsd-core/commands/gsd/');
131
+ if (required.has('agents'))
132
+ prefixes.push('gsd-core/agents/');
133
+ for (const prefix of prefixes) {
134
+ const expected = keys.filter((key) => key.startsWith(prefix));
135
+ if (expected.length === 0)
136
+ return false;
137
+ const expectedSet = new Set(expected);
138
+ const corpusRoot = node_path_1.default.resolve(runtimeConfigDir, ...prefix.slice(0, -1).split('/'));
139
+ if (!isPhysicallyConfinedTo(runtimeConfigDir, corpusRoot))
140
+ return false;
141
+ let actualFiles = 0;
142
+ const visit = (dir) => {
143
+ for (const name of io.readdirSync(dir)) {
144
+ const candidate = node_path_1.default.join(dir, name);
145
+ const stat = io.lstatSync(candidate);
146
+ if (stat.isSymbolicLink())
147
+ return false;
148
+ if (stat.isDirectory()) {
149
+ if (!visit(candidate))
150
+ return false;
151
+ }
152
+ else if (stat.isFile()) {
153
+ const relative = node_path_1.default.relative(corpusRoot, candidate).split(node_path_1.default.sep).join('/');
154
+ if (!expectedSet.has(prefix + relative))
155
+ return false;
156
+ actualFiles += 1;
157
+ }
158
+ else {
159
+ return false;
160
+ }
161
+ }
162
+ return true;
163
+ };
164
+ if (!visit(corpusRoot) || actualFiles !== expected.length)
165
+ return false;
166
+ for (const key of expected) {
167
+ const parts = key.split('/');
168
+ if (parts.some((part) => part === '' || part === '.' || part === '..'))
169
+ return false;
170
+ const candidate = node_path_1.default.resolve(runtimeConfigDir, ...parts);
171
+ const root = node_path_1.default.resolve(runtimeConfigDir);
172
+ if (!candidate.startsWith(root + node_path_1.default.sep))
173
+ return false;
174
+ const stat = io.lstatSync(candidate);
175
+ if (!stat.isFile() || stat.isSymbolicLink())
176
+ return false;
177
+ if (installerMigrations.classifyArtifact(runtimeConfigDir, key, manifest).classification !== 'managed-pristine') {
178
+ return false;
179
+ }
180
+ }
106
181
  }
107
- catch { /* not here — keep walking up */ }
108
- const parent = node_path_1.default.dirname(dir);
109
- if (parent === dir)
110
- break;
111
- dir = parent;
182
+ return true;
183
+ }
184
+ catch {
185
+ return false;
112
186
  }
113
- throw new Error(`findInstallSourceRoot: could not locate commands/gsd from ${__dirname}`);
114
187
  }
115
- /**
116
- * Locate the GSD agents source directory.
117
- *
118
- * Resolution order:
119
- * 1. If runtimeConfigDir provided, check <runtimeConfigDir>/.gsd-source marker.
120
- * 2. Walk up from __dirname using path.dirname (no literal .. segments).
121
- * 3. Throw a descriptive error if neither succeeds.
122
- */
123
- function findAgentsSourceRoot(runtimeConfigDir) {
124
- // Step 1: marker check (destination-relative — routed through installFs()).
125
- if (runtimeConfigDir) {
126
- const markerPath = node_path_1.default.join(runtimeConfigDir, '.gsd-source');
127
- if (installFs().existsSync(markerPath)) {
128
- try {
129
- const src = installFs().readFileSync(markerPath, 'utf8').trim();
130
- if (src && installFs().existsSync(src)) {
131
- // Marker points to commands/gsd; agents/ is a sibling of commands/
132
- const agentsCandidate = node_path_1.default.resolve(node_path_1.default.dirname(src), '..', 'agents');
133
- if (installFs().existsSync(agentsCandidate))
134
- return agentsCandidate;
188
+ function providerHasRequiredClasses(provider, required, routed, runtimeConfigDir) {
189
+ if (provider.kind === 'installed' && runtimeConfigDir) {
190
+ return installedManifestIsComplete(runtimeConfigDir, required);
191
+ }
192
+ return (!required.has('commands') || isReadableDirectory(provider.commandsRoot, routed)) &&
193
+ (!required.has('agents') || isReadableDirectory(provider.agentsRoot, routed));
194
+ }
195
+ function providersShareRequiredRoots(left, right, required) {
196
+ const leftFs = left.kind === 'package' ? node_fs_1.default : installFs();
197
+ const rightFs = right.kind === 'package' ? node_fs_1.default : installFs();
198
+ const physicalRootsOverlap = (leftRoot, rightRoot) => {
199
+ const canonicalize = (io, root) => {
200
+ let existing = node_path_1.default.resolve(root);
201
+ const missingSegments = [];
202
+ while (true) {
203
+ try {
204
+ return node_path_1.default.resolve(io.realpathSync(existing), ...missingSegments);
205
+ }
206
+ catch (error) {
207
+ if (error.code !== 'ENOENT')
208
+ return null;
209
+ const parent = node_path_1.default.dirname(existing);
210
+ if (parent === existing)
211
+ return null;
212
+ missingSegments.unshift(node_path_1.default.basename(existing));
213
+ existing = parent;
135
214
  }
136
215
  }
137
- catch { /* fall through */ }
216
+ };
217
+ const overlap = (leftPath, rightPath) => {
218
+ const relative = node_path_1.default.relative(leftPath, rightPath);
219
+ return relative === '' ||
220
+ (relative !== '..' && !relative.startsWith(`..${node_path_1.default.sep}`) && !node_path_1.default.isAbsolute(relative));
221
+ };
222
+ const physicalLeft = canonicalize(leftFs, leftRoot);
223
+ const physicalRight = canonicalize(rightFs, rightRoot);
224
+ if (!physicalLeft || !physicalRight)
225
+ return true;
226
+ return overlap(physicalLeft, physicalRight) || overlap(physicalRight, physicalLeft);
227
+ };
228
+ return (required.has('commands') && physicalRootsOverlap(left.commandsRoot, right.commandsRoot)) ||
229
+ (required.has('agents') && physicalRootsOverlap(left.agentsRoot, right.agentsRoot));
230
+ }
231
+ function markerProvider(runtimeConfigDir) {
232
+ const markerPath = node_path_1.default.join(runtimeConfigDir, '.gsd-source');
233
+ try {
234
+ if (!installFs().existsSync(markerPath))
235
+ return null;
236
+ const markerStat = installFs().lstatSync(markerPath);
237
+ if (!markerStat.isFile() || markerStat.isSymbolicLink())
238
+ return null;
239
+ const commandsRoot = installFs().readFileSync(markerPath, 'utf8').trim();
240
+ if (!commandsRoot)
241
+ return null;
242
+ // A marker written by the installer may point at this process's executing
243
+ // package. Preserve package-source IO on real fs so the existing injected
244
+ // destination adapter remains destination-only (#2874).
245
+ const packaged = packageProvider(new Set(['commands']));
246
+ if (packaged && node_path_1.default.resolve(commandsRoot) === node_path_1.default.resolve(packaged.commandsRoot)) {
247
+ return packaged;
138
248
  }
249
+ return {
250
+ kind: 'marker',
251
+ commandsRoot,
252
+ agentsRoot: node_path_1.default.resolve(node_path_1.default.dirname(commandsRoot), '..', 'agents'),
253
+ };
139
254
  }
140
- // Step 2: walk up from __dirname — locates THIS package's own agents/
141
- // source tree, not the install destination. See findInstallSourceRoot's
142
- // Step 2 comment (#2874) for why this stays unrouted, real-fs-only, and why
143
- // it uses `statSync` rather than `existsSync` (load-bearing against F2's
144
- // poison of the routed fs surface, not a style choice).
255
+ catch {
256
+ return null;
257
+ }
258
+ }
259
+ function packageProvider(required) {
260
+ // Package-source IO deliberately stays on real fs; injected install adapters
261
+ // model destinations, not the executing package tree (#2874).
145
262
  let dir = __dirname;
146
263
  for (let i = 0; i < 6; i++) {
147
- const candidate = node_path_1.default.join(dir, 'agents');
148
- try {
149
- node_fs_1.default.statSync(candidate);
264
+ const candidate = {
265
+ kind: 'package',
266
+ commandsRoot: node_path_1.default.join(dir, 'commands', 'gsd'),
267
+ agentsRoot: node_path_1.default.join(dir, 'agents'),
268
+ };
269
+ if (providerHasRequiredClasses(candidate, required, false))
150
270
  return candidate;
151
- }
152
- catch { /* not here — keep walking up */ }
153
271
  const parent = node_path_1.default.dirname(dir);
154
272
  if (parent === dir)
155
273
  break;
156
274
  dir = parent;
157
275
  }
158
- throw new Error(`findAgentsSourceRoot: could not locate agents/ from ${__dirname}`);
276
+ return null;
277
+ }
278
+ /**
279
+ * Select one complete source provider for an entire resolved layout.
280
+ * Provider mixing is forbidden: a skills+agents layout cannot take commands
281
+ * from one package version and agents from another.
282
+ */
283
+ function resolveSourceProvider(runtimeConfigDir, requiredClasses, scope = 'global', authority = 'compatible') {
284
+ const required = new Set(requiredClasses);
285
+ let rejectedInstalled = null;
286
+ if (required.size === 0) {
287
+ return { kind: 'package', commandsRoot: '', agentsRoot: '' };
288
+ }
289
+ if (runtimeConfigDir && scope === 'global') {
290
+ const installed = {
291
+ kind: 'installed',
292
+ commandsRoot: node_path_1.default.join(runtimeConfigDir, 'gsd-core', 'commands', 'gsd'),
293
+ agentsRoot: node_path_1.default.join(runtimeConfigDir, 'gsd-core', 'agents'),
294
+ };
295
+ if (providerHasRequiredClasses(installed, required, true, runtimeConfigDir))
296
+ return installed;
297
+ rejectedInstalled = installed;
298
+ const marker = markerProvider(runtimeConfigDir);
299
+ if (marker && !providersShareRequiredRoots(marker, installed, required) && providerHasRequiredClasses(marker, required, marker.kind !== 'package')) {
300
+ return marker;
301
+ }
302
+ }
303
+ else if (runtimeConfigDir) {
304
+ const marker = markerProvider(runtimeConfigDir);
305
+ if (marker && providerHasRequiredClasses(marker, required, marker.kind !== 'package'))
306
+ return marker;
307
+ }
308
+ if (authority === 'compatible') {
309
+ const packaged = packageProvider(required);
310
+ if (packaged &&
311
+ (!rejectedInstalled || !providersShareRequiredRoots(packaged, rejectedInstalled, required))) {
312
+ return packaged;
313
+ }
314
+ }
315
+ throw new Error(`Runtime Surface source is unavailable or incomplete for ${[...required].sort().join('+')}; ` +
316
+ 'install or upgrade gsd-core before materializing this surface.');
317
+ }
318
+ function sourceRootFor(context, sourceClass) {
319
+ context.provider ??= resolveSourceProvider(context.runtimeConfigDir, context.required, context.scope, context.authority);
320
+ return sourceClass === 'commands' ? context.provider.commandsRoot : context.provider.agentsRoot;
321
+ }
322
+ function findInstallSourceRoot(runtimeConfigDir) {
323
+ return resolveSourceProvider(runtimeConfigDir, ['commands']).commandsRoot;
159
324
  }
160
325
  // ---------------------------------------------------------------------------
161
326
  // Layout table builders
162
327
  // ---------------------------------------------------------------------------
163
- function commandsKind(destSubpath, prefix, configDir) {
328
+ function commandsKind(destSubpath, prefix, sourceContext) {
164
329
  return {
165
330
  kind: 'commands',
166
331
  destSubpath,
167
332
  prefix,
168
- stage: (resolved) => stageSkillsForProfile(findInstallSourceRoot(configDir), resolved),
333
+ stage: (resolved) => stageSkillsForProfile(sourceRootFor(sourceContext, 'commands'), resolved),
169
334
  };
170
335
  }
171
- function agentsKind(destSubpath, prefix, configDir) {
336
+ function agentsKind(destSubpath, prefix, configDir, sourceContext) {
172
337
  return {
173
338
  kind: 'agents',
174
339
  destSubpath,
@@ -191,7 +356,7 @@ function agentsKind(destSubpath, prefix, configDir) {
191
356
  // signature silently dropped the second arg every caller already passed —
192
357
  // a caller with NO agentCtx in scope is unaffected (row I2: converter-only,
193
358
  // as today), matching stageAgentsForRuntimeWithConverter's own contract.
194
- stage: (resolved, agentCtx) => stageAgentsForRuntimeWithConverter(findAgentsSourceRoot(configDir), resolved, (content) => content, false, agentCtx),
359
+ stage: (resolved, agentCtx) => stageAgentsForRuntimeWithConverter(sourceRootFor(sourceContext, 'agents'), resolved, (content) => content, false, agentCtx?.runtime ? agentCtx : undefined),
195
360
  };
196
361
  }
197
362
  /**
@@ -323,7 +488,7 @@ function _resolveNamedConverter(converterName, kindLabel) {
323
488
  * @param converterName name of converter function in Runtime Artifact Conversion exports
324
489
  * @param configDir runtime config dir (for .gsd-source marker resolution)
325
490
  */
326
- function convertedAgentsKind(destSubpath, prefix, converterName, configDir, scope = 'global') {
491
+ function convertedAgentsKind(destSubpath, prefix, converterName, configDir, sourceContext, scope) {
327
492
  return {
328
493
  kind: 'agents',
329
494
  destSubpath,
@@ -396,20 +561,20 @@ function convertedAgentsKind(destSubpath, prefix, converterName, configDir, scop
396
561
  // ADR-1235 §1: when agentCtx is provided (by createRuntimeArtifactInstallPlan
397
562
  // for descriptor-driven runtimes), thread it through so stageAgentsForRuntimeWithConverter
398
563
  // can apply the full pre-converter + post-converter sequence in the correct order.
399
- return stageAgentsForRuntimeWithConverter(findAgentsSourceRoot(configDir), resolved, converter, (0, install_scope_cjs_1.isGlobalScope)(scope), agentCtx);
564
+ return stageAgentsForRuntimeWithConverter(sourceRootFor(sourceContext, 'agents'), resolved, converter, (0, install_scope_cjs_1.isGlobalScope)(scope), agentCtx?.runtime ? agentCtx : undefined);
400
565
  },
401
566
  };
402
567
  }
403
- function kimiAgentsKind(destSubpath, prefix, configDir) {
568
+ function kimiAgentsKind(destSubpath, prefix, configDir, sourceContext) {
404
569
  return {
405
570
  kind: 'kimi-agents',
406
571
  destSubpath,
407
572
  prefix,
408
- stage: (resolved) => {
573
+ stage: (resolved, agentCtx) => {
409
574
  const buildKimiAgentArtifacts = conversionExports['buildKimiAgentArtifacts'];
410
575
  // #2995: compose at staging (identity converter) so the readFileSync below
411
576
  // sees marker-free content — same single composing stager as agentsKind.
412
- const stagedAgents = stageAgentsForRuntimeWithConverter(findAgentsSourceRoot(configDir), resolved, (content) => content);
577
+ const stagedAgents = stageAgentsForRuntimeWithConverter(sourceRootFor(sourceContext, 'agents'), resolved, (content) => content, false, agentCtx);
413
578
  const subagents = [];
414
579
  if (installFs().existsSync(stagedAgents)) {
415
580
  for (const entry of installFs().readdirSync(stagedAgents, { withFileTypes: true })) {
@@ -456,7 +621,7 @@ function kimiAgentsKind(destSubpath, prefix, configDir) {
456
621
  * their declaring capId at staging time. Absent -> stage() stages
457
622
  * nothing third-party (fail closed).
458
623
  */
459
- function skillsKind(destSubpath, prefix, converterName, runtime, configDir, nested = false, scope = 'global', capabilityRegistry) {
624
+ function skillsKind(destSubpath, prefix, converterName, runtime, configDir, nested, scope, sourceContext, capabilityRegistry) {
460
625
  return {
461
626
  kind: 'skills',
462
627
  destSubpath,
@@ -489,7 +654,7 @@ function skillsKind(destSubpath, prefix, converterName, runtime, configDir, nest
489
654
  // it must run AFTER it instead, once the `@`-include is in its final
490
655
  // rewritten shape.
491
656
  const wrappedConverter = (content, skillName) => realConverter(content, skillName, runtime, cmdNames, isGlobal);
492
- return stageSkillsForRuntimeAsSkills(findInstallSourceRoot(configDir), resolved, wrappedConverter, prefix, nested, capabilityRegistry);
657
+ return stageSkillsForRuntimeAsSkills(sourceRootFor(sourceContext, 'commands'), resolved, wrappedConverter, prefix, nested, capabilityRegistry);
493
658
  },
494
659
  };
495
660
  }
@@ -509,14 +674,14 @@ function skillsKind(destSubpath, prefix, converterName, runtime, configDir, nest
509
674
  * @param converterName name of converter function in Runtime Artifact Conversion exports
510
675
  * @param configDir runtime config dir (for .gsd-source marker resolution)
511
676
  */
512
- function convertedCommandsKind(destSubpath, prefix, converterName, configDir) {
677
+ function convertedCommandsKind(destSubpath, prefix, converterName, sourceContext) {
513
678
  return {
514
679
  kind: 'commands',
515
680
  destSubpath,
516
681
  prefix,
517
682
  stage: (resolved) => {
518
683
  const converter = _resolveNamedConverter(converterName, 'commands');
519
- return stageCommandsForRuntimeFlat(findInstallSourceRoot(configDir), resolved, converter, prefix);
684
+ return stageCommandsForRuntimeFlat(sourceRootFor(sourceContext, 'commands'), resolved, converter, prefix);
520
685
  },
521
686
  };
522
687
  }
@@ -527,29 +692,29 @@ function getRegistry() {
527
692
  * Map a single ArtifactKindDescriptor entry to an ArtifactKind using the
528
693
  * matching builder function. Mirrors the hand-built calls in the old switch.
529
694
  */
530
- function dispatchKindEntry(entry, runtime, configDir, scope, capabilityRegistry) {
695
+ function dispatchKindEntry(entry, runtime, configDir, scope, capabilityRegistry, sourceContext) {
531
696
  const { kind, destSubpath, prefix, nesting, converter } = entry;
532
697
  const nested = nesting === 'nested';
533
698
  let result;
534
699
  switch (kind) {
535
700
  case 'commands':
536
701
  result = converter == null
537
- ? commandsKind(destSubpath, prefix, configDir)
538
- : convertedCommandsKind(destSubpath, prefix, converter, configDir);
702
+ ? commandsKind(destSubpath, prefix, sourceContext)
703
+ : convertedCommandsKind(destSubpath, prefix, converter, sourceContext);
539
704
  break;
540
705
  case 'agents':
541
706
  result = converter == null
542
- ? agentsKind(destSubpath, prefix, configDir)
543
- : convertedAgentsKind(destSubpath, prefix, converter, configDir, scope);
707
+ ? agentsKind(destSubpath, prefix, configDir, sourceContext)
708
+ : convertedAgentsKind(destSubpath, prefix, converter, configDir, sourceContext, scope);
544
709
  break;
545
710
  case 'skills':
546
711
  if (converter == null) {
547
712
  throw new TypeError(`resolveRuntimeArtifactLayout: skills entry for '${runtime}' has converter=null (converter is required for skills)`);
548
713
  }
549
- result = skillsKind(destSubpath, prefix, converter, runtime, configDir, nested, scope, capabilityRegistry);
714
+ result = skillsKind(destSubpath, prefix, converter, runtime, configDir, nested, scope, sourceContext, capabilityRegistry);
550
715
  break;
551
716
  case 'kimi-agents':
552
- result = kimiAgentsKind(destSubpath, prefix, configDir);
717
+ result = kimiAgentsKind(destSubpath, prefix, configDir, sourceContext);
553
718
  break;
554
719
  default:
555
720
  throw new TypeError(`resolveRuntimeArtifactLayout: unknown kind '${kind}' in descriptor for runtime '${runtime}'`);
@@ -594,7 +759,20 @@ function resolveRuntimeArtifactLayoutFromRegistry(registry, runtime, configDir,
594
759
  throw new TypeError(`Unknown runtime: '${runtime}' — add to runtime-artifact-layout.cjs table`);
595
760
  }
596
761
  const entries = desc[scope] ?? [];
597
- const kinds = entries.map((entry) => dispatchKindEntry(entry, runtime, configDir, scope, capabilityRegistry));
762
+ const required = requiredRuntimeSurfaceSourceClasses(entries);
763
+ // Stage closures share one lazy source-resolution context, so the first kind
764
+ // to stage selects and caches one complete provider for every required source
765
+ // class. Global layouts accept only installed or marker providers; the
766
+ // installer owns its private package fallback by retrying through a transient
767
+ // compatibility marker after source resolution fails. Local layouts retain
768
+ // compatible marker/package resolution and never provision the global corpus.
769
+ const sourceContext = {
770
+ runtimeConfigDir: configDir,
771
+ scope,
772
+ required,
773
+ authority: scope === 'local' ? 'compatible' : 'runtime',
774
+ };
775
+ const kinds = entries.map((entry) => dispatchKindEntry(entry, runtime, configDir, scope, capabilityRegistry, sourceContext));
598
776
  return { runtime, configDir, scope, kinds };
599
777
  }
600
778
  function getTriggerRegistry() {