@opengsd/gsd-core 1.7.0-rc.5 → 1.7.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/agents/gsd-executor.md +2 -1
- package/agents/gsd-security-auditor.md +13 -15
- package/agents/gsd-ui-checker.md +2 -0
- package/agents/gsd-ui-researcher.md +1 -0
- package/bin/install.js +975 -196
- package/commands/gsd/mempalace-capture.md +27 -1
- package/commands/gsd/surface.md +6 -6
- package/gsd-core/bin/gsd-tools.cjs +63 -2
- package/gsd-core/bin/lib/api-coverage.cjs +3 -4
- package/gsd-core/bin/lib/audit.cjs +7 -6
- package/gsd-core/bin/lib/capability-registry.cjs +503 -87
- package/gsd-core/bin/lib/capability-validator.cjs +56 -18
- package/gsd-core/bin/lib/check-command-router.cjs +1 -1
- package/gsd-core/bin/lib/clock.cjs +19 -0
- package/gsd-core/bin/lib/commands.cjs +48 -9
- package/gsd-core/bin/lib/config-loader.cjs +6 -2
- package/gsd-core/bin/lib/config.cjs +12 -0
- package/gsd-core/bin/lib/core-utils.cjs +8 -2
- package/gsd-core/bin/lib/drift.cjs +4 -4
- package/gsd-core/bin/lib/frontmatter.cjs +22 -0
- package/gsd-core/bin/lib/gsd2-import.cjs +2 -1
- package/gsd-core/bin/lib/host-integration.cjs +33 -8
- package/gsd-core/bin/lib/init.cjs +60 -53
- package/gsd-core/bin/lib/install-engine.cjs +93 -22
- package/gsd-core/bin/lib/installer-migration-authoring.cjs +2 -1
- package/gsd-core/bin/lib/installer-migration-report.cjs +3 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +1 -1
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +342 -0
- package/gsd-core/bin/lib/markdown-table.cjs +698 -0
- package/gsd-core/bin/lib/mcp-server.cjs +18 -7
- package/gsd-core/bin/lib/milestone.cjs +217 -31
- package/gsd-core/bin/lib/phase-command-router.cjs +50 -2
- package/gsd-core/bin/lib/phase-lifecycle.cjs +62 -36
- package/gsd-core/bin/lib/phase-locator.cjs +23 -2
- package/gsd-core/bin/lib/phase.cjs +436 -61
- package/gsd-core/bin/lib/plan-scan.cjs +3 -0
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +24 -7
- package/gsd-core/bin/lib/roadmap-parser.cjs +218 -13
- package/gsd-core/bin/lib/roadmap.cjs +100 -49
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +242 -44
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +3 -2
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +24 -14
- 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 +526 -29
- package/gsd-core/bin/lib/runtime-name-policy.cjs +63 -5
- package/gsd-core/bin/lib/schema-detect.cjs +2 -1
- package/gsd-core/bin/lib/security.cjs +7 -37
- package/gsd-core/bin/lib/shell-command-projection.cjs +176 -27
- package/gsd-core/bin/lib/smart-entry.cjs +4 -3
- package/gsd-core/bin/lib/stale-bake-guard.cjs +30 -10
- package/gsd-core/bin/lib/state-transition.cjs +100 -45
- package/gsd-core/bin/lib/state.cjs +391 -126
- package/gsd-core/bin/lib/surface.cjs +12 -8
- package/gsd-core/bin/lib/template.cjs +2 -1
- package/gsd-core/bin/lib/uat.cjs +54 -8
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +249 -0
- package/gsd-core/bin/lib/ui-safety-gate.cjs +23 -1
- package/gsd-core/bin/lib/verify.cjs +4 -3
- package/gsd-core/bin/lib/workstream.cjs +3 -2
- package/gsd-core/bin/lib/worktree-safety.cjs +1 -1
- package/gsd-core/bin/lib/write-set.cjs +38 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +2 -0
- package/gsd-core/bin/shared/model-catalog.json +8 -3
- package/gsd-core/references/checkpoints.md +12 -0
- 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/add-tests.md +1 -1
- package/gsd-core/workflows/audit-milestone.md +7 -4
- package/gsd-core/workflows/debug.md +2 -0
- package/gsd-core/workflows/execute-phase.md +5 -3
- package/gsd-core/workflows/fast.md +8 -22
- package/gsd-core/workflows/plan-phase.md +6 -0
- package/gsd-core/workflows/progress.md +2 -2
- package/gsd-core/workflows/quick.md +2 -0
- package/gsd-core/workflows/review.md +42 -3
- package/gsd-core/workflows/secure-phase.md +1 -1
- package/gsd-core/workflows/settings-advanced.md +7 -4
- package/gsd-core/workflows/ship.md +8 -2
- package/gsd-core/workflows/spec-phase.md +1 -1
- package/gsd-core/workflows/transition.md +1 -1
- package/gsd-core/workflows/ui-phase.md +146 -1
- package/gsd-core/workflows/validate-phase.md +2 -2
- package/hooks/dist/gsd-statusline.js +164 -14
- 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-statusline.js +164 -14
- 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 +10 -4
- package/pi/gsd.cjs +354 -0
- package/scripts/build-hooks.js +3 -0
- package/scripts/ci-test-scope.cjs +39 -1
- package/scripts/gen-golden-install-parity-zcode.cjs +35 -35
- package/scripts/gen-install-tree-fixtures.cjs +75 -0
- package/scripts/gen-registry.cjs +128 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -1
- package/scripts/lint-table-schema-drift.cjs +157 -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/skills/gsd-mempalace-capture/SKILL.md +27 -1
- package/skills/gsd-surface/SKILL.md +6 -6
- 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
package/bin/install.js
CHANGED
|
@@ -32,6 +32,7 @@ const {
|
|
|
32
32
|
resolveAntigravityGlobalDir,
|
|
33
33
|
getGlobalConfigDir,
|
|
34
34
|
getGlobalSkillsBase,
|
|
35
|
+
resolveKimiHooksTomlDir,
|
|
35
36
|
} = require('../gsd-core/bin/lib/runtime-homes.cjs');
|
|
36
37
|
// getDirName (runtime -> local config dir name) is relocated out of this
|
|
37
38
|
// installer to the runtime-name-policy leaf (ADR-1508 / #1510 Phase 1) so the
|
|
@@ -58,25 +59,20 @@ const INSTALLED_HOOK_FILES = new Set(_HOOKS_TO_COPY);
|
|
|
58
59
|
const hooksSurface = require('../gsd-core/bin/lib/runtime-hooks-surface.cjs');
|
|
59
60
|
|
|
60
61
|
/**
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
62
|
+
* #3677 predicate — true when an agent body needs `/gsd:<cmd>` → `/gsd-<cmd>`
|
|
63
|
+
* normalization at install time. Descriptor-driven
|
|
64
|
+
* (capabilities/<runtime>/capability.json -> runtime.hostBehaviors.hyphenNameAgentBody)
|
|
65
|
+
* instead of a hardcoded runtime allow-list (ADR-1239 / #2086). Sibling fixes
|
|
64
66
|
* #3583 / #3629 covered SKILL.md bodies, #3584 / #3606 covered runtime
|
|
65
67
|
* emissions — this is the agent-body surface (#3677).
|
|
66
68
|
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*/
|
|
71
|
-
const HYPHEN_NAME_AGENT_RUNTIMES = new Set(['claude', 'qwen', 'hermes']);
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* #3677 predicate — true when an agent body needs `/gsd:<cmd>` → `/gsd-<cmd>`
|
|
75
|
-
* normalization at install time.
|
|
69
|
+
* Unknown / future runtimes that don't declare the flag default to "no
|
|
70
|
+
* rewrite" (better to leak than to mangle a runtime whose namespace
|
|
71
|
+
* behavior we haven't verified).
|
|
76
72
|
*/
|
|
77
73
|
function shouldNormalizeHyphenNamespaceInAgentBody(runtime) {
|
|
78
74
|
if (typeof runtime !== 'string' || runtime === '') return false;
|
|
79
|
-
return
|
|
75
|
+
return _hostBehaviors(runtime).hyphenNameAgentBody === true;
|
|
80
76
|
}
|
|
81
77
|
|
|
82
78
|
/**
|
|
@@ -282,6 +278,28 @@ const GSD_CURSOR_HOOK_SCRIPTS = [
|
|
|
282
278
|
// Marker comment embedded in managed hook entries so GSD can find+remove them.
|
|
283
279
|
const GSD_CURSOR_HOOK_MARKER = 'gsd-managed';
|
|
284
280
|
|
|
281
|
+
// #2100 Stage 2 — Windsurf/Cascade lifecycle hook constants.
|
|
282
|
+
// Windsurf/Cascade reads hook configs from <project-root>/.windsurf/hooks.json
|
|
283
|
+
// (local) or ~/.codeium/windsurf/hooks.json (global) with the shape
|
|
284
|
+
// { hooks: { <event>: [ { command, ... } ] } } — note: no top-level `version`
|
|
285
|
+
// field, and each entry carries a bare `command` shell string (no `type`
|
|
286
|
+
// field), unlike Cursor's hooks.json. GSD registers two managed BLOCKING
|
|
287
|
+
// hooks (exit code 2 to block, vs. Cursor's stdout-JSON form):
|
|
288
|
+
// pre_write_code → gsd-windsurf-pre-write.js (write-path guard)
|
|
289
|
+
// pre_run_command → gsd-windsurf-pre-command.js (destructive-command guard)
|
|
290
|
+
// Cascade has no context-injection channel, so the 4 advisory hooks GSD
|
|
291
|
+
// registers on Cursor (sessionStart, postToolUse, stop, subagentStart/Stop)
|
|
292
|
+
// have no Windsurf counterpart and are deliberately NOT ported.
|
|
293
|
+
// Cascade hooks docs (reference): https://docs.windsurf.com/llms-full.txt ,
|
|
294
|
+
// https://docs.devin.ai/desktop/cascade/hooks
|
|
295
|
+
const GSD_WINDSURF_PRE_WRITE_HOOK_SCRIPT = 'gsd-windsurf-pre-write.js';
|
|
296
|
+
const GSD_WINDSURF_PRE_COMMAND_HOOK_SCRIPT = 'gsd-windsurf-pre-command.js';
|
|
297
|
+
// All GSD-managed Windsurf hook scripts (used by uninstall cleanup).
|
|
298
|
+
const GSD_WINDSURF_HOOK_SCRIPTS = [
|
|
299
|
+
GSD_WINDSURF_PRE_WRITE_HOOK_SCRIPT,
|
|
300
|
+
GSD_WINDSURF_PRE_COMMAND_HOOK_SCRIPT,
|
|
301
|
+
];
|
|
302
|
+
|
|
285
303
|
// GSD-managed files under hooks/lib/ (helpers required by gsd-*.sh hooks).
|
|
286
304
|
// git-cmd.js does not start with "gsd-" (shared classifier for #3129), gsd-graphify-rebuild.sh does.
|
|
287
305
|
const GSD_HOOK_LIB_FILES = ['git-cmd.js', 'gsd-graphify-rebuild.sh'];
|
|
@@ -386,7 +404,16 @@ const FALLBACK_HOST_BEHAVIORS = Object.freeze({
|
|
|
386
404
|
settingsFileByScope: Object.freeze({ local: 'settings.local.json', global: 'settings.json' }),
|
|
387
405
|
permissionsSchema: 'claude',
|
|
388
406
|
sourceMarkerFile: '.gsd-source',
|
|
407
|
+
hyphenNameAgentBody: true,
|
|
408
|
+
legacyCommandsGsdInstallMigration: true,
|
|
409
|
+
legacyCommandsGsdUninstall: 'global',
|
|
389
410
|
}),
|
|
411
|
+
// antigravity's global config dir is resolved dynamically (env-overridable,
|
|
412
|
+
// multi-segment) via resolveAntigravityGlobalDir in getConfigDirFromHome. If the
|
|
413
|
+
// registry fails to load, this floor keeps that routing intact instead of
|
|
414
|
+
// silently falling through to the generic getGlobalConfigHomeFragment default
|
|
415
|
+
// (which would return the wrong '.claude' fragment). (ADR-1239 / #2096)
|
|
416
|
+
antigravity: Object.freeze({ globalDirResolver: 'antigravity' }),
|
|
390
417
|
});
|
|
391
418
|
|
|
392
419
|
/**
|
|
@@ -480,6 +507,7 @@ const {
|
|
|
480
507
|
installRuntimeArtifacts,
|
|
481
508
|
uninstallRuntimeArtifacts,
|
|
482
509
|
installOpencodeFamilySkills,
|
|
510
|
+
_installNativePluginIfDeclared,
|
|
483
511
|
_copyStaged,
|
|
484
512
|
hasExistingSymlinkBetween,
|
|
485
513
|
preserveUserArtifacts,
|
|
@@ -529,7 +557,7 @@ if (hasMinimal && _profileArgRaw) {
|
|
|
529
557
|
|
|
530
558
|
function selectRuntimesFromArgs(runtimeArgs) {
|
|
531
559
|
if (runtimeArgs.includes('--all')) {
|
|
532
|
-
return ['claude', 'kimi', 'kilo', 'opencode', 'codex', 'copilot', 'antigravity', 'cursor', 'windsurf', 'augment', 'trae', 'qwen', 'hermes', 'codebuddy', 'cline', 'zcode'];
|
|
560
|
+
return ['claude', 'kimi', 'kilo', 'opencode', 'pi', 'codex', 'copilot', 'antigravity', 'cursor', 'windsurf', 'augment', 'trae', 'qwen', 'hermes', 'codebuddy', 'cline', 'zcode'];
|
|
533
561
|
}
|
|
534
562
|
if (runtimeArgs.includes('--both')) {
|
|
535
563
|
return ['claude', 'opencode'];
|
|
@@ -538,6 +566,7 @@ function selectRuntimesFromArgs(runtimeArgs) {
|
|
|
538
566
|
const selected = [];
|
|
539
567
|
if (runtimeArgs.includes('--claude')) selected.push('claude');
|
|
540
568
|
if (runtimeArgs.includes('--opencode')) selected.push('opencode');
|
|
569
|
+
if (runtimeArgs.includes('--pi')) selected.push('pi');
|
|
541
570
|
if (runtimeArgs.includes('--kilo')) selected.push('kilo');
|
|
542
571
|
if (runtimeArgs.includes('--codex')) selected.push('codex');
|
|
543
572
|
if (runtimeArgs.includes('--copilot')) selected.push('copilot');
|
|
@@ -641,7 +670,15 @@ function getConfigDirFromHome(runtime, isGlobal) {
|
|
|
641
670
|
// multi-segment via resolveAntigravityGlobalDir + path.relative) — not a table
|
|
642
671
|
// entry. (The prior inner `if (!isGlobal) return "'.agents'"` was unreachable:
|
|
643
672
|
// !isGlobal returns at the top of this function.)
|
|
644
|
-
|
|
673
|
+
// Descriptor-driven (ADR-1239 / #2096): folded from a hardcoded
|
|
674
|
+
// `runtime === 'antigravity'` literal into a read of the runtime's
|
|
675
|
+
// `hostBehaviors.globalDirResolver` descriptor field (via _hostBehaviors, which
|
|
676
|
+
// also degrades to FALLBACK_HOST_BEHAVIORS on registry-load failure). This is
|
|
677
|
+
// antigravity-unique: unlike `configHome.kind === 'dot-home-nested'` (which
|
|
678
|
+
// windsurf also declares — see capabilities/windsurf/capability.json — and
|
|
679
|
+
// would wrongly route windsurf's global dir through
|
|
680
|
+
// resolveAntigravityGlobalDir), `globalDirResolver` is only set by antigravity.
|
|
681
|
+
if (_hostBehaviors(runtime).globalDirResolver === 'antigravity') {
|
|
645
682
|
const antigravityDir = resolveAntigravityGlobalDir();
|
|
646
683
|
const rel = path.relative(os.homedir(), antigravityDir);
|
|
647
684
|
const segments = rel.split(path.sep).filter(Boolean);
|
|
@@ -675,7 +712,7 @@ const banner = '\n' +
|
|
|
675
712
|
' GSD Core ' + dim + 'v' + pkg.version + reset + '\n' +
|
|
676
713
|
' Git. Ship. Done.\n' +
|
|
677
714
|
' A meta-prompting, context engineering and spec-driven\n' +
|
|
678
|
-
' development workflows for Claude Code, OpenCode, Kimi CLI, Kilo, Codex, Copilot, Antigravity, Cursor, Windsurf, Augment, Trae, Qwen Code, Hermes Agent, Cline, CodeBuddy and
|
|
715
|
+
' development workflows for Claude Code, OpenCode, Kimi CLI, Kilo, Codex, Copilot, Antigravity, Cursor, Windsurf, Augment, Trae, Qwen Code, Hermes Agent, Cline, CodeBuddy, ZCode and pi.\n';
|
|
679
716
|
|
|
680
717
|
// Pure seam: parse --config-dir / -c from an arbitrary args array.
|
|
681
718
|
// Returns the path string, '' for an empty equals-form value, or null when the
|
|
@@ -756,6 +793,10 @@ const referencesHook = hooksSurface.referencesHook;
|
|
|
756
793
|
// applySettingsJsonHooks: mutates settings.hooks.* in place with all GSD-managed
|
|
757
794
|
// hook registrations for settings.json-surface runtimes (ADR-857 phase 5f-1b).
|
|
758
795
|
const applySettingsJsonHooks = hooksSurface.applySettingsJsonHooks;
|
|
796
|
+
// writeKimiHooksToml / removeKimiHooksToml: kimi's native config.toml [[hooks]]
|
|
797
|
+
// surface (#2095 EoS/kimi Upgrade 1) — separate from settings.json entirely.
|
|
798
|
+
const writeKimiHooksToml = hooksSurface.writeKimiHooksToml;
|
|
799
|
+
const removeKimiHooksToml = hooksSurface.removeKimiHooksToml;
|
|
759
800
|
// processAttribution: pure Co-Authored-By content transform, relocated to the
|
|
760
801
|
// conversion module (ADR-1508 / #1510 Phase 1). Bound here so install.js
|
|
761
802
|
// callers continue to work and there is a single implementation. (All call
|
|
@@ -1098,9 +1139,10 @@ function writeSettings(settingsPath, settings) {
|
|
|
1098
1139
|
* Used by Codex TOML and OpenCode agent file generators to embed per-agent
|
|
1099
1140
|
* model assignments so that model_overrides is respected on non-Claude runtimes (#2256).
|
|
1100
1141
|
*/
|
|
1101
|
-
function readGsdGlobalModelOverrides() {
|
|
1142
|
+
function readGsdGlobalModelOverrides(options = {}) {
|
|
1102
1143
|
try {
|
|
1103
|
-
const
|
|
1144
|
+
const home = options.homedir ? options.homedir() : os.homedir();
|
|
1145
|
+
const defaultsPath = path.join(home, '.gsd', 'defaults.json');
|
|
1104
1146
|
if (!fs.existsSync(defaultsPath)) return null;
|
|
1105
1147
|
const raw = fs.readFileSync(defaultsPath, 'utf-8');
|
|
1106
1148
|
const parsed = JSON.parse(raw);
|
|
@@ -1137,8 +1179,8 @@ function readGsdGlobalModelOverrides() {
|
|
|
1137
1179
|
* Returns a plain `{ agentName: modelId }` object, or `null` when neither
|
|
1138
1180
|
* source defines `model_overrides`.
|
|
1139
1181
|
*/
|
|
1140
|
-
function readGsdEffectiveModelOverrides(targetDir = null) {
|
|
1141
|
-
const global = readGsdGlobalModelOverrides();
|
|
1182
|
+
function readGsdEffectiveModelOverrides(targetDir = null, options = {}) {
|
|
1183
|
+
const global = readGsdGlobalModelOverrides(options);
|
|
1142
1184
|
|
|
1143
1185
|
let projectOverrides = null;
|
|
1144
1186
|
if (targetDir) {
|
|
@@ -1852,10 +1894,12 @@ function convertClaudeCommandToClaudeSkill(content, skillName, runtime = null, c
|
|
|
1852
1894
|
// Track GSD's package version so Hermes' skill_view() reports a stable
|
|
1853
1895
|
// identifier per install.
|
|
1854
1896
|
if (_hostBehaviors(runtime).skillFrontmatterVersion) fm += `version: ${yamlQuote(pkg.version)}\n`;
|
|
1855
|
-
// #778 (b) —
|
|
1856
|
-
//
|
|
1857
|
-
//
|
|
1858
|
-
|
|
1897
|
+
// #778 (b) — numeric priority for /skills ordering, declared on the runtime
|
|
1898
|
+
// descriptor (runtime.hostBehaviors.skillPriorityFrontmatter). Scoped to
|
|
1899
|
+
// runtimes that declare the flag so Claude/Hermes skill frontmatter is
|
|
1900
|
+
// unchanged (they ignore the field, but we keep their output byte-stable).
|
|
1901
|
+
// skillName is the `gsd-<stem>` dir name. (ADR-1239 / #2086)
|
|
1902
|
+
if (_hostBehaviors(runtime).skillPriorityFrontmatter) {
|
|
1859
1903
|
const stem = typeof skillName === 'string' && skillName.startsWith('gsd-')
|
|
1860
1904
|
? skillName.slice(4)
|
|
1861
1905
|
: skillName;
|
|
@@ -1900,6 +1944,14 @@ function convertGsdCommandReferencesToKimiSkillInvocations(content, cmdNames) {
|
|
|
1900
1944
|
.replace(hyphenPattern, (_, cmd) => `/skill:gsd-${cmd}`);
|
|
1901
1945
|
}
|
|
1902
1946
|
|
|
1947
|
+
// DEFECT.GENERATIVE-FIX: this body is mirrored in
|
|
1948
|
+
// src/runtime-artifact-conversion.cts's convertClaudeCommandToKimiSkill (dead
|
|
1949
|
+
// for the live skills-install path, which routes here via
|
|
1950
|
+
// install-engine.cts's SKILLS_CONVERTER_REGISTRY through the kimi capability
|
|
1951
|
+
// descriptor's artifactLayout `converter: "convertClaudeCommandToKimiSkill"`;
|
|
1952
|
+
// kept for bin/install.js's own module-level export/test surface). Neither
|
|
1953
|
+
// copy re-exports the other — mirror any behavior change into both. Guarded
|
|
1954
|
+
// by the output-parity test in tests/runtime-converters.test.cjs (#2095).
|
|
1903
1955
|
function convertClaudeCommandToKimiSkill(content, skillName, _runtime = null, cmdNames = null) {
|
|
1904
1956
|
const { frontmatter, body } = extractFrontmatterAndBody(content);
|
|
1905
1957
|
const kimiSkillName = normalizeKimiSkillName(skillName);
|
|
@@ -2044,6 +2096,16 @@ function buildKimiSubagentYaml({ name, description, tools }) {
|
|
|
2044
2096
|
return `${lines.join('\n')}\n`;
|
|
2045
2097
|
}
|
|
2046
2098
|
|
|
2099
|
+
// DEFECT.GENERATIVE-FIX: this body is mirrored in
|
|
2100
|
+
// src/runtime-artifact-conversion.cts's buildKimiAgentArtifacts (dead for the
|
|
2101
|
+
// live install path, which routes here via runtime-artifact-layout.cts's
|
|
2102
|
+
// kimiAgentsKind — see its `conversionExports['buildKimiAgentArtifacts']`
|
|
2103
|
+
// dynamic lookup against the compiled runtime-artifact-conversion.cjs; kept
|
|
2104
|
+
// for bin/install.js's own module-level export/test surface). Neither copy
|
|
2105
|
+
// re-exports the other — mirror any behavior change into both, including the
|
|
2106
|
+
// kimi_cli.tools.agent:Agent grant that enables background dispatch
|
|
2107
|
+
// (#2095 Upgrade 2). Guarded by the output-parity test in
|
|
2108
|
+
// tests/runtime-converters.test.cjs (#2095).
|
|
2047
2109
|
function buildKimiAgentArtifacts({
|
|
2048
2110
|
rootAgent = '',
|
|
2049
2111
|
subagents = [],
|
|
@@ -2569,14 +2631,6 @@ function convertClaudeAgentToWindsurfAgent(content) {
|
|
|
2569
2631
|
// Augment uses a tool set similar to Cursor/Windsurf.
|
|
2570
2632
|
// Config lives in .augment/ (local) and ~/.augment/ (global).
|
|
2571
2633
|
|
|
2572
|
-
const claudeToAugmentTools = {
|
|
2573
|
-
Bash: 'launch-process',
|
|
2574
|
-
Edit: 'str-replace-editor',
|
|
2575
|
-
AskUserQuestion: null,
|
|
2576
|
-
SlashCommand: null,
|
|
2577
|
-
TodoWrite: 'add_tasks',
|
|
2578
|
-
};
|
|
2579
|
-
|
|
2580
2634
|
// #1675 (ADR-1508): the augment converter family below was a byte-identical
|
|
2581
2635
|
// duplicate of runtime-artifact-conversion.cjs:
|
|
2582
2636
|
// convertSlashCommandsToAugmentSkillMentions, convertClaudeToAugmentMarkdown,
|
|
@@ -2618,6 +2672,14 @@ function convertClaudeToTraeMarkdown(content) {
|
|
|
2618
2672
|
return converted;
|
|
2619
2673
|
}
|
|
2620
2674
|
|
|
2675
|
+
// DEFECT.GENERATIVE-FIX: this body is mirrored in
|
|
2676
|
+
// src/runtime-artifact-conversion.cts's convertClaudeCommandToTraeSkill (used
|
|
2677
|
+
// by src/install-engine.cts's skills-install path via
|
|
2678
|
+
// SKILLS_CONVERTER_REGISTRY). This bin/install.js copy is dead for the live
|
|
2679
|
+
// skills-install path — kept for this file's own module-level export/test
|
|
2680
|
+
// surface. Neither copy re-exports the other — mirror any behavior change
|
|
2681
|
+
// into both. Guarded by the output-parity test in
|
|
2682
|
+
// tests/runtime-converters.test.cjs (#2094).
|
|
2621
2683
|
function convertClaudeCommandToTraeSkill(content, skillName) {
|
|
2622
2684
|
const converted = convertClaudeToTraeMarkdown(content);
|
|
2623
2685
|
const { frontmatter, body } = extractFrontmatterAndBody(converted);
|
|
@@ -2632,7 +2694,16 @@ function convertClaudeCommandToTraeSkill(content, skillName) {
|
|
|
2632
2694
|
const shortDescription = description.length > 180 ? `${description.slice(0, 177)}...` : description;
|
|
2633
2695
|
// #2876: quote so YAML flow indicators (`[BETA] …`) don't break Trae's
|
|
2634
2696
|
// frontmatter parser.
|
|
2635
|
-
|
|
2697
|
+
let fm = `---\nname: ${yamlIdentifier(skillName)}\ndescription: ${yamlQuote(shortDescription)}\n`;
|
|
2698
|
+
// #2094: emit `stage:` so Trae's SOLO agent can auto-invoke GSD skills at
|
|
2699
|
+
// the corresponding stage (docs.trae.ai/ide/agent). The field name/schema
|
|
2700
|
+
// is not formally documented (thin SPA docs) — descriptor-driven, single
|
|
2701
|
+
// fixed GSD-side value (runtime.hostBehaviors.soloStageMetadata), inferred/
|
|
2702
|
+
// best-effort.
|
|
2703
|
+
const soloStage = _hostBehaviors('trae').soloStageMetadata;
|
|
2704
|
+
if (soloStage) fm += `stage: ${soloStage}\n`;
|
|
2705
|
+
fm += '---';
|
|
2706
|
+
return `${fm}\n${body}`;
|
|
2636
2707
|
}
|
|
2637
2708
|
|
|
2638
2709
|
function convertClaudeAgentToTraeAgent(content) {
|
|
@@ -5773,6 +5844,37 @@ function removeCursorHooksJson(targetDir) {
|
|
|
5773
5844
|
return hooksSurface.removeCursorHooksJson(targetDir);
|
|
5774
5845
|
}
|
|
5775
5846
|
|
|
5847
|
+
/**
|
|
5848
|
+
* #2100 Stage 2 — Write GSD-managed Windsurf/Cascade lifecycle hooks into
|
|
5849
|
+
* <targetDir>/hooks.json. Both managed hook scripts
|
|
5850
|
+
* (gsd-windsurf-pre-write.js, gsd-windsurf-pre-command.js) are copied from
|
|
5851
|
+
* the GSD hooks/ source to <targetDir>/hooks/ first, so the hooks.json
|
|
5852
|
+
* entries never reference a script that wasn't installed. Mirrors
|
|
5853
|
+
* writeCursorHooksJson's structure; Cascade's blocking protocol (exit code 2)
|
|
5854
|
+
* and entry shape (bare `command` string, no `type` field) are distinct from
|
|
5855
|
+
* Cursor's.
|
|
5856
|
+
*
|
|
5857
|
+
* @param {string} targetDir - The Windsurf config dir (global: ~/.codeium/windsurf; local: .windsurf)
|
|
5858
|
+
* @param {string} src - The GSD install source root (for copying hook scripts)
|
|
5859
|
+
* @param {{ platform?: string }} opts
|
|
5860
|
+
* @returns {{ hooksJsonPath: string, changed: boolean }}
|
|
5861
|
+
*/
|
|
5862
|
+
function writeWindsurfHooksJson(targetDir, src, opts) {
|
|
5863
|
+
return hooksSurface.writeWindsurfHooksJson(targetDir, src, opts);
|
|
5864
|
+
}
|
|
5865
|
+
|
|
5866
|
+
/**
|
|
5867
|
+
* Remove all GSD-managed Windsurf/Cascade lifecycle hook entries from
|
|
5868
|
+
* hooks.json. User-owned entries are preserved. If the file becomes empty,
|
|
5869
|
+
* it is removed.
|
|
5870
|
+
*
|
|
5871
|
+
* @param {string} targetDir - The Windsurf config dir
|
|
5872
|
+
* @returns {{ changed: boolean }}
|
|
5873
|
+
*/
|
|
5874
|
+
function removeWindsurfHooksJson(targetDir) {
|
|
5875
|
+
return hooksSurface.removeWindsurfHooksJson(targetDir);
|
|
5876
|
+
}
|
|
5877
|
+
|
|
5776
5878
|
/**
|
|
5777
5879
|
* #786 — Build the GSD-managed GitHub Copilot lifecycle hook config object.
|
|
5778
5880
|
*
|
|
@@ -6077,7 +6179,12 @@ function convertClaudeToOpencodeFrontmatter(content, { isAgent = false, modelOve
|
|
|
6077
6179
|
}
|
|
6078
6180
|
|
|
6079
6181
|
// Kilo CLI — same conversion logic as OpenCode, different config paths.
|
|
6080
|
-
|
|
6182
|
+
// DEFECT.GENERATIVE-FIX: this body is mirrored in
|
|
6183
|
+
// src/runtime-artifact-conversion.cts's convertClaudeToKiloFrontmatter (used by
|
|
6184
|
+
// src/install-engine.cts's install path). Neither copy re-exports the other —
|
|
6185
|
+
// mirror any behavior change into both. Guarded by the output-parity test in
|
|
6186
|
+
// tests/runtime-converters.test.cjs (#2093).
|
|
6187
|
+
function convertClaudeToKiloFrontmatter(content, { isAgent = false, modelOverride = null } = {}) {
|
|
6081
6188
|
// Replace tool name references in content (applies to all files)
|
|
6082
6189
|
let convertedContent = content;
|
|
6083
6190
|
convertedContent = convertedContent.replace(/\bAskUserQuestion\b/g, 'question');
|
|
@@ -6236,6 +6343,13 @@ function convertClaudeToKiloFrontmatter(content, { isAgent = false } = {}) {
|
|
|
6236
6343
|
// For agents: add required Kilo agent fields
|
|
6237
6344
|
if (isAgent) {
|
|
6238
6345
|
newLines.push('mode: subagent');
|
|
6346
|
+
// Embed model override from ~/.gsd/defaults.json so model_overrides is
|
|
6347
|
+
// respected on Kilo (which uses static agent frontmatter, not inline
|
|
6348
|
+
// Task() model parameters) — mirrors convertClaudeToOpencodeFrontmatter's
|
|
6349
|
+
// model emission exactly (#2093 UPGRADE 2 / ADR-1239). See #2256.
|
|
6350
|
+
if (modelOverride) {
|
|
6351
|
+
newLines.push(['model:', modelOverride].join(' '));
|
|
6352
|
+
}
|
|
6239
6353
|
newLines.push(...buildKiloAgentPermissionBlock(agentTools));
|
|
6240
6354
|
}
|
|
6241
6355
|
|
|
@@ -6456,33 +6570,53 @@ const RUNTIME_CONTENT_DISPATCH = {
|
|
|
6456
6570
|
return content;
|
|
6457
6571
|
},
|
|
6458
6572
|
},
|
|
6573
|
+
// qwen/hermes: brand VALUES are descriptor-driven (ADR-1239 / #2092) via
|
|
6574
|
+
// _hostBehaviors(ctx.runtime).brandingRewrites — EXACT regexes/ordering
|
|
6575
|
+
// preserved from the prior hardcoded-literal versions (including the
|
|
6576
|
+
// qwen-specific `.claude/skills/` -> `.qwen/skills/` pre-rewrite, whose
|
|
6577
|
+
// target is derived as `${b['.claude/']}skills/`).
|
|
6459
6578
|
qwen: {
|
|
6460
|
-
md: (content) => {
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
|
|
6579
|
+
md: (content, ctx) => {
|
|
6580
|
+
// Guarded (post-review #2092): degrade closed to a no-op if the
|
|
6581
|
+
// registry fails to load, instead of throwing on `b['CLAUDE.md']`.
|
|
6582
|
+
const b = _hostBehaviors(ctx.runtime).brandingRewrites;
|
|
6583
|
+
if (b) {
|
|
6584
|
+
content = content.replace(/CLAUDE\.md/g, b['CLAUDE.md']);
|
|
6585
|
+
content = content.replace(/\bClaude Code\b/g, b['Claude Code']);
|
|
6586
|
+
content = content.replace(/\.claude\//g, b['.claude/']);
|
|
6587
|
+
}
|
|
6464
6588
|
return content;
|
|
6465
6589
|
},
|
|
6466
|
-
js: (content) => {
|
|
6467
|
-
|
|
6468
|
-
|
|
6469
|
-
|
|
6470
|
-
|
|
6590
|
+
js: (content, ctx) => {
|
|
6591
|
+
const b = _hostBehaviors(ctx.runtime).brandingRewrites;
|
|
6592
|
+
if (b) {
|
|
6593
|
+
content = content.replace(/\.claude\/skills\//g, `${b['.claude/']}skills/`);
|
|
6594
|
+
content = content.replace(/\.claude\//g, b['.claude/']);
|
|
6595
|
+
content = content.replace(/CLAUDE\.md/g, b['CLAUDE.md']);
|
|
6596
|
+
content = content.replace(/\bClaude Code\b/g, b['Claude Code']);
|
|
6597
|
+
}
|
|
6471
6598
|
return content;
|
|
6472
6599
|
},
|
|
6473
6600
|
},
|
|
6474
6601
|
hermes: {
|
|
6475
|
-
md: (content) => {
|
|
6476
|
-
|
|
6477
|
-
|
|
6478
|
-
|
|
6602
|
+
md: (content, ctx) => {
|
|
6603
|
+
// Guarded (post-review #2092): see qwen entry above.
|
|
6604
|
+
const b = _hostBehaviors(ctx.runtime).brandingRewrites;
|
|
6605
|
+
if (b) {
|
|
6606
|
+
content = content.replace(/CLAUDE\.md/g, b['CLAUDE.md']);
|
|
6607
|
+
content = content.replace(/\bClaude Code\b/g, b['Claude Code']);
|
|
6608
|
+
content = content.replace(/\.claude\//g, b['.claude/']);
|
|
6609
|
+
}
|
|
6479
6610
|
return content;
|
|
6480
6611
|
},
|
|
6481
|
-
js: (content) => {
|
|
6482
|
-
|
|
6483
|
-
|
|
6484
|
-
|
|
6485
|
-
|
|
6612
|
+
js: (content, ctx) => {
|
|
6613
|
+
const b = _hostBehaviors(ctx.runtime).brandingRewrites;
|
|
6614
|
+
if (b) {
|
|
6615
|
+
content = content.replace(/\.claude\/skills\//g, `${b['.claude/']}skills/`);
|
|
6616
|
+
content = content.replace(/\.claude\//g, b['.claude/']);
|
|
6617
|
+
content = content.replace(/CLAUDE\.md/g, b['CLAUDE.md']);
|
|
6618
|
+
content = content.replace(/\bClaude Code\b/g, b['Claude Code']);
|
|
6619
|
+
}
|
|
6486
6620
|
return content;
|
|
6487
6621
|
},
|
|
6488
6622
|
},
|
|
@@ -6741,25 +6875,21 @@ function validateHookFields(settings) {
|
|
|
6741
6875
|
* GSD hook filenames removed during uninstall.
|
|
6742
6876
|
* Module-level so tests can assert structurally instead of regex-parsing source
|
|
6743
6877
|
* (retires pending-migration-to-typed-ir on hooks-opt-in.test.cjs, per #455).
|
|
6878
|
+
*
|
|
6879
|
+
* Derived from _HOOKS_TO_COPY (scripts/build-hooks.js — the SAME single source
|
|
6880
|
+
* of truth INSTALLED_HOOK_FILES uses for manifest-tracking above) instead of a
|
|
6881
|
+
* separately hand-maintained literal array. The hand-maintained array had
|
|
6882
|
+
* silently drifted out of sync with the install-time set — missing
|
|
6883
|
+
* gsd-check-update-worker.js, gsd-ensure-canonical-path.js,
|
|
6884
|
+
* managed-hooks-registry.cjs, gsd-cursor-pre-tool.js, gsd-cursor-stop.js,
|
|
6885
|
+
* gsd-cursor-subagent-start.js, gsd-cursor-subagent-stop.js, and
|
|
6886
|
+
* gsd-worktree-path-guard.js — so every one of those files (and the hooks/ dir
|
|
6887
|
+
* itself, via the non-empty-dir rmdir guard) was left behind on uninstall for
|
|
6888
|
+
* every settings-json-hook runtime. `gsd-check-update.cmd` is added on top: a
|
|
6889
|
+
* Windows-only SessionStart shim generated at install time (not copied from
|
|
6890
|
+
* hooks/dist/, so it is not in _HOOKS_TO_COPY).
|
|
6744
6891
|
*/
|
|
6745
|
-
const GSD_UNINSTALL_HOOKS = [
|
|
6746
|
-
'gsd-statusline.js',
|
|
6747
|
-
'gsd-check-update.js',
|
|
6748
|
-
'gsd-check-update.cmd',
|
|
6749
|
-
'gsd-config-reload.js',
|
|
6750
|
-
'gsd-context-monitor.js',
|
|
6751
|
-
'gsd-cursor-session-start.js',
|
|
6752
|
-
'gsd-cursor-post-tool.js',
|
|
6753
|
-
'gsd-prompt-guard.js',
|
|
6754
|
-
'gsd-read-guard.js',
|
|
6755
|
-
'gsd-read-injection-scanner.js',
|
|
6756
|
-
'gsd-update-banner.js',
|
|
6757
|
-
'gsd-workflow-guard.js',
|
|
6758
|
-
'gsd-session-state.sh',
|
|
6759
|
-
'gsd-validate-commit.sh',
|
|
6760
|
-
'gsd-phase-boundary.sh',
|
|
6761
|
-
'gsd-graphify-update.sh',
|
|
6762
|
-
];
|
|
6892
|
+
const GSD_UNINSTALL_HOOKS = [..._HOOKS_TO_COPY, 'gsd-check-update.cmd'];
|
|
6763
6893
|
|
|
6764
6894
|
/**
|
|
6765
6895
|
* Uninstall GSD from the specified directory for a specific runtime
|
|
@@ -6768,7 +6898,19 @@ const GSD_UNINSTALL_HOOKS = [
|
|
|
6768
6898
|
* @param {string} runtime - Target runtime ('claude', 'opencode', 'codex', 'copilot')
|
|
6769
6899
|
*/
|
|
6770
6900
|
function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
|
6771
|
-
|
|
6901
|
+
// #2093: isKilo dropped — the Kilo permission-cleanup branch below is
|
|
6902
|
+
// descriptor-driven (resolveInstallPlan(runtime).finishPermissionWriter),
|
|
6903
|
+
// not gated on this flag.
|
|
6904
|
+
// #2094: isTrae dropped — unused in this function after the
|
|
6905
|
+
// skipSharedHooksInstall fold (was never referenced here besides the
|
|
6906
|
+
// destructure). #2095: isKimi likewise dropped — kimi is now a hooks/
|
|
6907
|
+
// consumer, so its former `&& !isKimi` uninstall guards were removed.
|
|
6908
|
+
// #2096: isAntigravity dropped — unused in this function.
|
|
6909
|
+
// #2098: isCodebuddy dropped — unused in this function.
|
|
6910
|
+
// #2099: isCopilot dropped — both Copilot side-effect branches below are now
|
|
6911
|
+
// gated on resolveInstallPlan(runtime).installSurface === 'copilot-instructions'.
|
|
6912
|
+
// #2100: isWindsurf dropped — unused in this function.
|
|
6913
|
+
const { isOpencode, isCodex, isCursor, isAugment, isQwen, isHermes, isCline } = runtimeFlags(runtime);
|
|
6772
6914
|
const dirName = getDirName(runtime);
|
|
6773
6915
|
|
|
6774
6916
|
// Get the target directory based on runtime and install type. Cline local
|
|
@@ -6797,7 +6939,13 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
|
|
6797
6939
|
// #786: AGENTS.md lives at the repo root (outside targetDir) for local Copilot
|
|
6798
6940
|
// installs, so its cleanup must run even when .github (targetDir) was already
|
|
6799
6941
|
// removed — i.e. BEFORE the "target directory missing" early-return below.
|
|
6800
|
-
|
|
6942
|
+
// #2099: descriptor-driven via resolveInstallPlan(runtime).installSurface ===
|
|
6943
|
+
// 'copilot-instructions' (was hardcoded `isCopilot`). Mirrors the install-time
|
|
6944
|
+
// gate at the 'copilot-instructions' branch below (~line 10471 equivalent),
|
|
6945
|
+
// which writes this same repo-root AGENTS.md only for local ('!isGlobal')
|
|
6946
|
+
// installs — 'copilot-instructions' is unique to copilot's descriptor, so
|
|
6947
|
+
// this is byte-parity.
|
|
6948
|
+
if (resolveInstallPlan(runtime).installSurface === 'copilot-instructions' && !isGlobal) {
|
|
6801
6949
|
const agentsMdPath = path.join(process.cwd(), 'AGENTS.md');
|
|
6802
6950
|
if (fs.existsSync(agentsMdPath)) {
|
|
6803
6951
|
const content = fs.readFileSync(agentsMdPath, 'utf8');
|
|
@@ -6907,8 +7055,84 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
|
|
6907
7055
|
}
|
|
6908
7056
|
}
|
|
6909
7057
|
|
|
7058
|
+
// 1a-kimi. Non-layout Kimi side-effect (#2095 EoS/kimi Upgrade 1): kimi's
|
|
7059
|
+
// native config.toml lives outside targetDir entirely (resolveKimiHooksTomlDir
|
|
7060
|
+
// resolves ~/.kimi, a sibling of targetDir's ~/.config/agents), so its
|
|
7061
|
+
// cleanup can't be driven by anything under targetDir the way every other
|
|
7062
|
+
// hook surface above is.
|
|
7063
|
+
if (resolveInstallPlan(runtime).hooksSurface === 'kimi-hooks-toml') {
|
|
7064
|
+
const kimiHooksRoot = resolveKimiHooksTomlDir();
|
|
7065
|
+
const kimiHooksTomlPath = path.join(kimiHooksRoot, 'config.toml');
|
|
7066
|
+
const kimiHooksCleanup = removeKimiHooksToml(kimiHooksTomlPath);
|
|
7067
|
+
if (kimiHooksCleanup.changed) {
|
|
7068
|
+
removedCount++;
|
|
7069
|
+
console.log(` ${green}✓${reset} Removed GSD hooks from ${kimiHooksTomlPath}`);
|
|
7070
|
+
}
|
|
7071
|
+
|
|
7072
|
+
// Kimi's shared hook scripts + CommonJS package.json marker are installed
|
|
7073
|
+
// into this SAME ~/.kimi root (installSharedHooksBundle, install()'s
|
|
7074
|
+
// kimi-hooks-toml branch) rather than under targetDir — mirror steps "4.
|
|
7075
|
+
// Remove GSD hooks" / "5. Remove GSD package.json" below, but scoped to
|
|
7076
|
+
// kimiHooksRoot. ~/.kimi is Kimi's own native config home (shared space —
|
|
7077
|
+
// may hold the user's real config.toml/providers), so only the exact
|
|
7078
|
+
// GSD-owned filenames are removed, and directories are pruned only if left
|
|
7079
|
+
// empty by that removal.
|
|
7080
|
+
const kimiHooksDir = path.join(kimiHooksRoot, 'hooks');
|
|
7081
|
+
if (fs.existsSync(kimiHooksDir)) {
|
|
7082
|
+
let kimiHookCount = 0;
|
|
7083
|
+
for (const hook of GSD_UNINSTALL_HOOKS) {
|
|
7084
|
+
const hookPath = path.join(kimiHooksDir, hook);
|
|
7085
|
+
if (fs.existsSync(hookPath)) {
|
|
7086
|
+
fs.unlinkSync(hookPath);
|
|
7087
|
+
kimiHookCount++;
|
|
7088
|
+
}
|
|
7089
|
+
}
|
|
7090
|
+
if (kimiHookCount > 0) {
|
|
7091
|
+
removedCount++;
|
|
7092
|
+
console.log(` ${green}✓${reset} Removed ${kimiHookCount} GSD hooks from ${kimiHooksDir}`);
|
|
7093
|
+
}
|
|
7094
|
+
|
|
7095
|
+
const kimiHooksLibDir = path.join(kimiHooksDir, 'lib');
|
|
7096
|
+
if (fs.existsSync(kimiHooksLibDir)) {
|
|
7097
|
+
let removedKimiLibFiles = 0;
|
|
7098
|
+
for (const file of GSD_HOOK_LIB_FILES) {
|
|
7099
|
+
try {
|
|
7100
|
+
fs.unlinkSync(path.join(kimiHooksLibDir, file));
|
|
7101
|
+
removedKimiLibFiles++;
|
|
7102
|
+
} catch (_) { /* best-effort */ }
|
|
7103
|
+
}
|
|
7104
|
+
try { fs.rmdirSync(kimiHooksLibDir); } catch (_) { /* not empty or other error — leave it */ }
|
|
7105
|
+
if (removedKimiLibFiles > 0) {
|
|
7106
|
+
removedCount++;
|
|
7107
|
+
console.log(` ${green}✓${reset} Removed ${removedKimiLibFiles} hooks/lib/ helper(s) from ${kimiHooksLibDir}`);
|
|
7108
|
+
}
|
|
7109
|
+
}
|
|
7110
|
+
|
|
7111
|
+
try {
|
|
7112
|
+
if (fs.readdirSync(kimiHooksDir).length === 0) fs.rmdirSync(kimiHooksDir);
|
|
7113
|
+
} catch (_) { /* not empty — leave it */ }
|
|
7114
|
+
}
|
|
7115
|
+
|
|
7116
|
+
const kimiPkgJsonPath = path.join(kimiHooksRoot, 'package.json');
|
|
7117
|
+
if (fs.existsSync(kimiPkgJsonPath)) {
|
|
7118
|
+
try {
|
|
7119
|
+
const content = fs.readFileSync(kimiPkgJsonPath, 'utf8').trim();
|
|
7120
|
+
if (content === '{"type":"commonjs"}') {
|
|
7121
|
+
fs.unlinkSync(kimiPkgJsonPath);
|
|
7122
|
+
removedCount++;
|
|
7123
|
+
console.log(` ${green}✓${reset} Removed GSD package.json from ${kimiHooksRoot}`);
|
|
7124
|
+
}
|
|
7125
|
+
} catch (e) {
|
|
7126
|
+
// Ignore read errors
|
|
7127
|
+
}
|
|
7128
|
+
}
|
|
7129
|
+
}
|
|
7130
|
+
|
|
6910
7131
|
// 1b. Non-layout Copilot side-effect: copilot-instructions.md cleanup
|
|
6911
|
-
|
|
7132
|
+
// #2099: descriptor-driven via resolveInstallPlan(runtime).installSurface ===
|
|
7133
|
+
// 'copilot-instructions' (was hardcoded `isCopilot`), mirroring the same
|
|
7134
|
+
// gate used at the install-time 'copilot-instructions' branch.
|
|
7135
|
+
if (resolveInstallPlan(runtime).installSurface === 'copilot-instructions') {
|
|
6912
7136
|
const instructionsPath = path.join(targetDir, 'copilot-instructions.md');
|
|
6913
7137
|
if (fs.existsSync(instructionsPath)) {
|
|
6914
7138
|
const content = fs.readFileSync(instructionsPath, 'utf8');
|
|
@@ -7023,6 +7247,38 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
|
|
7023
7247
|
} catch { /* best-effort */ }
|
|
7024
7248
|
}
|
|
7025
7249
|
|
|
7250
|
+
// 1b-windsurf. Descriptor-driven hook-bus cleanup (ADR-1239 / #2100 Stage 2):
|
|
7251
|
+
// remove GSD-managed Cascade hook entries from hooks.json and clean up the
|
|
7252
|
+
// managed hook scripts. Gated on resolveInstallPlan(runtime).hooksSurface
|
|
7253
|
+
// === 'windsurf-hooks-json' (mirrors the kimi-hooks-toml gate above) —
|
|
7254
|
+
// NOT the shared hostBehaviors.hooksJsonSurface flag the Cursor block above
|
|
7255
|
+
// uses, since that flag drives Cursor's own remove function + script list
|
|
7256
|
+
// and is not (and must not be) set for Windsurf.
|
|
7257
|
+
if (resolveInstallPlan(runtime).hooksSurface === 'windsurf-hooks-json') {
|
|
7258
|
+
const windsurfHooksJsonCleanup = removeWindsurfHooksJson(targetDir);
|
|
7259
|
+
if (windsurfHooksJsonCleanup.changed) {
|
|
7260
|
+
removedCount++;
|
|
7261
|
+
console.log(` ${green}✓${reset} Removed GSD-managed Windsurf hooks from hooks.json`);
|
|
7262
|
+
}
|
|
7263
|
+
// Remove all GSD-managed hook scripts (pre_write_code, pre_run_command).
|
|
7264
|
+
const windsurfHooksDir = path.join(targetDir, 'hooks');
|
|
7265
|
+
for (const script of GSD_WINDSURF_HOOK_SCRIPTS) {
|
|
7266
|
+
const p = path.join(windsurfHooksDir, script);
|
|
7267
|
+
try {
|
|
7268
|
+
if (fs.existsSync(p)) {
|
|
7269
|
+
fs.unlinkSync(p);
|
|
7270
|
+
removedCount++;
|
|
7271
|
+
}
|
|
7272
|
+
} catch { /* best-effort */ }
|
|
7273
|
+
}
|
|
7274
|
+
// Prune hooks/ if empty.
|
|
7275
|
+
try {
|
|
7276
|
+
if (fs.existsSync(windsurfHooksDir) && fs.readdirSync(windsurfHooksDir).length === 0) {
|
|
7277
|
+
fs.rmdirSync(windsurfHooksDir);
|
|
7278
|
+
}
|
|
7279
|
+
} catch { /* best-effort */ }
|
|
7280
|
+
}
|
|
7281
|
+
|
|
7026
7282
|
// 1c. Claude local: remove flat gsd-*.md commands from commands/ (current layout,
|
|
7027
7283
|
// #1367 fix). Also remove legacy commands/gsd/ subdirectory from prior installs.
|
|
7028
7284
|
if (!isGlobal && _hostBehaviors(runtime).localInstallStyle === 'legacy-flat') {
|
|
@@ -7067,7 +7323,7 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
|
|
7067
7323
|
// removes the directory; we must preserve/restore user artifacts before that path.
|
|
7068
7324
|
// This block runs AFTER uninstallRuntimeArtifacts, so we check if the directory
|
|
7069
7325
|
// was already removed and skip if so (idempotent).
|
|
7070
|
-
if (
|
|
7326
|
+
if (_hostBehaviors(runtime).legacyCommandsGsdCleanup === true) {
|
|
7071
7327
|
// dev-preferences may have survived in skills/ as SKILL.md — nothing to do for
|
|
7072
7328
|
// that case. If a stale commands/gsd/ still exists (e.g. legacy was not removed),
|
|
7073
7329
|
// attempt migration. In practice _runLegacyUninstallCleanup removes it first,
|
|
@@ -7177,9 +7433,11 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
|
|
7177
7433
|
}
|
|
7178
7434
|
}
|
|
7179
7435
|
|
|
7180
|
-
// 4z. Remove the
|
|
7181
|
-
//
|
|
7182
|
-
//
|
|
7436
|
+
// 4z. Remove the native plugin adapter (#1914, extended to Kilo by #2093).
|
|
7437
|
+
// Descriptor-driven via hostBehaviors.nativePlugin — covers every runtime
|
|
7438
|
+
// that declares the block (OpenCode, Kilo, ...), not just OpenCode. Only
|
|
7439
|
+
// GSD's own plugin file is removed; the plugins/ dir is pruned only if it
|
|
7440
|
+
// becomes empty, preserving any user-authored plugins for that host.
|
|
7183
7441
|
const _np = _hostBehaviors(runtime).nativePlugin;
|
|
7184
7442
|
if (_np) {
|
|
7185
7443
|
const pluginsDir = path.join(targetDir, _np.dir);
|
|
@@ -7188,7 +7446,7 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
|
|
7188
7446
|
try {
|
|
7189
7447
|
fs.unlinkSync(pluginPath);
|
|
7190
7448
|
removedCount++;
|
|
7191
|
-
console.log(` ${green}✓${reset} Removed
|
|
7449
|
+
console.log(` ${green}✓${reset} Removed native plugin adapter (${runtime})`);
|
|
7192
7450
|
} catch (_) { /* best-effort */ }
|
|
7193
7451
|
try { fs.rmdirSync(pluginsDir); } catch (_) { /* not empty — user plugins present */ }
|
|
7194
7452
|
}
|
|
@@ -7355,6 +7613,47 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
|
|
7355
7613
|
}
|
|
7356
7614
|
}
|
|
7357
7615
|
|
|
7616
|
+
// #2096 Phase B Upgrade 1 — Remove GSD-owned Antigravity permissions.allow
|
|
7617
|
+
// rules from settings.json. Symmetric to the Claude branch above: filters
|
|
7618
|
+
// only the exact GSD-owned rule strings (regenerated from the current
|
|
7619
|
+
// configDir) to preserve any user-added allow entries and all deny/ask.
|
|
7620
|
+
if (resolveInstallPlan(runtime).finishPermissionWriter === 'antigravity' && settings.permissions) {
|
|
7621
|
+
let antigravityPermissionsModified = false;
|
|
7622
|
+
if (Array.isArray(settings.permissions.allow)) {
|
|
7623
|
+
const gsdRules = new Set(buildAntigravityAllowRules(targetDir));
|
|
7624
|
+
const before = settings.permissions.allow.length;
|
|
7625
|
+
settings.permissions.allow = settings.permissions.allow.filter((e) => !gsdRules.has(e));
|
|
7626
|
+
if (settings.permissions.allow.length !== before) {
|
|
7627
|
+
antigravityPermissionsModified = true;
|
|
7628
|
+
}
|
|
7629
|
+
if (settings.permissions.allow.length === 0) {
|
|
7630
|
+
delete settings.permissions.allow;
|
|
7631
|
+
}
|
|
7632
|
+
}
|
|
7633
|
+
if (Object.keys(settings.permissions).length === 0) {
|
|
7634
|
+
delete settings.permissions;
|
|
7635
|
+
}
|
|
7636
|
+
if (antigravityPermissionsModified) {
|
|
7637
|
+
settingsModified = true;
|
|
7638
|
+
console.log(` ${green}✓${reset} Removed GSD permissions from settings.json`);
|
|
7639
|
+
}
|
|
7640
|
+
}
|
|
7641
|
+
|
|
7642
|
+
// #2097 UPGRADE 3 — Remove the MCP companion entry from settings.json for
|
|
7643
|
+
// runtimes that host MCP there (Augment), symmetric to the mcp_config.json
|
|
7644
|
+
// removal for Antigravity below. Only the GSD-owned mcpServers.gsd key is
|
|
7645
|
+
// removed — any other user-configured MCP servers are preserved.
|
|
7646
|
+
if (_hostBehaviors(runtime).mcpCompanion === 'settings-json' &&
|
|
7647
|
+
settings.mcpServers && typeof settings.mcpServers === 'object' &&
|
|
7648
|
+
settings.mcpServers.gsd !== undefined) {
|
|
7649
|
+
delete settings.mcpServers.gsd;
|
|
7650
|
+
if (Object.keys(settings.mcpServers).length === 0) {
|
|
7651
|
+
delete settings.mcpServers;
|
|
7652
|
+
}
|
|
7653
|
+
settingsModified = true;
|
|
7654
|
+
console.log(` ${green}✓${reset} Removed GSD MCP companion server from settings.json`);
|
|
7655
|
+
}
|
|
7656
|
+
|
|
7358
7657
|
if (settingsModified) {
|
|
7359
7658
|
writeSettings(settingsPath, settings);
|
|
7360
7659
|
removedCount++;
|
|
@@ -7403,7 +7702,9 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
|
|
7403
7702
|
}
|
|
7404
7703
|
|
|
7405
7704
|
// 7. For Kilo, clean up permissions from kilo.json or kilo.jsonc
|
|
7406
|
-
|
|
7705
|
+
// #2093: descriptor-driven via resolveInstallPlan(runtime).finishPermissionWriter,
|
|
7706
|
+
// mirroring the OpenCode branch above (was hardcoded `isKilo`).
|
|
7707
|
+
if (resolveInstallPlan(runtime).finishPermissionWriter === 'kilo') {
|
|
7407
7708
|
const configPath = resolveKiloConfigPath(targetDir);
|
|
7408
7709
|
if (fs.existsSync(configPath)) {
|
|
7409
7710
|
try {
|
|
@@ -7443,6 +7744,29 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
|
|
7443
7744
|
}
|
|
7444
7745
|
}
|
|
7445
7746
|
|
|
7747
|
+
// 8. For Antigravity, remove the MCP companion entry from mcp_config.json
|
|
7748
|
+
// (#2096 Phase B Upgrade 2). Only the GSD-owned mcpServers.gsd key is
|
|
7749
|
+
// removed — any other user-configured MCP servers are preserved.
|
|
7750
|
+
if (resolveInstallPlan(runtime).finishPermissionWriter === 'antigravity') {
|
|
7751
|
+
const mcpConfigPath = path.join(targetDir, 'mcp_config.json');
|
|
7752
|
+
if (fs.existsSync(mcpConfigPath)) {
|
|
7753
|
+
try {
|
|
7754
|
+
const mcpConfig = JSON.parse(fs.readFileSync(mcpConfigPath, 'utf8'));
|
|
7755
|
+
if (mcpConfig && typeof mcpConfig === 'object' && mcpConfig.mcpServers && mcpConfig.mcpServers.gsd !== undefined) {
|
|
7756
|
+
delete mcpConfig.mcpServers.gsd;
|
|
7757
|
+
if (Object.keys(mcpConfig.mcpServers).length === 0) {
|
|
7758
|
+
delete mcpConfig.mcpServers;
|
|
7759
|
+
}
|
|
7760
|
+
fs.writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2) + '\n');
|
|
7761
|
+
removedCount++;
|
|
7762
|
+
console.log(` ${green}✓${reset} Removed GSD MCP companion server from mcp_config.json`);
|
|
7763
|
+
}
|
|
7764
|
+
} catch (e) {
|
|
7765
|
+
// Ignore JSON parse errors
|
|
7766
|
+
}
|
|
7767
|
+
}
|
|
7768
|
+
}
|
|
7769
|
+
|
|
7446
7770
|
// Remove the file manifest that the installer wrote at install time.
|
|
7447
7771
|
// Without this step the metadata file persists after uninstall (#1908).
|
|
7448
7772
|
const manifestPath = path.join(targetDir, MANIFEST_NAME);
|
|
@@ -7696,6 +8020,193 @@ function configureKiloPermissions(isGlobal = true, configDir = null) {
|
|
|
7696
8020
|
console.log(` ${green}✓${reset} Configured read permission for GSD docs`);
|
|
7697
8021
|
}
|
|
7698
8022
|
|
|
8023
|
+
/**
|
|
8024
|
+
* Convert an absolute path to a `~`-relative form when it lives under the
|
|
8025
|
+
* user's home directory (generalizes configureKiloPermissions'
|
|
8026
|
+
* single-default-dir shorthand to Antigravity's three probed sibling config
|
|
8027
|
+
* dirs — antigravity/antigravity-ide/antigravity-cli under ~/.gemini — none of
|
|
8028
|
+
* which is a single fixed "default").
|
|
8029
|
+
*/
|
|
8030
|
+
function toTildePosixPath(absPath) {
|
|
8031
|
+
const posixPath = absPath.replace(/\\/g, '/');
|
|
8032
|
+
const posixHome = os.homedir().replace(/\\/g, '/');
|
|
8033
|
+
return posixPath === posixHome || posixPath.startsWith(`${posixHome}/`)
|
|
8034
|
+
? `~${posixPath.slice(posixHome.length)}`
|
|
8035
|
+
: posixPath;
|
|
8036
|
+
}
|
|
8037
|
+
|
|
8038
|
+
/**
|
|
8039
|
+
* Antigravity permission rule strings this installer contributes.
|
|
8040
|
+
* Schema: antigravity.google/docs/cli/permissions — "action(target)" rule
|
|
8041
|
+
* strings in permissions.{allow,deny,ask}, evaluated deny > ask > allow. GSD
|
|
8042
|
+
* only ever contributes to `allow` — never deny/ask (those are user-owned risk
|
|
8043
|
+
* decisions this installer has no business making).
|
|
8044
|
+
*/
|
|
8045
|
+
function buildAntigravityAllowRules(configDir) {
|
|
8046
|
+
const gsdPath = toTildePosixPath(configDir);
|
|
8047
|
+
return [
|
|
8048
|
+
`read_file(${gsdPath}/gsd-core/*)`,
|
|
8049
|
+
`read_file(${gsdPath}/agents/gsd-*)`,
|
|
8050
|
+
`read_file(${gsdPath}/skills/gsd-*)`,
|
|
8051
|
+
`command(node ${gsdPath}/hooks/*)`,
|
|
8052
|
+
];
|
|
8053
|
+
}
|
|
8054
|
+
|
|
8055
|
+
/**
|
|
8056
|
+
* Configure Antigravity permissions to allow reading/executing GSD's installed
|
|
8057
|
+
* tree without per-call approval prompts (#2096 Phase B Upgrade 1 — mirrors
|
|
8058
|
+
* configureKiloPermissions/configureOpencodePermissions).
|
|
8059
|
+
*
|
|
8060
|
+
* Antigravity's permission schema (antigravity.google/docs/cli/permissions) is
|
|
8061
|
+
* `{"permissions":{"allow":[...],"deny":[...],"ask":[...]}}`, living in the
|
|
8062
|
+
* SAME settings.json GSD's own hook registration writes for this runtime
|
|
8063
|
+
* (installSurface: 'settings-json', writesSharedSettings: true) — unlike
|
|
8064
|
+
* Kilo/OpenCode, which write a separate native config file. This function
|
|
8065
|
+
* re-reads the file (already containing GSD's hooks by the time finishInstall
|
|
8066
|
+
* reaches this call) and only appends to permissions.allow.
|
|
8067
|
+
*
|
|
8068
|
+
* Non-destructive + idempotent: only `permissions.allow` is touched; an
|
|
8069
|
+
* existing user permissions block (including any deny/ask entries, or
|
|
8070
|
+
* unrelated allow entries) is preserved untouched.
|
|
8071
|
+
*
|
|
8072
|
+
* @param {boolean} isGlobal - Whether this is a global or local install
|
|
8073
|
+
* @param {string|null} configDir - Resolved config directory when already known
|
|
8074
|
+
*/
|
|
8075
|
+
function configureAntigravityPermissions(isGlobal = true, configDir = null) {
|
|
8076
|
+
// For local installs, use ./.agents/ (GSD's antigravity localConfigDir)
|
|
8077
|
+
// For global installs, use the resolved ~/.gemini/antigravity{,-ide,-cli}
|
|
8078
|
+
const antigravityConfigDir = configDir || (isGlobal
|
|
8079
|
+
? getGlobalConfigDir('antigravity', explicitConfigDir)
|
|
8080
|
+
: path.join(process.cwd(), '.agents'));
|
|
8081
|
+
// Ensure config directory exists
|
|
8082
|
+
fs.mkdirSync(antigravityConfigDir, { recursive: true });
|
|
8083
|
+
|
|
8084
|
+
const configPath = path.join(antigravityConfigDir, 'settings.json');
|
|
8085
|
+
|
|
8086
|
+
// Read existing settings.json (readSettings tolerates JSONC + missing file;
|
|
8087
|
+
// returns null — and warns — only when the file exists but fails to parse).
|
|
8088
|
+
const config = readSettings(configPath);
|
|
8089
|
+
if (config === null) {
|
|
8090
|
+
// Cannot parse — DO NOT overwrite user's config (readSettings already warned).
|
|
8091
|
+
return;
|
|
8092
|
+
}
|
|
8093
|
+
|
|
8094
|
+
// Ensure permission structure exists
|
|
8095
|
+
if (!config.permissions || typeof config.permissions !== 'object' || Array.isArray(config.permissions)) {
|
|
8096
|
+
config.permissions = {};
|
|
8097
|
+
}
|
|
8098
|
+
if (!Array.isArray(config.permissions.allow)) {
|
|
8099
|
+
config.permissions.allow = [];
|
|
8100
|
+
}
|
|
8101
|
+
|
|
8102
|
+
let modified = false;
|
|
8103
|
+
for (const rule of buildAntigravityAllowRules(antigravityConfigDir)) {
|
|
8104
|
+
if (!config.permissions.allow.includes(rule)) {
|
|
8105
|
+
config.permissions.allow.push(rule);
|
|
8106
|
+
modified = true;
|
|
8107
|
+
}
|
|
8108
|
+
}
|
|
8109
|
+
|
|
8110
|
+
if (!modified) {
|
|
8111
|
+
return; // Already configured
|
|
8112
|
+
}
|
|
8113
|
+
|
|
8114
|
+
writeSettings(configPath, config);
|
|
8115
|
+
console.log(` ${green}✓${reset} Configured Antigravity permissions for GSD paths`);
|
|
8116
|
+
}
|
|
8117
|
+
|
|
8118
|
+
/**
|
|
8119
|
+
* Configure Antigravity's MCP companion server config (#2096 Phase B
|
|
8120
|
+
* Upgrade 2).
|
|
8121
|
+
*
|
|
8122
|
+
* Antigravity CLI manages MCP servers via standalone `mcp_config.json`
|
|
8123
|
+
* profiles rather than nesting them in settings.json (antigravity.google/docs/
|
|
8124
|
+
* cli/gcli-migration: "Antigravity CLI uses standalone mcp_config.json
|
|
8125
|
+
* profiles in ~/.gemini/config/ for global servers and .agents/mcp_config.json
|
|
8126
|
+
* for workspace servers"). The raw schema for the Antigravity IDE surface
|
|
8127
|
+
* itself is unpublished (docs are JS-rendered), so this follows the CLI's
|
|
8128
|
+
* documented standalone-profile convention plus the standard Gemini/MCP
|
|
8129
|
+
* `mcpServers` shape.
|
|
8130
|
+
*
|
|
8131
|
+
* BEST-EFFORT PATH CHOICE: rather than the CLI doc's separate `~/.gemini/config/`
|
|
8132
|
+
* directory for global scope, this writes `<configDir>/mcp_config.json` — the
|
|
8133
|
+
* SAME resolved configDir as settings.json (configureAntigravityPermissions) —
|
|
8134
|
+
* because (1) GSD's own antigravity configDir resolution already varies
|
|
8135
|
+
* per-user across three sibling dirs (antigravity/antigravity-ide/
|
|
8136
|
+
* antigravity-cli — see resolveAntigravityGlobalDir), so a hardcoded separate
|
|
8137
|
+
* shared path would not track that resolution, and (2) it matches the doc's
|
|
8138
|
+
* OWN workspace-scope convention exactly (`.agents/mcp_config.json`, which IS
|
|
8139
|
+
* GSD's local configDir for antigravity), keeping global/local symmetric and
|
|
8140
|
+
* consistent with the configDir-relative convention every other GSD
|
|
8141
|
+
* permission writer (kilo/opencode) already uses.
|
|
8142
|
+
*
|
|
8143
|
+
* Non-destructive + idempotent: only adds mcpServers.gsd when entirely absent;
|
|
8144
|
+
* any other user-configured mcpServers entries (or a user's OWN "gsd" override)
|
|
8145
|
+
* are preserved untouched (Hyrum's Law — mirrors OpenCode's config.mcp.gsd guard).
|
|
8146
|
+
*
|
|
8147
|
+
* @param {boolean} isGlobal - Whether this is a global or local install
|
|
8148
|
+
* @param {string|null} configDir - Resolved config directory when already known
|
|
8149
|
+
*/
|
|
8150
|
+
function configureAntigravityMcpConfig(isGlobal = true, configDir = null) {
|
|
8151
|
+
const antigravityConfigDir = configDir || (isGlobal
|
|
8152
|
+
? getGlobalConfigDir('antigravity', explicitConfigDir)
|
|
8153
|
+
: path.join(process.cwd(), '.agents'));
|
|
8154
|
+
fs.mkdirSync(antigravityConfigDir, { recursive: true });
|
|
8155
|
+
|
|
8156
|
+
const configPath = path.join(antigravityConfigDir, 'mcp_config.json');
|
|
8157
|
+
|
|
8158
|
+
let config = {};
|
|
8159
|
+
if (fs.existsSync(configPath)) {
|
|
8160
|
+
try {
|
|
8161
|
+
const parsed = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
8162
|
+
config = (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) ? parsed : {};
|
|
8163
|
+
} catch (e) {
|
|
8164
|
+
// Cannot parse - DO NOT overwrite user's config
|
|
8165
|
+
console.log(` ${yellow}⚠${reset} Could not parse mcp_config.json - skipping MCP companion config`);
|
|
8166
|
+
console.log(` ${dim}Reason: ${e.message}${reset}`);
|
|
8167
|
+
console.log(` ${dim}Your config was NOT modified. Fix the syntax manually if needed.${reset}`);
|
|
8168
|
+
return;
|
|
8169
|
+
}
|
|
8170
|
+
}
|
|
8171
|
+
|
|
8172
|
+
if (!config.mcpServers || typeof config.mcpServers !== 'object' || Array.isArray(config.mcpServers)) {
|
|
8173
|
+
config.mcpServers = {};
|
|
8174
|
+
}
|
|
8175
|
+
|
|
8176
|
+
if (config.mcpServers.gsd !== undefined) {
|
|
8177
|
+
return; // Already configured (or a user-owned override) — never clobber.
|
|
8178
|
+
}
|
|
8179
|
+
|
|
8180
|
+
config.mcpServers.gsd = {
|
|
8181
|
+
command: 'npx',
|
|
8182
|
+
args: ['-y', '-p', PACKAGE_NAME, 'gsd-mcp-server'],
|
|
8183
|
+
};
|
|
8184
|
+
|
|
8185
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
8186
|
+
console.log(` ${green}✓${reset} Configured Antigravity MCP companion server (gsd)`);
|
|
8187
|
+
}
|
|
8188
|
+
|
|
8189
|
+
/**
|
|
8190
|
+
* #2097 (ADR-1239 transport:mcp): register the GSD companion MCP server inside a
|
|
8191
|
+
* runtime's settings.json (Augment hosts MCP in settings.json.mcpServers, unlike
|
|
8192
|
+
* Antigravity's standalone mcp_config.json). Mutates the in-memory settings object
|
|
8193
|
+
* that finishInstall already writes — non-destructive + idempotent: only sets
|
|
8194
|
+
* mcpServers.gsd, preserving any user-defined servers (a user's own `gsd` override
|
|
8195
|
+
* is respected — Hyrum's Law).
|
|
8196
|
+
* @param {object} settings - the in-memory settings object finishInstall will write
|
|
8197
|
+
*/
|
|
8198
|
+
function mergeGsdMcpServerIntoSettings(settings) {
|
|
8199
|
+
if (!settings.mcpServers || typeof settings.mcpServers !== 'object' || Array.isArray(settings.mcpServers)) {
|
|
8200
|
+
settings.mcpServers = {};
|
|
8201
|
+
}
|
|
8202
|
+
if (settings.mcpServers.gsd === undefined) {
|
|
8203
|
+
settings.mcpServers.gsd = {
|
|
8204
|
+
command: 'npx',
|
|
8205
|
+
args: ['-y', '-p', PACKAGE_NAME, 'gsd-mcp-server'],
|
|
8206
|
+
};
|
|
8207
|
+
}
|
|
8208
|
+
}
|
|
8209
|
+
|
|
7699
8210
|
/**
|
|
7700
8211
|
* Verify a directory exists and contains files
|
|
7701
8212
|
*/
|
|
@@ -7805,7 +8316,18 @@ function resolveInstallRelativePath(baseDir, relPath) {
|
|
|
7805
8316
|
* Write file manifest after installation for future modification detection
|
|
7806
8317
|
*/
|
|
7807
8318
|
function writeManifest(configDir, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
7808
|
-
|
|
8319
|
+
// #2093: isKilo dropped — unused in this function.
|
|
8320
|
+
// #2094: isTrae dropped — was only used in the hooks-tracking conditional
|
|
8321
|
+
// above, now covered by hostBehaviors.skipSharedHooksInstall.
|
|
8322
|
+
// #2095: isKimi dropped — kimi is now a hooks/ consumer like every other
|
|
8323
|
+
// settings-json-adjacent runtime, so the `&& !isKimi` term below was removed.
|
|
8324
|
+
// #2096: isAntigravity dropped — unused in this function.
|
|
8325
|
+
// #2098: isCodebuddy dropped — unused in this function.
|
|
8326
|
+
// #2099: isCopilot dropped — was only used in the hooks-tracking conditional
|
|
8327
|
+
// above, now covered by hostBehaviors.skipSharedHooksInstall.
|
|
8328
|
+
// #2100: isWindsurf dropped — was only used in the hooks-tracking conditional
|
|
8329
|
+
// above, now covered by hostBehaviors.skipSharedHooksInstall.
|
|
8330
|
+
const { isOpencode, isCodex, isCursor, isAugment, isQwen, isHermes, isCline } = runtimeFlags(runtime);
|
|
7809
8331
|
const gsdDir = path.join(configDir, 'gsd-core');
|
|
7810
8332
|
// #1367: Claude local now writes flat gsd-*.md files at commands/ (not commands/gsd/).
|
|
7811
8333
|
// Claude local uses flatCommandsDir instead for manifest recording.
|
|
@@ -7879,7 +8401,7 @@ function writeManifest(configDir, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
7879
8401
|
}
|
|
7880
8402
|
}
|
|
7881
8403
|
}
|
|
7882
|
-
if (
|
|
8404
|
+
if (_hostBehaviors(runtime).agentManifestStyle === 'kimi-nested' && fs.existsSync(agentsDir)) {
|
|
7883
8405
|
const agentHashes = generateManifest(agentsDir);
|
|
7884
8406
|
for (const [rel, hash] of Object.entries(agentHashes)) {
|
|
7885
8407
|
const isRootAgent = rel === 'gsd.yaml' || rel === 'gsd.md';
|
|
@@ -7913,7 +8435,15 @@ function writeManifest(configDir, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
7913
8435
|
// Hooks are only installed for runtimes that use settings.json (not Codex/Copilot/Cline)
|
|
7914
8436
|
// Descriptor-driven (ADR-1239 / #2089+#2090): cline's exclusion is via
|
|
7915
8437
|
// hostBehaviors.skipSharedHooksInstall (was hardcoded !isCline).
|
|
7916
|
-
|
|
8438
|
+
// #2094: Trae's exclusion is likewise descriptor-driven (trae declares
|
|
8439
|
+
// skipSharedHooksInstall:true) — the redundant `&& !isTrae` was removed.
|
|
8440
|
+
// #2095: kimi is now a hooks/ consumer (native config.toml [[hooks]] bus) —
|
|
8441
|
+
// the redundant `&& !isKimi` was removed so its hook files are tracked too.
|
|
8442
|
+
// #2099: Copilot's exclusion is likewise descriptor-driven (copilot declares
|
|
8443
|
+
// skipSharedHooksInstall:true) — the redundant `&& !isCopilot` was removed.
|
|
8444
|
+
// #2100: Windsurf's exclusion is likewise descriptor-driven (windsurf declares
|
|
8445
|
+
// skipSharedHooksInstall:true) — the redundant `&& !isWindsurf` was removed.
|
|
8446
|
+
if (!isCodex && _hostBehaviors(runtime).skipSharedHooksInstall !== true) {
|
|
7917
8447
|
const hooksDir = path.join(configDir, 'hooks');
|
|
7918
8448
|
if (fs.existsSync(hooksDir)) {
|
|
7919
8449
|
// Drive from INSTALLED_HOOK_FILES (the canonical HOOKS_TO_COPY set from
|
|
@@ -8274,11 +8804,7 @@ function reportLocalPatches(configDir, runtime = DEFAULT_RUNTIME) {
|
|
|
8274
8804
|
try { meta = JSON.parse(fs.readFileSync(metaPath, 'utf8')); } catch { return []; }
|
|
8275
8805
|
|
|
8276
8806
|
if (meta.files && meta.files.length > 0) {
|
|
8277
|
-
const reapplyCommand = _hostBehaviors(runtime).reapplyCommand
|
|
8278
|
-
? _hostBehaviors(runtime).reapplyCommand
|
|
8279
|
-
: runtime === 'kimi'
|
|
8280
|
-
? '/skill:gsd-update --reapply'
|
|
8281
|
-
: '/gsd-update --reapply';
|
|
8807
|
+
const reapplyCommand = _hostBehaviors(runtime).reapplyCommand || '/gsd-update --reapply';
|
|
8282
8808
|
console.log('');
|
|
8283
8809
|
console.log(' ' + yellow + 'Local patches detected' + reset + ' (from v' + meta.from_version + '):');
|
|
8284
8810
|
for (const f of meta.files) {
|
|
@@ -8305,12 +8831,44 @@ function reportInstallerMigrationResult(result) {
|
|
|
8305
8831
|
}
|
|
8306
8832
|
|
|
8307
8833
|
function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
8308
|
-
|
|
8834
|
+
// #2093: isKilo dropped — Kilo's agent/model-override handling below reads
|
|
8835
|
+
// _hostBehaviors(runtime).frontmatterDialect === 'kilo' instead of this flag.
|
|
8836
|
+
// #2095: isKimi dropped — kimi is now a hooks/ consumer like every other
|
|
8837
|
+
// settings-json-adjacent runtime; the two `&& !isKimi` hooks-copy guards
|
|
8838
|
+
// below were removed, leaving isKimi unused in this function (the kimi
|
|
8839
|
+
// local-install-deferred branch above already reads
|
|
8840
|
+
// _hostBehaviors(runtime).localInstallDeferred instead of this flag).
|
|
8841
|
+
// #2096: isAntigravity dropped — antigravity is in
|
|
8842
|
+
// _DESCRIPTOR_AGENTS_RUNTIMES below, so its two legacy-agent-loop branches
|
|
8843
|
+
// (the path-rewrite skip and the converter dispatch) were unreachable dead
|
|
8844
|
+
// code; both were removed rather than re-gated on hostBehaviors.
|
|
8845
|
+
// #2098: isCodebuddy dropped — codebuddy is also in
|
|
8846
|
+
// _DESCRIPTOR_AGENTS_RUNTIMES below, so its legacy converter-dispatch branch
|
|
8847
|
+
// (the `isCodebuddy` arm calling convertClaudeAgentToCodebuddyAgent) was
|
|
8848
|
+
// unreachable dead code and was removed rather than re-gated.
|
|
8849
|
+
// #2099: isCopilot dropped — copilot is also in _DESCRIPTOR_AGENTS_RUNTIMES
|
|
8850
|
+
// below, so its three legacy-agent-loop branches (the path-rewrite skip,
|
|
8851
|
+
// the converter dispatch, and the .agent.md destName ternary) were
|
|
8852
|
+
// unreachable dead code and were removed rather than re-gated; the
|
|
8853
|
+
// .agent.md suffix now lives on hostBehaviors.agentFileExtension in
|
|
8854
|
+
// src/install-engine.cts, and the skipSharedHooksInstall check above no
|
|
8855
|
+
// longer needs `&& !isCopilot`.
|
|
8856
|
+
// #2100: isWindsurf dropped — its four former isWindsurf-gated branches
|
|
8857
|
+
// (legacy .devin/skills/gsd-* cleanup, the #1629 command-bodies copy, the
|
|
8858
|
+
// workflow-verification report, and the shared-hooks-install exclusion) are
|
|
8859
|
+
// now descriptor-driven via hostBehaviors.legacyDevinSkillsCleanup,
|
|
8860
|
+
// hostBehaviors.installsCommandBodiesForWorkflowDelegation,
|
|
8861
|
+
// hostBehaviors.verificationStyle === 'windsurf-workflows', and
|
|
8862
|
+
// hostBehaviors.skipSharedHooksInstall respectively; its legacy-agent-loop
|
|
8863
|
+
// converter arm was likewise unreachable dead code (windsurf is in
|
|
8864
|
+
// _DESCRIPTOR_AGENTS_RUNTIMES) and was removed above.
|
|
8865
|
+
// #2101: isZcode dropped — folded onto hostBehaviors.skipSharedHooksInstall.
|
|
8866
|
+
const { isOpencode, isCodex, isCursor, isAugment, isTrae, isQwen, isHermes, isCline } = runtimeFlags(runtime);
|
|
8309
8867
|
const plan = resolveInstallPlan(runtime);
|
|
8310
8868
|
const dirName = getDirName(runtime);
|
|
8311
8869
|
const src = path.join(__dirname, '..');
|
|
8312
8870
|
|
|
8313
|
-
if (
|
|
8871
|
+
if (_hostBehaviors(runtime).localInstallDeferred && !isGlobal) {
|
|
8314
8872
|
console.log(` ${yellow}⚠${reset} Kimi local install is deferred for Phase 2.`);
|
|
8315
8873
|
console.log(` No .kimi-code/skills or .agents/skills project artifacts were written.`);
|
|
8316
8874
|
console.log(` Project-level Kimi install semantics remain deferred.`);
|
|
@@ -8815,7 +9373,10 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
8815
9373
|
// dirs from pre-#1615 installs. #1615 moved Windsurf to .windsurf/workflows/
|
|
8816
9374
|
// but never cleaned up the old .devin/skills/ layout (#1085). User-owned
|
|
8817
9375
|
// content is preserved (non-gsd- dirs, gsd-dev-preferences, symlinks).
|
|
8818
|
-
|
|
9376
|
+
// Descriptor-driven (ADR-1239 / #2100): folded from `isWindsurf` into
|
|
9377
|
+
// hostBehaviors.legacyDevinSkillsCleanup (windsurf is the only runtime that
|
|
9378
|
+
// declares it, so this is byte-parity).
|
|
9379
|
+
if (_hostBehaviors(runtime).legacyDevinSkillsCleanup && !isGlobal) {
|
|
8819
9380
|
const removedCount = cleanupWindsurfLegacyDevinSkills(process.cwd());
|
|
8820
9381
|
if (removedCount > 0) {
|
|
8821
9382
|
console.log(` ${green}✓${reset} Removed ${removedCount} legacy .devin/skills/gsd-* dir(s) (pre-#1615 Windsurf layout)`);
|
|
@@ -8842,7 +9403,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
8842
9403
|
} else {
|
|
8843
9404
|
failures.push('skills/gsd/*');
|
|
8844
9405
|
}
|
|
8845
|
-
} else if (
|
|
9406
|
+
} else if (_hostBehaviors(runtime).verificationStyle === 'kimi') {
|
|
8846
9407
|
const skillsDir = path.join(targetDir, 'skills');
|
|
8847
9408
|
const rootAgentPath = path.join(targetDir, 'agents', 'gsd.yaml');
|
|
8848
9409
|
if (fs.existsSync(skillsDir)) {
|
|
@@ -8862,7 +9423,11 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
8862
9423
|
} else {
|
|
8863
9424
|
failures.push('agents/gsd.yaml');
|
|
8864
9425
|
}
|
|
8865
|
-
|
|
9426
|
+
// Descriptor-driven (ADR-1239 / #2100): folded from `isWindsurf` into
|
|
9427
|
+
// hostBehaviors.verificationStyle === 'windsurf-workflows' (extends the
|
|
9428
|
+
// same mechanism the 'kimi' verificationStyle branch above uses; windsurf
|
|
9429
|
+
// is the only runtime that declares this value, so this is byte-parity).
|
|
9430
|
+
} else if (_hostBehaviors(runtime).verificationStyle === 'windsurf-workflows') {
|
|
8866
9431
|
if (isGlobal) {
|
|
8867
9432
|
console.log(` ${green}✓${reset} Windsurf global install skipped workflow artifacts (workspace-only)`);
|
|
8868
9433
|
} else {
|
|
@@ -8924,22 +9489,6 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
8924
9489
|
failures.push('commands/gsd-*');
|
|
8925
9490
|
}
|
|
8926
9491
|
}
|
|
8927
|
-
|
|
8928
|
-
// CodeBuddy only: also report the commands/ output (#789 — slash commands)
|
|
8929
|
-
if (isCodebuddy) {
|
|
8930
|
-
const commandsDir = path.join(targetDir, 'commands');
|
|
8931
|
-
if (fs.existsSync(commandsDir)) {
|
|
8932
|
-
const cmdCount = fs.readdirSync(commandsDir)
|
|
8933
|
-
.filter(f => f.startsWith('gsd-') && f.endsWith('.md')).length;
|
|
8934
|
-
if (cmdCount > 0) {
|
|
8935
|
-
console.log(` ${green}✓${reset} Installed ${cmdCount} slash commands to commands/`);
|
|
8936
|
-
} else {
|
|
8937
|
-
failures.push('commands/gsd-*');
|
|
8938
|
-
}
|
|
8939
|
-
} else {
|
|
8940
|
-
failures.push('commands/gsd-*');
|
|
8941
|
-
}
|
|
8942
|
-
}
|
|
8943
9492
|
}
|
|
8944
9493
|
} else if (_hostBehaviors(runtime).localCommandsViaRules) {
|
|
8945
9494
|
// Cline local install: rules-based only — commands are embedded in .clinerules (generated below).
|
|
@@ -8948,6 +9497,16 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
8948
9497
|
// Descriptor-driven (ADR-1239 / #2090): folded from `isCline` into
|
|
8949
9498
|
// hostBehaviors.localCommandsViaRules.
|
|
8950
9499
|
console.log(` ${green}✓${reset} Cline: commands will be available via .clinerules`);
|
|
9500
|
+
} else if (_hostBehaviors(runtime).pluginOnlyInstall) {
|
|
9501
|
+
// pi (ADR-1239 / #2102 Stage 1): plugin-only install — pi's /gsd command is
|
|
9502
|
+
// registered programmatically by the native extension (pi/gsd.cjs →
|
|
9503
|
+
// extensions/gsd.cjs, staged separately below) and dispatches in-process
|
|
9504
|
+
// through the embedded gsd-core command-routing hub. pi has no host-read
|
|
9505
|
+
// markdown surface (unlike Claude/OpenCode/etc., which scan commands/ or
|
|
9506
|
+
// command/ directories), so writing flat gsd-<cmd>.md files here would be
|
|
9507
|
+
// dead weight the extension never reads. Skip the flat-commands fallback
|
|
9508
|
+
// entirely for pluginOnlyInstall runtimes.
|
|
9509
|
+
console.log(` ${green}✓${reset} pi: /gsd registered via native extension (no declarative command files)`);
|
|
8951
9510
|
} else {
|
|
8952
9511
|
// Claude Code local: flat gsd-<cmd>.md layout — Claude Code registers
|
|
8953
9512
|
// commands from .claude/commands/ using the filename stem as the command
|
|
@@ -9018,6 +9577,18 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
9018
9577
|
}
|
|
9019
9578
|
}
|
|
9020
9579
|
|
|
9580
|
+
// Native-extension/plugin staging for runtimes OUTSIDE the layout-driven
|
|
9581
|
+
// _isSkillsRuntime branch above (ADR-1239 / #2102 Stage 1: pi). OpenCode/Kilo
|
|
9582
|
+
// already get their nativePlugin file from installOpencodeFamilyArtifacts
|
|
9583
|
+
// (called inside the _isSkillsRuntime branch, since both declare a non-empty
|
|
9584
|
+
// artifactLayout) — guard on `!_isSkillsRuntime` so this standalone call never
|
|
9585
|
+
// double-stages their plugin file. A runtime like pi, whose artifactLayout is
|
|
9586
|
+
// intentionally empty for both scopes (`_isSkillsRuntime` is false), still
|
|
9587
|
+
// needs its declared hostBehaviors.nativePlugin file copied into targetDir.
|
|
9588
|
+
if (!_isSkillsRuntime && _hostBehaviors(runtime).nativePlugin) {
|
|
9589
|
+
_installNativePluginIfDeclared(runtime, targetDir, _hostBehaviors(runtime), src);
|
|
9590
|
+
}
|
|
9591
|
+
|
|
9021
9592
|
// Copy gsd-core skill with path replacement
|
|
9022
9593
|
// Preserve user-generated files before the wipe-and-copy so they survive re-install
|
|
9023
9594
|
const skillSrc = path.join(src, 'gsd-core');
|
|
@@ -9069,7 +9640,11 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
9069
9640
|
// this copy, every /gsd-* workflow in Cascade references a missing file and the LLM
|
|
9070
9641
|
// cannot execute the command body. Surfaced by the #1629 regression test after the
|
|
9071
9642
|
// original adversarial review of #1622 missed it.
|
|
9072
|
-
|
|
9643
|
+
// Descriptor-driven (ADR-1239 / #2100): folded from `isWindsurf` into
|
|
9644
|
+
// hostBehaviors.installsCommandBodiesForWorkflowDelegation (windsurf is the
|
|
9645
|
+
// only runtime that declares it, so this is byte-parity — the #1629 fix
|
|
9646
|
+
// itself is unchanged).
|
|
9647
|
+
if (_hostBehaviors(runtime).installsCommandBodiesForWorkflowDelegation && !isGlobal) {
|
|
9073
9648
|
const commandsSrc = path.join(src, 'commands', 'gsd');
|
|
9074
9649
|
const commandsDest = path.join(skillDest, 'commands', 'gsd');
|
|
9075
9650
|
if (fs.existsSync(commandsSrc)) {
|
|
@@ -9125,16 +9700,24 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
9125
9700
|
// Trivial group (cursor/windsurf/augment/trae/codebuddy) cut over together.
|
|
9126
9701
|
// #1575: copilot and antigravity cut over — copilot gets .agent.md filename
|
|
9127
9702
|
// rename via _copyStaged(runtime); antigravity uses scope-aware converter.
|
|
9703
|
+
// #2092 Phase B Upgrade 1: qwen cut over — native .qwen/agents/*.md subagent
|
|
9704
|
+
// projection via convertClaudeAgentToQwenAgent. Without this exclusion the
|
|
9705
|
+
// legacy inline loop below deletes+re-copies qwen's agents RAW (bypassing the
|
|
9706
|
+
// new converter entirely, since qwen has no dedicated branch in the inline
|
|
9707
|
+
// loop's if/else-if chain — it would silently fall through to the generic
|
|
9708
|
+
// brandingRewrites-only branch).
|
|
9128
9709
|
// cline remains excluded: rules-only local branch + local/global complication
|
|
9129
9710
|
// that the descriptor-driven path does not handle correctly.
|
|
9130
|
-
const _DESCRIPTOR_AGENTS_RUNTIMES = new Set(['cursor', 'windsurf', 'augment', 'trae', 'codebuddy', 'copilot', 'antigravity']);
|
|
9711
|
+
const _DESCRIPTOR_AGENTS_RUNTIMES = new Set(['cursor', 'windsurf', 'augment', 'trae', 'codebuddy', 'copilot', 'antigravity', 'qwen', 'kimi']);
|
|
9131
9712
|
|
|
9132
9713
|
// Always remove stale gsd-* agents first so re-installing with
|
|
9133
9714
|
// `--minimal` actually shrinks a previously-full install.
|
|
9134
9715
|
// For Codex this also covers per-agent `.toml` files alongside the `.md`
|
|
9135
9716
|
// sources so a full → minimal switch doesn't leave stale registrations.
|
|
9136
|
-
// Skipped for descriptor-agent runtimes (installRuntimeArtifacts prunes)
|
|
9137
|
-
|
|
9717
|
+
// Skipped for descriptor-agent runtimes (installRuntimeArtifacts prunes) and
|
|
9718
|
+
// for pluginOnlyInstall runtimes (pi, ADR-1239 / #2102 Stage 1 — no agents/
|
|
9719
|
+
// dir is ever written for them, see the leading branch below).
|
|
9720
|
+
if (!_DESCRIPTOR_AGENTS_RUNTIMES.has(runtime) && !_hostBehaviors(runtime).pluginOnlyInstall && fs.existsSync(agentsDest)) {
|
|
9138
9721
|
for (const file of fs.readdirSync(agentsDest)) {
|
|
9139
9722
|
if (
|
|
9140
9723
|
file.startsWith('gsd-') &&
|
|
@@ -9145,8 +9728,11 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
9145
9728
|
}
|
|
9146
9729
|
}
|
|
9147
9730
|
|
|
9148
|
-
if (
|
|
9149
|
-
|
|
9731
|
+
if (_hostBehaviors(runtime).pluginOnlyInstall) {
|
|
9732
|
+
// pi (ADR-1239 / #2102 Stage 1): programmatic dispatch has no named-dispatch
|
|
9733
|
+
// subagent toolkit (dispatch.subagentToolkit: "undocumented", no Agent-tool
|
|
9734
|
+
// equivalent) and no host-read markdown surface — skip writing agents/ entirely.
|
|
9735
|
+
console.log(` ${green}✓${reset} pi: no subagent files (programmatic dispatch, no named-dispatch toolkit)`);
|
|
9150
9736
|
} else if (_DESCRIPTOR_AGENTS_RUNTIMES.has(runtime)) {
|
|
9151
9737
|
// installRuntimeArtifacts already wrote agents + handles stale-file cleanup
|
|
9152
9738
|
// via its own prune pass. No further action needed.
|
|
@@ -9183,12 +9769,18 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
9183
9769
|
const bareDirRegex = /~\/\.claude\b/g;
|
|
9184
9770
|
const bareHomeDirRegex = /\$HOME\/\.claude\b/g;
|
|
9185
9771
|
const normalizedPathPrefix = pathPrefix.replace(/\/$/, '');
|
|
9186
|
-
|
|
9187
|
-
|
|
9188
|
-
|
|
9189
|
-
|
|
9190
|
-
|
|
9191
|
-
|
|
9772
|
+
// #2096: `&& !isAntigravity` dropped — antigravity is in
|
|
9773
|
+
// _DESCRIPTOR_AGENTS_RUNTIMES above, so this whole branch is already
|
|
9774
|
+
// unreachable for it; the path-rewrite skip for antigravity now lives
|
|
9775
|
+
// in the descriptor-driven `applyAgentPathRewrites` (hostBehaviors.noPathRewrite).
|
|
9776
|
+
// #2099: `if (!isCopilot)` guard dropped — copilot is ALSO in
|
|
9777
|
+
// _DESCRIPTOR_AGENTS_RUNTIMES (line ~9564 above), so this whole
|
|
9778
|
+
// `else if (fs.existsSync(agentsSrc))` branch is unreachable for it;
|
|
9779
|
+
// isCopilot was therefore always false here, making the guard a no-op.
|
|
9780
|
+
content = content.replace(dirRegex, pathPrefix);
|
|
9781
|
+
content = content.replace(homeDirRegex, pathPrefix);
|
|
9782
|
+
content = content.replace(bareDirRegex, normalizedPathPrefix);
|
|
9783
|
+
content = content.replace(bareHomeDirRegex, normalizedPathPrefix);
|
|
9192
9784
|
content = processAttribution(content, getCommitAttribution(runtime));
|
|
9193
9785
|
// Convert frontmatter for runtime compatibility (agents need different handling)
|
|
9194
9786
|
if (_hostBehaviors(runtime).frontmatterDialect === 'opencode') {
|
|
@@ -9211,33 +9803,52 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
9211
9803
|
}
|
|
9212
9804
|
content = convertClaudeToOpencodeFrontmatter(content, { isAgent: true, modelOverride: _ocModelOverride });
|
|
9213
9805
|
} else if (_hostBehaviors(runtime).frontmatterDialect === 'kilo') {
|
|
9214
|
-
|
|
9806
|
+
// Resolve per-agent model for Kilo agents (#2093 UPGRADE 2; Kilo is an
|
|
9807
|
+
// OpenCode fork with the same static-frontmatter model constraint).
|
|
9808
|
+
// Precedence: model_overrides[agent] > model_profile_overrides.kilo.<tier> > omit.
|
|
9809
|
+
// model_overrides (#2256): explicit per-agent override, highest precedence.
|
|
9810
|
+
// model_profile_overrides (#2794): tier-based runtime resolver, same parity as OpenCode.
|
|
9811
|
+
const _kiloAgentName = entry.name.replace(/\.md$/, '');
|
|
9812
|
+
const _kiloModelOverrides = readGsdEffectiveModelOverrides(targetDir);
|
|
9813
|
+
let _kiloModelOverride = _kiloModelOverrides?.[_kiloAgentName] || null;
|
|
9814
|
+
if (!_kiloModelOverride) {
|
|
9815
|
+
// Fall back to tier-based resolution via model_profile_overrides.kilo.<tier>.
|
|
9816
|
+
const _kiloRuntimeResolver = readGsdRuntimeProfileResolver(targetDir);
|
|
9817
|
+
if (_kiloRuntimeResolver) {
|
|
9818
|
+
const _kiloEntry = _kiloRuntimeResolver.resolve(_kiloAgentName);
|
|
9819
|
+
if (_kiloEntry?.model) {
|
|
9820
|
+
_kiloModelOverride = _kiloEntry.model;
|
|
9821
|
+
}
|
|
9822
|
+
}
|
|
9823
|
+
}
|
|
9824
|
+
content = convertClaudeToKiloFrontmatter(content, { isAgent: true, modelOverride: _kiloModelOverride });
|
|
9215
9825
|
} else if (_hostBehaviors(runtime).frontmatterDialect === 'codex') {
|
|
9216
9826
|
content = convertClaudeAgentToCodexAgent(content);
|
|
9217
|
-
|
|
9218
|
-
|
|
9219
|
-
|
|
9220
|
-
|
|
9221
|
-
|
|
9222
|
-
|
|
9223
|
-
|
|
9224
|
-
|
|
9225
|
-
|
|
9226
|
-
|
|
9227
|
-
|
|
9228
|
-
|
|
9827
|
+
// #2099: `else if (isCopilot)` arm dropped — copilot is unreachable
|
|
9828
|
+
// here (see the isCopilot-guard-drop comment above); its content
|
|
9829
|
+
// conversion is applied pre-staging via the descriptor's
|
|
9830
|
+
// artifactLayout.converter (runtime-artifact-layout.cts), independent
|
|
9831
|
+
// of this legacy loop.
|
|
9832
|
+
// #2100: `else if (isWindsurf)` arm dropped — windsurf is ALSO in
|
|
9833
|
+
// _DESCRIPTOR_AGENTS_RUNTIMES (line ~9575 above), so this whole
|
|
9834
|
+
// `else if (fs.existsSync(agentsSrc))` branch is unreachable for it;
|
|
9835
|
+
// isWindsurf was therefore always false here, making the arm dead.
|
|
9836
|
+
// Its content conversion is applied pre-staging via the descriptor's
|
|
9837
|
+
// artifactLayout.converter (convertClaudeAgentToWindsurfAgent),
|
|
9838
|
+
// independent of this legacy loop.
|
|
9229
9839
|
} else if (_hostBehaviors(runtime).frontmatterDialect === 'cline') {
|
|
9230
9840
|
// Descriptor-driven (ADR-1239 / #2090): folded from `isCline` into
|
|
9231
9841
|
// hostBehaviors.frontmatterDialect === 'cline'.
|
|
9232
9842
|
content = convertClaudeAgentToClineAgent(content);
|
|
9233
|
-
} else if (isQwen) {
|
|
9234
|
-
content = content.replace(/CLAUDE\.md/g, 'QWEN.md');
|
|
9235
|
-
content = content.replace(/\bClaude Code\b/g, 'Qwen Code');
|
|
9236
|
-
content = content.replace(/\.claude\//g, '.qwen/');
|
|
9237
9843
|
} else if (_hostBehaviors(runtime).brandingRewrites) {
|
|
9238
|
-
|
|
9239
|
-
|
|
9240
|
-
|
|
9844
|
+
// Descriptor-driven (ADR-1239 / #2092): folded from separate
|
|
9845
|
+
// `isQwen` / hermes-hardcoded branches into a single read of
|
|
9846
|
+
// runtime.hostBehaviors.brandingRewrites (qwen -> QWEN.md/Qwen
|
|
9847
|
+
// Code/.qwen/, hermes -> HERMES.md/Hermes Agent/.hermes/).
|
|
9848
|
+
const _b = _hostBehaviors(runtime).brandingRewrites;
|
|
9849
|
+
content = content.replace(/CLAUDE\.md/g, _b['CLAUDE.md']);
|
|
9850
|
+
content = content.replace(/\bClaude Code\b/g, _b['Claude Code']);
|
|
9851
|
+
content = content.replace(/\.claude\//g, _b['.claude/']);
|
|
9241
9852
|
}
|
|
9242
9853
|
// #443 — Inject `effort:` into the Claude .md frontmatter ONLY.
|
|
9243
9854
|
// OpenCode/Qwen/Hermes also produce .md files but break on
|
|
@@ -9262,7 +9873,12 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
9262
9873
|
// shouldNormalizeHyphenNamespaceInAgentBody above. Mirrors the
|
|
9263
9874
|
// SKILL.md-body fix shipped via #3629.
|
|
9264
9875
|
content = normalizeAgentBodyForRuntime(content, runtime, readGsdCommandNames());
|
|
9265
|
-
|
|
9876
|
+
// #2099: `isCopilot ? ... : entry.name` ternary dropped — copilot is
|
|
9877
|
+
// unreachable here (see the isCopilot-guard-drop comment above), so
|
|
9878
|
+
// the ternary always evaluated to entry.name in practice; its
|
|
9879
|
+
// .agent.md suffix is applied by the descriptor-driven fold in
|
|
9880
|
+
// src/install-engine.cts (hostBehaviors.agentFileExtension).
|
|
9881
|
+
const destName = entry.name;
|
|
9266
9882
|
fs.writeFileSync(path.join(agentsDest, destName), content);
|
|
9267
9883
|
}
|
|
9268
9884
|
}
|
|
@@ -9294,29 +9910,41 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
9294
9910
|
failures.push('VERSION');
|
|
9295
9911
|
}
|
|
9296
9912
|
|
|
9297
|
-
//
|
|
9298
|
-
//
|
|
9299
|
-
//
|
|
9300
|
-
//
|
|
9301
|
-
//
|
|
9302
|
-
//
|
|
9303
|
-
//
|
|
9304
|
-
// hostBehaviors.skipSharedHooksInstall
|
|
9305
|
-
//
|
|
9306
|
-
//
|
|
9307
|
-
|
|
9913
|
+
// Reusable: copy hooks/dist/ + hooks/lib/ into destRootDir, writing the
|
|
9914
|
+
// CommonJS package.json marker alongside them. Used below for the generic
|
|
9915
|
+
// configDir install path (guarded by hostBehaviors.skipSharedHooksInstall),
|
|
9916
|
+
// and — since #2095 — for Kimi's OWN native hook-install root (~/.kimi,
|
|
9917
|
+
// resolved by resolveKimiHooksTomlDir), a directory entirely separate from
|
|
9918
|
+
// Kimi's configDir/agents-root. Kimi's contract forbids hooks/ or
|
|
9919
|
+
// package.json under its generic Agent-Skills root (see
|
|
9920
|
+
// capabilities/kimi/capability.json hostBehaviors.skipSharedHooksInstall
|
|
9921
|
+
// and the kimi-hooks-toml branch further below), so its shared-hooks bundle
|
|
9922
|
+
// is installed into its own root via this same helper instead.
|
|
9923
|
+
// Returns false when hooks/dist/ exists but failed to verify post-copy (a
|
|
9924
|
+
// genuine failure the caller should surface); true otherwise (including
|
|
9925
|
+
// when hooks/dist/ is absent from the package — nothing to verify).
|
|
9926
|
+
function installSharedHooksBundle(destRootDir) {
|
|
9927
|
+
// destRootDir already exists for the generic call site (targetDir — created
|
|
9928
|
+
// earlier in install() by the skills/agents writes above). It does NOT yet
|
|
9929
|
+
// exist for kimi's call site (~/.kimi, resolved by resolveKimiHooksTomlDir):
|
|
9930
|
+
// a fresh install has never created that dir before. mkdirSync recursive is
|
|
9931
|
+
// a safe no-op when the dir is already present.
|
|
9932
|
+
fs.mkdirSync(destRootDir, { recursive: true });
|
|
9933
|
+
|
|
9308
9934
|
// Write package.json to force CommonJS mode for GSD scripts
|
|
9309
9935
|
// Prevents "require is not defined" errors when project has "type": "module"
|
|
9310
9936
|
// Node.js walks up looking for package.json - this stops inheritance from project
|
|
9311
|
-
const pkgJsonDest = path.join(
|
|
9937
|
+
const pkgJsonDest = path.join(destRootDir, 'package.json');
|
|
9312
9938
|
fs.writeFileSync(pkgJsonDest, '{"type":"commonjs"}\n');
|
|
9313
9939
|
console.log(` ${green}✓${reset} Wrote package.json (CommonJS mode)`);
|
|
9314
9940
|
|
|
9941
|
+
let hooksOk = true;
|
|
9942
|
+
|
|
9315
9943
|
// Copy hooks from dist/ (bundled with dependencies)
|
|
9316
9944
|
// Template paths for the target runtime (replaces '.claude' with correct config dir)
|
|
9317
9945
|
const hooksSrc = path.join(src, 'hooks', 'dist');
|
|
9318
9946
|
if (fs.existsSync(hooksSrc)) {
|
|
9319
|
-
const hooksDest = path.join(
|
|
9947
|
+
const hooksDest = path.join(destRootDir, 'hooks');
|
|
9320
9948
|
fs.mkdirSync(hooksDest, { recursive: true });
|
|
9321
9949
|
const hookEntries = fs.readdirSync(hooksSrc);
|
|
9322
9950
|
const configDirReplacement = getConfigDirFromHome(runtime, isGlobal);
|
|
@@ -9329,13 +9957,15 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
9329
9957
|
content = content.replace(/'\.claude'/g, configDirReplacement);
|
|
9330
9958
|
content = content.replace(/\/\.claude\//g, `/${getDirName(runtime)}/`);
|
|
9331
9959
|
content = content.replace(/\.claude\//g, `${getDirName(runtime)}/`);
|
|
9332
|
-
|
|
9333
|
-
|
|
9334
|
-
|
|
9335
|
-
|
|
9336
|
-
|
|
9337
|
-
|
|
9338
|
-
|
|
9960
|
+
// Descriptor-driven (ADR-1239 / #2092): folded from separate
|
|
9961
|
+
// `isQwen` / hermes-hardcoded branches into a single read of
|
|
9962
|
+
// runtime.hostBehaviors.brandingRewrites. This site only
|
|
9963
|
+
// rewrites the two brand-name keys (no `.claude/` here — the
|
|
9964
|
+
// config-dir replace above already handled path fragments).
|
|
9965
|
+
const _b2 = _hostBehaviors(runtime).brandingRewrites;
|
|
9966
|
+
if (_b2) {
|
|
9967
|
+
content = content.replace(/CLAUDE\.md/g, _b2['CLAUDE.md']);
|
|
9968
|
+
content = content.replace(/\bClaude Code\b/g, _b2['Claude Code']);
|
|
9339
9969
|
}
|
|
9340
9970
|
// #376: rewrite gsd: → gsd- for hyphen-namespace runtimes
|
|
9341
9971
|
if (shouldNormalizeHyphenNamespaceInAgentBody(runtime)) {
|
|
@@ -9388,27 +10018,63 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
9388
10018
|
}
|
|
9389
10019
|
}
|
|
9390
10020
|
} else {
|
|
9391
|
-
|
|
10021
|
+
hooksOk = false;
|
|
9392
10022
|
}
|
|
9393
10023
|
}
|
|
10024
|
+
|
|
10025
|
+
// Gate hooks/lib/ install on the same set of runtimes that receive hooks/.
|
|
10026
|
+
// Codex/Copilot/Cursor/Windsurf/Trae/Cline/Kilo do not use the shared
|
|
10027
|
+
// hooks/lib/ helpers (Cursor uses standalone .js hook scripts registered
|
|
10028
|
+
// via hooks.json — gated descriptor-driven via
|
|
10029
|
+
// hostBehaviors.skipSharedHooksInstall, #2089; Cline likewise #2090; Kilo
|
|
10030
|
+
// likewise #2093; Trae likewise #2094; Codex uses hooks.json directly;
|
|
10031
|
+
// the others skip hooks entirely); Kilo and ZCode also skip hooks entirely
|
|
10032
|
+
// (hooksSurface:'none' with no plugin surface — #1821). None of the
|
|
10033
|
+
// excluded runtimes must receive the hooks/lib/ helpers — otherwise the
|
|
10034
|
+
// Codex comment downstream ("we deliberately do *not* copy hooks/lib/ for
|
|
10035
|
+
// Codex") is contradicted in practice. (Gating lives at the call sites
|
|
10036
|
+
// below; this helper itself only checks source presence.)
|
|
10037
|
+
const hooksLibSrc = path.join(src, 'hooks', 'lib');
|
|
10038
|
+
if (fs.existsSync(hooksLibSrc)) {
|
|
10039
|
+
const hooksLibDest = path.join(destRootDir, 'hooks', 'lib');
|
|
10040
|
+
fs.mkdirSync(hooksLibDest, { recursive: true });
|
|
10041
|
+
copyLibDir(hooksLibSrc, hooksLibDest, GSD_HOOK_LIB_FILES);
|
|
10042
|
+
console.log(` ${green}✓${reset} Installed hooks/lib/ helpers (git-cmd, graphify-rebuild, ...)`);
|
|
10043
|
+
}
|
|
10044
|
+
|
|
10045
|
+
return hooksOk;
|
|
9394
10046
|
}
|
|
9395
10047
|
|
|
9396
|
-
//
|
|
9397
|
-
//
|
|
9398
|
-
//
|
|
9399
|
-
//
|
|
9400
|
-
//
|
|
9401
|
-
//
|
|
9402
|
-
//
|
|
9403
|
-
//
|
|
9404
|
-
//
|
|
9405
|
-
//
|
|
9406
|
-
|
|
9407
|
-
|
|
9408
|
-
|
|
9409
|
-
|
|
9410
|
-
|
|
9411
|
-
|
|
10048
|
+
// #1821: Kilo and ZCode declare hooksSurface:'none' AND have no plugin surface,
|
|
10049
|
+
// so the staged hook scripts are dead weight for them — exclude both here.
|
|
10050
|
+
// OpenCode also declares hooksSurface:'none' but is deliberately NOT excluded:
|
|
10051
|
+
// its native plugin adapter (#1914, installed above under plugins/gsd-core.js)
|
|
10052
|
+
// spawns the staged hooks/*.js scripts via OpenCode's event bus and needs both
|
|
10053
|
+
// them and the CommonJS package.json marker written below.
|
|
10054
|
+
// #2089: Cursor's exclusion is now descriptor-driven via
|
|
10055
|
+
// hostBehaviors.skipSharedHooksInstall (was hardcoded !isCursor).
|
|
10056
|
+
// #2090: Cline's exclusion is likewise descriptor-driven (cline declares
|
|
10057
|
+
// skipSharedHooksInstall:true) — the redundant `&& !isCline` was removed.
|
|
10058
|
+
// #2093: Kilo's exclusion is likewise descriptor-driven (kilo declares
|
|
10059
|
+
// skipSharedHooksInstall:true) — the redundant `&& !isKilo` was removed.
|
|
10060
|
+
// #2094: Trae's exclusion is likewise descriptor-driven (trae declares
|
|
10061
|
+
// skipSharedHooksInstall:true) — the redundant `&& !isTrae` was removed.
|
|
10062
|
+
// #2101: ZCode's exclusion is likewise descriptor-driven (zcode declares
|
|
10063
|
+
// skipSharedHooksInstall:true) — the redundant `&& !isZcode` was removed.
|
|
10064
|
+
// #2095: Kimi's exclusion is likewise descriptor-driven (kimi declares
|
|
10065
|
+
// skipSharedHooksInstall:true) — kimi's shared hooks/ + package.json marker
|
|
10066
|
+
// are instead installed into its OWN native hook root (~/.kimi, resolved by
|
|
10067
|
+
// resolveKimiHooksTomlDir) via installSharedHooksBundle, at the
|
|
10068
|
+
// kimi-hooks-toml branch further below — never under the generic
|
|
10069
|
+
// Agent-Skills configDir GSD installs skills/agents into for kimi.
|
|
10070
|
+
// #2099: Copilot's exclusion is likewise descriptor-driven (copilot declares
|
|
10071
|
+
// skipSharedHooksInstall:true) — the redundant `&& !isCopilot` was removed.
|
|
10072
|
+
// #2100: Windsurf's exclusion is likewise descriptor-driven (windsurf declares
|
|
10073
|
+
// skipSharedHooksInstall:true) — the redundant `&& !isWindsurf` was removed.
|
|
10074
|
+
if (!isCodex && _hostBehaviors(runtime).skipSharedHooksInstall !== true) {
|
|
10075
|
+
if (!installSharedHooksBundle(targetDir)) {
|
|
10076
|
+
failures.push('hooks');
|
|
10077
|
+
}
|
|
9412
10078
|
}
|
|
9413
10079
|
|
|
9414
10080
|
// Install scripts/changeset/ and scripts/lib/ into <configDir>/scripts/
|
|
@@ -10030,14 +10696,86 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
10030
10696
|
} else {
|
|
10031
10697
|
console.log(` ${green}✓${reset} Cursor lifecycle hooks already up to date`);
|
|
10032
10698
|
}
|
|
10033
|
-
// Re-run the manifest pass
|
|
10699
|
+
// Re-run the manifest pass to capture any files the hooks-json write path
|
|
10700
|
+
// produced. NOTE: hooks.json and the gsd-cursor-*.js scripts are NOT
|
|
10701
|
+
// manifest-tracked (verified) — uninstall removes them explicitly via
|
|
10702
|
+
// removeCursorHooksJson + its script list, and reconcile is idempotent.
|
|
10703
|
+
// The re-run is retained for parity with the settings.json install path.
|
|
10034
10704
|
writeManifest(targetDir, runtime, { mode: _effectiveInstallMode, scope: isGlobal ? 'global' : 'local' });
|
|
10035
10705
|
persistActiveProfileMarker();
|
|
10036
10706
|
return { settingsPath: null, settings: null, statuslineCommand: null, updateBannerCommand: null, runtime, configDir: targetDir };
|
|
10037
10707
|
}
|
|
10038
10708
|
|
|
10039
10709
|
if (plan.installSurface === 'profile-marker-only') {
|
|
10040
|
-
// Windsurf/Trae
|
|
10710
|
+
// Windsurf/Trae use artifact-only surfaces — no config.toml or settings.json
|
|
10711
|
+
// hooks needed. Kimi is also artifact-only for its INSTALL surface (skills +
|
|
10712
|
+
// kimi-agents, no settings.json) but #2095 Upgrade 1 gives it its own
|
|
10713
|
+
// independent hooksSurface: kimi's native config.toml [[hooks]] array, which
|
|
10714
|
+
// lives outside targetDir entirely (resolveKimiHooksTomlDir resolves ~/.kimi,
|
|
10715
|
+
// a sibling of targetDir's ~/.config/agents) — hence writing it here, inside
|
|
10716
|
+
// this early-return, rather than requiring installSurface to change.
|
|
10717
|
+
//
|
|
10718
|
+
// GATED TO GLOBAL ONLY (belt-and-suspenders): kimi local installs already
|
|
10719
|
+
// return early at the top of install() via hostBehaviors.localInstallDeferred,
|
|
10720
|
+
// long before this point is ever reached — so `isGlobal` is always true here
|
|
10721
|
+
// in practice. The explicit check documents that invariant and fails closed
|
|
10722
|
+
// if that early-return is ever refactored away.
|
|
10723
|
+
//
|
|
10724
|
+
// Kimi's contract forbids hooks/ or package.json under its generic
|
|
10725
|
+
// Agent-Skills configDir (targetDir) — capabilities/kimi/capability.json
|
|
10726
|
+
// declares hostBehaviors.skipSharedHooksInstall:true, which excludes it from
|
|
10727
|
+
// the shared installSharedHooksBundle(targetDir) call above. Kimi still needs
|
|
10728
|
+
// those SAME hook scripts + the CommonJS package.json marker, but SELF-
|
|
10729
|
+
// CONTAINED under its own native hook root instead — so install them there,
|
|
10730
|
+
// and point buildHookCommand (via writeKimiHooksToml's second arg) at that
|
|
10731
|
+
// same root so the generated [[hooks]] command paths reference
|
|
10732
|
+
// ~/.kimi/hooks/<script> rather than a script that doesn't exist under
|
|
10733
|
+
// targetDir/hooks (which kimi no longer receives).
|
|
10734
|
+
if (plan.hooksSurface === 'kimi-hooks-toml' && isGlobal) {
|
|
10735
|
+
const kimiHooksRoot = resolveKimiHooksTomlDir();
|
|
10736
|
+
// Note: the `failures` array's hard-fail gate (`if (failures.length > 0)
|
|
10737
|
+
// process.exit(1)`) runs earlier in this function, before this
|
|
10738
|
+
// profile-marker-only branch is ever reached — pushing to it here would
|
|
10739
|
+
// be silently ineffective. Warn instead; a failed hooks copy still
|
|
10740
|
+
// leaves kimi's skills/agents artifacts installed correctly.
|
|
10741
|
+
if (!installSharedHooksBundle(kimiHooksRoot)) {
|
|
10742
|
+
console.warn(` ${yellow}⚠${reset} Kimi hook bundle did not verify at ${path.join(kimiHooksRoot, 'hooks')} — GSD lifecycle hooks may be incomplete`);
|
|
10743
|
+
}
|
|
10744
|
+
const kimiHookOpts = { portableHooks: hasPortableHooks, runtime };
|
|
10745
|
+
const kimiHooksTomlPath = path.join(kimiHooksRoot, 'config.toml');
|
|
10746
|
+
const kimiHooksResult = writeKimiHooksToml(kimiHooksTomlPath, kimiHooksRoot, { hookOpts: kimiHookOpts });
|
|
10747
|
+
if (kimiHooksResult.changed) {
|
|
10748
|
+
console.log(` ${green}✓${reset} Configured ${kimiHooksResult.entryCount} GSD hook(s) in ${kimiHooksTomlPath}`);
|
|
10749
|
+
}
|
|
10750
|
+
}
|
|
10751
|
+
|
|
10752
|
+
// ADR-1239 / #2100 Stage 2: Windsurf's own independent hooksSurface —
|
|
10753
|
+
// Cascade's native hooks.json blocking hook bus (pre_write_code,
|
|
10754
|
+
// pre_run_command), wired via runtime-hooks-surface.cts exactly like
|
|
10755
|
+
// Cursor's writeCursorHooksJson but with Cascade's exit-code-2 blocking
|
|
10756
|
+
// protocol instead of Cursor's stdout-JSON form. Unlike kimi's branch
|
|
10757
|
+
// above, this is NOT gated to `isGlobal` — Windsurf has no
|
|
10758
|
+
// hostBehaviors.localInstallDeferred early-return, so both local
|
|
10759
|
+
// (.windsurf/hooks.json) and global (~/.codeium/windsurf/hooks.json)
|
|
10760
|
+
// installs reach this branch and must get the hook bus wired.
|
|
10761
|
+
if (plan.hooksSurface === 'windsurf-hooks-json') {
|
|
10762
|
+
const windsurfHookResult = writeWindsurfHooksJson(targetDir, src, {
|
|
10763
|
+
platform: process.platform,
|
|
10764
|
+
});
|
|
10765
|
+
if (windsurfHookResult.changed) {
|
|
10766
|
+
console.log(` ${green}✓${reset} Configured Windsurf lifecycle hooks (pre_write_code, pre_run_command)`);
|
|
10767
|
+
} else {
|
|
10768
|
+
console.log(` ${green}✓${reset} Windsurf lifecycle hooks already up to date`);
|
|
10769
|
+
}
|
|
10770
|
+
// Re-run the manifest pass, mirroring the cursor writer's pattern above
|
|
10771
|
+
// for parity. This does NOT hash-track hooks.json or the
|
|
10772
|
+
// gsd-windsurf-*.js scripts (same as cursor): uninstall removes them
|
|
10773
|
+
// explicitly via removeWindsurfHooksJson, and reconcileWindsurfHooksJson
|
|
10774
|
+
// is idempotent on repeated installs, so manifest tracking isn't needed
|
|
10775
|
+
// for correctness here.
|
|
10776
|
+
writeManifest(targetDir, runtime, { mode: _effectiveInstallMode, scope: isGlobal ? 'global' : 'local' });
|
|
10777
|
+
}
|
|
10778
|
+
|
|
10041
10779
|
persistActiveProfileMarker();
|
|
10042
10780
|
return { settingsPath: null, settings: null, statuslineCommand: null, updateBannerCommand: null, runtime, configDir: targetDir };
|
|
10043
10781
|
}
|
|
@@ -10165,7 +10903,10 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
10165
10903
|
// Claude Code sets $CLAUDE_PROJECT_DIR; Antigravity does not — and on
|
|
10166
10904
|
// Windows its own substitution logic doubles the path (#2557). It runs
|
|
10167
10905
|
// project hooks with the project dir as cwd, so bare relative paths work.
|
|
10168
|
-
|
|
10906
|
+
// Descriptor-driven (ADR-1239 / #2096): hookPathStyle comes from the
|
|
10907
|
+
// runtime's hostBehaviors instead of a hardcoded `runtime === 'antigravity'`
|
|
10908
|
+
// check inside projectLocalHookPrefix.
|
|
10909
|
+
const localPrefix = projectLocalHookPrefix({ runtime, dirName, hookPathStyle: _hostBehaviors(runtime).hookPathStyle });
|
|
10169
10910
|
const hookOpts = { portableHooks: hasPortableHooks, runtime };
|
|
10170
10911
|
// #2979: local-install hook commands also use the absolute node path so
|
|
10171
10912
|
// GUI/minimal-PATH runtimes can resolve them. Bare `node` fails when the
|
|
@@ -10338,7 +11079,16 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
10338
11079
|
* Apply statusline config, then print completion message
|
|
10339
11080
|
*/
|
|
10340
11081
|
function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallStatusline, runtime = DEFAULT_RUNTIME, isGlobal = true, configDir = null, bannerOpts = {}) {
|
|
10341
|
-
|
|
11082
|
+
// #2093: isKilo dropped — the Kilo permissions-writer call below is gated
|
|
11083
|
+
// on plan.finishPermissionWriter === 'kilo' (descriptor-driven), not this flag.
|
|
11084
|
+
// #2094: isTrae dropped — unused in this function.
|
|
11085
|
+
// #2095: isKimi dropped — the Kimi "Done!" banner below reads
|
|
11086
|
+
// _hostBehaviors(runtime).doneBannerStyle === 'kimi-agent-file' (descriptor-driven), not this flag.
|
|
11087
|
+
// #2096: isAntigravity dropped — unused in this function.
|
|
11088
|
+
// #2098: isCodebuddy dropped — unused in this function.
|
|
11089
|
+
// #2099: isCopilot dropped — unused in this function.
|
|
11090
|
+
// #2100: isWindsurf dropped — unused in this function.
|
|
11091
|
+
const { isOpencode, isCodex, isCursor, isAugment, isQwen, isHermes, isCline } = runtimeFlags(runtime);
|
|
10342
11092
|
const plan = resolveInstallPlan(runtime);
|
|
10343
11093
|
|
|
10344
11094
|
if (shouldInstallStatusline && plan.writesSharedSettings && !_hostBehaviors(runtime).skipSettingsUi) {
|
|
@@ -10400,6 +11150,12 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
|
|
|
10400
11150
|
mergeClaudePermissions(settings);
|
|
10401
11151
|
}
|
|
10402
11152
|
|
|
11153
|
+
// #2097 UPGRADE 3 (transport:mcp): companion MCP server for runtimes that host
|
|
11154
|
+
// MCP in settings.json (Augment). settings.json is golden-excluded, so no golden change.
|
|
11155
|
+
if (_hostBehaviors(runtime).mcpCompanion === 'settings-json' && settings && plan.writesSharedSettings) {
|
|
11156
|
+
mergeGsdMcpServerIntoSettings(settings);
|
|
11157
|
+
}
|
|
11158
|
+
|
|
10403
11159
|
// Write settings when runtime supports settings.json.
|
|
10404
11160
|
// #3002 CR: defense-in-depth — re-run validateHookFields right before
|
|
10405
11161
|
// serialization. The push-site guards above already skip null-command
|
|
@@ -10421,6 +11177,15 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
|
|
|
10421
11177
|
configureKiloPermissions(isGlobal, configDir);
|
|
10422
11178
|
}
|
|
10423
11179
|
|
|
11180
|
+
// Configure Antigravity permissions + MCP companion server (#2096 Phase B
|
|
11181
|
+
// Upgrades 1+2). Not GSD_TEST_MODE-gated — mirrors Kilo's dispatch exactly;
|
|
11182
|
+
// both writers target files (settings.json, mcp_config.json) scoped under
|
|
11183
|
+
// this runtime's own configDir, so they are safe to run unconditionally.
|
|
11184
|
+
if (plan.finishPermissionWriter === 'antigravity') {
|
|
11185
|
+
configureAntigravityPermissions(isGlobal, configDir);
|
|
11186
|
+
configureAntigravityMcpConfig(isGlobal, configDir);
|
|
11187
|
+
}
|
|
11188
|
+
|
|
10424
11189
|
// For non-Claude runtimes, DEFAULT resolve_model_ids to "omit" in ~/.gsd/defaults.json
|
|
10425
11190
|
// when it is absent or falsy, so resolveModelInternal() returns '' instead of Claude
|
|
10426
11191
|
// aliases (opus/sonnet/haiku) the runtime can't resolve. An explicit `true` opt-in
|
|
@@ -10480,7 +11245,7 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
|
|
|
10480
11245
|
return;
|
|
10481
11246
|
}
|
|
10482
11247
|
|
|
10483
|
-
if (runtime === 'kimi') {
|
|
11248
|
+
if (_hostBehaviors(runtime).doneBannerStyle === 'kimi-agent-file') {
|
|
10484
11249
|
const agentPath = configDir ? path.join(configDir, 'agents', 'gsd.yaml') : 'agents/gsd.yaml';
|
|
10485
11250
|
console.log(`
|
|
10486
11251
|
${green}Done!${reset} Start ${program} with ${cyan}kimi --agent-file ${agentPath}${reset}, then run ${cyan}${command}${reset}.
|
|
@@ -10568,13 +11333,14 @@ const runtimeMap = {
|
|
|
10568
11333
|
'10': 'kimi',
|
|
10569
11334
|
'11': 'kilo',
|
|
10570
11335
|
'12': 'opencode',
|
|
10571
|
-
'13': '
|
|
10572
|
-
'14': '
|
|
10573
|
-
'15': '
|
|
10574
|
-
'16': '
|
|
11336
|
+
'13': 'pi',
|
|
11337
|
+
'14': 'qwen',
|
|
11338
|
+
'15': 'trae',
|
|
11339
|
+
'16': 'windsurf',
|
|
11340
|
+
'17': 'zcode'
|
|
10575
11341
|
};
|
|
10576
|
-
const allRuntimes = ['claude', 'antigravity', 'augment', 'cline', 'codebuddy', 'codex', 'copilot', 'cursor', 'hermes', 'kimi', 'kilo', 'opencode', 'qwen', 'trae', 'windsurf', 'zcode'];
|
|
10577
|
-
const ALL_RUNTIMES_OPTION = '
|
|
11342
|
+
const allRuntimes = ['claude', 'antigravity', 'augment', 'cline', 'codebuddy', 'codex', 'copilot', 'cursor', 'hermes', 'kimi', 'kilo', 'opencode', 'pi', 'qwen', 'trae', 'windsurf', 'zcode'];
|
|
11343
|
+
const ALL_RUNTIMES_OPTION = '18';
|
|
10578
11344
|
|
|
10579
11345
|
/**
|
|
10580
11346
|
* Build the runtime-selection prompt text shown by the interactive installer.
|
|
@@ -10594,11 +11360,12 @@ function buildRuntimePromptText() {
|
|
|
10594
11360
|
${cyan}10${reset}) Kimi ${dim}(~/.config/agents, then ~/.agents if existing)${reset}
|
|
10595
11361
|
${cyan}11${reset}) Kilo ${dim}(~/.config/kilo)${reset}
|
|
10596
11362
|
${cyan}12${reset}) OpenCode ${dim}(~/.config/opencode)${reset}
|
|
10597
|
-
${cyan}13${reset})
|
|
10598
|
-
${cyan}14${reset})
|
|
10599
|
-
${cyan}15${reset})
|
|
10600
|
-
${cyan}16${reset})
|
|
10601
|
-
${cyan}17${reset})
|
|
11363
|
+
${cyan}13${reset}) pi ${dim}(~/.pi/agent)${reset}
|
|
11364
|
+
${cyan}14${reset}) Qwen Code ${dim}(~/.qwen)${reset}
|
|
11365
|
+
${cyan}15${reset}) Trae ${dim}(~/.trae)${reset}
|
|
11366
|
+
${cyan}16${reset}) Windsurf ${dim}(~/.codeium/windsurf)${reset}
|
|
11367
|
+
${cyan}17${reset}) ZCode ${dim}(~/.zcode)${reset}
|
|
11368
|
+
${cyan}18${reset}) All
|
|
10602
11369
|
|
|
10603
11370
|
${dim}Select multiple: 1,2,6 or 1 2 6${reset}
|
|
10604
11371
|
`;
|
|
@@ -11145,8 +11912,8 @@ const _LEGACY_SCAN_SUBDIR_NAMES = [
|
|
|
11145
11912
|
'.agents', // antigravity local form (canonical, #791)
|
|
11146
11913
|
'.agent', // antigravity local form (legacy, backward-compat)
|
|
11147
11914
|
'.cursor',
|
|
11148
|
-
'.devin', // windsurf local form (
|
|
11149
|
-
'.windsurf', // windsurf local form (
|
|
11915
|
+
'.devin', // windsurf local form (legacy, pre-#1615; Devin Desktop preferred dir, #1085)
|
|
11916
|
+
'.windsurf', // windsurf local form (canonical since #1615; capability.json localConfigDir)
|
|
11150
11917
|
'.codeium/windsurf',
|
|
11151
11918
|
'.augment',
|
|
11152
11919
|
'.trae',
|
|
@@ -11398,6 +12165,13 @@ module.exports = {
|
|
|
11398
12165
|
getConfigDirFromHome,
|
|
11399
12166
|
resolveKiloConfigPath,
|
|
11400
12167
|
configureKiloPermissions,
|
|
12168
|
+
// #2096 Phase B Upgrades 1+2 — Antigravity permission-writer + MCP companion
|
|
12169
|
+
toTildePosixPath,
|
|
12170
|
+
buildAntigravityAllowRules,
|
|
12171
|
+
configureAntigravityPermissions,
|
|
12172
|
+
configureAntigravityMcpConfig,
|
|
12173
|
+
// #2097 UPGRADE 3 — Augment MCP companion (settings.json-hosted)
|
|
12174
|
+
mergeGsdMcpServerIntoSettings,
|
|
11401
12175
|
claudeToCopilotTools,
|
|
11402
12176
|
convertCopilotToolName,
|
|
11403
12177
|
convertClaudeToCopilotContent,
|
|
@@ -11450,6 +12224,11 @@ module.exports = {
|
|
|
11450
12224
|
reconcileCursorHooksJson,
|
|
11451
12225
|
writeCursorHooksJson,
|
|
11452
12226
|
removeCursorHooksJson,
|
|
12227
|
+
GSD_WINDSURF_PRE_WRITE_HOOK_SCRIPT,
|
|
12228
|
+
GSD_WINDSURF_PRE_COMMAND_HOOK_SCRIPT,
|
|
12229
|
+
GSD_WINDSURF_HOOK_SCRIPTS,
|
|
12230
|
+
writeWindsurfHooksJson,
|
|
12231
|
+
removeWindsurfHooksJson,
|
|
11453
12232
|
stripGsdFromAgentsMd,
|
|
11454
12233
|
GSD_AGENTS_MD_MARKER,
|
|
11455
12234
|
GSD_AGENTS_MD_CLOSE_MARKER,
|