@opengsd/gsd-core 1.7.0-rc.2 → 1.7.0-rc.4

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 (184) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/GEMINI.md +25 -23
  4. package/README.ja-JP.md +4 -3
  5. package/README.ko-KR.md +4 -3
  6. package/README.md +7 -6
  7. package/README.pt-BR.md +4 -3
  8. package/README.zh-CN.md +4 -3
  9. package/agents/gsd-advisor-researcher.md +1 -1
  10. package/agents/gsd-ai-researcher.md +1 -1
  11. package/agents/gsd-code-fixer.md +1 -1
  12. package/agents/gsd-debug-session-manager.md +1 -1
  13. package/agents/gsd-debugger.md +1 -1
  14. package/agents/gsd-domain-researcher.md +1 -1
  15. package/agents/gsd-eval-auditor.md +1 -1
  16. package/agents/gsd-executor.md +4 -4
  17. package/agents/gsd-intel-updater.md +1 -1
  18. package/agents/gsd-mempalace-curator.md +1 -1
  19. package/agents/gsd-phase-researcher.md +2 -2
  20. package/agents/gsd-plan-checker.md +1 -1
  21. package/agents/gsd-planner.md +2 -2
  22. package/agents/gsd-project-researcher.md +2 -2
  23. package/agents/gsd-research-synthesizer.md +1 -1
  24. package/agents/gsd-ui-researcher.md +2 -2
  25. package/agents/gsd-verifier.md +2 -2
  26. package/bin/install.js +132 -464
  27. package/commands/gsd/map-codebase.md +4 -4
  28. package/commands/gsd/mempalace-capture.md +1 -1
  29. package/commands/gsd/mempalace-recall.md +9 -5
  30. package/commands/gsd/next.md +29 -0
  31. package/commands/gsd/ns-project.md +2 -1
  32. package/commands/gsd/ns-workflow.md +5 -2
  33. package/commands/gsd/onboard.md +46 -0
  34. package/gsd-core/bin/ensure-runtime-build.cjs +246 -0
  35. package/gsd-core/bin/gsd-tools.cjs +72 -3
  36. package/gsd-core/bin/lib/capability-registry.cjs +684 -264
  37. package/gsd-core/bin/lib/capability-state.cjs +117 -18
  38. package/gsd-core/bin/lib/capability-writer.cjs +11 -1
  39. package/gsd-core/bin/lib/check-command-router.cjs +111 -1
  40. package/gsd-core/bin/lib/claude-orchestration-command-router.cjs +136 -0
  41. package/gsd-core/bin/lib/claude-orchestration.cjs +404 -0
  42. package/gsd-core/bin/lib/clusters.cjs +2 -0
  43. package/gsd-core/bin/lib/command-aliases.cjs +8 -0
  44. package/gsd-core/bin/lib/config.cjs +19 -1
  45. package/gsd-core/bin/lib/core-utils.cjs +41 -0
  46. package/gsd-core/bin/lib/external-job.cjs +287 -0
  47. package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +151 -0
  48. package/gsd-core/bin/lib/graphify.cjs +41 -9
  49. package/gsd-core/bin/lib/init-command-router.cjs +4 -0
  50. package/gsd-core/bin/lib/init.cjs +25 -69
  51. package/gsd-core/bin/lib/install-engine.cjs +8 -5
  52. package/gsd-core/bin/lib/install-profiles.cjs +2 -0
  53. package/gsd-core/bin/lib/loop-resolver.cjs +7 -1
  54. package/gsd-core/bin/lib/milestone.cjs +7 -1
  55. package/gsd-core/bin/lib/model-resolver.cjs +69 -4
  56. package/gsd-core/bin/lib/onboard-projection.cjs +309 -0
  57. package/gsd-core/bin/lib/phase.cjs +14 -2
  58. package/gsd-core/bin/lib/plan-scan.cjs +9 -1
  59. package/gsd-core/bin/lib/profile-output.cjs +1 -1
  60. package/gsd-core/bin/lib/prohibition-enforcement.cjs +21 -19
  61. package/gsd-core/bin/lib/roadmap.cjs +17 -2
  62. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +18 -13
  63. package/gsd-core/bin/lib/runtime-name-policy.cjs +11 -11
  64. package/gsd-core/bin/lib/shell-command-projection.cjs +12 -8
  65. package/gsd-core/bin/lib/smart-entry.cjs +521 -0
  66. package/gsd-core/bin/lib/surface.cjs +63 -33
  67. package/gsd-core/bin/lib/update-context.cjs +0 -5
  68. package/gsd-core/bin/shared/config-schema.manifest.json +1 -0
  69. package/gsd-core/bin/shared/model-catalog.json +5 -5
  70. package/gsd-core/bin/shared/runtime-aliases.manifest.json +0 -5
  71. package/gsd-core/references/gates.md +1 -1
  72. package/gsd-core/references/gsd-run-resolver.md +8 -0
  73. package/gsd-core/references/planning-config.md +1 -1
  74. package/gsd-core/templates/project.md +1 -1
  75. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  76. package/gsd-core/workflows/add-backlog.md +1 -1
  77. package/gsd-core/workflows/add-phase.md +1 -1
  78. package/gsd-core/workflows/add-tests.md +1 -1
  79. package/gsd-core/workflows/add-todo.md +1 -1
  80. package/gsd-core/workflows/ai-integration-phase.md +1 -1
  81. package/gsd-core/workflows/audit-fix.md +1 -1
  82. package/gsd-core/workflows/audit-milestone.md +1 -1
  83. package/gsd-core/workflows/audit-uat.md +1 -1
  84. package/gsd-core/workflows/autonomous.md +1 -1
  85. package/gsd-core/workflows/check-todos.md +1 -1
  86. package/gsd-core/workflows/cleanup.md +1 -1
  87. package/gsd-core/workflows/code-review-fix.md +1 -1
  88. package/gsd-core/workflows/code-review.md +1 -1
  89. package/gsd-core/workflows/complete-milestone.md +1 -1
  90. package/gsd-core/workflows/debug.md +1 -1
  91. package/gsd-core/workflows/diagnose-issues.md +1 -1
  92. package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
  93. package/gsd-core/workflows/discuss-phase/modes/auto.md +1 -1
  94. package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
  95. package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
  96. package/gsd-core/workflows/discuss-phase.md +2 -2
  97. package/gsd-core/workflows/do.md +5 -4
  98. package/gsd-core/workflows/docs-update.md +1 -1
  99. package/gsd-core/workflows/edit-phase.md +1 -1
  100. package/gsd-core/workflows/eval-review.md +1 -1
  101. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
  102. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +1 -1
  103. package/gsd-core/workflows/execute-phase.md +4 -5
  104. package/gsd-core/workflows/execute-plan.md +1 -1
  105. package/gsd-core/workflows/explore.md +1 -1
  106. package/gsd-core/workflows/extract-learnings.md +1 -1
  107. package/gsd-core/workflows/forensics.md +1 -1
  108. package/gsd-core/workflows/graduation.md +1 -1
  109. package/gsd-core/workflows/health.md +1 -1
  110. package/gsd-core/workflows/help/modes/brief.md +2 -1
  111. package/gsd-core/workflows/help/modes/default.md +2 -1
  112. package/gsd-core/workflows/help/modes/full.md +25 -1
  113. package/gsd-core/workflows/help/modes/topic.md +2 -1
  114. package/gsd-core/workflows/import.md +1 -1
  115. package/gsd-core/workflows/ingest-docs.md +1 -1
  116. package/gsd-core/workflows/insert-phase.md +1 -1
  117. package/gsd-core/workflows/list-seeds.md +1 -1
  118. package/gsd-core/workflows/list-workspaces.md +1 -1
  119. package/gsd-core/workflows/manager.md +1 -1
  120. package/gsd-core/workflows/map-codebase.md +1 -1
  121. package/gsd-core/workflows/milestone-summary.md +1 -1
  122. package/gsd-core/workflows/mvp-phase.md +1 -1
  123. package/gsd-core/workflows/new-milestone.md +1 -1
  124. package/gsd-core/workflows/new-project.md +1 -1
  125. package/gsd-core/workflows/new-workspace.md +1 -1
  126. package/gsd-core/workflows/next.md +1 -1
  127. package/gsd-core/workflows/onboard.md +277 -0
  128. package/gsd-core/workflows/pause-work.md +1 -1
  129. package/gsd-core/workflows/plan-milestone-gaps.md +1 -1
  130. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +1 -1
  131. package/gsd-core/workflows/plan-phase.md +9 -2
  132. package/gsd-core/workflows/plan-review-convergence.md +1 -1
  133. package/gsd-core/workflows/plant-seed.md +1 -1
  134. package/gsd-core/workflows/pr-branch.md +1 -1
  135. package/gsd-core/workflows/profile-user.md +1 -1
  136. package/gsd-core/workflows/progress.md +1 -1
  137. package/gsd-core/workflows/quick.md +21 -1
  138. package/gsd-core/workflows/reapply-patches.md +1 -1
  139. package/gsd-core/workflows/remove-phase.md +1 -1
  140. package/gsd-core/workflows/remove-workspace.md +1 -1
  141. package/gsd-core/workflows/resume-project.md +1 -1
  142. package/gsd-core/workflows/review.md +43 -7
  143. package/gsd-core/workflows/scan.md +1 -1
  144. package/gsd-core/workflows/secure-phase.md +1 -1
  145. package/gsd-core/workflows/settings-advanced.md +3 -1
  146. package/gsd-core/workflows/settings-integrations.md +1 -1
  147. package/gsd-core/workflows/settings.md +1 -1
  148. package/gsd-core/workflows/ship.md +1 -1
  149. package/gsd-core/workflows/sketch-wrap-up.md +1 -1
  150. package/gsd-core/workflows/sketch.md +1 -1
  151. package/gsd-core/workflows/smart-entry.md +120 -0
  152. package/gsd-core/workflows/spec-phase.md +8 -6
  153. package/gsd-core/workflows/spike-wrap-up.md +1 -1
  154. package/gsd-core/workflows/spike.md +1 -1
  155. package/gsd-core/workflows/stats.md +1 -1
  156. package/gsd-core/workflows/thread.md +1 -1
  157. package/gsd-core/workflows/transition.md +1 -1
  158. package/gsd-core/workflows/ui-phase.md +1 -1
  159. package/gsd-core/workflows/ui-review.md +1 -1
  160. package/gsd-core/workflows/ultraplan-phase.md +1 -1
  161. package/gsd-core/workflows/update.md +3 -6
  162. package/gsd-core/workflows/validate-phase.md +1 -1
  163. package/gsd-core/workflows/verify-phase.md +1 -1
  164. package/gsd-core/workflows/verify-work.md +62 -4
  165. package/package.json +3 -5
  166. package/scripts/ci-test-scope.cjs +19 -1
  167. package/scripts/gen-golden-install-parity-zcode.cjs +77 -0
  168. package/scripts/generate-package-identity.cjs +27 -2
  169. package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -148
  170. package/scripts/lint-regression-test-names.allowlist.json +1 -273
  171. package/scripts/lint-test-file-count.allowlist.json +7 -91
  172. package/scripts/research-profiles.cjs +6 -6
  173. package/scripts/run-tests.cjs +18 -3
  174. package/scripts/slurm-adapter.cjs +269 -0
  175. package/scripts/sync-manifest-versions.cjs +3 -2
  176. package/scripts/sync-runtime-launcher.cjs +20 -2
  177. package/skills/gsd-map-codebase/SKILL.md +3 -3
  178. package/skills/gsd-mempalace-capture/SKILL.md +1 -1
  179. package/skills/gsd-mempalace-recall/SKILL.md +9 -5
  180. package/skills/gsd-next/SKILL.md +30 -0
  181. package/skills/gsd-ns-project/SKILL.md +1 -0
  182. package/skills/gsd-ns-workflow/SKILL.md +4 -1
  183. package/skills/gsd-onboard/SKILL.md +46 -0
  184. package/gemini-extension.json +0 -6
