@opengsd/gsd-core 1.7.0 → 1.9.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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.opencode/plugins/gsd-core.js +45 -1
- package/README.md +2 -0
- package/agents/gsd-code-fixer.md +1 -1
- package/agents/gsd-codebase-mapper.md +1 -1
- package/agents/gsd-debug-session-manager.md +78 -4
- package/agents/gsd-debugger.md +87 -29
- package/agents/gsd-executor.md +49 -9
- package/agents/gsd-intel-updater.md +3 -3
- package/agents/gsd-phase-researcher.md +4 -2
- package/agents/gsd-plan-checker.md +20 -0
- package/agents/gsd-planner.md +44 -59
- package/agents/gsd-project-researcher.md +2 -2
- package/agents/gsd-ui-auditor.md +0 -40
- package/agents/gsd-verifier.md +2 -2
- package/bin/install.js +1338 -135
- package/commands/gsd/ai-integration-phase.md +1 -1
- package/commands/gsd/mempalace-capture.md +9 -5
- package/commands/gsd/new-milestone.md +1 -1
- package/commands/gsd/plan-phase.md +5 -3
- package/commands/gsd/plan-review-convergence.md +7 -2
- package/gsd-core/bin/gsd-tools.cjs +2690 -2472
- package/gsd-core/bin/lib/adapter-imperative.cjs +8 -1
- package/gsd-core/bin/lib/agent-command-router.cjs +20 -5
- package/gsd-core/bin/lib/api-coverage.cjs +360 -53
- package/gsd-core/bin/lib/audit.cjs +8 -8
- package/gsd-core/bin/lib/broken-windows.cjs +716 -0
- package/gsd-core/bin/lib/capability-command-router.cjs +733 -0
- package/gsd-core/bin/lib/capability-consent.cjs +40 -1
- package/gsd-core/bin/lib/capability-lifecycle.cjs +58 -0
- package/gsd-core/bin/lib/capability-loader.cjs +23 -1
- package/gsd-core/bin/lib/capability-registry.cjs +1450 -160
- package/gsd-core/bin/lib/capability-trust.cjs +468 -33
- package/gsd-core/bin/lib/capability-validator.cjs +882 -6
- package/gsd-core/bin/lib/capability-writer.cjs +6 -1
- package/gsd-core/bin/lib/check-command-router.cjs +140 -27
- package/gsd-core/bin/lib/cjs-command-router-adapter.cjs +15 -0
- package/gsd-core/bin/lib/claude-orchestration-command-router.cjs +209 -31
- package/gsd-core/bin/lib/claude-orchestration.cjs +203 -25
- package/gsd-core/bin/lib/command-aliases.cjs +14 -0
- package/gsd-core/bin/lib/commands.cjs +326 -21
- package/gsd-core/bin/lib/config-loader.cjs +214 -30
- package/gsd-core/bin/lib/config.cjs +158 -22
- package/gsd-core/bin/lib/core-utils.cjs +6 -1
- package/gsd-core/bin/lib/decisions.cjs +32 -8
- package/gsd-core/bin/lib/docs.cjs +6 -0
- package/gsd-core/bin/lib/estimate-cli.cjs +336 -0
- package/gsd-core/bin/lib/external-descriptor-trust.cjs +14 -2
- package/gsd-core/bin/lib/frontmatter.cjs +125 -15
- package/gsd-core/bin/lib/gap-checker.cjs +17 -2
- package/gsd-core/bin/lib/host-integration.cjs +215 -8
- package/gsd-core/bin/lib/init.cjs +155 -66
- package/gsd-core/bin/lib/install-engine.cjs +299 -23
- package/gsd-core/bin/lib/install-profiles.cjs +239 -1
- package/gsd-core/bin/lib/installer-migrations/005-opencode-baseline-commands-dir.cjs +146 -0
- package/gsd-core/bin/lib/installer-migrations/006-pi-extension-cjs-to-js.cjs +91 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +44 -5
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +107 -0
- package/gsd-core/bin/lib/milestone.cjs +248 -14
- package/gsd-core/bin/lib/model-catalog.cjs +69 -4
- package/gsd-core/bin/lib/model-resolver.cjs +189 -7
- package/gsd-core/bin/lib/observability/logger.cjs +7 -2
- package/gsd-core/bin/lib/onboard-projection.cjs +11 -8
- package/gsd-core/bin/lib/phase-command-router.cjs +10 -1
- package/gsd-core/bin/lib/phase-estimation.cjs +398 -0
- package/gsd-core/bin/lib/phase-id.cjs +304 -9
- package/gsd-core/bin/lib/phase.cjs +258 -17
- package/gsd-core/bin/lib/plan-drift-guard.cjs +1 -1
- package/gsd-core/bin/lib/plan-scan.cjs +70 -2
- package/gsd-core/bin/lib/planning-workspace.cjs +9 -2
- package/gsd-core/bin/lib/profile-output.cjs +34 -8
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +927 -0
- package/gsd-core/bin/lib/review-lane-invocation.cjs +348 -0
- package/gsd-core/bin/lib/review-lane-runner.cjs +594 -0
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +114 -32
- package/gsd-core/bin/lib/roadmap-parser.cjs +61 -10
- package/gsd-core/bin/lib/roadmap.cjs +23 -7
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +38 -5
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +23 -9
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +156 -0
- package/gsd-core/bin/lib/runtime-name-policy.cjs +15 -2
- package/gsd-core/bin/lib/smart-entry.cjs +70 -5
- package/gsd-core/bin/lib/state-document.cjs +171 -24
- package/gsd-core/bin/lib/state-transition.cjs +50 -11
- package/gsd-core/bin/lib/state.cjs +206 -32
- package/gsd-core/bin/lib/surface.cjs +51 -9
- package/gsd-core/bin/lib/uat-predicate.cjs +6 -4
- package/gsd-core/bin/lib/uat.cjs +428 -11
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +2 -2
- package/gsd-core/bin/lib/unusable-input.cjs +216 -0
- package/gsd-core/bin/lib/validate.cjs +44 -8
- package/gsd-core/bin/lib/verification.cjs +163 -31
- package/gsd-core/bin/lib/verify.cjs +348 -42
- package/gsd-core/bin/lib/worktree-safety.cjs +360 -15
- package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +4 -15
- package/gsd-core/bin/shared/model-catalog.json +5 -0
- package/gsd-core/bin/shared/runtime-aliases.manifest.json +5 -0
- package/gsd-core/references/api-coverage.md +37 -7
- package/gsd-core/references/checkpoints.md +1 -1
- package/gsd-core/references/common-bug-patterns.md +13 -0
- package/gsd-core/references/context-budget.md +40 -0
- package/gsd-core/references/debugger-bug-taxonomy.md +111 -0
- package/gsd-core/references/debugger-fix-acceptance.md +157 -0
- package/gsd-core/references/debugger-philosophy.md +1 -0
- package/gsd-core/references/debugger-prevention.md +98 -0
- package/gsd-core/references/debugger-rca-branching.md +98 -0
- package/gsd-core/references/debugger-repro-hardening.md +130 -0
- package/gsd-core/references/debugger-sbfl.md +110 -0
- package/gsd-core/references/debugger-semantic-recall.md +81 -0
- package/gsd-core/references/execute-phase-quota-recovery.md +55 -0
- package/gsd-core/references/execute-phase-requirement-revert.md +8 -0
- package/gsd-core/references/execute-phase-response-language.md +7 -0
- package/gsd-core/references/gate-prompts.md +6 -3
- package/gsd-core/references/model-profile-resolution.md +64 -13
- package/gsd-core/references/offer-next.md +88 -0
- package/gsd-core/references/planner-antipatterns.md +6 -0
- package/gsd-core/references/planner-mvp-mode.md +12 -13
- package/gsd-core/references/planner-preconditions.md +156 -0
- package/gsd-core/references/planner-reversibility.md +132 -0
- package/gsd-core/references/planning-config.md +2 -1
- package/gsd-core/references/reviewer-instances.md +28 -19
- package/gsd-core/references/runtime-aware-dispatch.md +42 -0
- package/gsd-core/references/skeleton-template.md +1 -1
- package/gsd-core/references/thinking-models-planning.md +3 -1
- package/gsd-core/references/ui-consideration-probe.md +2 -2
- package/gsd-core/references/worktree-branch-check.md +4 -4
- package/gsd-core/templates/DEBUG.md +5 -3
- package/gsd-core/templates/summary-minimal.md +4 -0
- package/gsd-core/templates/summary-standard.md +4 -0
- package/gsd-core/templates/summary.md +7 -0
- package/gsd-core/workflows/add-phase.md +2 -0
- package/gsd-core/workflows/add-tests.md +3 -1
- package/gsd-core/workflows/add-todo.md +32 -1
- package/gsd-core/workflows/ai-integration-phase.md +8 -6
- package/gsd-core/workflows/audit-fix.md +6 -2
- package/gsd-core/workflows/audit-milestone.md +8 -0
- package/gsd-core/workflows/autonomous.md +19 -15
- package/gsd-core/workflows/check-todos.md +5 -3
- package/gsd-core/workflows/cleanup.md +7 -1
- package/gsd-core/workflows/code-review-fix.md +14 -6
- package/gsd-core/workflows/code-review.md +93 -24
- package/gsd-core/workflows/complete-milestone.md +3 -0
- package/gsd-core/workflows/debug.md +35 -7
- package/gsd-core/workflows/diagnose-issues.md +5 -1
- package/gsd-core/workflows/discovery-phase.md +7 -0
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +2 -4
- package/gsd-core/workflows/discuss-phase/modes/auto.md +0 -6
- package/gsd-core/workflows/discuss-phase/templates/context.md +16 -2
- package/gsd-core/workflows/discuss-phase-assumptions.md +18 -9
- package/gsd-core/workflows/discuss-phase.md +2 -2
- package/gsd-core/workflows/do.md +7 -1
- package/gsd-core/workflows/docs-update.md +9 -0
- package/gsd-core/workflows/eval-review.md +4 -1
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +4 -0
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +160 -0
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +4 -4
- package/gsd-core/workflows/execute-phase/steps/regression-gate.md +2 -2
- package/gsd-core/workflows/execute-phase.md +110 -149
- package/gsd-core/workflows/execute-plan.md +20 -8
- package/gsd-core/workflows/explore.md +4 -0
- package/gsd-core/workflows/extract-learnings.md +21 -0
- package/gsd-core/workflows/graduation.md +3 -0
- package/gsd-core/workflows/health.md +7 -1
- package/gsd-core/workflows/help/modes/full.md +9 -5
- package/gsd-core/workflows/import.md +11 -2
- package/gsd-core/workflows/inbox.md +7 -0
- package/gsd-core/workflows/ingest-docs.md +19 -10
- package/gsd-core/workflows/manager.md +3 -1
- package/gsd-core/workflows/map-codebase.md +17 -10
- package/gsd-core/workflows/mvp-phase.md +3 -0
- package/gsd-core/workflows/new-milestone.md +79 -23
- package/gsd-core/workflows/new-project.md +28 -19
- package/gsd-core/workflows/new-workspace.md +3 -1
- package/gsd-core/workflows/next.md +5 -2
- package/gsd-core/workflows/onboard.md +3 -0
- package/gsd-core/workflows/plan-phase.md +56 -51
- package/gsd-core/workflows/plan-review-convergence.md +61 -12
- package/gsd-core/workflows/plant-seed.md +3 -0
- package/gsd-core/workflows/profile-user.md +7 -1
- package/gsd-core/workflows/progress.md +31 -3
- package/gsd-core/workflows/quick.md +33 -10
- package/gsd-core/workflows/remove-workspace.md +3 -0
- package/gsd-core/workflows/review.md +172 -585
- package/gsd-core/workflows/scan.md +10 -2
- package/gsd-core/workflows/secure-phase.md +13 -2
- package/gsd-core/workflows/settings-integrations.md +3 -0
- package/gsd-core/workflows/settings.md +3 -0
- package/gsd-core/workflows/ship.md +88 -11
- package/gsd-core/workflows/sketch.md +3 -0
- package/gsd-core/workflows/smart-entry.md +4 -1
- package/gsd-core/workflows/spike.md +7 -1
- package/gsd-core/workflows/ui-phase.md +11 -2
- package/gsd-core/workflows/ui-review.md +11 -1
- package/gsd-core/workflows/undo.md +7 -0
- package/gsd-core/workflows/update.md +106 -5
- package/gsd-core/workflows/validate-phase.md +13 -2
- package/gsd-core/workflows/verify-phase.md +2 -2
- package/gsd-core/workflows/verify-work.md +15 -4
- package/hooks/dist/gsd-context-monitor.js +27 -9
- package/hooks/dist/gsd-cursor-session-start.js +6 -2
- package/hooks/dist/gsd-cursor-stop.js +6 -2
- package/hooks/dist/gsd-cursor-subagent-start.js +6 -2
- package/hooks/dist/gsd-graphify-update.sh +9 -0
- package/hooks/dist/gsd-phase-boundary.sh +14 -2
- package/hooks/dist/gsd-prompt-guard.js +101 -2
- package/hooks/dist/gsd-read-guard.js +100 -2
- package/hooks/dist/gsd-read-injection-scanner.js +109 -2
- package/hooks/dist/gsd-statusline.js +97 -9
- package/hooks/dist/gsd-workflow-guard.js +110 -6
- package/hooks/dist/gsd-worktree-path-guard.js +132 -8
- package/hooks/dist/lib/cursor-workspace.js +74 -0
- package/hooks/gsd-context-monitor.js +27 -9
- package/hooks/gsd-cursor-session-start.js +6 -2
- package/hooks/gsd-cursor-stop.js +6 -2
- package/hooks/gsd-cursor-subagent-start.js +6 -2
- package/hooks/gsd-graphify-update.sh +9 -0
- package/hooks/gsd-phase-boundary.sh +14 -2
- package/hooks/gsd-prompt-guard.js +101 -2
- package/hooks/gsd-read-guard.js +100 -2
- package/hooks/gsd-read-injection-scanner.js +109 -2
- package/hooks/gsd-statusline.js +97 -9
- package/hooks/gsd-workflow-guard.js +110 -6
- package/hooks/gsd-worktree-path-guard.js +132 -8
- package/hooks/lib/cursor-workspace.js +74 -0
- package/package.json +10 -8
- package/pi/gsd.cjs +34 -3
- package/scripts/changeset/lint.cjs +1 -0
- package/scripts/changeset/parse.cjs +26 -0
- package/scripts/check-coverage-gate.cjs +51 -0
- package/scripts/check-glossary-refs.cjs +244 -0
- package/scripts/ci-rebase-check.cjs +48 -4
- package/scripts/ci-test-scope.cjs +67 -17
- package/scripts/gen-adr-index.cjs +528 -0
- package/scripts/gen-capability-matrix.cjs +26 -2
- package/scripts/gen-capability-registry.cjs +132 -34
- package/scripts/gen-emitted-baseline.cjs +145 -0
- package/scripts/gen-test-timings.cjs +201 -0
- package/scripts/lint-compiled-artifact-sync.cjs +146 -0
- package/scripts/lint-emitted-drift-ack.cjs +149 -0
- package/scripts/lint-fix-has-regression-test.cjs +131 -0
- package/scripts/lint-portable-timeout.cjs +140 -0
- package/scripts/lint-resolution-provenance.cjs +9 -0
- package/scripts/lint-test-file-count.allowlist.json +1 -0
- package/scripts/mutation-matrix.cjs +4 -0
- package/scripts/prompt-injection-scan.sh +6 -0
- package/scripts/registry-schema.cjs +57 -8
- package/scripts/release-notes/conventional-title.cjs +19 -1
- package/scripts/release-notes/format-github-release-notes.cjs +7 -3
- package/scripts/release-tarball-smoke.cjs +18 -11
- package/scripts/run-tests.cjs +420 -58
- package/scripts/workflow-size.cjs +16 -8
- package/skills/gsd-ai-integration-phase/SKILL.md +1 -1
- package/skills/gsd-mempalace-capture/SKILL.md +9 -5
- package/skills/gsd-new-milestone/SKILL.md +1 -1
- package/skills/gsd-plan-phase/SKILL.md +5 -3
- package/skills/gsd-plan-review-convergence/SKILL.md +7 -2
- package/vscode/package.json +1 -1
- package/scripts/gen-golden-install-parity-zcode.cjs +0 -77
- package/scripts/update-size-baseline.cjs +0 -68
|
@@ -16,41 +16,108 @@
|
|
|
16
16
|
* detected. The instance→cli mapping lives HERE (single source; see the parity
|
|
17
17
|
* test in tests/review-reviewer-instances.test.cjs — DEFECT.GENERATIVE-FIX).
|
|
18
18
|
*
|
|
19
|
-
* KNOWN_REVIEWER_SLUGS (
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
19
|
+
* KNOWN_REVIEWER_SLUGS (ADR-2782 D9, Phase 5a #2798): derived from declared
|
|
20
|
+
* `reviewer` bodies in the capability registry, not a hand-maintained tail.
|
|
21
|
+
* A capability of EITHER `role: "runtime"` (the six dual-purpose hosts —
|
|
22
|
+
* antigravity/claude/codex/cursor/opencode/qwen) or the lane-only
|
|
23
|
+
* `role: "reviewer"` (gemini/coderabbit/ollama/lm_studio/llama_cpp) may carry
|
|
24
|
+
* a `reviewer` body, and it is the body's `reviewer.slug` — NOT the capability
|
|
25
|
+
* id — that becomes the roster entry: the two differ for `lm-studio` (id) /
|
|
26
|
+
* `lm_studio` (slug) and `llama-cpp` (id) / `llama_cpp` (slug), ADR-2782's
|
|
27
|
+
* three-namespace trap (`id` must be kebab; `slug` keeps the shipped roster's
|
|
28
|
+
* snake form).
|
|
29
|
+
*
|
|
30
|
+
* `runtime.hostBehaviors.reviewerCli: true` survives as a DERIVED LEGACY ALIAS
|
|
31
|
+
* for one release (D9): a capability that only sets the flag (no `reviewer`
|
|
32
|
+
* body yet) still contributes its capability id as a slug, exactly as before
|
|
33
|
+
* this phase. Where a capability carries BOTH a declared body and the alias,
|
|
34
|
+
* the BODY WINS — that capability contributes only the body's slug, never
|
|
35
|
+
* both. Alias removal is a named later phase (#2801), not done here.
|
|
36
|
+
*
|
|
37
|
+
* Before this phase the five non-runtime reviewers (`gemini`, `coderabbit`,
|
|
38
|
+
* `ollama`, `lm_studio`, `llama_cpp`) had no `capabilities/<id>/` descriptor at
|
|
39
|
+
* all and were a hardcoded `NON_RUNTIME_REVIEWER_SLUGS` tail. Phase 5a gives
|
|
40
|
+
* each one a lane-only `role: "reviewer"` capability (ADR-2782 D3), so the
|
|
41
|
+
* tail is deleted outright. See
|
|
42
|
+
* `docs/adr/2782-reviewer-lane-capability-surface.md`.
|
|
31
43
|
*/
|
|
32
44
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
33
45
|
exports.INSTANCE_NAME_PATTERN = exports.KNOWN_REVIEWER_SLUGS = void 0;
|
|
46
|
+
exports.deriveReviewerSlugs = deriveReviewerSlugs;
|
|
34
47
|
exports.normalizeConfiguredDefaultReviewers = normalizeConfiguredDefaultReviewers;
|
|
35
48
|
exports.normalizeReviewerInstances = normalizeReviewerInstances;
|
|
36
49
|
exports.resolveReviewerSelection = resolveReviewerSelection;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Derive the reviewer-slug roster from a capability registry.
|
|
52
|
+
*
|
|
53
|
+
* Exported (rather than a require()-time-only side effect) so tests can
|
|
54
|
+
* exercise the derivation directly against a synthetic registry — the real
|
|
55
|
+
* roster below is a single call against the generated
|
|
56
|
+
* `capability-registry.cjs`, and `tests/review-lane-descriptor.test.cjs`'s
|
|
57
|
+
* `checkReviewerLaneParity` separately guards that the real roster still
|
|
58
|
+
* agrees with `src/review-lane-descriptor.cts` and the workflow.
|
|
59
|
+
*
|
|
60
|
+
* Reads `registry.capabilities` — every declared capability regardless of
|
|
61
|
+
* role — because a `role: "reviewer"` lane-only capability is never stored in
|
|
62
|
+
* `registry.runtimes` (that map is role:"runtime" only). Per capability: a
|
|
63
|
+
* declared `reviewer.slug` wins outright (D9) and the capability contributes
|
|
64
|
+
* ONLY that slug; only when there is no body does the legacy
|
|
65
|
+
* `hostBehaviors.reviewerCli` alias contribute the capability id instead. The
|
|
66
|
+
* result is collected into a Set (so a slug can never appear twice, even if
|
|
67
|
+
* two distinct capabilities somehow named the same one) and returned as a
|
|
68
|
+
* SORTED array, so the roster's order never depends on `Object.entries()`
|
|
69
|
+
* iteration / registry build order.
|
|
70
|
+
*/
|
|
71
|
+
function deriveReviewerSlugs(registry) {
|
|
72
|
+
const capabilities = registry.capabilities || {};
|
|
73
|
+
const slugs = new Set();
|
|
74
|
+
for (const [capId, cap] of Object.entries(capabilities)) {
|
|
75
|
+
// Trim before the emptiness test: `length > 0` alone admits a whitespace-only
|
|
76
|
+
// slug (" ") verbatim into the roster, where it can never match a real lane
|
|
77
|
+
// but still occupies a roster entry. Unreachable through the checked-in
|
|
78
|
+
// registry today, but this function is EXPORTED for reuse and carries no
|
|
79
|
+
// other validation, so it should not depend on its caller's hygiene.
|
|
80
|
+
const rawSlug = cap?.reviewer?.slug;
|
|
81
|
+
const declaredSlug = typeof rawSlug === 'string' ? rawSlug.trim() : '';
|
|
82
|
+
if (declaredSlug.length > 0) {
|
|
83
|
+
slugs.add(declaredSlug);
|
|
84
|
+
continue; // the body wins — never ALSO add this capability's alias below.
|
|
85
|
+
}
|
|
86
|
+
if (cap?.runtime?.hostBehaviors?.reviewerCli === true) {
|
|
87
|
+
slugs.add(capId);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return [...slugs].sort();
|
|
49
91
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
92
|
+
/**
|
|
93
|
+
* The roster, derived once at module load.
|
|
94
|
+
*
|
|
95
|
+
* GUARDED, because this runs at `require()` time: an uncaught throw here does not
|
|
96
|
+
* degrade reviewer selection, it breaks `require()` of this module for EVERY
|
|
97
|
+
* consumer. The sibling `capability-trust.cjs` already holds this line — its
|
|
98
|
+
* `discloseExecutableSurfaces` is documented "TOTAL: never throws, for any
|
|
99
|
+
* manifest shape" and wrapped accordingly — and this module handles the same
|
|
100
|
+
* class of registry-derived input, so it should not be the asymmetric one.
|
|
101
|
+
*
|
|
102
|
+
* A malformed registry yields an EMPTY roster rather than a crash. That is a
|
|
103
|
+
* visible degradation, not a silent one: under ADR-2782 D4 an explicitly
|
|
104
|
+
* requested reviewer that is unavailable is an ERROR, so `/gsd:review --claude`
|
|
105
|
+
* against an empty roster fails loudly instead of quietly reviewing with nobody.
|
|
106
|
+
*
|
|
107
|
+
* Unreachable through the checked-in registry today (it is generated, JSON-sourced
|
|
108
|
+
* and code-reviewed, so it cannot carry a getter or a Proxy). Guarded anyway —
|
|
109
|
+
* reachability analysis is not a contract, and the next caller should not have to
|
|
110
|
+
* redo it.
|
|
111
|
+
*/
|
|
112
|
+
exports.KNOWN_REVIEWER_SLUGS = (() => {
|
|
113
|
+
try {
|
|
114
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
115
|
+
return deriveReviewerSlugs(require('./capability-registry.cjs'));
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
})();
|
|
54
121
|
/** Instance names are lowercase slugs that must not shadow a built-in slug. */
|
|
55
122
|
exports.INSTANCE_NAME_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
|
|
56
123
|
function normalizeConfiguredDefaultReviewers(rawValue) {
|
|
@@ -164,12 +231,27 @@ function resolveReviewerSelection(input) {
|
|
|
164
231
|
let selected = [];
|
|
165
232
|
if (explicitFlags.size > 0) {
|
|
166
233
|
source = 'explicit_flags';
|
|
234
|
+
// ADR-2782 D4: absent-safe governs DISCOVERY, never explicit selection.
|
|
235
|
+
// Not finding a lane nobody asked for is normal; failing to run a lane
|
|
236
|
+
// somebody asked for is an error. Every miss used to be an `info`, so a
|
|
237
|
+
// PARTIAL miss (`--gemini --qwen` with qwen absent) ran the review with a
|
|
238
|
+
// thinner reviewer set while present_results reported success — "a cross-AI
|
|
239
|
+
// review that silently drops a lane is blind in one eye" (review.md).
|
|
240
|
+
// A total miss already errored, but only as a side effect of the selected
|
|
241
|
+
// set being empty; the partial case had no signal at all.
|
|
242
|
+
const priorErrorCount = errors.length;
|
|
167
243
|
selected = [...explicitFlags].filter((slug) => detected.has(slug));
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
244
|
+
// Sorted so the error order does not depend on flag order on the command line.
|
|
245
|
+
const missing = [...explicitFlags].filter((slug) => !detected.has(slug)).sort();
|
|
246
|
+
for (const slug of missing) {
|
|
247
|
+
errors.push(`explicit reviewer '${slug}' is not available on this host`);
|
|
171
248
|
}
|
|
172
|
-
|
|
249
|
+
// Guarded on the error count as it stood BEFORE this branch, so the
|
|
250
|
+
// aggregate message fires under exactly the conditions it did previously.
|
|
251
|
+
// Guarding on `errors.length` would let the per-slug errors just pushed
|
|
252
|
+
// suppress it, silently dropping a message this change did not intend to
|
|
253
|
+
// remove.
|
|
254
|
+
if (selected.length === 0 && priorErrorCount === 0) {
|
|
173
255
|
errors.push('no selected reviewers are available for explicit flags');
|
|
174
256
|
}
|
|
175
257
|
}
|
|
@@ -31,6 +31,9 @@ roadmapPhaseLookupSources, } = phaseIdModule;
|
|
|
31
31
|
const planningWorkspace = require("./planning-workspace.cjs");
|
|
32
32
|
const { planningDir } = planningWorkspace;
|
|
33
33
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
34
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
35
|
+
const unusableInputMod = require("./unusable-input.cjs");
|
|
36
|
+
const { UNUSABLE_REASON, warnUnusableInput } = unusableInputMod;
|
|
34
37
|
const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
|
|
35
38
|
// ─── Roadmap milestone scoping ───────────────────────────────────────────────
|
|
36
39
|
/**
|
|
@@ -271,10 +274,18 @@ function getRoadmapPhaseInternal(cwd, phaseNum) {
|
|
|
271
274
|
const normalizedPhase = stripProjectCodePrefix(phaseNum);
|
|
272
275
|
if (/^999(?:\.|$)/.test(normalizedPhase))
|
|
273
276
|
return null;
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
+
// Resolved INSIDE the try for the same reason as getMilestoneInfo below: planningDir
|
|
278
|
+
// throws a plain Error for an invalid GSD_WORKSTREAM/GSD_PROJECT segment, and resolving
|
|
279
|
+
// it outside let that escape uncaught, crashing every caller for a malformed workstream
|
|
280
|
+
// name. ADR-227 is explicit that throwing breaks pipeline continuity, and this read path
|
|
281
|
+
// has no reason to be the exception -- it already degrades to null for every other
|
|
282
|
+
// failure. Absence still returns null before any diagnostic, and when the path never
|
|
283
|
+
// resolved there is nothing to name.
|
|
284
|
+
let roadmapPath;
|
|
277
285
|
try {
|
|
286
|
+
roadmapPath = node_path_1.default.join(planningDir(cwd), 'ROADMAP.md');
|
|
287
|
+
if (!node_fs_1.default.existsSync(roadmapPath))
|
|
288
|
+
return null;
|
|
278
289
|
const roadmapRaw = (0, shell_command_projection_cjs_1.platformReadSync)(roadmapPath);
|
|
279
290
|
if (roadmapRaw === null)
|
|
280
291
|
throw new Error('missing');
|
|
@@ -301,10 +312,33 @@ function getRoadmapPhaseInternal(cwd, phaseNum) {
|
|
|
301
312
|
}
|
|
302
313
|
return null;
|
|
303
314
|
}
|
|
304
|
-
catch {
|
|
315
|
+
catch (err) {
|
|
316
|
+
// Absence already returned above via existsSync; anything caught here is a read fault
|
|
317
|
+
// or the synthetic missing-marker. The null is preserved exactly either way.
|
|
318
|
+
if (roadmapPath !== undefined)
|
|
319
|
+
reportUnreadableRoadmap(err, roadmapPath);
|
|
305
320
|
return null;
|
|
306
321
|
}
|
|
307
322
|
}
|
|
323
|
+
/**
|
|
324
|
+
* Report a ROADMAP.md that exists but could not be read (#1881, ADR-1411).
|
|
325
|
+
*
|
|
326
|
+
* The discriminator is the errno, and it matters in the SILENT direction.
|
|
327
|
+
* platformReadSync returns null for ENOENT and both callers convert that null into a
|
|
328
|
+
* synthetic Error carrying no code, which lands in the same catch as a real EACCES.
|
|
329
|
+
* Reporting unconditionally here would flag every project that has no ROADMAP.md yet --
|
|
330
|
+
* every brand-new project -- as corrupt. A genuine read fault always carries an errno;
|
|
331
|
+
* absence never does.
|
|
332
|
+
*
|
|
333
|
+
* The parse itself is regex over text and cannot throw, so anything reaching a catch is
|
|
334
|
+
* either a read fault or that synthetic absence marker. Nothing else gets here.
|
|
335
|
+
*/
|
|
336
|
+
function reportUnreadableRoadmap(err, roadmapPath) {
|
|
337
|
+
const code = err?.code;
|
|
338
|
+
if (typeof code !== 'string')
|
|
339
|
+
return;
|
|
340
|
+
warnUnusableInput({ reason: UNUSABLE_REASON.ROADMAP_UNREADABLE, source: roadmapPath });
|
|
341
|
+
}
|
|
308
342
|
/**
|
|
309
343
|
* Strip a leading delimiter run (whitespace, em/en-dash, colon, hyphen) from a
|
|
310
344
|
* milestone-name capture. Markdown headings commonly take the shape
|
|
@@ -318,8 +352,16 @@ function stripLeadingDelimiter(s) {
|
|
|
318
352
|
return s.replace(/^[\s—–:-]+/, '').trim();
|
|
319
353
|
}
|
|
320
354
|
function getMilestoneInfo(cwd) {
|
|
355
|
+
// Declared here but RESOLVED INSIDE the try, so the catch can name the file without
|
|
356
|
+
// moving planningDir() out of the protected region. planningDir throws a plain Error
|
|
357
|
+
// for an invalid GSD_WORKSTREAM/GSD_PROJECT segment, and hoisting the call let that
|
|
358
|
+
// escape uncaught — breaking the invariant #2245 relies on, that this function never
|
|
359
|
+
// throws. When the path never resolved there is nothing to name, so the diagnostic is
|
|
360
|
+
// skipped and the default is returned exactly as before.
|
|
361
|
+
let roadmapPath;
|
|
321
362
|
try {
|
|
322
|
-
|
|
363
|
+
roadmapPath = node_path_1.default.join(planningDir(cwd), 'ROADMAP.md');
|
|
364
|
+
const roadmap = (0, shell_command_projection_cjs_1.platformReadSync)(roadmapPath);
|
|
323
365
|
if (roadmap === null)
|
|
324
366
|
throw new Error('missing');
|
|
325
367
|
let stateVersion = null;
|
|
@@ -388,7 +430,13 @@ function getMilestoneInfo(cwd) {
|
|
|
388
430
|
name: 'milestone',
|
|
389
431
|
};
|
|
390
432
|
}
|
|
391
|
-
catch {
|
|
433
|
+
catch (err) {
|
|
434
|
+
// This function has no existsSync guard, so an absent ROADMAP arrives here too, as a
|
|
435
|
+
// synthetic Error with no errno. Only a real read fault is reported; the populated
|
|
436
|
+
// default is returned unchanged either way, and a plausible-looking default needs the
|
|
437
|
+
// diagnostic more than an empty sentinel does, not less (ADR-1411).
|
|
438
|
+
if (roadmapPath !== undefined)
|
|
439
|
+
reportUnreadableRoadmap(err, roadmapPath);
|
|
392
440
|
return { version: 'v1.0', name: 'milestone' };
|
|
393
441
|
}
|
|
394
442
|
}
|
|
@@ -511,12 +559,15 @@ function getMilestonePhaseFilter(cwd, versionOverride, phaseIdConvention) {
|
|
|
511
559
|
return id.split('-').map(seg => seg.replace(/^0+(?=\d)/, '') || '0').join('-');
|
|
512
560
|
}
|
|
513
561
|
const roadmapUsesHyphenedIds = [...normalized].some(n => n.includes('-'));
|
|
514
|
-
// #2043: milestone-prefixed sub-phase components must be zero-padded
|
|
515
|
-
//
|
|
562
|
+
// #2043: milestone-prefixed sub-phase components must be zero-padded — so a
|
|
563
|
+
// single-digit slug word after the phase
|
|
516
564
|
// number (e.g. dir "46-6-rs-…") captures "46" and is not silently excluded from
|
|
517
|
-
// the milestone as a bogus "46-6" id.
|
|
565
|
+
// the milestone as a bogus "46-6" id. #2232: the continuation width is exactly 2
|
|
566
|
+
// (PHASE_CONTINUATION_SEGMENT_SOURCE), so a year-leading slug word (dir
|
|
567
|
+
// "14-2026-photos-…") captures "14" and is not excluded as a bogus "14-2026" id.
|
|
568
|
+
// Built via new RegExp (no /i — the [A-Za-z] letter class does real case handling).
|
|
518
569
|
const numericRe = roadmapUsesHyphenedIds
|
|
519
|
-
?
|
|
570
|
+
? new RegExp(`^0*(\\d+(?:-${phaseIdModule.PHASE_CONTINUATION_SEGMENT_SOURCE})*[A-Za-z]?(?:\\.\\d+)*)`)
|
|
520
571
|
// phase-id-owner: the [A-Za-z] letter class does real case handling here — this regex carries NO /i flag; kept literal, not source-byte-equal to the canonical PHASE_NUMBER_TOKEN_SOURCE.
|
|
521
572
|
: /^0*(\d+[A-Za-z]?(?:\.\d+)*)/;
|
|
522
573
|
function isDirInMilestone(dirName) {
|
|
@@ -137,10 +137,16 @@ function searchPhaseInContent(content, escapedPhase, phaseNum) {
|
|
|
137
137
|
// comparison; unrecognized values are preserved verbatim for forward-compat.
|
|
138
138
|
const modeMatch = section.match(/\*\*Mode(?::\*\*|\*\*:)\s*([^\n]+)/i);
|
|
139
139
|
const mode = modeMatch ? modeMatch[1].trim().toLowerCase() : null;
|
|
140
|
-
// Extract success criteria as structured array
|
|
141
|
-
|
|
140
|
+
// Extract success criteria as structured array. A criterion may wrap onto extra
|
|
141
|
+
// indented lines (no `N.` prefix); those continuations must fold INTO their
|
|
142
|
+
// criterion, not end the run (#2522 — the old `(?:\s*\d+\.\s*[^\n]+)+` broke on a
|
|
143
|
+
// wrapped line, truncating it and silently dropping every criterion below it).
|
|
144
|
+
// `\n*` before each numbered line keeps blank-line-separated criteria working.
|
|
145
|
+
const criteriaMatch = section.match(/\*\*Success Criteria\*\*[^\n]*:\s*\n((?:\n*[ \t]*\d+\.[^\n]*\n?(?:[ \t]+(?!\d+\.)[^\n]*\n?)*)+)/i);
|
|
142
146
|
const success_criteria = criteriaMatch
|
|
143
|
-
? criteriaMatch[1].trim().split(
|
|
147
|
+
? criteriaMatch[1].trim().split(/\n+(?=[ \t]*\d+\.)/)
|
|
148
|
+
.map(entry => entry.replace(/^\s*\d+\.\s*/, '').replace(/\s*\n\s*/g, ' ').trim())
|
|
149
|
+
.filter(Boolean)
|
|
144
150
|
: [];
|
|
145
151
|
return {
|
|
146
152
|
found: true,
|
|
@@ -170,9 +176,19 @@ function getRoadmapPhaseWithFallback(cwd, phaseNum) {
|
|
|
170
176
|
if (/^999(?:\.|$)/.test(stripProjectCodePrefix(phaseNum)))
|
|
171
177
|
return null;
|
|
172
178
|
const roadmapPath = planningPaths(cwd).roadmap;
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
179
|
+
// Read directly rather than gating on fs.existsSync: existsSync returns false
|
|
180
|
+
// on EACCES/EIO too, which would mask an UNREADABLE roadmap as "missing" and
|
|
181
|
+
// let a blocking gate certify empty scope (#2365 review). Honor the documented
|
|
182
|
+
// contract — null only when genuinely absent (ENOENT), otherwise throw.
|
|
183
|
+
let rawContent;
|
|
184
|
+
try {
|
|
185
|
+
rawContent = node_fs_1.default.readFileSync(roadmapPath, 'utf-8');
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
if (err?.code === 'ENOENT')
|
|
189
|
+
return null;
|
|
190
|
+
throw err;
|
|
191
|
+
}
|
|
176
192
|
const milestoneContent = extractCurrentMilestone(rawContent, cwd);
|
|
177
193
|
const fullContent = stripShippedMilestones(rawContent);
|
|
178
194
|
// #2121/#2114: iterate the shared lookup-source list (exact → numeric →
|
|
@@ -648,7 +664,7 @@ function cmdRoadmapAnnotateDependencies(cwd, phaseNum, raw) {
|
|
|
648
664
|
const planPath = node_path_1.default.join(node_path_1.default.resolve(cwd, phaseInfo.directory), planFile);
|
|
649
665
|
try {
|
|
650
666
|
const content = node_fs_1.default.readFileSync(planPath, 'utf-8');
|
|
651
|
-
const fm = extractFrontmatter(content);
|
|
667
|
+
const fm = extractFrontmatter(content, planPath);
|
|
652
668
|
const wave = parseInt(fm.wave, 10) || 1;
|
|
653
669
|
const planId = planFile.replace(/-PLAN\.md$/i, '').replace(/PLAN\.md$/i, '');
|
|
654
670
|
const truths = parseMustHavesBlock(content, 'truths') || [];
|
|
@@ -518,6 +518,28 @@ function convertClaudeCommandToKimiSkill(content, skillName, _runtime = null, cm
|
|
|
518
518
|
const normalizedBody = convertGsdCommandReferencesToKimiSkillInvocations(frontmatter ? body : content, names);
|
|
519
519
|
return `---\nname: ${kimiSkillName}\ndescription: ${yamlQuote(toSingleLine(description))}\n---\nInvoke this Kimi skill with \`/skill:${kimiSkillName}\`.\n\n${normalizedBody}`;
|
|
520
520
|
}
|
|
521
|
+
/**
|
|
522
|
+
* Convert a Claude command-markdown source into a Kimi Code Agent Skill.
|
|
523
|
+
*
|
|
524
|
+
* Kimi Code (Moonshot's Node CLI) uses the standard Agent Skills format —
|
|
525
|
+
* a directory containing SKILL.md with frontmatter (name/description) and
|
|
526
|
+
* body — auto-discovered from `~/.kimi-code/skills/` (per Kimi Code docs:
|
|
527
|
+
* "Agent Skills is an open format for adding specialized knowledge and
|
|
528
|
+
* workflows to AI agents"). The invocation prefix is `/skill:<name>`,
|
|
529
|
+
* identical to Python kimi-cli.
|
|
530
|
+
*
|
|
531
|
+
* Today the output is byte-identical to `convertClaudeCommandToKimiSkill`
|
|
532
|
+
* (the Python kimi-cli converter) because both products consume the same
|
|
533
|
+
* Agent Skills format + `/skill:` invocation. The distinct function name
|
|
534
|
+
* lets a future divergence land cleanly if Kimi Code's skill format evolves
|
|
535
|
+
* independently of Python kimi-cli.
|
|
536
|
+
*
|
|
537
|
+
* Registered as `convertClaudeCommandToKimiCodeSkill` in the capabilities/
|
|
538
|
+
* kimi-code/capability.json `artifactLayout` `converter` field (#2454 PR 2).
|
|
539
|
+
*/
|
|
540
|
+
function convertClaudeCommandToKimiCodeSkill(content, skillName, _runtime = null, cmdNames = null) {
|
|
541
|
+
return convertClaudeCommandToKimiSkill(content, skillName, _runtime, cmdNames);
|
|
542
|
+
}
|
|
521
543
|
const KIMI_CANONICAL_GSD_AGENT_RE = /^gsd-[a-z0-9-]+$/;
|
|
522
544
|
function parseKimiAgentSource(source) {
|
|
523
545
|
if (typeof source === 'string') {
|
|
@@ -868,7 +890,13 @@ function convertClaudeCommandToCursorSkill(content, skillName) {
|
|
|
868
890
|
description = toSingleLine(description);
|
|
869
891
|
const shortDescription = description.length > 180 ? `${description.slice(0, 177)}...` : description;
|
|
870
892
|
const adapter = getCursorSkillAdapterHeader(skillName);
|
|
871
|
-
|
|
893
|
+
// #2341: mark user-invocable:false so the skill is NOT shown in Cursor's '/'
|
|
894
|
+
// menu (it defaults to true). Cursor also writes a commands/ surface (#785),
|
|
895
|
+
// and surfacing both duplicated every /gsd-* entry. This mirrors the #789
|
|
896
|
+
// CodeBuddy de-dup: the commands/ surface is the sole '/' entry point; skills
|
|
897
|
+
// stay model-invocable background knowledge. (user-invocable:false hides from
|
|
898
|
+
// '/' while keeping model invocation — distinct from disable-model-invocation.)
|
|
899
|
+
return `---\nname: ${yamlIdentifier(skillName)}\ndescription: ${yamlQuote(shortDescription)}\nuser-invocable: false\n---\n\n${adapter}\n\n${body.trimStart()}`;
|
|
872
900
|
}
|
|
873
901
|
/**
|
|
874
902
|
* Convert a Claude Code command to a Cursor 1.6 slash command (#785).
|
|
@@ -1385,10 +1413,14 @@ Typed mapping (agent_type-capable schema only):
|
|
|
1385
1413
|
inherited, or unsupported values; do not invent one-off effort literals in
|
|
1386
1414
|
workflow prose.
|
|
1387
1415
|
- \`fork_context: false\` by default — GSD agents load their own context via \`<files_to_read>\` blocks
|
|
1388
|
-
- \`Task(isolation="worktree")\` / \`Agent(isolation="worktree")\` → no direct
|
|
1389
|
-
Codex \`
|
|
1390
|
-
|
|
1391
|
-
|
|
1416
|
+
- \`Task(isolation="worktree")\` / \`Agent(isolation="worktree")\` → no direct \`spawn_agent\` mapping,
|
|
1417
|
+
but Codex declares \`dispatch.isolation: orchestrator-worktree\` (#2584). Codex
|
|
1418
|
+
\`spawn_agent\` still does not create or bind a git worktree; instead GSD itself
|
|
1419
|
+
creates the worktree and process-spawns the executor into it with
|
|
1420
|
+
\`codex exec --cd <dir>\`, performing every git operation on the executor's behalf
|
|
1421
|
+
(its \`workspace-write\` sandbox makes \`.git\` read-only). Workflows must therefore
|
|
1422
|
+
never fabricate a manual worktree protocol — route through the negotiated
|
|
1423
|
+
isolation adapter, which still fails closed for hosts declaring \`none\` (#3360).
|
|
1392
1424
|
|
|
1393
1425
|
Generic-agent workaround (multi_agent_v1 schema — NO agent_type field):
|
|
1394
1426
|
When only the generic \`multi_agent_v1\` schema is available, typed GSD agent dispatch
|
|
@@ -2597,6 +2629,7 @@ module.exports = {
|
|
|
2597
2629
|
convertClaudeCommandToAntigravitySkill,
|
|
2598
2630
|
convertClaudeCommandToClaudeSkill,
|
|
2599
2631
|
convertClaudeCommandToKimiSkill,
|
|
2632
|
+
convertClaudeCommandToKimiCodeSkill,
|
|
2600
2633
|
buildKimiAgentArtifacts,
|
|
2601
2634
|
convertClaudeToCursorMarkdown,
|
|
2602
2635
|
convertClaudeCommandToCursorSkill,
|
|
@@ -227,8 +227,12 @@ function kimiAgentsKind(destSubpath, prefix, configDir) {
|
|
|
227
227
|
* arg so scope-aware converters (antigravity, copilot) can choose
|
|
228
228
|
* between global home paths and workspace-relative paths without
|
|
229
229
|
* colliding with the `runtime` string at position 3.
|
|
230
|
+
* @param capabilityRegistry #2322: optional capability registry — captured in the
|
|
231
|
+
* stage() closure so third-party capability skills are bound to
|
|
232
|
+
* their declaring capId at staging time. Absent -> stage() stages
|
|
233
|
+
* nothing third-party (fail closed).
|
|
230
234
|
*/
|
|
231
|
-
function skillsKind(destSubpath, prefix, converterName, runtime, configDir, nested = false, scope = 'global') {
|
|
235
|
+
function skillsKind(destSubpath, prefix, converterName, runtime, configDir, nested = false, scope = 'global', capabilityRegistry) {
|
|
232
236
|
return {
|
|
233
237
|
kind: 'skills',
|
|
234
238
|
destSubpath,
|
|
@@ -247,7 +251,7 @@ function skillsKind(destSubpath, prefix, converterName, runtime, configDir, nest
|
|
|
247
251
|
: [];
|
|
248
252
|
const isGlobal = scope === 'global';
|
|
249
253
|
const wrappedConverter = (content, skillName) => realConverter(content, skillName, runtime, cmdNames, isGlobal);
|
|
250
|
-
return stageSkillsForRuntimeAsSkills(findInstallSourceRoot(configDir), resolved, wrappedConverter, prefix, nested);
|
|
254
|
+
return stageSkillsForRuntimeAsSkills(findInstallSourceRoot(configDir), resolved, wrappedConverter, prefix, nested, capabilityRegistry);
|
|
251
255
|
},
|
|
252
256
|
};
|
|
253
257
|
}
|
|
@@ -285,7 +289,7 @@ function getRegistry() {
|
|
|
285
289
|
* Map a single ArtifactKindDescriptor entry to an ArtifactKind using the
|
|
286
290
|
* matching builder function. Mirrors the hand-built calls in the old switch.
|
|
287
291
|
*/
|
|
288
|
-
function dispatchKindEntry(entry, runtime, configDir, scope) {
|
|
292
|
+
function dispatchKindEntry(entry, runtime, configDir, scope, capabilityRegistry) {
|
|
289
293
|
const { kind, destSubpath, prefix, nesting, converter } = entry;
|
|
290
294
|
const nested = nesting === 'nested';
|
|
291
295
|
let result;
|
|
@@ -304,7 +308,7 @@ function dispatchKindEntry(entry, runtime, configDir, scope) {
|
|
|
304
308
|
if (converter == null) {
|
|
305
309
|
throw new TypeError(`resolveRuntimeArtifactLayout: skills entry for '${runtime}' has converter=null (converter is required for skills)`);
|
|
306
310
|
}
|
|
307
|
-
result = skillsKind(destSubpath, prefix, converter, runtime, configDir, nested, scope);
|
|
311
|
+
result = skillsKind(destSubpath, prefix, converter, runtime, configDir, nested, scope, capabilityRegistry);
|
|
308
312
|
break;
|
|
309
313
|
case 'kimi-agents':
|
|
310
314
|
result = kimiAgentsKind(destSubpath, prefix, configDir);
|
|
@@ -312,7 +316,7 @@ function dispatchKindEntry(entry, runtime, configDir, scope) {
|
|
|
312
316
|
default:
|
|
313
317
|
throw new TypeError(`resolveRuntimeArtifactLayout: unknown kind '${kind}' in descriptor for runtime '${runtime}'`);
|
|
314
318
|
}
|
|
315
|
-
if (typeof entry.home === 'string' && entry.home !== '') {
|
|
319
|
+
if (scope === 'global' && typeof entry.home === 'string' && entry.home !== '') {
|
|
316
320
|
result.home = node_path_1.default.join(node_os_1.default.homedir(), entry.home);
|
|
317
321
|
}
|
|
318
322
|
return result;
|
|
@@ -322,11 +326,21 @@ function dispatchKindEntry(entry, runtime, configDir, scope) {
|
|
|
322
326
|
*
|
|
323
327
|
* ADR-857 phase 5d: driven by the capability-registry artifactLayout descriptor
|
|
324
328
|
* instead of a hardcoded switch statement.
|
|
329
|
+
*
|
|
330
|
+
* @param capabilityRegistry #2322: optional — when the caller has a composed
|
|
331
|
+
* capability registry in scope (e.g. capability-writer.cts's `capability set`
|
|
332
|
+
* path, or a fresh install's registry-aware profile resolution), pass it here
|
|
333
|
+
* so the skills kind's stage() closure can materialize installed third-party
|
|
334
|
+
* capability skills bound to their declaring capId. Both call paths (surface
|
|
335
|
+
* apply AND the installer) must pass their registry here — resolveProfile's
|
|
336
|
+
* own `'*'` (full profile) short-circuit never carries a registry, so if it
|
|
337
|
+
* is not threaded in at layout-build time a `full`-profile install stages no
|
|
338
|
+
* third-party capability skills regardless of registration (#2322 blocker 2).
|
|
325
339
|
*/
|
|
326
|
-
function resolveRuntimeArtifactLayout(runtime, configDir, scope = 'global') {
|
|
327
|
-
return resolveRuntimeArtifactLayoutFromRegistry(getRegistry(), runtime, configDir, scope);
|
|
340
|
+
function resolveRuntimeArtifactLayout(runtime, configDir, scope = 'global', capabilityRegistry) {
|
|
341
|
+
return resolveRuntimeArtifactLayoutFromRegistry(getRegistry(), runtime, configDir, scope, capabilityRegistry);
|
|
328
342
|
}
|
|
329
|
-
function resolveRuntimeArtifactLayoutFromRegistry(registry, runtime, configDir, scope = 'global') {
|
|
343
|
+
function resolveRuntimeArtifactLayoutFromRegistry(registry, runtime, configDir, scope = 'global', capabilityRegistry) {
|
|
330
344
|
if (typeof configDir !== 'string' || configDir === '') {
|
|
331
345
|
throw new TypeError('configDir must be a non-empty string');
|
|
332
346
|
}
|
|
@@ -338,7 +352,7 @@ function resolveRuntimeArtifactLayoutFromRegistry(registry, runtime, configDir,
|
|
|
338
352
|
throw new TypeError(`Unknown runtime: '${runtime}' — add to runtime-artifact-layout.cjs table`);
|
|
339
353
|
}
|
|
340
354
|
const entries = desc[scope] ?? [];
|
|
341
|
-
const kinds = entries.map((entry) => dispatchKindEntry(entry, runtime, configDir, scope));
|
|
355
|
+
const kinds = entries.map((entry) => dispatchKindEntry(entry, runtime, configDir, scope, capabilityRegistry));
|
|
342
356
|
return { runtime, configDir, scope, kinds };
|
|
343
357
|
}
|
|
344
358
|
module.exports = { resolveRuntimeArtifactLayout, resolveRuntimeArtifactLayoutFromRegistry, findInstallSourceRoot };
|