@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
|
@@ -66,6 +66,38 @@ function gsdVersion() {
|
|
|
66
66
|
cachedVersion = resolveVersionFrom(__dirname);
|
|
67
67
|
return cachedVersion;
|
|
68
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Host-specific install behaviors declared on the runtime descriptor
|
|
71
|
+
* (capabilities/<runtime>/capability.json -> runtime.hostBehaviors). Mirrors
|
|
72
|
+
* bin/install.js's / install-engine.cts's `_hostBehaviors` (ADR-1239 / #2086
|
|
73
|
+
* / #2092). Returns {} for runtimes that declare none, so every behavior
|
|
74
|
+
* branch degrades to the generic path by default. Unlike the bin/install.js
|
|
75
|
+
* and install-engine.cts variants, this module already imports
|
|
76
|
+
* `capabilityRegistry` statically (see NON_CLAUDE_RUNTIMES below), so this
|
|
77
|
+
* reads it directly rather than re-require()-ing inside a try/catch.
|
|
78
|
+
*/
|
|
79
|
+
function _hostBehaviors(runtime) {
|
|
80
|
+
return ((capabilityRegistry &&
|
|
81
|
+
capabilityRegistry.runtimes &&
|
|
82
|
+
capabilityRegistry.runtimes[runtime] &&
|
|
83
|
+
capabilityRegistry.runtimes[runtime].runtime &&
|
|
84
|
+
capabilityRegistry.runtimes[runtime].runtime.hostBehaviors) ||
|
|
85
|
+
{});
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Public accessor for the `hostBehaviors.agentFileExtension` descriptor field
|
|
89
|
+
* (ADR-1239 / #2099 / #2103). Returns the runtime's declared agent-file
|
|
90
|
+
* destination-suffix rename target (e.g. copilot's `.agent.md`), or
|
|
91
|
+
* `undefined` when the runtime declares none (the generic no-rename
|
|
92
|
+
* default). Exported so callers outside this module (surface.cts's
|
|
93
|
+
* `_syncGsdDir`) can derive the SAME rename decision as
|
|
94
|
+
* install-engine.cts's staged-copy loop from ONE descriptor read, instead of
|
|
95
|
+
* duplicating a hardcoded `runtime === 'copilot'` check (#2103 fold).
|
|
96
|
+
*/
|
|
97
|
+
function agentFileExtensionFor(runtime) {
|
|
98
|
+
const ext = _hostBehaviors(runtime).agentFileExtension;
|
|
99
|
+
return typeof ext === 'string' ? ext : undefined;
|
|
100
|
+
}
|
|
69
101
|
const colorNameToHex = {
|
|
70
102
|
cyan: '#00FFFF',
|
|
71
103
|
red: '#FF0000',
|
|
@@ -412,10 +444,12 @@ function convertClaudeCommandToClaudeSkill(content, skillName, runtime = null, c
|
|
|
412
444
|
if (version)
|
|
413
445
|
fm += `version: ${yamlQuote(version)}\n`;
|
|
414
446
|
}
|
|
415
|
-
// #778 (b) —
|
|
416
|
-
//
|
|
417
|
-
//
|
|
418
|
-
|
|
447
|
+
// #778 (b) — numeric priority for /skills ordering, declared on the runtime
|
|
448
|
+
// descriptor (runtime.hostBehaviors.skillPriorityFrontmatter). Scoped to
|
|
449
|
+
// runtimes that declare the flag so Claude/Hermes skill frontmatter is
|
|
450
|
+
// unchanged (they ignore the field, but we keep their output byte-stable).
|
|
451
|
+
// skillName is the `gsd-<stem>` dir name. (ADR-1239 / #2092)
|
|
452
|
+
if (_hostBehaviors(runtime).skillPriorityFrontmatter) {
|
|
419
453
|
const stem = typeof skillName === 'string' && skillName.startsWith('gsd-')
|
|
420
454
|
? skillName.slice(4)
|
|
421
455
|
: skillName;
|
|
@@ -465,6 +499,14 @@ function convertGsdCommandReferencesToKimiSkillInvocations(content, cmdNames) {
|
|
|
465
499
|
.replace(colonPattern, (_, cmd) => `/skill:gsd-${cmd}`)
|
|
466
500
|
.replace(hyphenPattern, (_, cmd) => `/skill:gsd-${cmd}`);
|
|
467
501
|
}
|
|
502
|
+
// DEFECT.GENERATIVE-FIX: this body is mirrored in bin/install.js's
|
|
503
|
+
// convertClaudeCommandToKimiSkill (kept for bin/install.js's own
|
|
504
|
+
// module-level export/test surface; dead for the live skills-install path,
|
|
505
|
+
// which routes here via install-engine.cts's SKILLS_CONVERTER_REGISTRY
|
|
506
|
+
// through the kimi capability descriptor's artifactLayout
|
|
507
|
+
// `converter: "convertClaudeCommandToKimiSkill"`). Neither copy re-exports
|
|
508
|
+
// the other — mirror any behavior change into both. Guarded by the
|
|
509
|
+
// output-parity test in tests/runtime-converters.test.cjs (#2095).
|
|
468
510
|
function convertClaudeCommandToKimiSkill(content, skillName, _runtime = null, cmdNames = null) {
|
|
469
511
|
const { frontmatter, body } = extractFrontmatterAndBody(content);
|
|
470
512
|
const kimiSkillName = normalizeKimiSkillName(skillName);
|
|
@@ -592,6 +634,15 @@ function buildKimiSubagentYaml({ name, description, tools }) {
|
|
|
592
634
|
pushKimiToolsYaml(lines, 2, tools);
|
|
593
635
|
return `${lines.join('\n')}\n`;
|
|
594
636
|
}
|
|
637
|
+
// DEFECT.GENERATIVE-FIX: this body is mirrored in bin/install.js's
|
|
638
|
+
// buildKimiAgentArtifacts (kept for bin/install.js's own module-level
|
|
639
|
+
// export/test surface; dead for the live install path, which routes here via
|
|
640
|
+
// runtime-artifact-layout.cts's kimiAgentsKind through a dynamic
|
|
641
|
+
// `conversionExports['buildKimiAgentArtifacts']` lookup against this
|
|
642
|
+
// compiled module). Neither copy re-exports the other — mirror any behavior
|
|
643
|
+
// change into both, including the kimi_cli.tools.agent:Agent grant that
|
|
644
|
+
// enables background dispatch (#2095 Upgrade 2). Guarded by the
|
|
645
|
+
// output-parity test in tests/runtime-converters.test.cjs (#2095).
|
|
595
646
|
function buildKimiAgentArtifacts({ rootAgent = '', subagents = [], requestedSubagents = null, } = {}) {
|
|
596
647
|
const diagnostics = [];
|
|
597
648
|
const rootSource = parseKimiAgentSource(rootAgent) || { path: null, content: '' };
|
|
@@ -971,6 +1022,10 @@ function convertClaudeToAugmentMarkdown(content) {
|
|
|
971
1022
|
converted = converted.replace(/\bClaude Code\b/g, 'Augment');
|
|
972
1023
|
return converted;
|
|
973
1024
|
}
|
|
1025
|
+
// #2097 (ADR-1239): command-body converters selected by descriptor
|
|
1026
|
+
// (runtime.hostBehaviors.commandBodyConverter) instead of a runtime-name
|
|
1027
|
+
// branch. Degrade-closed: unknown/absent name → no conversion.
|
|
1028
|
+
const COMMAND_BODY_CONVERTERS = { convertClaudeToAugmentMarkdown };
|
|
974
1029
|
function getAugmentSkillAdapterHeader(skillName) {
|
|
975
1030
|
return `<augment_skill_adapter>
|
|
976
1031
|
## A. Skill Invocation
|
|
@@ -1045,6 +1100,12 @@ function convertClaudeToTraeMarkdown(content) {
|
|
|
1045
1100
|
converted = converted.replace(/\bClaude Code\b/g, 'Trae');
|
|
1046
1101
|
return converted;
|
|
1047
1102
|
}
|
|
1103
|
+
// DEFECT.GENERATIVE-FIX: this body is mirrored in bin/install.js's
|
|
1104
|
+
// convertClaudeCommandToTraeSkill (dead for the live skills-install path,
|
|
1105
|
+
// which routes here via install-engine.cts's SKILLS_CONVERTER_REGISTRY; kept
|
|
1106
|
+
// for bin/install.js's own module-level export/test surface). Neither copy
|
|
1107
|
+
// re-exports the other — mirror any behavior change into both. Guarded by
|
|
1108
|
+
// the output-parity test in tests/runtime-converters.test.cjs (#2094).
|
|
1048
1109
|
function convertClaudeCommandToTraeSkill(content, skillName) {
|
|
1049
1110
|
const converted = convertClaudeToTraeMarkdown(content);
|
|
1050
1111
|
const { frontmatter, body } = extractFrontmatterAndBody(converted);
|
|
@@ -1059,7 +1120,17 @@ function convertClaudeCommandToTraeSkill(content, skillName) {
|
|
|
1059
1120
|
const shortDescription = description.length > 180 ? `${description.slice(0, 177)}...` : description;
|
|
1060
1121
|
// #2876: quote so YAML flow indicators (`[BETA] …`) don't break Trae's
|
|
1061
1122
|
// frontmatter parser.
|
|
1062
|
-
|
|
1123
|
+
let fm = `---\nname: ${yamlIdentifier(skillName)}\ndescription: ${yamlQuote(shortDescription)}\n`;
|
|
1124
|
+
// #2094: emit `stage:` so Trae's SOLO agent can auto-invoke GSD skills at
|
|
1125
|
+
// the corresponding stage (docs.trae.ai/ide/agent). The field name/schema
|
|
1126
|
+
// is not formally documented (thin SPA docs) — descriptor-driven, single
|
|
1127
|
+
// fixed GSD-side value (runtime.hostBehaviors.soloStageMetadata), inferred/
|
|
1128
|
+
// best-effort.
|
|
1129
|
+
const soloStage = _hostBehaviors('trae').soloStageMetadata;
|
|
1130
|
+
if (soloStage)
|
|
1131
|
+
fm += `stage: ${soloStage}\n`;
|
|
1132
|
+
fm += '---';
|
|
1133
|
+
return `${fm}\n${body}`;
|
|
1063
1134
|
}
|
|
1064
1135
|
function convertSlashCommandsToCodebuddySkillMentions(content) {
|
|
1065
1136
|
return content.replace(/\/gsd:([a-z0-9-]+)/g, (_, commandName) => {
|
|
@@ -1520,7 +1591,12 @@ function convertClaudeToOpencodeFrontmatter(content, { isAgent = false, modelOve
|
|
|
1520
1591
|
return `---\n${newFrontmatter}\n---${body}`;
|
|
1521
1592
|
}
|
|
1522
1593
|
// Kilo CLI — same conversion logic as OpenCode, different config paths.
|
|
1523
|
-
|
|
1594
|
+
// DEFECT.GENERATIVE-FIX: this body is mirrored in bin/install.js's
|
|
1595
|
+
// convertClaudeToKiloFrontmatter (used by bin/install.js's own legacy install
|
|
1596
|
+
// path). Neither copy re-exports the other — mirror any behavior change into
|
|
1597
|
+
// both. Guarded by the output-parity test in tests/runtime-converters.test.cjs
|
|
1598
|
+
// (#2093).
|
|
1599
|
+
function convertClaudeToKiloFrontmatter(content, { isAgent = false, modelOverride = null } = {}) {
|
|
1524
1600
|
// Replace tool name references in content (applies to all files)
|
|
1525
1601
|
let convertedContent = content;
|
|
1526
1602
|
convertedContent = convertedContent.replace(/\bAskUserQuestion\b/g, 'question');
|
|
@@ -1666,6 +1742,14 @@ function convertClaudeToKiloFrontmatter(content, { isAgent = false } = {}) {
|
|
|
1666
1742
|
// For agents: add required Kilo agent fields
|
|
1667
1743
|
if (isAgent) {
|
|
1668
1744
|
newLines.push('mode: subagent');
|
|
1745
|
+
// Embed model override from ~/.gsd/defaults.json so model_overrides is
|
|
1746
|
+
// respected on Kilo (which uses static agent frontmatter, not inline
|
|
1747
|
+
// Task() model parameters) — mirrors convertClaudeToOpencodeFrontmatter's
|
|
1748
|
+
// model emission exactly (#2093 UPGRADE 2 / ADR-1239; Kilo is an OpenCode
|
|
1749
|
+
// fork with the same static-frontmatter model constraint). See #2256.
|
|
1750
|
+
if (modelOverride) {
|
|
1751
|
+
newLines.push(['model:', modelOverride].join(' '));
|
|
1752
|
+
}
|
|
1669
1753
|
newLines.push(...buildKiloAgentPermissionBlock(agentTools));
|
|
1670
1754
|
}
|
|
1671
1755
|
// For commands: add tools object if we had allowed-tools or tools
|
|
@@ -1875,6 +1959,76 @@ function convertClaudeAgentToTraeAgent(content) {
|
|
|
1875
1959
|
const cleanFrontmatter = `---\nname: ${yamlIdentifier(name)}\ndescription: ${yamlQuote(toSingleLine(description))}\n---`;
|
|
1876
1960
|
return `${cleanFrontmatter}\n${body}`;
|
|
1877
1961
|
}
|
|
1962
|
+
/**
|
|
1963
|
+
* Convert a Claude agent (.md) to a native Qwen Code subagent file
|
|
1964
|
+
* (`.qwen/agents/gsd-*.md` / `<qwenhome>/agents/gsd-*.md`, ADR-1239 / #2092
|
|
1965
|
+
* Phase B Upgrade 1). Qwen Code is a Claude-dialect host: its docs' "Claude
|
|
1966
|
+
* Code Compatibility Fields" section confirms CC agent files parse under
|
|
1967
|
+
* `.qwen/agents/` (https://qwenlm.github.io/qwen-code-docs/en/users/features/sub-agents),
|
|
1968
|
+
* so — unlike Cursor/Trae/Copilot/Antigravity — tool names pass through
|
|
1969
|
+
* UNCHANGED (no remapping table).
|
|
1970
|
+
*
|
|
1971
|
+
* Emits DETERMINISTIC frontmatter: `name:` + `description:` (mirrors
|
|
1972
|
+
* convertClaudeAgentToCursorAgent), plus `tools:` as a YAML block list when the
|
|
1973
|
+
* source declares one. Qwen's documented `tools:` schema is a YAML array
|
|
1974
|
+
* (`tools:\n- tool1\n- tool2`), not Claude's single-line comma-separated string
|
|
1975
|
+
* — passing the raw single-line string through unchanged would parse as one
|
|
1976
|
+
* malformed tool name and be silently dropped ("Optional fields with invalid
|
|
1977
|
+
* values are silently dropped at parse time" — same docs page). Reuses
|
|
1978
|
+
* `parseFrontmatterTools` (already relied on by the Kimi agent path), which
|
|
1979
|
+
* tolerates BOTH source formats Claude's own agents/*.md files use — the
|
|
1980
|
+
* single-line comma list (most agents) and the YAML block list (e.g.
|
|
1981
|
+
* agents/gsd-nyquist-auditor.md, agents/gsd-security-auditor.md) — so no tools
|
|
1982
|
+
* are lost regardless of which the source agent uses.
|
|
1983
|
+
*
|
|
1984
|
+
* `color` IS preserved: Qwen's docs list `color` under "Claude Code
|
|
1985
|
+
* Compatibility Fields" as a supported optional field, so it is passed
|
|
1986
|
+
* through as a plain scalar (unlike the cursor/trae/augment/windsurf
|
|
1987
|
+
* reduced-frontmatter converters, which drop it — those hosts have no such
|
|
1988
|
+
* compatibility field). `model:` and `approvalMode:` are intentionally NOT
|
|
1989
|
+
* emitted: both are optional per the docs and out of scope for #2092 (model:
|
|
1990
|
+
* would couple to the model catalog and introduce nondeterminism;
|
|
1991
|
+
* approvalMode is a deliberate follow-on).
|
|
1992
|
+
*
|
|
1993
|
+
* Body: preserved verbatim after the qwen branding rewrite (CLAUDE.md /
|
|
1994
|
+
* Claude Code / .claude/ literal-substring values — descriptor-driven via
|
|
1995
|
+
* runtime.hostBehaviors.brandingRewrites, mirrors the qwen case in
|
|
1996
|
+
* _applyRuntimeRewrites). The anchored `~/.claude/` / `$HOME/.claude/` forms
|
|
1997
|
+
* are already rewritten upstream by applyAgentPathRewrites (agentCtx Step 1 in
|
|
1998
|
+
* stageAgentsForRuntimeWithConverter) before this converter runs, so only the
|
|
1999
|
+
* bare/non-anchored forms are handled here — mirrors how
|
|
2000
|
+
* convertClaudeToTraeMarkdown orders its bare-form rewrites after the slash
|
|
2001
|
+
* forms to avoid double-rewriting the same substring.
|
|
2002
|
+
*/
|
|
2003
|
+
function convertClaudeAgentToQwenAgent(content) {
|
|
2004
|
+
const _b = _hostBehaviors('qwen').brandingRewrites || {};
|
|
2005
|
+
let converted = content;
|
|
2006
|
+
if (_b['CLAUDE.md'])
|
|
2007
|
+
converted = converted.replace(/CLAUDE\.md/g, _b['CLAUDE.md']);
|
|
2008
|
+
if (_b['Claude Code'])
|
|
2009
|
+
converted = converted.replace(/\bClaude Code\b/g, _b['Claude Code']);
|
|
2010
|
+
if (_b['.claude/'])
|
|
2011
|
+
converted = converted.replace(/\.claude\//g, _b['.claude/']);
|
|
2012
|
+
const { frontmatter, body } = extractFrontmatterAndBody(converted);
|
|
2013
|
+
if (!frontmatter)
|
|
2014
|
+
return converted;
|
|
2015
|
+
const name = extractFrontmatterField(frontmatter, 'name') || 'unknown';
|
|
2016
|
+
const description = extractFrontmatterField(frontmatter, 'description') || '';
|
|
2017
|
+
const tools = parseFrontmatterTools(frontmatter);
|
|
2018
|
+
const color = extractFrontmatterField(frontmatter, 'color');
|
|
2019
|
+
let fm = `---\nname: ${yamlIdentifier(name)}\ndescription: ${yamlQuote(toSingleLine(description))}\n`;
|
|
2020
|
+
if (tools.length > 0) {
|
|
2021
|
+
fm += 'tools:\n';
|
|
2022
|
+
for (const tool of tools) {
|
|
2023
|
+
fm += ` - ${yamlIdentifier(tool)}\n`;
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
if (color) {
|
|
2027
|
+
fm += `color: ${yamlIdentifier(color)}\n`;
|
|
2028
|
+
}
|
|
2029
|
+
fm += '---';
|
|
2030
|
+
return `${fm}\n${body}`;
|
|
2031
|
+
}
|
|
1878
2032
|
function convertClaudeAgentToCodebuddyAgent(content) {
|
|
1879
2033
|
const converted = convertClaudeToCodebuddyMarkdown(content);
|
|
1880
2034
|
const { frontmatter, body } = extractFrontmatterAndBody(converted);
|
|
@@ -2087,19 +2241,24 @@ function _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal = false, a
|
|
|
2087
2241
|
content = processAttribution(content, attribution);
|
|
2088
2242
|
break;
|
|
2089
2243
|
}
|
|
2090
|
-
case 'augment':
|
|
2244
|
+
case 'augment': {
|
|
2091
2245
|
content = content.replace(/~\/\.claude\//g, pathPrefix);
|
|
2092
2246
|
content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
|
|
2093
2247
|
content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
|
|
2094
2248
|
content = content.replace(/~\/\.claude(?![\w-])/g, normalizedPathPrefix);
|
|
2095
2249
|
content = content.replace(/\$HOME\/\.claude(?![\w-])/g, normalizedPathPrefix);
|
|
2096
2250
|
content = content.replace(/\.\/\.claude(?![\w-])/g, `./${dirName}`);
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2251
|
+
// #2097: dot-dir self-references (~/.augment/…) → resolved prefix,
|
|
2252
|
+
// dirName-derived (no runtime literal). getDirName('augment') resolves
|
|
2253
|
+
// to '.augment', so this is byte-identical to the prior hardcoded regexes.
|
|
2254
|
+
const _dd = escapeRegExp(dirName);
|
|
2255
|
+
content = content.replace(new RegExp('~/' + _dd + '/', 'g'), pathPrefix);
|
|
2256
|
+
content = content.replace(new RegExp('\\$HOME/' + _dd + '/', 'g'), pathPrefix);
|
|
2257
|
+
content = content.replace(new RegExp('~/' + _dd + '(?![\\w-])', 'g'), normalizedPathPrefix);
|
|
2258
|
+
content = content.replace(new RegExp('\\$HOME/' + _dd + '(?![\\w-])', 'g'), normalizedPathPrefix);
|
|
2101
2259
|
content = processAttribution(content, attribution);
|
|
2102
2260
|
break;
|
|
2261
|
+
}
|
|
2103
2262
|
case 'trae':
|
|
2104
2263
|
content = content.replace(/~\/\.claude\//g, pathPrefix);
|
|
2105
2264
|
content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
|
|
@@ -2107,7 +2266,10 @@ function _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal = false, a
|
|
|
2107
2266
|
content = content.replace(/~\/\.claude\b/g, normalizedPathPrefix);
|
|
2108
2267
|
content = content.replace(/\$HOME\/\.claude\b/g, normalizedPathPrefix);
|
|
2109
2268
|
content = content.replace(/\.\/\.claude\b/g, `./${dirName}`);
|
|
2110
|
-
|
|
2269
|
+
// #2094: descriptor-driven — dirName resolves to '.trae' via
|
|
2270
|
+
// getDirName()/localConfigDir, so this regex is built rather than
|
|
2271
|
+
// hardcoded as `/~\/\.trae\//g` (byte-identical output for trae).
|
|
2272
|
+
content = content.replace(new RegExp('~/' + escapeRegExp(dirName) + '/', 'g'), pathPrefix);
|
|
2111
2273
|
content = processAttribution(content, attribution);
|
|
2112
2274
|
break;
|
|
2113
2275
|
case 'codebuddy':
|
|
@@ -2135,9 +2297,20 @@ function _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal = false, a
|
|
|
2135
2297
|
content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
|
|
2136
2298
|
content = processAttribution(content, attribution);
|
|
2137
2299
|
break;
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2300
|
+
// Descriptor-driven brand literals (ADR-1239 / #2092): the qwen/hermes
|
|
2301
|
+
// brand VALUES (CLAUDE.md/Claude Code/.claude/ replacements) now read from
|
|
2302
|
+
// runtime.hostBehaviors.brandingRewrites instead of hardcoded literals.
|
|
2303
|
+
// EXACT regexes/order preserved — only the replacement values changed.
|
|
2304
|
+
case 'qwen': {
|
|
2305
|
+
// Guarded (post-review #2092): brandingRewrites is undefined if the
|
|
2306
|
+
// capability registry fails to load — degrade closed (skip the
|
|
2307
|
+
// brand-literal replacements, still apply the non-branding path
|
|
2308
|
+
// rewrites below) instead of throwing on `_b['CLAUDE.md']`.
|
|
2309
|
+
const _b = _hostBehaviors(runtime).brandingRewrites;
|
|
2310
|
+
if (_b) {
|
|
2311
|
+
content = content.replace(/CLAUDE\.md/g, _b['CLAUDE.md']);
|
|
2312
|
+
content = content.replace(/\bClaude Code\b/g, _b['Claude Code']);
|
|
2313
|
+
}
|
|
2141
2314
|
content = content.replace(/~\/\.claude\//g, pathPrefix);
|
|
2142
2315
|
content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
|
|
2143
2316
|
content = content.replace(/~\/\.qwen\//g, pathPrefix);
|
|
@@ -2146,14 +2319,22 @@ function _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal = false, a
|
|
|
2146
2319
|
content = content.replace(/\$HOME\/\.claude(?![\w-])/g, normalizedPathPrefix);
|
|
2147
2320
|
content = content.replace(/~\/\.qwen(?![\w-])/g, normalizedPathPrefix);
|
|
2148
2321
|
content = content.replace(/\$HOME\/\.qwen(?![\w-])/g, normalizedPathPrefix);
|
|
2149
|
-
|
|
2322
|
+
if (_b) {
|
|
2323
|
+
content = content.replace(/\.claude\//g, _b['.claude/']);
|
|
2324
|
+
}
|
|
2150
2325
|
content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
|
|
2151
2326
|
content = content.replace(/\.\/\.qwen\//g, `./${dirName}/`);
|
|
2152
2327
|
content = processAttribution(content, attribution);
|
|
2153
2328
|
break;
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2329
|
+
}
|
|
2330
|
+
case 'hermes': {
|
|
2331
|
+
// Guarded (post-review #2092): see qwen case above — same degrade-closed
|
|
2332
|
+
// rationale.
|
|
2333
|
+
const _b = _hostBehaviors(runtime).brandingRewrites;
|
|
2334
|
+
if (_b) {
|
|
2335
|
+
content = content.replace(/CLAUDE\.md/g, _b['CLAUDE.md']);
|
|
2336
|
+
content = content.replace(/\bClaude Code\b/g, _b['Claude Code']);
|
|
2337
|
+
}
|
|
2157
2338
|
content = content.replace(/~\/\.claude\//g, pathPrefix);
|
|
2158
2339
|
content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
|
|
2159
2340
|
content = content.replace(/~\/\.hermes\//g, pathPrefix);
|
|
@@ -2162,11 +2343,14 @@ function _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal = false, a
|
|
|
2162
2343
|
content = content.replace(/\$HOME\/\.claude(?![\w-])/g, normalizedPathPrefix);
|
|
2163
2344
|
content = content.replace(/~\/\.hermes(?![\w-])/g, normalizedPathPrefix);
|
|
2164
2345
|
content = content.replace(/\$HOME\/\.hermes(?![\w-])/g, normalizedPathPrefix);
|
|
2165
|
-
|
|
2346
|
+
if (_b) {
|
|
2347
|
+
content = content.replace(/\.claude\//g, _b['.claude/']);
|
|
2348
|
+
}
|
|
2166
2349
|
content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
|
|
2167
2350
|
content = content.replace(/\.\/\.hermes\//g, `./${dirName}/`);
|
|
2168
2351
|
content = processAttribution(content, attribution);
|
|
2169
2352
|
break;
|
|
2353
|
+
}
|
|
2170
2354
|
case 'kimi':
|
|
2171
2355
|
content = content.replace(/~\/\.claude\//g, pathPrefix);
|
|
2172
2356
|
content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
|
|
@@ -2241,8 +2425,11 @@ function applyRuntimeContentRewritesForCommandsInPlace(stagedDir, runtime, pathP
|
|
|
2241
2425
|
continue;
|
|
2242
2426
|
let content = node_fs_1.default.readFileSync(node_path_1.default.join(stagedDir, entry.name), 'utf8');
|
|
2243
2427
|
content = _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal, attribution);
|
|
2244
|
-
|
|
2245
|
-
|
|
2428
|
+
// #2097 (ADR-1239): descriptor-driven — commandBodyConverter name comes
|
|
2429
|
+
// from runtime.hostBehaviors instead of a hardcoded runtime-name branch.
|
|
2430
|
+
const _cmdConv = _hostBehaviors(runtime).commandBodyConverter;
|
|
2431
|
+
if (_cmdConv && COMMAND_BODY_CONVERTERS[_cmdConv]) {
|
|
2432
|
+
content = COMMAND_BODY_CONVERTERS[_cmdConv](content);
|
|
2246
2433
|
}
|
|
2247
2434
|
node_fs_1.default.writeFileSync(node_path_1.default.join(tempDir, entry.name), content);
|
|
2248
2435
|
}
|
|
@@ -2312,25 +2499,21 @@ function rewriteStagedCommandBodies(stagedDir, opts) {
|
|
|
2312
2499
|
const attribution = resolveAttribution ? resolveAttribution(runtime) : undefined;
|
|
2313
2500
|
return applyRuntimeContentRewritesForCommandsInPlace(stagedDir, runtime, pathPrefix, isGlobal, attribution);
|
|
2314
2501
|
}
|
|
2315
|
-
/**
|
|
2316
|
-
* Runtimes that use the hyphen-namespace form `/gsd-<cmd>` in agent bodies.
|
|
2317
|
-
* claude/qwen/hermes use hyphen-name:`...` frontmatter; cursor/windsurf/etc
|
|
2318
|
-
* self-convert. Mirrors the `HYPHEN_NAME_AGENT_RUNTIMES` set in bin/install.js.
|
|
2319
|
-
*
|
|
2320
|
-
* @private — export normalizeAgentBodyForRuntime for callers.
|
|
2321
|
-
*/
|
|
2322
|
-
const HYPHEN_NAME_AGENT_RUNTIMES = new Set(['claude', 'qwen', 'hermes']);
|
|
2323
2502
|
/**
|
|
2324
2503
|
* Normalize `/gsd:<cmd>` colon refs in the agent body to `/gsd-<cmd>` for
|
|
2325
|
-
*
|
|
2326
|
-
*
|
|
2504
|
+
* runtimes that declare `runtime.hostBehaviors.hyphenNameAgentBody` on their
|
|
2505
|
+
* descriptor (claude / qwen / hermes use hyphen-`name:` frontmatter;
|
|
2506
|
+
* cursor/windsurf/etc self-convert and don't declare the flag). Descriptor-
|
|
2507
|
+
* driven (ADR-1239 / #2092) — folded from the hardcoded
|
|
2508
|
+
* `HYPHEN_NAME_AGENT_RUNTIMES` allow-list set. Mirrors the per-file call in
|
|
2509
|
+
* bin/install.js line 9370 / `shouldNormalizeHyphenNamespaceInAgentBody`.
|
|
2327
2510
|
*
|
|
2328
2511
|
* @param content raw agent file content (post-converter)
|
|
2329
2512
|
* @param runtime canonical runtime ID
|
|
2330
2513
|
* @param cmdNames gsd command names from readGsdCommandNames()
|
|
2331
2514
|
*/
|
|
2332
2515
|
function normalizeAgentBodyForRuntime(content, runtime, cmdNames) {
|
|
2333
|
-
if (
|
|
2516
|
+
if (_hostBehaviors(runtime).hyphenNameAgentBody !== true)
|
|
2334
2517
|
return content;
|
|
2335
2518
|
return transformContentToHyphen(content, cmdNames);
|
|
2336
2519
|
}
|
|
@@ -2342,9 +2525,13 @@ function normalizeAgentBodyForRuntime(content, runtime, cmdNames) {
|
|
|
2342
2525
|
* ~/\.claude\b → normalizedPathPrefix
|
|
2343
2526
|
* $HOME/\.claude\b → normalizedPathPrefix
|
|
2344
2527
|
*
|
|
2345
|
-
* Skipped for
|
|
2346
|
-
*
|
|
2347
|
-
*
|
|
2528
|
+
* Skipped for any runtime that declares `hostBehaviors.noPathRewrite`
|
|
2529
|
+
* (descriptor-driven, ADR-1239 / #2096 — folds the prior hardcoded
|
|
2530
|
+
* `runtime === 'antigravity'` literal; Antigravity does NOT do path rewrites
|
|
2531
|
+
* in the inline loop / #2103 — folds the prior hardcoded
|
|
2532
|
+
* `runtime === 'copilot'` literal onto the same descriptor field, since
|
|
2533
|
+
* copilot also skips these rewrites). NO stamp
|
|
2534
|
+
* (_stampNonClaudeRuntimeDefaults) — agents are NOT stamped in the inline loop.
|
|
2348
2535
|
*
|
|
2349
2536
|
* ADR-1235 §1: pre-converter cross-cutting for descriptor-driven agent pipeline.
|
|
2350
2537
|
* Exported as `applyAgentPathRewrites` for testing and for injection into
|
|
@@ -2353,10 +2540,10 @@ function normalizeAgentBodyForRuntime(content, runtime, cmdNames) {
|
|
|
2353
2540
|
* @param content raw agent file content
|
|
2354
2541
|
* @param runtime canonical runtime ID
|
|
2355
2542
|
* @param pathPrefix trailing-slash path prefix (e.g. '$HOME/.cursor/')
|
|
2356
|
-
* @returns content with path-prefix rewrites applied (or unchanged for copilot
|
|
2543
|
+
* @returns content with path-prefix rewrites applied (or unchanged for noPathRewrite runtimes, e.g. copilot)
|
|
2357
2544
|
*/
|
|
2358
2545
|
function applyAgentPathRewrites(content, runtime, pathPrefix) {
|
|
2359
|
-
if (runtime ===
|
|
2546
|
+
if (_hostBehaviors(runtime).noPathRewrite === true)
|
|
2360
2547
|
return content;
|
|
2361
2548
|
const normalizedPathPrefix = pathPrefix.replace(/\/$/, '');
|
|
2362
2549
|
content = content.replace(/~\/\.claude\//g, pathPrefix);
|
|
@@ -2392,6 +2579,11 @@ function processAttribution(content, attribution) {
|
|
|
2392
2579
|
}
|
|
2393
2580
|
module.exports = {
|
|
2394
2581
|
processAttribution,
|
|
2582
|
+
// #2103: public accessor for hostBehaviors.agentFileExtension, exported so
|
|
2583
|
+
// surface.cts's _syncGsdDir can derive the .agent.md rename from the SAME
|
|
2584
|
+
// descriptor read as install-engine.cts (folds a duplicated hardcoded
|
|
2585
|
+
// `runtime === 'copilot'` literal).
|
|
2586
|
+
agentFileExtensionFor,
|
|
2395
2587
|
yamlIdentifier,
|
|
2396
2588
|
yamlQuote,
|
|
2397
2589
|
toSingleLine,
|
|
@@ -2451,6 +2643,11 @@ module.exports = {
|
|
|
2451
2643
|
convertClaudeAgentToCodebuddyAgent,
|
|
2452
2644
|
convertClaudeAgentToClineAgent,
|
|
2453
2645
|
convertClaudeAgentToCodexAgent,
|
|
2646
|
+
// ADR-1239 / #2092 Phase B Upgrade 1: native .qwen/agents/*.md subagent
|
|
2647
|
+
// projection — registered by name so convertedAgentsKind's
|
|
2648
|
+
// conversionExports[converterName] dispatch (runtime-artifact-layout.cts)
|
|
2649
|
+
// can resolve it from capabilities/qwen/capability.json's agents kind.
|
|
2650
|
+
convertClaudeAgentToQwenAgent,
|
|
2454
2651
|
// #1511 ADR-1508 Phase 2: rewrite engine deep seam
|
|
2455
2652
|
// Low-level walkers (pathPrefix + attribution pre-resolved by caller):
|
|
2456
2653
|
applyRuntimeContentRewritesInPlace,
|
|
@@ -130,19 +130,27 @@ function agentsKind(destSubpath, prefix, configDir) {
|
|
|
130
130
|
* Agent filenames are preserved verbatim (the prefix is already embedded in the
|
|
131
131
|
* agent stem — e.g. `gsd-planner.md`).
|
|
132
132
|
*
|
|
133
|
-
* #1173 SCOPE — plumbing only (
|
|
134
|
-
* converter dispatch + `isGlobal` scope threading for the descriptor's
|
|
135
|
-
* kind,
|
|
136
|
-
* `
|
|
137
|
-
* (
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
* `
|
|
145
|
-
*
|
|
133
|
+
* #1173 SCOPE — plumbing only (real install still elsewhere): this provides
|
|
134
|
+
* the converter dispatch + `isGlobal` scope threading for the descriptor's
|
|
135
|
+
* `agents` kind. As of #2092, 8 non-Claude runtimes DO declare a converted
|
|
136
|
+
* `agents` kind in their `capability.json` — qwen (`convertClaudeAgentToQwenAgent`)
|
|
137
|
+
* plus the 7 that already declared one before it (antigravity, augment,
|
|
138
|
+
* codebuddy, copilot, cursor, trae, windsurf) — so the descriptor-level
|
|
139
|
+
* declaration is no longer deferred. What IS still deferred is wiring
|
|
140
|
+
* `resolveRuntimeArtifactLayout`'s `agents` kind into the REAL install:
|
|
141
|
+
* `bin/install.js`'s agent-staging loop does not consume this module's
|
|
142
|
+
* `convertedAgentsKind` resolution at all — it dispatches the very same
|
|
143
|
+
* converter functions directly via `_hostBehaviors(runtime)` checks
|
|
144
|
+
* (`frontmatterDialect`, `brandingRewrites`, `isCopilot`/`isAntigravity`/…),
|
|
145
|
+
* duplicating the mapping declared here. That duplication is deliberate until
|
|
146
|
+
* the second `layout.kinds` consumer — `applySurface` / `/gsd:surface` /
|
|
147
|
+
* `--materialize` (`src/surface.cts`) — mirrors the legacy agent pipeline
|
|
148
|
+
* (Copilot's `.agent.md` filename rename, the cross-cutting path-prefix
|
|
149
|
+
* rewrite + attribution, stale-file cleanup, config-reading steps); declaring
|
|
150
|
+
* `bin/install.js` itself against this resolver before then would risk
|
|
151
|
+
* regressing the surface path. Until that follow-up lands, `bin/install.js`
|
|
152
|
+
* remains authoritative for the real install, and this `convertedAgentsKind`
|
|
153
|
+
* is exercised only by `/gsd:surface` and synthetic-descriptor seam tests.
|
|
146
154
|
*
|
|
147
155
|
* Mirrors the `convertedCommandsKind` pattern (#785).
|
|
148
156
|
*
|
|
@@ -224,6 +232,7 @@ function skillsKind(destSubpath, prefix, converterName, runtime, configDir, nest
|
|
|
224
232
|
kind: 'skills',
|
|
225
233
|
destSubpath,
|
|
226
234
|
prefix,
|
|
235
|
+
converter: converterName,
|
|
227
236
|
stage: (resolved) => {
|
|
228
237
|
const realConverter = conversionExports[converterName];
|
|
229
238
|
// Compute cmdNames once per stage call for performance (#3583).
|
|
@@ -21,17 +21,30 @@
|
|
|
21
21
|
* false for codex / copilot / kilo / cursor / windsurf / trae / cline / kimi (legacy exclusion list).
|
|
22
22
|
* true for all other runtimes.
|
|
23
23
|
* - `finishPermissionWriter` names the finishInstall-phase dedicated config writer:
|
|
24
|
-
* 'opencode'
|
|
25
|
-
* 'kilo'
|
|
26
|
-
*
|
|
24
|
+
* 'opencode' → writes BOTH shared settings AND its own permissions file.
|
|
25
|
+
* 'kilo' → writes only its own permissions file.
|
|
26
|
+
* 'antigravity' → writes BOTH shared settings.json permissions.allow AND a
|
|
27
|
+
* standalone mcp_config.json MCP companion profile (#2096
|
|
28
|
+
* Phase B Upgrades 1+2).
|
|
29
|
+
* null → no dedicated permission writer.
|
|
27
30
|
*/
|
|
28
31
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
29
32
|
const { runtimes } = require('./capability-registry.cjs');
|
|
30
33
|
/** Valid sandboxTier enum values — mirrors the gen-capability-registry validator vocabulary. */
|
|
31
34
|
const VALID_SANDBOX_TIERS = new Set(['none', 'codex-agent-sandbox']);
|
|
32
|
-
/**
|
|
35
|
+
/**
|
|
36
|
+
* The complete set of 16 supported runtimes for config-adapter dispatch.
|
|
37
|
+
*
|
|
38
|
+
* Excludes runtimes whose installSurface is 'none' (#2103 — e.g. VS Code): a
|
|
39
|
+
* 'none' installSurface means the runtime has NO CLI install surface at all
|
|
40
|
+
* (Marketplace/VSIX-distributed, never dispatched through
|
|
41
|
+
* install()/finishInstall()), so it is not a "config-adapter runtime" by
|
|
42
|
+
* definition. This keeps this set in lockstep with bin/install.js's
|
|
43
|
+
* `allRuntimes` (see tests/issue-57-runtime-install-no-drift.test.cjs) without
|
|
44
|
+
* needing a separate hand-kept exclusion list.
|
|
45
|
+
*/
|
|
33
46
|
const ALLOWED_CONFIG_RUNTIMES = new Set(Object.entries(runtimes)
|
|
34
|
-
.filter(([, cap]) => cap && cap.runtime && typeof cap.runtime['installSurface'] === 'string')
|
|
47
|
+
.filter(([, cap]) => cap && cap.runtime && typeof cap.runtime['installSurface'] === 'string' && cap.runtime['installSurface'] !== 'none')
|
|
35
48
|
.map(([id]) => id));
|
|
36
49
|
/** All valid installSurface values. */
|
|
37
50
|
const INSTALL_SURFACES = Object.freeze([
|
|
@@ -41,6 +54,7 @@ const INSTALL_SURFACES = Object.freeze([
|
|
|
41
54
|
'cline-rules',
|
|
42
55
|
'cursor-hooks-json',
|
|
43
56
|
'profile-marker-only',
|
|
57
|
+
'none',
|
|
44
58
|
]);
|
|
45
59
|
/**
|
|
46
60
|
* Resolve the config adapter intent for a given runtime.
|
|
@@ -38,6 +38,7 @@ exports.resolveConfigHomeFromDescriptor = resolveConfigHomeFromDescriptor;
|
|
|
38
38
|
exports.resolveAntigravityGlobalDir = resolveAntigravityGlobalDir;
|
|
39
39
|
exports.detectAntigravityDirAmbiguity = detectAntigravityDirAmbiguity;
|
|
40
40
|
exports.resolveKimiGlobalDir = resolveKimiGlobalDir;
|
|
41
|
+
exports.resolveKimiHooksTomlDir = resolveKimiHooksTomlDir;
|
|
41
42
|
exports.getGlobalConfigDir = getGlobalConfigDir;
|
|
42
43
|
exports.resolveSkillsBaseFromDescriptor = resolveSkillsBaseFromDescriptor;
|
|
43
44
|
exports.getGlobalSkillsBase = getGlobalSkillsBase;
|
|
@@ -255,6 +256,27 @@ function resolveKimiGlobalDir(opts = {}) {
|
|
|
255
256
|
probeExists: 'skills',
|
|
256
257
|
}, { env, home, existsSync: existsSyncFn });
|
|
257
258
|
}
|
|
259
|
+
/**
|
|
260
|
+
* Resolve the directory holding Kimi CLI's OWN native config.toml (the file
|
|
261
|
+
* Kimi itself reads for providers/models/hooks/etc — see
|
|
262
|
+
* moonshotai.github.io/kimi-cli/en/configuration/data-locations.html and
|
|
263
|
+
* .../reference/kimi-command.html). Default `~/.kimi`, overridden by
|
|
264
|
+
* `KIMI_SHARE_DIR` per Kimi's own upstream env-var (NOT `KIMI_CONFIG_DIR`,
|
|
265
|
+
* which is a GSD-installer write-location override for the unrelated generic
|
|
266
|
+
* Agent-Skills root resolved by resolveKimiGlobalDir above).
|
|
267
|
+
*
|
|
268
|
+
* This is deliberately a SEPARATE directory from GSD's kimi configHome
|
|
269
|
+
* (~/.config/agents): Kimi's own docs confirm the Agent-Skills search path is
|
|
270
|
+
* independent of KIMI_SHARE_DIR ("This variable does not affect Agent Skills
|
|
271
|
+
* search paths, which are handled separately"). #2095 Upgrade 1 writes GSD's
|
|
272
|
+
* native [[hooks]] entries into `<this dir>/config.toml`, never into the
|
|
273
|
+
* skills configDir.
|
|
274
|
+
*/
|
|
275
|
+
function resolveKimiHooksTomlDir(opts = {}) {
|
|
276
|
+
const env = opts.env ?? process.env;
|
|
277
|
+
const home = opts.home ?? node_os_1.default.homedir();
|
|
278
|
+
return resolveConfigHomeFromDescriptor({ kind: 'dot-home', name: '.kimi', env: ['KIMI_SHARE_DIR'] }, { env, home });
|
|
279
|
+
}
|
|
258
280
|
/**
|
|
259
281
|
* Return the global config base directory for the given runtime.
|
|
260
282
|
* Respects the same env-var overrides as bin/install.js getGlobalDir().
|