@@ -45,7 +45,7 @@ const configLoaderMod = require("./config-loader.cjs");
45
45
  const { loadConfig } = configLoaderMod;
46
46
  // eslint-disable-next-line @typescript-eslint/no-require-imports
47
47
  const installProfilesMod = require("./install-profiles.cjs");
48
- const { readActiveProfile, loadSkillsManifest, resolveProfile, parseRequires } = installProfilesMod;
48
+ const { readActiveProfile, loadSkillsManifest, resolveProfile, parseRequires, parseCallsAgents } = installProfilesMod;
49
49
  // eslint-disable-next-line @typescript-eslint/no-require-imports
50
50
  const surfaceMod = require("./surface.cjs");
51
51
  const { resolveSurface } = surfaceMod;
@@ -282,22 +282,90 @@ function _loadInstalledSkillsManifest(configDir) {
282
282
  }
283
283
  return manifest;
284
284
  }
285
+ /**
286
+ * #1858 — Build a skill dependency manifest from a FLAT commands/gsd-<stem>.md
287
+ * source layout (the Claude local project install shape, where the `gsd-`
288
+ * prefix is baked into each filename at the commands/ level and there is no
289
+ * commands/gsd/ subdir). Strips the `gsd-` prefix so stems match the nested
290
+ * loader's output (gsd-validate-phase.md → validate-phase, same as nested
291
+ * validate-phase.md).
292
+ *
293
+ * Map shape is identical to loadSkillsManifest: each stem maps to its
294
+ * `requires` deps (parsed via the same shared parseRequires) and carries a
295
+ * companion `_calls_agents_<stem>` key (parsed via parseCallsAgents) so the
296
+ * flat and nested paths cannot drift.
297
+ *
298
+ * Returns an empty Map when the parent directory does not exist or contains
299
+ * no gsd-*.md files (so _resolveManifest can use size>0 as the "flat layout
300
+ * present" signal and fall through to the installed-skills branch otherwise).
301
+ */
302
+ function _loadFlatCommandsGsdManifest(commandsParentDir) {
303
+ const manifest = new Map();
304
+ let entries;
305
+ try {
306
+ entries = node_fs_1.default.readdirSync(commandsParentDir, { withFileTypes: true });
307
+ }
308
+ catch {
309
+ return manifest;
310
+ }
311
+ for (const entry of entries) {
312
+ if (!entry.isFile())
313
+ continue;
314
+ if (!entry.name.startsWith('gsd-'))
315
+ continue;
316
+ if (!entry.name.endsWith('.md'))
317
+ continue;
318
+ // Strip 'gsd-' prefix (4 chars) and '.md' suffix (3 chars) → stem.
319
+ const stem = entry.name.slice(4, -3);
320
+ if (!stem)
321
+ continue;
322
+ // Mirror loadSkillsManifest's try/catch structure exactly: wrap read +
323
+ // parse + set together so an unreadable file OR a thrown parser degrades
324
+ // both keys to [] (parity; closes the latent catch-scope drift a reviewer
325
+ // flagged — both parsers are non-throwing today, but the structural
326
+ // match future-proofs the "identical Map shape" contract).
327
+ try {
328
+ const content = node_fs_1.default.readFileSync(node_path_1.default.join(commandsParentDir, entry.name), 'utf8');
329
+ manifest.set(stem, parseRequires(content));
330
+ manifest.set(`_calls_agents_${stem}`, parseCallsAgents(content));
331
+ }
332
+ catch {
333
+ manifest.set(stem, []);
334
+ manifest.set(`_calls_agents_${stem}`, []);
335
+ }
336
+ }
337
+ return manifest;
338
+ }
285
339
  /**
286
340
  * Resolve the skill dependency manifest for capability-state resolution.
287
341
  *
288
- * Resolution order (fixes #1160 — installed-runtime capability surface):
289
- * 1. If commandsGsdDir exists, load from source (repo-checkout behavior).
290
- * 2. Otherwise, fall back to installed skills at configDir/skills/gsd-[stem]/SKILL.md.
342
+ * Resolution order:
343
+ * 1. If commandsGsdDir exists, load from the nested source layout
344
+ * (repo-checkout behavior: <repo>/commands/gsd/*.md).
345
+ * 2. #1858 — otherwise, if the flat source layout is present (gsd-<stem>.md
346
+ * files in dirname(commandsGsdDir)), load from there. This is the Claude
347
+ * local project install shape where commands/gsd/ does not exist but
348
+ * commands/gsd-<stem>.md files do.
349
+ * 3. #1160 — otherwise, fall back to installed skills at
350
+ * configDir/skills/gsd-[stem]/SKILL.md.
291
351
  *
292
- * In an installed runtime the commands/gsd source tree is absent; only the
293
- * skills/ layout exists. Returning an empty manifest caused resolveSurface to
294
- * materialise the full-sentinel to an empty Set, making every capability appear
295
- * unsurfaced even when the skill was physically installed.
352
+ * In an installed runtime both source trees are absent; only the skills/
353
+ * layout exists. Returning an empty manifest caused resolveSurface to
354
+ * materialise the full-sentinel to an empty Set, making every skill-bearing
355
+ * capability appear unsurfaced even when the skill was physically installed
356
+ * (#1160) or authored as a flat command file (#1858).
296
357
  */
