@opengsd/gsd-core 1.7.0-rc.5 → 1.7.0-rc.6
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/agents/gsd-ui-checker.md +2 -0
- package/agents/gsd-ui-researcher.md +1 -0
- package/bin/install.js +970 -192
- package/gsd-core/bin/lib/capability-registry.cjs +501 -85
- package/gsd-core/bin/lib/capability-validator.cjs +56 -18
- package/gsd-core/bin/lib/host-integration.cjs +33 -8
- package/gsd-core/bin/lib/init.cjs +33 -40
- package/gsd-core/bin/lib/install-engine.cjs +90 -20
- package/gsd-core/bin/lib/installer-migration-report.cjs +3 -0
- package/gsd-core/bin/lib/mcp-server.cjs +18 -7
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +24 -7
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +235 -38
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +22 -13
- package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +19 -5
- package/gsd-core/bin/lib/runtime-homes.cjs +22 -0
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +501 -12
- package/gsd-core/bin/lib/runtime-name-policy.cjs +63 -5
- package/gsd-core/bin/lib/security.cjs +6 -36
- package/gsd-core/bin/lib/shell-command-projection.cjs +115 -2
- package/gsd-core/bin/lib/stale-bake-guard.cjs +30 -10
- package/gsd-core/bin/lib/surface.cjs +10 -6
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +249 -0
- package/gsd-core/bin/shared/model-catalog.json +8 -3
- package/gsd-core/references/ui-consideration-probe.md +73 -0
- package/gsd-core/templates/UI-SPEC.md +25 -0
- package/gsd-core/templates/VALIDATION.md +2 -0
- package/gsd-core/workflows/audit-milestone.md +7 -4
- package/gsd-core/workflows/plan-phase.md +6 -0
- package/gsd-core/workflows/settings-advanced.md +7 -4
- package/gsd-core/workflows/ui-phase.md +146 -1
- package/gsd-core/workflows/validate-phase.md +2 -2
- package/hooks/dist/gsd-windsurf-pre-command.js +275 -0
- package/hooks/dist/gsd-windsurf-pre-write.js +132 -0
- package/hooks/dist/managed-hooks-registry.cjs +2 -0
- package/hooks/gsd-windsurf-pre-command.js +275 -0
- package/hooks/gsd-windsurf-pre-write.js +132 -0
- package/hooks/managed-hooks-registry.cjs +2 -0
- package/package.json +8 -4
- package/pi/gsd.cjs +354 -0
- package/scripts/build-hooks.js +3 -0
- package/scripts/gen-golden-install-parity-zcode.cjs +11 -1
- package/scripts/gen-registry.cjs +128 -0
- package/scripts/lint-test-file-count.allowlist.json +2 -1
- package/scripts/registry-schema.cjs +565 -0
- package/scripts/validate-registry.cjs +117 -0
- package/vscode/browser.js +197 -0
- package/vscode/extension.js +383 -0
- package/vscode/host-binding.js +113 -0
- package/vscode/package.json +96 -0
|
@@ -697,26 +697,35 @@ const VALID_CONVERTER_NAMES = new Set([
|
|
|
697
697
|
'convertClaudeAgentToCodebuddyAgent',
|
|
698
698
|
'convertClaudeAgentToClineAgent',
|
|
699
699
|
'convertClaudeAgentToCodexAgent',
|
|
700
|
+
// ADR-1239 / #2092 Phase B Upgrade 1 — native .qwen/agents/*.md subagent projection.
|
|
701
|
+
'convertClaudeAgentToQwenAgent',
|
|
700
702
|
]);
|
|
701
703
|
|
|
702
704
|
// C3: Validate role:runtime body
|
|
703
705
|
const VALID_CONFIG_FORMATS = new Set(['settings-json', 'toml', 'markdown', 'markdown-dir', 'none']);
|
|
704
|
-
|
|
706
|
+
// 'none' added #2103 — Marketplace/VSIX-distributed hosts (e.g. VS Code) with
|
|
707
|
+
// no file-projected config directory at all.
|
|
708
|
+
const VALID_CONFIG_HOME_KINDS = new Set(['dot-home', 'dot-home-nested', 'xdg', 'generic-agents-root', 'none']);
|
|
705
709
|
const VALID_COMMAND_STYLES = new Set(['slash-hyphen', 'shell-var']);
|
|
706
|
-
const VALID_HOOKS_SURFACES = new Set(['settings-json', 'codex-hooks-json', 'cursor-hooks-json', 'copilot-inline', 'cline-rules', 'none']);
|
|
710
|
+
const VALID_HOOKS_SURFACES = new Set(['settings-json', 'codex-hooks-json', 'cursor-hooks-json', 'copilot-inline', 'cline-rules', 'kimi-hooks-toml', 'windsurf-hooks-json', 'none']);
|
|
707
711
|
const VALID_HOOK_EVENTS = new Set(['claude', 'gemini']);
|
|
708
712
|
// extensionEvents — the plugin/extension-system event dialect (ADR-1239 amendment / #1943).
|
|
709
713
|
// DISTINCT from hookEvents (managed-hook dialect): extensionEvents describes the
|
|
710
714
|
// plugin-owned event subset imperative hosts expose (opencode / pi); 'none' = the
|
|
711
715
|
// host exposes no extension surface (engine owns the bus, e.g. VS Code).
|
|
712
|
-
const VALID_EXTENSION_EVENTS = new Set(['opencode', 'pi', 'hermes', 'none']);
|
|
716
|
+
const VALID_EXTENSION_EVENTS = new Set(['opencode', 'pi', 'hermes', 'kilo', 'none']);
|
|
713
717
|
const VALID_SANDBOX_TIERS = new Set(['none', 'codex-agent-sandbox']);
|
|
714
718
|
const VALID_ARTIFACT_KIND_NAMES = new Set(['commands', 'agents', 'skills', 'kimi-agents']);
|
|
715
719
|
const VALID_ARTIFACT_NESTINGS = new Set(['flat', 'nested']);
|
|
716
720
|
const FEATURE_FIELDS_FORBIDDEN_ON_RUNTIME = ['skills', 'agents', 'steps', 'contributions', 'gates', 'hooks', 'activationKey'];
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
const
|
|
721
|
+
// 'none' added #2103 — Marketplace/VSIX-distributed hosts (e.g. VS Code) that
|
|
722
|
+
// are never CLI-installed (no allRuntimes membership, no install flag).
|
|
723
|
+
const VALID_INSTALL_SURFACES = new Set(['settings-json', 'codex-toml', 'copilot-instructions', 'cline-rules', 'cursor-hooks-json', 'profile-marker-only', 'none']);
|
|
724
|
+
// 'antigravity' added #2096 Phase B Upgrade 1 — settings.json permissions.allow writer.
|
|
725
|
+
const VALID_PERMISSION_WRITERS = new Set(['opencode', 'kilo', 'antigravity']);
|
|
726
|
+
// SubagentStart added #2092 Phase B Upgrade 2 (qwen-only today — see
|
|
727
|
+
// capabilities/qwen/capability.json's extendedHookEvents).
|
|
728
|
+
const VALID_EXTENDED_HOOK_EVENTS = new Set(['SubagentStop', 'Stop', 'PreCompact', 'FileChanged', 'BeforeAgent', 'AfterAgent', 'BeforeModel', 'SubagentStart']);
|
|
720
729
|
|
|
721
730
|
// ADR-1239 Phase A: hostIntegration axes (MUST stay parity-identical to HOST_INTEGRATION_AXES in src/host-integration.cts)
|
|
722
731
|
const VALID_EMBEDDING_MODES = new Set(['imperative', 'declarative']);
|
|
@@ -736,13 +745,18 @@ const INSTALL_SURFACE_TO_ALLOWED_HOOKS_SURFACES = new Map([
|
|
|
736
745
|
['copilot-instructions', new Set(['copilot-inline'])],
|
|
737
746
|
['cline-rules', new Set(['cline-rules'])],
|
|
738
747
|
['cursor-hooks-json', new Set(['cursor-hooks-json'])],
|
|
739
|
-
['profile-marker-only', new Set(['none'])],
|
|
748
|
+
['profile-marker-only', new Set(['none', 'kimi-hooks-toml', 'windsurf-hooks-json'])],
|
|
749
|
+
// 'none' added #2103 — VS Code has no CLI install surface at all; its only
|
|
750
|
+
// valid hooksSurface pairing is the other 'none' (engine owns the hook bus).
|
|
751
|
+
['none', new Set(['none'])],
|
|
740
752
|
]);
|
|
741
753
|
|
|
742
754
|
// GATE B: extended hook event families → required hookEvents value
|
|
743
755
|
// Gemini agent-events require hookEvents='gemini'; Claude-family events require hookEvents='claude'.
|
|
744
756
|
const GEMINI_AGENT_EVENTS = new Set(['BeforeAgent', 'AfterAgent', 'BeforeModel']);
|
|
745
|
-
|
|
757
|
+
// SubagentStart added #2092 Phase B Upgrade 2 — Claude hook-event dialect
|
|
758
|
+
// counterpart of SubagentStop (qwen-only today).
|
|
759
|
+
const CLAUDE_FAMILY_EVENTS = new Set(['SubagentStop', 'Stop', 'PreCompact', 'FileChanged', 'SubagentStart']);
|
|
746
760
|
|
|
747
761
|
/**
|
|
748
762
|
* Validate a runtime.configHome object per ADR-1016 Decision 1.
|
|
@@ -771,9 +785,18 @@ function validateConfigHome(capId, ch) {
|
|
|
771
785
|
);
|
|
772
786
|
}
|
|
773
787
|
|
|
774
|
-
// name — required string
|
|
775
|
-
|
|
776
|
-
|
|
788
|
+
// name — required string, except when kind === 'none': the runtime has no
|
|
789
|
+
// file-projected config directory at all, so a descriptive name is
|
|
790
|
+
// optional (a carve-out mirroring the dot-home-nested⇒parent conditional
|
|
791
|
+
// below, not a new validation mechanism). If present it must still be a
|
|
792
|
+
// non-empty string (e.g. vscode's configHome.name stays a descriptive
|
|
793
|
+
// "vscode" string even though it is never used to build a path).
|
|
794
|
+
if (ch.kind !== 'none') {
|
|
795
|
+
if (typeof ch.name !== 'string' || ch.name.length === 0) {
|
|
796
|
+
errors.push(ctx + '.name must be a non-empty string');
|
|
797
|
+
}
|
|
798
|
+
} else if (ch.name !== undefined && (typeof ch.name !== 'string' || ch.name.length === 0)) {
|
|
799
|
+
errors.push(ctx + '.name must be a non-empty string if present when kind is "none"');
|
|
777
800
|
}
|
|
778
801
|
|
|
779
802
|
// parent — required when kind == dot-home-nested
|
|
@@ -973,7 +996,7 @@ function validateRuntimeBody(cap) {
|
|
|
973
996
|
);
|
|
974
997
|
}
|
|
975
998
|
|
|
976
|
-
// hooksSurface — closed
|
|
999
|
+
// hooksSurface — closed 7-enum (ADR-1016 Decision 5); inline literal guard (CodeQL barrier)
|
|
977
1000
|
if (r.hooksSurface === '__proto__' || r.hooksSurface === 'constructor' || r.hooksSurface === 'prototype') {
|
|
978
1001
|
errors.push('runtime.hooksSurface "' + r.hooksSurface + '" is a reserved name');
|
|
979
1002
|
} else if (!VALID_HOOKS_SURFACES.has(r.hooksSurface)) {
|
|
@@ -1054,15 +1077,27 @@ function validateRuntimeBody(cap) {
|
|
|
1054
1077
|
// localConfigDir — REQUIRED non-empty dot-dir string (ADR-1239 Phase B #1679)
|
|
1055
1078
|
// Must start with '.' (e.g. ".claude", ".cursor"). Validated here so the registry
|
|
1056
1079
|
// generator catches any descriptor missing the field before regenerating.
|
|
1057
|
-
|
|
1080
|
+
//
|
|
1081
|
+
// #2103: conditional on configHome.kind !== 'none' — a Marketplace/VSIX
|
|
1082
|
+
// host with no file-projected config directory (e.g. VS Code) has no
|
|
1083
|
+
// local dir to name; localConfigDir may be null/absent for such runtimes.
|
|
1084
|
+
const configHomeKind = (r.configHome && typeof r.configHome === 'object') ? r.configHome.kind : undefined;
|
|
1085
|
+
if (configHomeKind !== 'none') {
|
|
1086
|
+
if (typeof r.localConfigDir !== 'string' || r.localConfigDir.length === 0) {
|
|
1087
|
+
errors.push(
|
|
1088
|
+
'runtime.localConfigDir is required and must be a non-empty string (e.g. ".claude"); ' +
|
|
1089
|
+
'got: ' + JSON.stringify(r.localConfigDir),
|
|
1090
|
+
);
|
|
1091
|
+
} else if (!r.localConfigDir.startsWith('.')) {
|
|
1092
|
+
errors.push(
|
|
1093
|
+
'runtime.localConfigDir must start with "." (a dot-dir); got: ' + JSON.stringify(r.localConfigDir),
|
|
1094
|
+
);
|
|
1095
|
+
}
|
|
1096
|
+
} else if (r.localConfigDir !== null && r.localConfigDir !== undefined) {
|
|
1058
1097
|
errors.push(
|
|
1059
|
-
'runtime.localConfigDir
|
|
1098
|
+
'runtime.localConfigDir must be null or absent when configHome.kind is "none"; ' +
|
|
1060
1099
|
'got: ' + JSON.stringify(r.localConfigDir),
|
|
1061
1100
|
);
|
|
1062
|
-
} else if (!r.localConfigDir.startsWith('.')) {
|
|
1063
|
-
errors.push(
|
|
1064
|
-
'runtime.localConfigDir must start with "." (a dot-dir); got: ' + JSON.stringify(r.localConfigDir),
|
|
1065
|
-
);
|
|
1066
1101
|
}
|
|
1067
1102
|
|
|
1068
1103
|
// extendedHookEvents — required array; every element must be in closed enum
|
|
@@ -2168,6 +2203,9 @@ const INSTALL_SURFACE_TO_CONFIG_FORMAT = new Map([
|
|
|
2168
2203
|
['cline-rules', 'markdown-dir'],
|
|
2169
2204
|
['cursor-hooks-json', 'none'],
|
|
2170
2205
|
['profile-marker-only', 'none'],
|
|
2206
|
+
// 'none' added #2103 — a runtime with NO CLI install surface at all (e.g.
|
|
2207
|
+
// VS Code) has no config-file format to write either.
|
|
2208
|
+
['none', 'none'],
|
|
2171
2209
|
]);
|
|
2172
2210
|
|
|
2173
2211
|
/**
|
|
@@ -430,14 +430,25 @@ function hookEventSurfaceFor(hookEvents) {
|
|
|
430
430
|
// events (session/tool/file/permission); pi ~30 fine-grained extension events;
|
|
431
431
|
// 'none' = the host exposes no extension surface and the engine owns the bus
|
|
432
432
|
// (VS Code). Declarative hosts (no plugin API) do not set `extensionEvents`.
|
|
433
|
+
// OpenCode's plugin event surface (ADR-1239 §research; ~25 documented events,
|
|
434
|
+
// GSD binds this subset). Hoisted to a named const — rather than duplicated
|
|
435
|
+
// object literals — so the `kilo` dialect below (#2093) can reuse the IDENTICAL
|
|
436
|
+
// array instead of a copy-pasted one that could silently drift out of sync.
|
|
437
|
+
const OPENCODE_EXTENSION_EVENTS = Object.freeze([
|
|
438
|
+
'session.created', 'session.idle', 'experimental.session.compacting',
|
|
439
|
+
'tool.execute.before', 'tool.execute.after', 'file.edited',
|
|
440
|
+
// #2087 — additional documented plugin events GSD binds (opencode.ai/docs/plugins):
|
|
441
|
+
// permission decisions + session error surface.
|
|
442
|
+
'permission.asked', 'permission.replied', 'session.error',
|
|
443
|
+
]);
|
|
433
444
|
const EXTENSION_EVENT_SURFACES = Object.freeze({
|
|
434
|
-
opencode:
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
445
|
+
opencode: OPENCODE_EXTENSION_EVENTS,
|
|
446
|
+
// #2093 — Kilo Code is an OpenCode fork sharing the same plugin/extension
|
|
447
|
+
// event bus (host hook bus, UPGRADE 1): reuses OPENCODE_EXTENSION_EVENTS
|
|
448
|
+
// verbatim (not a re-derivation), so the two dialects stay pinned together
|
|
449
|
+
// by construction. See .kilo/plugins/gsd-core.js (copied verbatim from
|
|
450
|
+
// .opencode/plugins/gsd-core.js).
|
|
451
|
+
kilo: OPENCODE_EXTENSION_EVENTS,
|
|
441
452
|
// #2091 — Hermes Agent real plugin hook vocabulary (13 events).
|
|
442
453
|
// Cite: https://github.com/nousresearch/hermes-agent/blob/main/website/docs/user-guide/features/hooks.md
|
|
443
454
|
// Replaces the borrowed `hookEvents: "claude"` 6-event surface that silently
|
|
@@ -451,7 +462,21 @@ const EXTENSION_EVENT_SURFACES = Object.freeze({
|
|
|
451
462
|
'pre_gateway_dispatch', 'pre_approval_request',
|
|
452
463
|
'transform_tool_result',
|
|
453
464
|
]),
|
|
454
|
-
pi
|
|
465
|
+
// #2102 Stage 2 — pi's real ExtensionAPI event vocabulary (~30 fine-grained
|
|
466
|
+
// extension events; documentation-sourced, ADR-1239 §research). Replaces the
|
|
467
|
+
// placeholder single-event ['tool_call'] surface — the Stage 1 value only
|
|
468
|
+
// covered the one event pi/gsd.cjs happened to bind at the time, not the
|
|
469
|
+
// full declared surface.
|
|
470
|
+
pi: Object.freeze([
|
|
471
|
+
'session_start', 'project_trust', 'resources_discover', 'input',
|
|
472
|
+
'before_agent_start', 'agent_start', 'message_start', 'message_update',
|
|
473
|
+
'message_end', 'turn_start', 'context', 'before_provider_request',
|
|
474
|
+
'after_provider_response', 'tool_execution_start', 'tool_execution_update',
|
|
475
|
+
'tool_execution_end', 'tool_call', 'tool_result', 'turn_end', 'agent_end',
|
|
476
|
+
'session_before_switch', 'session_shutdown', 'session_before_fork',
|
|
477
|
+
'session_info_changed', 'session_before_compact', 'session_compact',
|
|
478
|
+
'session_before_tree', 'session_tree', 'thinking_level_select', 'model_select',
|
|
479
|
+
]),
|
|
455
480
|
none: Object.freeze([]),
|
|
456
481
|
});
|
|
457
482
|
/**
|
|
@@ -63,7 +63,7 @@ const { resolveModelInternal, resolveGranularityInternal, assertValidGranularity
|
|
|
63
63
|
const { findPhaseInternal } = phaseLocator;
|
|
64
64
|
const { getRoadmapPhaseInternal, getMilestoneInfo, getMilestonePhaseFilter, stripShippedMilestones, extractCurrentMilestone, } = roadmapParser;
|
|
65
65
|
const { pathExistsInternal, generateSlugInternal, toPosixPath } = coreUtils;
|
|
66
|
-
const { escapeRegex, normalizePhaseName, phaseTokenMatches, stripProjectCodePrefix, PHASE_NUMBER_TOKEN_SOURCE } = phaseId;
|
|
66
|
+
const { escapeRegex, normalizePhaseName, phaseTokenMatches, stripProjectCodePrefix, PHASE_NUMBER_TOKEN_SOURCE, isForeignPrefixedPhaseQuery } = phaseId;
|
|
67
67
|
const { pruneOrphanedWorktrees } = worktreeSafety;
|
|
68
68
|
const { planningPaths, planningDir, planningRoot, listAvailableWorkstreams, getActiveWorkstream, findContextMdIn, } = planningWorkspace;
|
|
69
69
|
const { determinePhaseStatus } = commandsMod;
|
|
@@ -74,25 +74,10 @@ const { evaluateUatPassed } = uatPredicateMod;
|
|
|
74
74
|
void stripShippedMilestones;
|
|
75
75
|
// Accept all bold/colon variants of the Requirements header (#2769)
|
|
76
76
|
const REQUIREMENTS_HEADER_RE = /^\*\*Requirements:?\*\*[^\S\n]*:?[^\S\n]*([^\n]*)$/m;
|
|
77
|
-
// #2056:
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
// evidence — a phase dir whose token literally IS the prefixed query, or a
|
|
82
|
-
// roadmap entry literally headed with it — before accepting such a match. The
|
|
83
|
-
// configured project_code's own prefix (e.g. LKML-01 under project_code: LKML)
|
|
84
|
-
// is never foreign and always passes through.
|
|
85
|
-
function parsePhasePrefix(phase) {
|
|
86
|
-
const match = String(phase).match(/^([A-Z][A-Z0-9_]*)-(?=\d)/i);
|
|
87
|
-
return match ? match[1] : null;
|
|
88
|
-
}
|
|
89
|
-
function isForeignPrefixedPhaseQuery(phase, projectCode) {
|
|
90
|
-
const prefix = parsePhasePrefix(phase);
|
|
91
|
-
if (!prefix)
|
|
92
|
-
return false;
|
|
93
|
-
const configured = typeof projectCode === 'string' ? projectCode.trim() : '';
|
|
94
|
-
return !configured || prefix.toUpperCase() !== configured.toUpperCase();
|
|
95
|
-
}
|
|
77
|
+
// #2056/#2104: isForeignPrefixedPhaseQuery is imported from phase-id.cts
|
|
78
|
+
// (the canonical predicate). parsePhasePrefix is no longer needed locally.
|
|
79
|
+
// phaseInfoMatchesExactPrefix and roadmapPhaseMatchesExactPrefix are local
|
|
80
|
+
// helpers that post-filter the lookup results for foreign-prefix queries.
|
|
96
81
|
function phaseInfoMatchesExactPrefix(phaseInfo, phase) {
|
|
97
82
|
const num = phaseInfo?.['phase_number'];
|
|
98
83
|
const numStr = typeof num === 'string' ? num : (typeof num === 'number' ? String(num) : '');
|
|
@@ -103,6 +88,23 @@ function roadmapPhaseMatchesExactPrefix(roadmapPhase, phase) {
|
|
|
103
88
|
const section = typeof sectionRaw === 'string' ? sectionRaw : '';
|
|
104
89
|
return new RegExp(`^#{2,4}\\s*Phase\\s+${escapeRegex(phase)}(?:\\b|\\s|:)`, 'i').test(section);
|
|
105
90
|
}
|
|
91
|
+
// #2104: shared helpers that wrap findPhaseInternal / getRoadmapPhaseInternal
|
|
92
|
+
// with the #2056 foreign-prefix guard, so every init command gets the same
|
|
93
|
+
// protection without duplicating the guard logic at each call site.
|
|
94
|
+
function guardedFindPhase(cwd, phase, projectCode) {
|
|
95
|
+
let phaseInfo = findPhaseInternal(cwd, phase);
|
|
96
|
+
if (isForeignPrefixedPhaseQuery(phase, projectCode) && !phaseInfoMatchesExactPrefix(phaseInfo, phase)) {
|
|
97
|
+
phaseInfo = null;
|
|
98
|
+
}
|
|
99
|
+
return phaseInfo;
|
|
100
|
+
}
|
|
101
|
+
function guardedGetRoadmapPhase(cwd, phase, projectCode) {
|
|
102
|
+
let roadmapPhase = getRoadmapPhaseInternal(cwd, phase);
|
|
103
|
+
if (isForeignPrefixedPhaseQuery(phase, projectCode) && !roadmapPhaseMatchesExactPrefix(roadmapPhase, phase)) {
|
|
104
|
+
roadmapPhase = null;
|
|
105
|
+
}
|
|
106
|
+
return roadmapPhase;
|
|
107
|
+
}
|
|
106
108
|
function listPhaseSummaryFiles(phaseDir) {
|
|
107
109
|
return scanPhasePlans(phaseDir)['summaryFiles'];
|
|
108
110
|
}
|
|
@@ -258,9 +260,9 @@ function cmdInitExecutePhase(cwd, phase, raw, options = {}) {
|
|
|
258
260
|
error('phase required for init execute-phase');
|
|
259
261
|
}
|
|
260
262
|
const config = loadConfig(cwd);
|
|
261
|
-
let phaseInfo =
|
|
263
|
+
let phaseInfo = guardedFindPhase(cwd, phase, config.project_code);
|
|
262
264
|
const milestone = getMilestoneInfo(cwd);
|
|
263
|
-
const roadmapPhase =
|
|
265
|
+
const roadmapPhase = guardedGetRoadmapPhase(cwd, phase, config.project_code);
|
|
264
266
|
if (phaseInfo?.['archived'] && roadmapPhase?.['found']) {
|
|
265
267
|
phaseInfo = null;
|
|
266
268
|
}
|
|
@@ -361,18 +363,9 @@ function cmdInitPlanPhase(cwd, phase, raw, options = {}) {
|
|
|
361
363
|
error('phase required for init plan-phase');
|
|
362
364
|
}
|
|
363
365
|
const config = loadConfig(cwd);
|
|
364
|
-
|
|
365
|
-
let phaseInfo =
|
|
366
|
-
|
|
367
|
-
// IS the prefixed query (e.g. a real 'MEM-01-*' dir); otherwise the numeric
|
|
368
|
-
// fallback ('MEM-01' → '01') would resolve to the unrelated numeric phase.
|
|
369
|
-
if (foreignPrefixedPhase && !phaseInfoMatchesExactPrefix(phaseInfo, phase)) {
|
|
370
|
-
phaseInfo = null;
|
|
371
|
-
}
|
|
372
|
-
let roadmapPhase = getRoadmapPhaseInternal(cwd, phase);
|
|
373
|
-
if (foreignPrefixedPhase && !roadmapPhaseMatchesExactPrefix(roadmapPhase, phase)) {
|
|
374
|
-
roadmapPhase = null;
|
|
375
|
-
}
|
|
366
|
+
// #2056/#2104: foreign-prefixed queries must not collapse to numeric phases.
|
|
367
|
+
let phaseInfo = guardedFindPhase(cwd, phase, config.project_code);
|
|
368
|
+
const roadmapPhase = guardedGetRoadmapPhase(cwd, phase, config.project_code);
|
|
376
369
|
if (phaseInfo?.['archived'] && roadmapPhase?.['found']) {
|
|
377
370
|
phaseInfo = null;
|
|
378
371
|
}
|
|
@@ -679,15 +672,15 @@ function cmdInitVerifyWork(cwd, phase, raw) {
|
|
|
679
672
|
}
|
|
680
673
|
const config = loadConfig(cwd);
|
|
681
674
|
const _slashRuntime = (0, runtime_slash_cjs_1.resolveRuntime)(cwd);
|
|
682
|
-
let phaseInfo =
|
|
675
|
+
let phaseInfo = guardedFindPhase(cwd, phase, config.project_code);
|
|
683
676
|
if (phaseInfo?.['archived']) {
|
|
684
|
-
const roadmapPhase =
|
|
677
|
+
const roadmapPhase = guardedGetRoadmapPhase(cwd, phase, config.project_code);
|
|
685
678
|
if (roadmapPhase?.['found']) {
|
|
686
679
|
phaseInfo = null;
|
|
687
680
|
}
|
|
688
681
|
}
|
|
689
682
|
if (!phaseInfo) {
|
|
690
|
-
const roadmapPhase =
|
|
683
|
+
const roadmapPhase = guardedGetRoadmapPhase(cwd, phase, config.project_code);
|
|
691
684
|
if (roadmapPhase?.['found']) {
|
|
692
685
|
const phaseName = roadmapPhase['phase_name'];
|
|
693
686
|
phaseInfo = {
|
|
@@ -736,9 +729,9 @@ function cmdInitVerifyWork(cwd, phase, raw) {
|
|
|
736
729
|
}
|
|
737
730
|
function cmdInitPhaseOp(cwd, phase, raw) {
|
|
738
731
|
const config = loadConfig(cwd);
|
|
739
|
-
let phaseInfo =
|
|
732
|
+
let phaseInfo = guardedFindPhase(cwd, phase, config.project_code);
|
|
740
733
|
if (phaseInfo?.['archived']) {
|
|
741
|
-
const roadmapPhase =
|
|
734
|
+
const roadmapPhase = guardedGetRoadmapPhase(cwd, phase, config.project_code);
|
|
742
735
|
if (roadmapPhase?.['found']) {
|
|
743
736
|
const phaseName = roadmapPhase['phase_name'];
|
|
744
737
|
phaseInfo = {
|
|
@@ -759,7 +752,7 @@ function cmdInitPhaseOp(cwd, phase, raw) {
|
|
|
759
752
|
}
|
|
760
753
|
}
|
|
761
754
|
if (!phaseInfo) {
|
|
762
|
-
const roadmapPhase =
|
|
755
|
+
const roadmapPhase = guardedGetRoadmapPhase(cwd, phase, config.project_code);
|
|
763
756
|
if (roadmapPhase?.['found']) {
|
|
764
757
|
const phaseName = roadmapPhase['phase_name'];
|
|
765
758
|
phaseInfo = {
|
|
@@ -102,6 +102,17 @@ function convertClaudeCommandToOpencodeSkill(content, skillName) {
|
|
|
102
102
|
function convertClaudeCommandToKiloSkill(content, skillName) {
|
|
103
103
|
return runtimeArtifactConversion.convertClaudeCommandToKiloSkill(content, skillName);
|
|
104
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* Converter-name registry for the OpenCode-family combined skills installer
|
|
107
|
+
* (ADR-1239 / #2093). Maps the `converter` string declared on each runtime's
|
|
108
|
+
* artifactLayout skills-kind descriptor (capabilities/<runtime>/capability.json)
|
|
109
|
+
* to the actual conversion function, so `installOpencodeFamilySkills` dispatches
|
|
110
|
+
* off the descriptor instead of a `frontmatterDialect === 'kilo'` runtime check.
|
|
111
|
+
*/
|
|
112
|
+
const SKILLS_CONVERTER_REGISTRY = {
|
|
113
|
+
convertClaudeCommandToOpencodeSkill,
|
|
114
|
+
convertClaudeCommandToKiloSkill,
|
|
115
|
+
};
|
|
105
116
|
// ---------------------------------------------------------------------------
|
|
106
117
|
// User-artifact preservation helpers
|
|
107
118
|
// ---------------------------------------------------------------------------
|
|
@@ -302,9 +313,13 @@ function _copyStaged(stagedDir, destDir, kind, configDir, runtime) {
|
|
|
302
313
|
let destName;
|
|
303
314
|
if (kind.kind === 'agents') {
|
|
304
315
|
// Agent files already carry the gsd- prefix in the source dir.
|
|
305
|
-
// #
|
|
306
|
-
|
|
307
|
-
|
|
316
|
+
// #2099: descriptor-driven via hostBehaviors.agentFileExtension (was
|
|
317
|
+
// hardcoded `runtime === 'copilot'`). copilot declares '.agent.md';
|
|
318
|
+
// every other runtime's descriptor leaves this unset, so destName falls
|
|
319
|
+
// back to entry.name unchanged (byte-parity, #1575 origin comment).
|
|
320
|
+
const _agentExt = runtime ? _hostBehaviors(runtime).agentFileExtension : undefined;
|
|
321
|
+
destName = _agentExt
|
|
322
|
+
? entry.name.replace(/\.md$/, _agentExt)
|
|
308
323
|
: entry.name;
|
|
309
324
|
}
|
|
310
325
|
else if (namespacedByDir) {
|
|
@@ -440,7 +455,7 @@ function _runLegacyInstallMigrations(runtime, configDir, scope = 'global') {
|
|
|
440
455
|
// that for Hermes the flat skills/gsd-*/ removal (below) does not delete the freshly
|
|
441
456
|
// created skills/gsd-dev-preferences/ skill dir.
|
|
442
457
|
let savedLegacyArtifacts = null;
|
|
443
|
-
if (runtime
|
|
458
|
+
if (_hostBehaviors(runtime).legacyCommandsGsdInstallMigration) {
|
|
444
459
|
if (node_fs_1.default.existsSync(legacyCommandsGsd)) {
|
|
445
460
|
savedLegacyArtifacts = preserveUserArtifacts(legacyCommandsGsd, ['dev-preferences.md']);
|
|
446
461
|
node_fs_1.default.rmSync(legacyCommandsGsd, { recursive: true });
|
|
@@ -497,7 +512,8 @@ function _runLegacyUninstallCleanup(runtime, configDir, scope = 'global') {
|
|
|
497
512
|
// commands/gsd/ for claude local, preserving dev-preferences.md by restoring it
|
|
498
513
|
// to the same location (#1423). Using migrateLegacyDevPreferencesToSkill here
|
|
499
514
|
// (which would redirect to skills/) conflicts with the test contract for local installs.
|
|
500
|
-
const
|
|
515
|
+
const _lu = _hostBehaviors(runtime).legacyCommandsGsdUninstall;
|
|
516
|
+
const isLegacyCommandsGsd = _lu === true || (_lu === 'global' && scope === 'global');
|
|
501
517
|
if (isLegacyCommandsGsd) {
|
|
502
518
|
const legacyCommandsGsd = node_path_1.default.join(configDir, 'commands', 'gsd');
|
|
503
519
|
if (node_fs_1.default.existsSync(legacyCommandsGsd)) {
|
|
@@ -651,6 +667,18 @@ function installRuntimeArtifacts(runtime, configDir, scope, resolvedProfile, res
|
|
|
651
667
|
const nestedGsdDirForCleanup = node_path_1.default.join(configDir, 'skills', 'gsd');
|
|
652
668
|
_removeHermesBareStemDirs(nestedGsdDirForCleanup);
|
|
653
669
|
}
|
|
670
|
+
// Generic-branch nativePlugin staging (ADR-1239 / #2102 Stage 1): runtimes
|
|
671
|
+
// outside the OpenCode/Kilo combined-family install (e.g. pi, whose
|
|
672
|
+
// artifactLayout is empty and which never sets combinedFamilyInstall) still
|
|
673
|
+
// need their declared hostBehaviors.nativePlugin file copied into configDir.
|
|
674
|
+
// findInstallSourceRoot resolves the repo/package root independent of
|
|
675
|
+
// configDir contents (marker check, then a walk-up from __dirname), so this
|
|
676
|
+
// is safe even when configDir has no .gsd-source marker (artifactLayout: []).
|
|
677
|
+
if (behaviors.nativePlugin) {
|
|
678
|
+
const commandsGsdDir = runtimeArtifactLayout.findInstallSourceRoot(configDir);
|
|
679
|
+
const src = node_path_1.default.dirname(node_path_1.default.dirname(commandsGsdDir));
|
|
680
|
+
_installNativePluginIfDeclared(runtime, configDir, behaviors, src);
|
|
681
|
+
}
|
|
654
682
|
}
|
|
655
683
|
// ---------------------------------------------------------------------------
|
|
656
684
|
// installOpencodeFamilySkills
|
|
@@ -679,9 +707,16 @@ function installOpencodeFamilySkills(runtime, targetDir, rawCommandsDir, pathPre
|
|
|
679
707
|
const rawDir = rawCommandsDir;
|
|
680
708
|
if (!rawDir || !node_fs_1.default.existsSync(rawDir))
|
|
681
709
|
return 0;
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
710
|
+
// #2093: descriptor-driven — dispatch off the skills-kind entry's `converter`
|
|
711
|
+
// string (capabilities/<runtime>/capability.json artifactLayout) via the
|
|
712
|
+
// SKILLS_CONVERTER_REGISTRY, instead of a `frontmatterDialect === 'kilo'`
|
|
713
|
+
// runtime check. Fail loud if the descriptor names an unregistered converter
|
|
714
|
+
// (mirrors the converter=null throw in runtime-artifact-layout.cts).
|
|
715
|
+
const converterName = skillsKindEntry.converter;
|
|
716
|
+
const converter = converterName ? SKILLS_CONVERTER_REGISTRY[converterName] : undefined;
|
|
717
|
+
if (!converter) {
|
|
718
|
+
throw new TypeError(`installOpencodeFamilySkills: unknown skills converter '${String(converterName)}' for runtime '${runtime}'`);
|
|
719
|
+
}
|
|
685
720
|
const dest = runtimeArtifactInstallPlan.assertDestWithinConfigHome(targetDir, skillsKindEntry.destSubpath);
|
|
686
721
|
// Symlink-escape guard: reject if any path component between targetDir and
|
|
687
722
|
// dest is a symlink that would redirect writes outside the config root.
|
|
@@ -769,6 +804,16 @@ function installOpencodeFamilyCommands(runtime, destDir, srcDir, pathPrefix, res
|
|
|
769
804
|
let content = node_fs_1.default.readFileSync(srcPath, 'utf8');
|
|
770
805
|
content = applyOpencodeFamilyPathPrefix(content, runtime, pathPrefix);
|
|
771
806
|
content = processAttribution(content, resolveAttribution(runtime));
|
|
807
|
+
// #2093: this commands-kind entry's descriptor `converter` field is
|
|
808
|
+
// intentionally `null` (see capabilities/{kilo,opencode}/capability.json —
|
|
809
|
+
// the flattened-command writer above applies its own path/attribution
|
|
810
|
+
// rewrites and has no per-file converter slot to key on), so there is no
|
|
811
|
+
// descriptor string to dispatch through here. `frontmatterDialect` is the
|
|
812
|
+
// documented, intentional dispatch key for frontmatter-shape selection —
|
|
813
|
+
// it is itself descriptor-driven (not a `runtime === 'kilo'` check), so it
|
|
814
|
+
// already satisfies the fold-to-descriptor requirement. Only the SKILLS
|
|
815
|
+
// converter site above (installOpencodeFamilySkills) has a real
|
|
816
|
+
// `converter` string to key on via SKILLS_CONVERTER_REGISTRY.
|
|
772
817
|
content = _hostBehaviors(runtime).frontmatterDialect === 'kilo'
|
|
773
818
|
? runtimeArtifactConversion.convertClaudeToKiloFrontmatter(content)
|
|
774
819
|
: runtimeArtifactConversion.convertClaudeToOpencodeFrontmatter(content);
|
|
@@ -777,12 +822,44 @@ function installOpencodeFamilyCommands(runtime, destDir, srcDir, pathPrefix, res
|
|
|
777
822
|
}
|
|
778
823
|
}
|
|
779
824
|
// ---------------------------------------------------------------------------
|
|
825
|
+
// _installNativePluginIfDeclared
|
|
826
|
+
// ---------------------------------------------------------------------------
|
|
827
|
+
/**
|
|
828
|
+
* Copy a runtime's declared native-extension/plugin file (hostBehaviors.nativePlugin)
|
|
829
|
+
* into its resolved config dir, when the runtime descriptor declares one.
|
|
830
|
+
*
|
|
831
|
+
* Extracted (ADR-1239 / #2102 Stage 1) from the body previously inlined in
|
|
832
|
+
* installOpencodeFamilyArtifacts so a runtime that is NOT part of the
|
|
833
|
+
* OpenCode/Kilo combined-family install (e.g. pi, whose artifactLayout is
|
|
834
|
+
* empty and which never sets combinedFamilyInstall) can still get its
|
|
835
|
+
* nativePlugin file staged via the generic installRuntimeArtifacts branch.
|
|
836
|
+
* Behavior for opencode/kilo is unchanged — same source resolution, same
|
|
837
|
+
* mkdir + copyFileSync call, same silent no-op when the source is missing.
|
|
838
|
+
*
|
|
839
|
+
* @param runtime - canonical runtime id (only used for the assertDestWithinConfigHome guard)
|
|
840
|
+
* @param configDir - resolved runtime config directory
|
|
841
|
+
* @param behaviors - the runtime's hostBehaviors descriptor
|
|
842
|
+
* @param src - repo/package root (two levels up from the commands/gsd source dir)
|
|
843
|
+
*/
|
|
844
|
+
function _installNativePluginIfDeclared(runtime, configDir, behaviors, src) {
|
|
845
|
+
const np = behaviors.nativePlugin;
|
|
846
|
+
if (np && np.source) {
|
|
847
|
+
const pluginSrc = node_path_1.default.join(src, np.source);
|
|
848
|
+
if (node_fs_1.default.existsSync(pluginSrc)) {
|
|
849
|
+
const destDir = runtimeArtifactInstallPlan.assertDestWithinConfigHome(configDir, np.dir);
|
|
850
|
+
node_fs_1.default.mkdirSync(destDir, { recursive: true });
|
|
851
|
+
node_fs_1.default.copyFileSync(pluginSrc, node_path_1.default.join(destDir, np.file));
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
// ---------------------------------------------------------------------------
|
|
780
856
|
// installOpencodeFamilyArtifacts
|
|
781
857
|
// ---------------------------------------------------------------------------
|
|
782
858
|
/**
|
|
783
|
-
* Combined-family install orchestrator for OpenCode/Kilo (ADR-1239 / #2087
|
|
784
|
-
* Stages the flattened commands surface + skills surface + (
|
|
785
|
-
*
|
|
859
|
+
* Combined-family install orchestrator for OpenCode/Kilo (ADR-1239 / #2087,
|
|
860
|
+
* #2093). Stages the flattened commands surface + skills surface + (any
|
|
861
|
+
* runtime whose hostBehaviors declares `nativePlugin` — OpenCode and, since
|
|
862
|
+
* #2093, Kilo) native plugin adapter, mirroring the bespoke `else if (isOpencode ||
|
|
786
863
|
* isKilo)` block previously inlined in bin/install.js.
|
|
787
864
|
*
|
|
788
865
|
* @param runtime - 'opencode' or 'kilo'
|
|
@@ -812,15 +889,7 @@ function installOpencodeFamilyArtifacts(runtime, configDir, scope, resolvedProfi
|
|
|
812
889
|
const commandDir = runtimeArtifactInstallPlan.assertDestWithinConfigHome(configDir, 'command');
|
|
813
890
|
installOpencodeFamilyCommands(runtime, commandDir, rawCommandsDir, pathPrefix, resolveAttribution);
|
|
814
891
|
installOpencodeFamilySkills(runtime, configDir, rawCommandsDir, pathPrefix, resolveAttribution);
|
|
815
|
-
|
|
816
|
-
if (np && np.source) {
|
|
817
|
-
const pluginSrc = node_path_1.default.join(src, np.source);
|
|
818
|
-
if (node_fs_1.default.existsSync(pluginSrc)) {
|
|
819
|
-
const destDir = runtimeArtifactInstallPlan.assertDestWithinConfigHome(configDir, np.dir);
|
|
820
|
-
node_fs_1.default.mkdirSync(destDir, { recursive: true });
|
|
821
|
-
node_fs_1.default.copyFileSync(pluginSrc, node_path_1.default.join(destDir, np.file));
|
|
822
|
-
}
|
|
823
|
-
}
|
|
892
|
+
_installNativePluginIfDeclared(runtime, configDir, behaviors, src);
|
|
824
893
|
}
|
|
825
894
|
// ---------------------------------------------------------------------------
|
|
826
895
|
// uninstallRuntimeArtifacts
|
|
@@ -879,6 +948,7 @@ module.exports = {
|
|
|
879
948
|
installOpencodeFamilySkills,
|
|
880
949
|
installOpencodeFamilyCommands,
|
|
881
950
|
installOpencodeFamilyArtifacts,
|
|
951
|
+
_installNativePluginIfDeclared,
|
|
882
952
|
_hostBehaviors,
|
|
883
953
|
_copyStaged,
|
|
884
954
|
hasExistingSymlinkBetween,
|
|
@@ -42,6 +42,9 @@ exports.BUNDLED_GSD_HOOK_FILES = Object.freeze(new Set([
|
|
|
42
42
|
'hooks/gsd-cursor-stop.js',
|
|
43
43
|
'hooks/gsd-cursor-subagent-start.js',
|
|
44
44
|
'hooks/gsd-cursor-subagent-stop.js',
|
|
45
|
+
// Windsurf/Cascade blocking hooks — registered by writeWindsurfHooksJson (#2100).
|
|
46
|
+
'hooks/gsd-windsurf-pre-write.js',
|
|
47
|
+
'hooks/gsd-windsurf-pre-command.js',
|
|
45
48
|
'hooks/gsd-ensure-canonical-path.js',
|
|
46
49
|
'hooks/gsd-graphify-update.sh',
|
|
47
50
|
'hooks/gsd-phase-boundary.sh',
|
|
@@ -5,8 +5,16 @@
|
|
|
5
5
|
* so any MCP-consuming host (Claude/Codex/OpenCode/VS Code/Gemini/Cursor/Cline/
|
|
6
6
|
* Hermes) can drive GSD with NO bespoke plugin:
|
|
7
7
|
*
|
|
8
|
-
* - point 1 (command): tool `gsd_invoke_command` →
|
|
9
|
-
* (
|
|
8
|
+
* - point 1 (command): tool `gsd_invoke_command` → `dispatchGsdCommand`
|
|
9
|
+
* (src/shell-command-projection.cts), a bounded subprocess-shim to
|
|
10
|
+
* gsd-tools.cjs. #2102 Stage 2: `commandRoutingHub.createHub()` called
|
|
11
|
+
* with no args here always hit `if(!_cjsRegistry) return
|
|
12
|
+
* makeUnknownCommand()` — every dispatch was UnknownCommand. No
|
|
13
|
+
* fully-populated hub factory exists anywhere in gsd-core (every
|
|
14
|
+
* createHub() caller builds a single-family hub for its own narrow
|
|
15
|
+
* purpose), so the fix routes through the SAME shared dispatch helper
|
|
16
|
+
* the pi extension uses (pi/gsd.cjs), mirroring the SUBPROCESS-REUSE
|
|
17
|
+
* precedent already established for the OpenCode/Kilo hook bridge.
|
|
10
18
|
* - point 5 (state IO): tools `gsd_read_state` / `gsd_write_state` → the
|
|
11
19
|
* Phase 3 `stateIO` seam (src/state-io.cts, filesystem default).
|
|
12
20
|
*
|
|
@@ -24,9 +32,10 @@ exports.SERVER_NAME = exports.PROTOCOL_VERSION = void 0;
|
|
|
24
32
|
exports.handleMessage = handleMessage;
|
|
25
33
|
exports.runServer = runServer;
|
|
26
34
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
27
|
-
const commandRoutingHub = require("./command-routing-hub.cjs");
|
|
28
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
29
35
|
const stateIo = require("./state-io.cjs");
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
37
|
+
const shellCommandProjection = require("./shell-command-projection.cjs");
|
|
38
|
+
const { dispatchGsdCommand } = shellCommandProjection;
|
|
30
39
|
exports.PROTOCOL_VERSION = '2024-11-05';
|
|
31
40
|
exports.SERVER_NAME = 'gsd-core';
|
|
32
41
|
const SERVER_VERSION = '1.7.0';
|
|
@@ -94,9 +103,11 @@ function callTool(name, args, ctx) {
|
|
|
94
103
|
if (!family || !subcommand) {
|
|
95
104
|
return { isError: true, content: [{ type: 'text', text: 'gsd_invoke_command requires string "family" and "subcommand".' }] };
|
|
96
105
|
}
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
106
|
+
const res = dispatchGsdCommand({ family, subcommand, args: Array.isArray(a.args) ? a.args : [], cwd });
|
|
107
|
+
if (!res.ok) {
|
|
108
|
+
return { isError: true, content: [{ type: 'text', text: res.stderr || res.stdout || `dispatch failed (exit ${res.code})` }] };
|
|
109
|
+
}
|
|
110
|
+
return { content: [{ type: 'text', text: res.stdout }] };
|
|
100
111
|
}
|
|
101
112
|
if (name === 'gsd_read_state') {
|
|
102
113
|
const p = asString(a.path);
|
|
@@ -15,25 +15,42 @@
|
|
|
15
15
|
* per-instance CLI flags). An instance is available iff its base `cli` is
|
|
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
|
+
*
|
|
19
|
+
* KNOWN_REVIEWER_SLUGS (post-review #2092): registry-derived, not a flat
|
|
20
|
+
* hand-maintained array. Each capability-runtime descriptor that is a valid
|
|
21
|
+
* reviewer CLI declares `runtime.hostBehaviors.reviewerCli: true`
|
|
22
|
+
* (capabilities/<id>/capability.json); this module reads that flag off the
|
|
23
|
+
* generated capability-registry.cjs at require-time. A handful of reviewer
|
|
24
|
+
* CLIs are NOT install-time runtimes at all (no capabilities/<id>/ descriptor
|
|
25
|
+
* exists) — those stay a small hardcoded tail:
|
|
26
|
+
* - `gemini` — hook-event dialect name only (see runtime-hooks-surface.cts);
|
|
27
|
+
* the Gemini CLI reviewer is not an installable runtime (#1928 folded
|
|
28
|
+
* gemini into antigravity's descriptor).
|
|
29
|
+
* - `coderabbit` / `ollama` / `lm_studio` / `llama_cpp` — third-party
|
|
30
|
+
* review/model CLIs with no GSD install surface at all.
|
|
18
31
|
*/
|
|
19
32
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
33
|
exports.INSTANCE_NAME_PATTERN = exports.KNOWN_REVIEWER_SLUGS = void 0;
|
|
21
34
|
exports.normalizeConfiguredDefaultReviewers = normalizeConfiguredDefaultReviewers;
|
|
22
35
|
exports.normalizeReviewerInstances = normalizeReviewerInstances;
|
|
23
36
|
exports.resolveReviewerSelection = resolveReviewerSelection;
|
|
24
|
-
|
|
37
|
+
const NON_RUNTIME_REVIEWER_SLUGS = [
|
|
25
38
|
'gemini',
|
|
26
|
-
'claude',
|
|
27
|
-
'codex',
|
|
28
39
|
'coderabbit',
|
|
29
|
-
'opencode',
|
|
30
|
-
'qwen',
|
|
31
|
-
'cursor',
|
|
32
|
-
'antigravity',
|
|
33
40
|
'ollama',
|
|
34
41
|
'lm_studio',
|
|
35
42
|
'llama_cpp',
|
|
36
43
|
];
|
|
44
|
+
function deriveRuntimeReviewerSlugs() {
|
|
45
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
46
|
+
const registry = require('./capability-registry.cjs');
|
|
47
|
+
const runtimes = registry.runtimes || {};
|
|
48
|
+
return Object.keys(runtimes).filter((id) => runtimes[id]?.runtime?.hostBehaviors?.reviewerCli === true);
|
|
49
|
+
}
|
|
50
|
+
exports.KNOWN_REVIEWER_SLUGS = [
|
|
51
|
+
...deriveRuntimeReviewerSlugs(),
|
|
52
|
+
...NON_RUNTIME_REVIEWER_SLUGS,
|
|
53
|
+
];
|
|
37
54
|
/** Instance names are lowercase slugs that must not shadow a built-in slug. */
|
|
38
55
|
exports.INSTANCE_NAME_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
|
|
39
56
|
function normalizeConfiguredDefaultReviewers(rawValue) {
|