297
358
  function _resolveManifest(commandsGsdDir, configDir) {
298
359
  if (node_fs_1.default.existsSync(commandsGsdDir)) {
299
360
  return loadSkillsManifest(commandsGsdDir);
300
361
  }
362
+ // #1858: flat source layout — gsd-<stem>.md files at dirname(commandsGsdDir).
363
+ // Only claim the flat branch when it actually has gsd-*.md files; otherwise
364
+ // fall through to the installed-skills branch (a commands/ dir with no gsd
365
+ // files must not shadow an installed skills/ tree).
366
+ const flat = _loadFlatCommandsGsdManifest(node_path_1.default.dirname(commandsGsdDir));
367
+ if (flat.size > 0)
368
+ return flat;
301
369
  return _loadInstalledSkillsManifest(configDir);
302
370
  }
303
371
  /**
@@ -334,7 +402,7 @@ function _resolveManifest(commandsGsdDir, configDir) {
334
402
  * @param raw Whether to emit raw JSON (io.output raw mode)
335
403
  * @param _options Reserved for future use
336
404
  */
337
- function resolveCapabilityRuntimeState(cwd, runtimeConfigDir, configOverride) {
405
+ function resolveCapabilityRuntimeState(cwd, runtimeConfigDir, configOverride, runtimeOverride) {
338
406
  const warnings = [];
339
407
  // Resolve runtimeConfigDir using the canonical runtime-homes resolver.
340
408
  // When not provided, the active runtime is detected via the canonical
@@ -350,13 +418,40 @@ function resolveCapabilityRuntimeState(cwd, runtimeConfigDir, configOverride) {
350
418
  try {
351
419
  // eslint-disable-next-line @typescript-eslint/no-require-imports
352
420
  const runtimeHomes = require('./runtime-homes.cjs');
353
- // eslint-disable-next-line @typescript-eslint/no-require-imports
354
- const runtimeSlash = require('./runtime-slash.cjs');
355
- // Detect the active runtime via GSD_RUNTIME → config.runtime → 'claude'.
356
- // resolveRuntime reads config.json directly (no side effects) and returns
357
- // a lowercased canonical runtime name.
358
- const detectedRuntime = runtimeSlash.resolveRuntime(cwd);
359
- resolvedConfigDir = runtimeHomes.getGlobalConfigDir(detectedRuntime);
421
+ // #2003: an explicit --runtime override bypasses the persisted-runtime
422
+ // fallback (GSD_RUNTIME → config.runtime → 'claude') so, e.g., a repo with
423
+ // persisted runtime:"codex" resolves the Claude config dir when the operator
424
+ // is driving from Claude Code. Canonicalize via runtime-name-policy (handles
425
+ // aliases like codex-app → codex); if canonicalization yields nothing, fall
426
+ // through to the persisted-runtime resolution below. Mirrors the update-
427
+ // context / effort sync precedent (read/diagnostic paths accepting both
428
+ // --config-dir and --runtime).
429
+ if (typeof runtimeOverride === 'string' && runtimeOverride.trim() !== '') {
430
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
431
+ const runtimeNamePolicy = require('./runtime-name-policy.cjs');
432
+ const canonical = runtimeNamePolicy.canonicalizeRuntimeName(runtimeOverride);
433
+ if (canonical) {
434
+ resolvedConfigDir = runtimeHomes.getGlobalConfigDir(canonical);
435
+ }
436
+ else {
437
+ // #2003: unknown runtime override — warn (don't silently ignore the
438
+ // explicit input) and fall through to persisted-runtime resolution.
439
+ // Avoids a silent-wrong-result on this diagnostic command for typos
440
+ // (e.g. "cluade") or runtimes known to runtime-homes but not yet to
441
+ // the alias manifest (e.g. "grok"). The warning surfaces via the
442
+ // `warnings[]` channel consumed by cmdCapabilityState/cmdLoopRenderHooks.
443
+ warnings.push(`--runtime "${runtimeOverride}" is not a known runtime; falling back to auto-detected/persisted runtime resolution`);
444
+ }
445
+ }
446
+ if (!resolvedConfigDir) {
447
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
448
+ const runtimeSlash = require('./runtime-slash.cjs');
449
+ // Detect the active runtime via GSD_RUNTIME → config.runtime → 'claude'.
450
+ // resolveRuntime reads config.json directly (no side effects) and returns
451
+ // a lowercased canonical runtime name.
452
+ const detectedRuntime = runtimeSlash.resolveRuntime(cwd);
453
+ resolvedConfigDir = runtimeHomes.getGlobalConfigDir(detectedRuntime);
454
+ }
360
455
  }
361
456
  catch {
362
457
  // Defensive fallback: use ~/.claude if the canonical resolver throws.
@@ -450,8 +545,11 @@ function resolveCapabilityRuntimeState(cwd, runtimeConfigDir, configOverride) {
450
545
  capabilities: result.capabilities,
451
546
  };
452
547
  }
453
- function cmdCapabilityState(cwd, runtimeConfigDir, raw, _options = {}) {
454
- const result = resolveCapabilityRuntimeState(cwd, runtimeConfigDir);
548
+ function cmdCapabilityState(cwd, runtimeConfigDir, raw, options = {}) {
549
+ // #2003: thread an explicit --runtime override so the config-dir resolution
550
+ // bypasses the persisted-runtime fallback (GSD_RUNTIME → config.runtime).
551
+ const runtimeOverride = typeof options['runtime'] === 'string' ? options['runtime'] : undefined;
552
+ const result = resolveCapabilityRuntimeState(cwd, runtimeConfigDir, undefined, runtimeOverride);
455
553
  for (const warning of result.warnings) {
456
554
  coreError(`capability state: ${warning}`);
457
555
  }
@@ -491,6 +589,7 @@ module.exports = {
491
589
  // Exported for tests
492
590
  _resolveCommandsGsdDir,
493
591
  _loadInstalledSkillsManifest,
592
+ _loadFlatCommandsGsdManifest,
494
593
  _resolveManifest,
495
594
  _isSafePropKey,
496
595
  };
@@ -250,8 +250,18 @@ function setCapabilityState(cwd, runtimeConfigDir, desired, opts) {
250
250
  const layout = runtimeArtifactLayout.resolveRuntimeArtifactLayout(runtime, resolvedConfigDir, scope);
251
251
  const commandsGsdDir = _resolveCommandsGsdDir();
252
252
  const manifest = _resolveManifest(commandsGsdDir, resolvedConfigDir);
253
+ // #1575: applySurface now accepts opts.resolveAttribution so surface-path
254
+ // agents get the same Co-Authored-By trailer as the install path. The
255
+ // resolver is not threaded here yet — the CLI command handler does not have
256
+ // access to getCommitAttribution (which lives in bin/install.js). Until that
257
+ // is refactored into a shared module, surface-path agents for descriptor-
258
+ // driven runtimes will lack the Co-Authored-By trailer that install adds.
259
+ // Parity is proven when resolveAttribution IS provided (see
260
+ // tests/issue-1575-agent-descriptor-parity.test.cjs).
253
261
  // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
254
- applySurface(resolvedConfigDir, layout, manifest, undefined, registry);
262
+ applySurface(resolvedConfigDir, layout, manifest, undefined, registry, opts?.materialize?.resolveAttribution
263
+ ? { resolveAttribution: opts.materialize.resolveAttribution }
264
+ : undefined);
255
265
  }
256
266
  catch (err) {
257
267
  const msg = err instanceof Error ? err.message : String(err);
@@ -34,6 +34,10 @@ const { getRoadmapPhaseWithFallback } = roadmapModule;
34
34
  const gapCheckerModule = require("./gap-checker.cjs");
35
35
  const { runGapAnalysis } = gapCheckerModule;
36
36
  const prohibition_enforcement_cjs_1 = require("./prohibition-enforcement.cjs");
37
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
38
+ const gatePredicateEval = require("./gate-predicate-evaluator.cjs");
39
+ const { evaluatePredicate } = gatePredicateEval;
40
+ const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
37
41
  // ─── Helpers ──────────────────────────────────────────────────────────────────
38
42
  function normalizePhrase(text) {
39
43
  // eslint-disable-next-line @typescript-eslint/no-base-to-string
@@ -778,6 +782,100 @@ function cmdGapAnalysisPlanPost(projectDir, args, raw) {
778
782
  message: result.table || result.summary || '',
779
783
  }, raw, undefined);
780
784
  }
785
+ // ─── predicate (generic gate-predicate evaluator, #2008) ──────────────────────
786
+ /**
787
+ * Production subprocess binding for the gate-predicate evaluator. Wraps the
788
+ * bounded `execTool` seam (shell-command-projection) as a `runBoundedShell`
789
+ * the pure evaluator consumes. `sh -c` runs the interpolated command; the
790
+ * subprocess inherits the process env and is killed (SIGTERM) on timeout.
791
+ *
792
+ * `timedOut` is derived from the kill signal: spawnSync sets `signal: 'SIGTERM'`
793
+ * when the `timeout` fires, distinct from a normal non-zero exit code. A command
794
+ * that self-terminates with SIGTERM is indistinguishable at this seam and is
795
+ * reported as a timeout — either way the gate blocks (non-zero), so the outcome
796
+ * is fail-closed and correct. See ADR-2008.
797
+ */
798
+ function buildPredicateDeps() {
799
+ return {
800
+ runBoundedShell(opts) {
801
+ const r = (0, shell_command_projection_cjs_1.execTool)('sh', ['-c', opts.command], { cwd: opts.cwd, timeout: opts.timeoutMs });
802
+ return {
803
+ exitCode: r.exitCode,
804
+ stdout: r.stdout,
805
+ stderr: r.stderr,
806
+ signal: r.signal,
807
+ timedOut: r.signal === 'SIGTERM',
808
+ };
809
+ },
810
+ };
811
+ }
812
+ /** Parse `--flag value` pairs from an args array into a map (last write wins). */
813
+ function parsePredicateFlags(args) {
814
+ const out = {};
815
+ for (let i = 0; i < args.length; i++) {
816
+ const a = args[i];
817
+ if (typeof a !== 'string')
818
+ continue;
819
+ if (!a.startsWith('--'))
820
+ continue;
821
+ const key = a.slice(2);
822
+ const next = args[i + 1];
823
+ if (key.length > 0 && typeof next === 'string' && !next.startsWith('--')) {
824
+ out[key] = next;
825
+ i++;
826
+ }
827
+ }
828
+ return out;
829
+ }
830
+ /**
831
+ * `check predicate` — generic evaluator for capability gate `check.predicate`
832
+ * blocks (#2008). The workflow gate-dispatch invokes this for any gate whose
833
+ * `check` carries a `predicate` (instead of a `query`); the predicate object is
834
+ * passed as `--predicate '<json>'`. Emits the standard `{ block, message,
835
+ * details? }` gate contract on success. A malformed predicate / unknown kind
836
+ * THROWS inside the evaluator and is mapped here to `error()` (non-zero exit),
837
+ * which the workflow's two-step gate contract treats as a step-1 command failure
838
+ * routed per the gate's `onError`.
839
+ *
840
+ * Invocation:
841
+ * gsd_run check predicate --predicate '<json>' \
842
+ * [--phase-dir <dir>] [--phase-number <n>] [--phase-req-ids <ids>] --raw
843
+ *
844
+ * The subprocess runs at the runtime project root (the `cwd` passed to this
845
+ * router), inheriting the process env. Interpolation placeholders
846
+ * ${PHASE_NUMBER}/${PHASE_DIR}/${PHASE_REQ_IDS} are substituted from the flags.
847
+ */
848
+ function cmdCheckPredicate(projectDir, args, raw) {
849
+ const flags = parsePredicateFlags(args);
850
+ const predicateJson = flags['predicate'];
851
+ if (!predicateJson) {
852
+ error('predicate requires --predicate <json> (the gate hook check.predicate object)', ERROR_REASON.SDK_MISSING_ARG);
853
+ return;
854
+ }
855
+ let predicate;
856
+ try {
857
+ predicate = JSON.parse(predicateJson);
858
+ }
859
+ catch {
860
+ error('predicate --predicate value must be valid JSON', ERROR_REASON.USAGE);
861
+ return;
862
+ }
863
+ const ctx = {
864
+ cwd: projectDir,
865
+ phaseNumber: flags['phase-number'],
866
+ phaseDir: flags['phase-dir'],
867
+ phaseReqIds: flags['phase-req-ids'],
868
+ };
869
+ let result;
870
+ try {
871
+ result = evaluatePredicate(predicate, ctx, buildPredicateDeps());
872
+ }
873
+ catch (e) {
874
+ error(`gate predicate evaluation failed: ${e.message}`, ERROR_REASON.USAGE);
875
+ return;
876
+ }
877
+ output(result, raw, undefined);
878
+ }
781
879
  function routeCheckCommand({ args, cwd, raw }) {
782
880
  // Normalize dots to hyphens in the subcommand so both forms are accepted.
783
881
  // This makes `check.query = "ui.plan-gate"` (dotted form in capability.json gates)
@@ -830,6 +928,15 @@ function routeCheckCommand({ args, cwd, raw }) {
830
928
  cmdVerifyCodebaseDrift(cwd, raw);
831
929
  return;
832
930
  }
931
+ if (subcommand === 'predicate') {
932
+ // Generic gate-predicate evaluator (#2008). The workflow gate-dispatch calls
933
+ // this for any gate whose `check` carries a `predicate` (instead of a `query`),
934
+ // passing the predicate object as --predicate '<json>'. NOTE: unlike the
935
+ // `check.query` subcommands above (which take positional phase args), this
936
+ // subcommand parses --flag value pairs.
937
+ cmdCheckPredicate(cwd, args, raw);
938
+ return;
939
+ }
833
940
  if (subcommand === 'prohibition-enforcement') {
834
941
  // The deterministic test-tier prohibition PRODUCER/gate (#1259, ADR-550 D5d). Locates the
835
942
  // wired mechanical check (node-test or lint-rule), confirms fail-first, runs it, builds
@@ -838,7 +945,7 @@ function routeCheckCommand({ args, cwd, raw }) {
838
945
  (0, prohibition_enforcement_cjs_1.routeProhibitionEnforcement)(args, raw);
839
946
  return;
840
947
  }
841
- error('Unknown check subcommand. Available: auto-mode, decision-coverage-plan, decision-coverage-verify, gap-analysis-plan-post, prohibition-enforcement, tdd-review-checkpoint, ui-plan-gate, ui-safety-gate, verify-schema-drift, verify-codebase-drift', ERROR_REASON.SDK_UNKNOWN_COMMAND);
948
+ error('Unknown check subcommand. Available: auto-mode, decision-coverage-plan, decision-coverage-verify, gap-analysis-plan-post, predicate, prohibition-enforcement, tdd-review-checkpoint, ui-plan-gate, ui-safety-gate, verify-schema-drift, verify-codebase-drift', ERROR_REASON.SDK_UNKNOWN_COMMAND);
842
949
  }
843
950
  module.exports = {
844
951
  routeCheckCommand,
@@ -848,4 +955,7 @@ module.exports = {
848
955
  computeUiSafetyGate,
849
956
  cmdGapAnalysisPlanPost,
850
957
  cmdTddReviewCheckpoint,
958
+ cmdCheckPredicate,
959
+ buildPredicateDeps,
960
+ parsePredicateFlags,
851
961
  };
@@ -0,0 +1,136 @@
1
+ "use strict";
2
+ /**
3
+ * Claude orchestration command router — CLI dispatcher for
4
+ * `gsd-tools claude-orchestration <subcommand>`.
5
+ *
6
+ * #1143 — thin CLI adapter over the pure `claude-orchestration.cjs` module.
7
+ * Lets execute-phase (or any orchestrator) invoke the Workflow-backend
8
+ * detection and the Workflow-script emitter through the standard capability
9
+ * command surface (ADR-959) instead of a bare `require()`.
10
+ *
11
+ * Router signature: { args, cwd, raw, error } — identical to the other host
12
+ * routers; discovered by dispatchCapabilityCommand via the registry's
13
+ * commandFamilies index.
14
+ *
15
+ * Subcommands:
16
+ * detect-backend [--runtime <id>] [--agent-sdk-version <ver>] [--no-nested-dispatch]
17
+ * Resolves whether the Workflow backend should activate. `--runtime`
18
+ * defaults to the GSD_RUNTIME env var (or 'unknown'). Reads the
19
+ * `claude_orchestration.*` keys from .planning/config.json. Emits
20
+ * { available, backend, reason }.
21
+ *
22
+ * emit-workflow --waves <path> --run-id <id> [--phase-dir <dir>] [--budget <n>]
23
+ * Reads a wave/plan manifest JSON file and emits the generated Workflow
24
+ * script + summary. The manifest shape matches emitWorkflowScript's input:
25
+ * { waves: [{ id, plans: [{ id, brief, files_modified: string[] }] }] }.
26
+ */
27
+ var __importDefault = (this && this.__importDefault) || function (mod) {
28
+ return (mod && mod.__esModule) ? mod : { "default": mod };
29
+ };
30
+ const node_fs_1 = __importDefault(require("node:fs"));
31
+ const node_path_1 = __importDefault(require("node:path"));
32
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
33
+ const io = require("./io.cjs");
34
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
35
+ const core = require("./claude-orchestration.cjs");
36
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
37
+ const configLoader = require("./config-loader.cjs");
38
+ const { output } = io;
39
+ const { detectWorkflowBackend, emitWorkflowScript } = core;
40
+ const CAPABLE_HOST = { dispatch: { nested: true, background: true } };
41
+ function usage(error) {
42
+ error('Usage: gsd-tools claude-orchestration <detect-backend|emit-workflow> [...]\n' +
43
+ ' detect-backend [--runtime <id>] [--agent-sdk-version <ver>] [--no-nested-dispatch]\n' +
44
+ ' emit-workflow --waves <path> --run-id <id> [--phase-dir <dir>] [--budget <n>]');
45
+ }
46
+ function argValue(args, flag) {
47
+ const i = args.indexOf(flag);
48
+ return i !== -1 && i + 1 < args.length ? args[i + 1] : undefined;
49
+ }
50
+ /**
51
+ * Detect whether the Workflow backend should activate for the current/given
52
+ * runtime. Reads `claude_orchestration.*` from the project config; runtime and
53
+ * SDK version come from flags (the orchestrator already knows these) or env.
54
+ */
55
+ function cmdDetectBackend(args, cwd, raw) {
56
+ const runtimeId = argValue(args, '--runtime') || process.env['GSD_RUNTIME'] || 'unknown';
57
+ const agentSdkVersion = argValue(args, '--agent-sdk-version');
58
+ const noNested = args.includes('--no-nested-dispatch');
59
+ const hostIntegration = noNested ? { dispatch: { nested: false, background: true } } : CAPABLE_HOST;
60
+ // Resolve the claude_orchestration.* slice from the project config (federated
61
+ // keys are merged by loadConfig as a nested object). A config read failure
62
+ // degrades to inline — it must not break the core loop.
63
+ let claudeSlice = {};
64
+ try {
65
+ const loaded = configLoader.loadConfig(cwd);
66
+ const slice = loaded['claude_orchestration'];
67
+ if (slice && typeof slice === 'object' && !Array.isArray(slice)) {
68
+ claudeSlice = slice;
69
+ }
70
+ }
71
+ catch {
72
+ claudeSlice = {};
73
+ }
74
+ // Flatten the nested slice into the dotted-key shape detectWorkflowBackend expects.
75
+ const flatConfig = {};
76
+ for (const k of Object.keys(claudeSlice)) {
77
+ flatConfig['claude_orchestration.' + k] = claudeSlice[k];
78
+ }
79
+ const result = detectWorkflowBackend({ runtimeId, hostIntegration, config: flatConfig, agentSdkVersion });
80
+ output(result, raw);
81
+ }
82
+ /**
83
+ * Emit a Workflow script from a wave/plan manifest file.
84
+ */
85
+ function cmdEmitWorkflow(args, _cwd, raw, error) {
86
+ const wavesPath = argValue(args, '--waves');
87
+ const runId = argValue(args, '--run-id');
88
+ const phaseDir = argValue(args, '--phase-dir') || '.planning/phases/current';
89
+ const budgetRaw = argValue(args, '--budget');
90
+ if (!wavesPath) {
91
+ error('emit-workflow requires --waves <path>');
92
+ return;
93
+ }
94
+ if (!runId) {
95
+ error('emit-workflow requires --run-id <id>');
96
+ return;
97
+ }
98
+ let waves;
99
+ try {
100
+ const content = node_fs_1.default.readFileSync(node_path_1.default.resolve(wavesPath), 'utf8');
101
+ const parsed = JSON.parse(content);
102
+ waves = parsed['waves'];
103
+ }
104
+ catch (e) {
105
+ error('emit-workflow: could not read/parse --waves file "' + wavesPath + '": ' + (e instanceof Error ? e.message : String(e)));
106
+ return;
107
+ }
108
+ const budgetTokens = budgetRaw !== undefined ? parseInt(budgetRaw, 10) : undefined;
109
+ const budget = (typeof budgetTokens === 'number' && !Number.isNaN(budgetTokens)) ? budgetTokens : undefined;
110
+ const result = emitWorkflowScript({
111
+ phaseDir,
112
+ runId,
113
+ waves: waves,
114
+ budgetTokens: budget,
115
+ });
116
+ if (!result.ok) {
117
+ error('emit-workflow: ' + result.reason);
118
+ return;
119
+ }
120
+ output({ script: result.script, summary: result.summary }, raw);
121
+ }
122
+ function routeClaudeOrchestrationCommand(opts) {
123
+ const { args, cwd, raw, error } = opts;
124
+ // args[0] is the family ('claude-orchestration'); the subcommand is args[1].
125
+ const subcommand = args[1];
126
+ if (subcommand === 'detect-backend') {
127
+ cmdDetectBackend(args, cwd, raw);
128
+ }
129
+ else if (subcommand === 'emit-workflow') {
130
+ cmdEmitWorkflow(args, cwd, raw, error);
131
+ }
132
+ else {
133
+ usage(error);
134
+ }
135
+ }
136
+ module.exports = { routeClaudeOrchestrationCommand };