@opengsd/gsd-core 1.4.2 → 1.5.0-rc.1
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/plugin.json +1 -1
- package/README.md +3 -3
- package/agents/gsd-executor.md +1 -1
- package/agents/gsd-plan-checker.md +11 -2
- package/agents/gsd-planner.md +11 -7
- package/agents/gsd-verifier.md +3 -3
- package/bin/install.js +947 -149
- package/commands/gsd/autonomous.md +5 -1
- package/commands/gsd/ns-manage.md +8 -1
- package/commands/gsd/ns-project.md +5 -0
- package/commands/gsd/ns-review.md +4 -1
- package/commands/gsd/ns-workflow.md +7 -1
- package/commands/gsd/plan-review-convergence.md +6 -5
- package/commands/gsd/surface.md +12 -5
- package/gemini-extension.json +1 -1
- package/gsd-core/bin/gsd-tools.cjs +198 -101
- package/gsd-core/bin/lib/audit-command-router.cjs +61 -0
- package/gsd-core/bin/lib/capability-registry.cjs +417 -0
- package/gsd-core/bin/lib/capability-state.cjs +336 -0
- package/gsd-core/bin/lib/cli-exit.cjs +22 -3
- package/gsd-core/bin/lib/config-loader.cjs +716 -0
- package/gsd-core/bin/lib/configuration.cjs +4 -34
- package/gsd-core/bin/lib/core-utils.cjs +198 -0
- package/gsd-core/bin/lib/core.cjs +57 -1817
- package/gsd-core/bin/lib/federated-config.cjs +182 -0
- package/gsd-core/bin/lib/graphify-command-router.cjs +74 -0
- package/gsd-core/bin/lib/init.cjs +58 -12
- package/gsd-core/bin/lib/install-profiles.cjs +157 -3
- package/gsd-core/bin/lib/installer-migrations/004-prune-stale-pristine-snapshots.cjs +123 -0
- package/gsd-core/bin/lib/intel-command-router.cjs +116 -0
- package/gsd-core/bin/lib/io.cjs +167 -0
- package/gsd-core/bin/lib/loop-host-contract.cjs +105 -0
- package/gsd-core/bin/lib/loop-resolver.cjs +460 -0
- package/gsd-core/bin/lib/model-resolver.cjs +426 -0
- package/gsd-core/bin/lib/phase-id.cjs +215 -0
- package/gsd-core/bin/lib/phase-locator.cjs +148 -0
- package/gsd-core/bin/lib/profile-pipeline.cjs +2 -2
- package/gsd-core/bin/lib/roadmap-parser.cjs +443 -0
- package/gsd-core/bin/lib/roadmap.cjs +4 -1
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +96 -9
- package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +2 -1
- package/gsd-core/bin/lib/runtime-homes.cjs +40 -0
- package/gsd-core/bin/lib/runtime-name-policy.cjs +1 -0
- package/gsd-core/bin/lib/shell-command-projection.cjs +13 -0
- package/gsd-core/bin/lib/state.cjs +114 -2
- package/gsd-core/bin/lib/surface.cjs +66 -14
- package/gsd-core/bin/lib/verify.cjs +1 -1
- package/gsd-core/bin/shared/model-catalog.json +5 -0
- package/gsd-core/bin/shared/runtime-aliases.manifest.json +3 -0
- package/gsd-core/bin/verify-reapply-patches.cjs +57 -7
- package/gsd-core/references/planner-reviews.md +5 -2
- package/gsd-core/templates/phase-prompt.md +7 -7
- package/gsd-core/templates/project.md +19 -2
- package/gsd-core/templates/summary-complex.md +1 -0
- package/gsd-core/templates/summary-minimal.md +1 -0
- package/gsd-core/templates/summary-standard.md +1 -0
- package/gsd-core/templates/summary.md +1 -0
- package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
- package/gsd-core/workflows/add-backlog.md +1 -1
- package/gsd-core/workflows/add-phase.md +1 -1
- package/gsd-core/workflows/add-tests.md +1 -1
- package/gsd-core/workflows/add-todo.md +1 -1
- package/gsd-core/workflows/ai-integration-phase.md +1 -1
- package/gsd-core/workflows/audit-fix.md +1 -1
- package/gsd-core/workflows/audit-milestone.md +1 -1
- package/gsd-core/workflows/audit-uat.md +1 -1
- package/gsd-core/workflows/autonomous.md +86 -15
- package/gsd-core/workflows/check-todos.md +1 -1
- package/gsd-core/workflows/cleanup.md +1 -1
- package/gsd-core/workflows/code-review-fix.md +6 -4
- package/gsd-core/workflows/code-review.md +3 -2
- package/gsd-core/workflows/complete-milestone.md +11 -5
- package/gsd-core/workflows/debug.md +1 -1
- package/gsd-core/workflows/diagnose-issues.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/auto.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
- package/gsd-core/workflows/discuss-phase.md +8 -1
- package/gsd-core/workflows/do.md +1 -1
- package/gsd-core/workflows/docs-update.md +1 -1
- package/gsd-core/workflows/edit-phase.md +1 -1
- package/gsd-core/workflows/eval-review.md +4 -1
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +1 -1
- package/gsd-core/workflows/execute-phase.md +8 -1
- package/gsd-core/workflows/execute-plan.md +1 -1
- package/gsd-core/workflows/explore.md +1 -1
- package/gsd-core/workflows/extract-learnings.md +1 -1
- package/gsd-core/workflows/forensics.md +1 -1
- package/gsd-core/workflows/graduation.md +1 -1
- package/gsd-core/workflows/health.md +1 -1
- package/gsd-core/workflows/help/modes/full.md +1 -1
- package/gsd-core/workflows/import.md +1 -1
- package/gsd-core/workflows/ingest-docs.md +1 -1
- package/gsd-core/workflows/insert-phase.md +1 -1
- package/gsd-core/workflows/list-workspaces.md +1 -1
- package/gsd-core/workflows/manager.md +1 -1
- package/gsd-core/workflows/map-codebase.md +1 -1
- package/gsd-core/workflows/milestone-summary.md +1 -1
- package/gsd-core/workflows/mvp-phase.md +1 -1
- package/gsd-core/workflows/new-milestone.md +1 -1
- package/gsd-core/workflows/new-project.md +1 -1
- package/gsd-core/workflows/new-workspace.md +1 -1
- package/gsd-core/workflows/next.md +1 -1
- package/gsd-core/workflows/pause-work.md +1 -1
- package/gsd-core/workflows/plan-milestone-gaps.md +1 -1
- package/gsd-core/workflows/plan-phase.md +27 -2
- package/gsd-core/workflows/plan-review-convergence.md +76 -63
- package/gsd-core/workflows/plant-seed.md +1 -1
- package/gsd-core/workflows/profile-user.md +1 -1
- package/gsd-core/workflows/progress.md +1 -1
- package/gsd-core/workflows/quick.md +2 -2
- package/gsd-core/workflows/reapply-patches.md +18 -1
- package/gsd-core/workflows/remove-phase.md +1 -1
- package/gsd-core/workflows/remove-workspace.md +1 -1
- package/gsd-core/workflows/resume-project.md +1 -1
- package/gsd-core/workflows/review.md +1 -1
- package/gsd-core/workflows/scan.md +1 -1
- package/gsd-core/workflows/secure-phase.md +1 -1
- package/gsd-core/workflows/settings-advanced.md +1 -1
- package/gsd-core/workflows/settings-integrations.md +1 -1
- package/gsd-core/workflows/settings.md +1 -1
- package/gsd-core/workflows/ship.md +8 -1
- package/gsd-core/workflows/sketch-wrap-up.md +1 -1
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/spec-phase.md +1 -1
- package/gsd-core/workflows/spike-wrap-up.md +1 -1
- package/gsd-core/workflows/spike.md +1 -1
- package/gsd-core/workflows/stats.md +1 -1
- package/gsd-core/workflows/thread.md +1 -1
- package/gsd-core/workflows/transition.md +1 -1
- package/gsd-core/workflows/ui-phase.md +1 -1
- package/gsd-core/workflows/ui-review.md +1 -1
- package/gsd-core/workflows/ultraplan-phase.md +1 -1
- package/gsd-core/workflows/update.md +20 -15
- package/gsd-core/workflows/validate-phase.md +1 -1
- package/gsd-core/workflows/verify-phase.md +1 -1
- package/gsd-core/workflows/verify-work.md +8 -1
- package/package.json +7 -2
- package/scripts/base64-scan.sh +1 -1
- package/scripts/changeset/cli.cjs +9 -2
- package/scripts/changeset/lint.cjs +38 -2
- package/scripts/ci-test-scope.cjs +21 -10
- package/scripts/gen-capability-registry.cjs +1808 -0
- package/scripts/gen-loop-host-contract.cjs +471 -0
- package/scripts/lint-regression-test-names.allowlist.json +269 -0
- package/scripts/lint-regression-test-names.cjs +117 -0
- package/scripts/lint-test-file-count.allowlist.json +12 -4
- package/scripts/prompt-injection-scan.sh +4 -4
- package/scripts/run-tests.cjs +79 -16
- package/scripts/secret-scan.sh +3 -3
- package/scripts/run-cross-platform-tests.cjs +0 -67
package/bin/install.js
CHANGED
|
@@ -31,12 +31,19 @@ const {
|
|
|
31
31
|
const {
|
|
32
32
|
resolveAntigravityGlobalDir,
|
|
33
33
|
getGlobalConfigDir,
|
|
34
|
+
getGlobalSkillsBase,
|
|
34
35
|
} = require('../gsd-core/bin/lib/runtime-homes.cjs');
|
|
35
36
|
const {
|
|
36
37
|
applyWorktreeBaseRef,
|
|
37
38
|
readBaseRefFromSettings,
|
|
38
39
|
} = require('../gsd-core/bin/lib/worktree-base-ref.cjs');
|
|
39
40
|
const { resolveRuntimeConfigIntent } = require('../gsd-core/bin/lib/runtime-config-adapter-registry.cjs');
|
|
41
|
+
// Canonical set of hook files shipped to users. Imported here so writeManifest()
|
|
42
|
+
// records exactly the same set that build-hooks.js copies to hooks/dist/, making
|
|
43
|
+
// the manifest and the installed hooks/ dir structurally identical. Avoids the
|
|
44
|
+
// prefix/extension-regex approach that missed managed-hooks-registry.cjs (#941).
|
|
45
|
+
const { HOOKS_TO_COPY: _HOOKS_TO_COPY } = require('../scripts/build-hooks.js');
|
|
46
|
+
const INSTALLED_HOOK_FILES = new Set(_HOOKS_TO_COPY);
|
|
40
47
|
|
|
41
48
|
/**
|
|
42
49
|
* Runtimes that register hyphen-form `name:` per #2808 AND copy agent bodies
|
|
@@ -323,6 +330,13 @@ const {
|
|
|
323
330
|
stageAgentsForProfile,
|
|
324
331
|
stageSkillsForRuntimeAsSkills,
|
|
325
332
|
} = require(path.join(_gsdLibDir, 'install-profiles.cjs'));
|
|
333
|
+
// ADR-857 phase 4c: load capability registry (optional; missing → falls back to undefined)
|
|
334
|
+
let _capabilityRegistry;
|
|
335
|
+
try {
|
|
336
|
+
_capabilityRegistry = require(path.join(_gsdLibDir, 'capability-registry.cjs'));
|
|
337
|
+
} catch (_) {
|
|
338
|
+
_capabilityRegistry = undefined;
|
|
339
|
+
}
|
|
326
340
|
const {
|
|
327
341
|
applyInstallerMigrationPlan,
|
|
328
342
|
discoverInstallerMigrations,
|
|
@@ -348,23 +362,6 @@ const {
|
|
|
348
362
|
const args = process.argv.slice(2);
|
|
349
363
|
const hasGlobal = args.includes('--global') || args.includes('-g');
|
|
350
364
|
const hasLocal = args.includes('--local') || args.includes('-l');
|
|
351
|
-
const hasOpencode = args.includes('--opencode');
|
|
352
|
-
const hasClaude = args.includes('--claude');
|
|
353
|
-
const hasGemini = args.includes('--gemini');
|
|
354
|
-
const hasKilo = args.includes('--kilo');
|
|
355
|
-
const hasCodex = args.includes('--codex');
|
|
356
|
-
const hasCopilot = args.includes('--copilot');
|
|
357
|
-
const hasAntigravity = args.includes('--antigravity');
|
|
358
|
-
const hasCursor = args.includes('--cursor');
|
|
359
|
-
const hasWindsurf = args.includes('--windsurf');
|
|
360
|
-
const hasAugment = args.includes('--augment');
|
|
361
|
-
const hasTrae = args.includes('--trae');
|
|
362
|
-
const hasQwen = args.includes('--qwen');
|
|
363
|
-
const hasHermes = args.includes('--hermes');
|
|
364
|
-
const hasCodebuddy = args.includes('--codebuddy');
|
|
365
|
-
const hasCline = args.includes('--cline');
|
|
366
|
-
const hasBoth = args.includes('--both'); // Legacy flag, keeps working
|
|
367
|
-
const hasAll = args.includes('--all');
|
|
368
365
|
const hasUninstall = args.includes('--uninstall') || args.includes('-u');
|
|
369
366
|
const hasSkillsRoot = args.includes('--skills-root');
|
|
370
367
|
const hasPortableHooks = args.includes('--portable-hooks') || process.env.GSD_PORTABLE_HOOKS === '1';
|
|
@@ -391,30 +388,37 @@ if (hasMinimal && _profileArgRaw) {
|
|
|
391
388
|
process.exit(1);
|
|
392
389
|
}
|
|
393
390
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
if (
|
|
404
|
-
if (
|
|
405
|
-
if (
|
|
406
|
-
if (
|
|
407
|
-
if (
|
|
408
|
-
if (
|
|
409
|
-
if (
|
|
410
|
-
if (
|
|
411
|
-
if (
|
|
412
|
-
if (
|
|
413
|
-
if (
|
|
414
|
-
if (
|
|
415
|
-
if (
|
|
391
|
+
function selectRuntimesFromArgs(runtimeArgs) {
|
|
392
|
+
if (runtimeArgs.includes('--all')) {
|
|
393
|
+
return ['claude', 'kimi', 'kilo', 'opencode', 'gemini', 'codex', 'copilot', 'antigravity', 'cursor', 'windsurf', 'augment', 'trae', 'qwen', 'hermes', 'codebuddy', 'cline'];
|
|
394
|
+
}
|
|
395
|
+
if (runtimeArgs.includes('--both')) {
|
|
396
|
+
return ['claude', 'opencode'];
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const selected = [];
|
|
400
|
+
if (runtimeArgs.includes('--claude')) selected.push('claude');
|
|
401
|
+
if (runtimeArgs.includes('--opencode')) selected.push('opencode');
|
|
402
|
+
if (runtimeArgs.includes('--gemini')) selected.push('gemini');
|
|
403
|
+
if (runtimeArgs.includes('--kilo')) selected.push('kilo');
|
|
404
|
+
if (runtimeArgs.includes('--codex')) selected.push('codex');
|
|
405
|
+
if (runtimeArgs.includes('--copilot')) selected.push('copilot');
|
|
406
|
+
if (runtimeArgs.includes('--antigravity')) selected.push('antigravity');
|
|
407
|
+
if (runtimeArgs.includes('--cursor')) selected.push('cursor');
|
|
408
|
+
if (runtimeArgs.includes('--windsurf')) selected.push('windsurf');
|
|
409
|
+
if (runtimeArgs.includes('--augment')) selected.push('augment');
|
|
410
|
+
if (runtimeArgs.includes('--trae')) selected.push('trae');
|
|
411
|
+
if (runtimeArgs.includes('--qwen')) selected.push('qwen');
|
|
412
|
+
if (runtimeArgs.includes('--hermes')) selected.push('hermes');
|
|
413
|
+
if (runtimeArgs.includes('--kimi')) selected.push('kimi');
|
|
414
|
+
if (runtimeArgs.includes('--codebuddy')) selected.push('codebuddy');
|
|
415
|
+
if (runtimeArgs.includes('--cline')) selected.push('cline');
|
|
416
|
+
return selected;
|
|
416
417
|
}
|
|
417
418
|
|
|
419
|
+
// Runtime selection - can be set by flags or interactive prompt
|
|
420
|
+
let selectedRuntimes = selectRuntimesFromArgs(args);
|
|
421
|
+
|
|
418
422
|
// WSL + Windows Node.js detection
|
|
419
423
|
// When Windows-native Node runs on WSL, os.homedir() and path.join() produce
|
|
420
424
|
// backslash paths that don't resolve correctly on the Linux filesystem.
|
|
@@ -463,6 +467,7 @@ function getDirName(runtime) {
|
|
|
463
467
|
if (runtime === 'trae') return '.trae';
|
|
464
468
|
if (runtime === 'qwen') return '.qwen';
|
|
465
469
|
if (runtime === 'hermes') return '.hermes';
|
|
470
|
+
if (runtime === 'kimi') return '.kimi-code';
|
|
466
471
|
if (runtime === 'codebuddy') return '.codebuddy';
|
|
467
472
|
if (runtime === 'cline') return '.cline';
|
|
468
473
|
return '.claude';
|
|
@@ -509,9 +514,17 @@ function getConfigDirFromHome(runtime, isGlobal) {
|
|
|
509
514
|
if (runtime === 'hermes') return "'.hermes'";
|
|
510
515
|
if (runtime === 'codebuddy') return "'.codebuddy'";
|
|
511
516
|
if (runtime === 'cline') return "'.cline'";
|
|
517
|
+
if (runtime === 'kimi') return "'.config', 'agents'";
|
|
512
518
|
return "'.claude'";
|
|
513
519
|
}
|
|
514
520
|
|
|
521
|
+
/**
|
|
522
|
+
* Compatibility seam for tests and older installer consumers.
|
|
523
|
+
* Runtime home resolution now lives in runtime-homes.cjs.
|
|
524
|
+
*/
|
|
525
|
+
function getGlobalDir(runtime, explicitDir = null) {
|
|
526
|
+
return getGlobalConfigDir(runtime, explicitDir);
|
|
527
|
+
}
|
|
515
528
|
const banner = '\n' +
|
|
516
529
|
cyan + ' ██████╗ ███████╗██████╗\n' +
|
|
517
530
|
' ██╔════╝ ██╔════╝██╔══██╗\n' +
|
|
@@ -523,7 +536,7 @@ const banner = '\n' +
|
|
|
523
536
|
' GSD Core ' + dim + 'v' + pkg.version + reset + '\n' +
|
|
524
537
|
' Git. Ship. Done.\n' +
|
|
525
538
|
' A meta-prompting, context engineering and spec-driven\n' +
|
|
526
|
-
' development workflows for Claude Code, OpenCode, Gemini, Kilo, Codex, Copilot, Antigravity, Cursor, Windsurf, Augment, Trae, Qwen Code, Hermes Agent, Cline and CodeBuddy.\n';
|
|
539
|
+
' development workflows for Claude Code, OpenCode, Gemini, Kimi CLI, Kilo, Codex, Copilot, Antigravity, Cursor, Windsurf, Augment, Trae, Qwen Code, Hermes Agent, Cline and CodeBuddy.\n';
|
|
527
540
|
|
|
528
541
|
// Pure seam: parse --config-dir / -c from an arbitrary args array.
|
|
529
542
|
// Returns the path string, '' for an empty equals-form value, or null when the
|
|
@@ -580,7 +593,7 @@ if (hasUninstall) {
|
|
|
580
593
|
|
|
581
594
|
// Show help if requested
|
|
582
595
|
if (hasHelp) {
|
|
583
|
-
console.log(` ${yellow}Usage:${reset} npx ${pkg.name} [options]\n\n ${yellow}Options:${reset}\n ${cyan}-g, --global${reset} Install globally (to config directory)\n ${cyan}-l, --local${reset} Install locally (to current directory)\n ${cyan}--claude${reset} Install for Claude Code only\n ${cyan}--opencode${reset} Install for OpenCode only\n ${cyan}--gemini${reset} Install for Gemini only\n ${cyan}--kilo${reset} Install for Kilo only\n ${cyan}--codex${reset} Install for Codex only\n ${cyan}--copilot${reset} Install for Copilot only\n ${cyan}--antigravity${reset} Install for Antigravity only\n ${cyan}--cursor${reset} Install for Cursor only\n ${cyan}--windsurf${reset} Install for Windsurf only\n ${cyan}--augment${reset} Install for Augment only\n ${cyan}--trae${reset} Install for Trae only\n ${cyan}--qwen${reset} Install for Qwen Code only\n ${cyan}--hermes${reset} Install for Hermes Agent only\n ${cyan}--cline${reset} Install for Cline only\n ${cyan}--codebuddy${reset} Install for CodeBuddy only\n ${cyan}--all${reset} Install for all runtimes\n ${cyan}-u, --uninstall${reset} Uninstall GSD (remove all GSD files)\n ${cyan}-c, --config-dir <path>${reset} Specify custom config directory\n ${cyan}-h, --help${reset} Show this help message\n ${cyan}--force-statusline${reset} Replace existing statusline config\n ${cyan}--portable-hooks${reset} Emit \$HOME-relative hook paths in settings.json\n (for WSL/Docker bind-mount setups; also GSD_PORTABLE_HOOKS=1)\n ${cyan}--profile=<name>${reset} Install a named skill profile. Profiles:\n core — ${PROFILES.core.length} main-loop skills incl. phase (~130 desc tokens)\n standard — ${PROFILES.standard.length} skills incl. phase, review, config (~700)\n full — all skills (default)\n Composable: --profile=core,audit installs union of closures.\n Profile is persisted and respected by \`gsd update\`.\n ${cyan}--minimal${reset} Alias for --profile=core (back-compat).\n Cuts cold-start overhead from ~12k tokens to ~700.\n Alias: --core-only.\n\n ${yellow}Examples:${reset}\n ${dim}# Interactive install (prompts for runtime and location)${reset}\n npx ${pkg.name}\n\n ${dim}# Install for Claude Code globally${reset}\n npx ${pkg.name} --claude --global\n\n ${dim}# Install for Gemini globally${reset}\n npx ${pkg.name} --gemini --global\n\n ${dim}# Install for Kilo globally${reset}\n npx ${pkg.name} --kilo --global\n\n ${dim}# Install for Codex globally${reset}\n npx ${pkg.name} --codex --global\n\n ${dim}# Install for Copilot globally${reset}\n npx ${pkg.name} --copilot --global\n\n ${dim}# Install for Copilot locally${reset}\n npx ${pkg.name} --copilot --local\n\n ${dim}# Install for Antigravity globally${reset}\n npx ${pkg.name} --antigravity --global\n\n ${dim}# Install for Antigravity locally${reset}\n npx ${pkg.name} --antigravity --local\n\n ${dim}# Install for Cursor globally${reset}\n npx ${pkg.name} --cursor --global\n\n ${dim}# Install for Cursor locally${reset}\n npx ${pkg.name} --cursor --local\n\n ${dim}# Install for Windsurf globally${reset}\n npx ${pkg.name} --windsurf --global\n\n ${dim}# Install for Windsurf locally${reset}\n npx ${pkg.name} --windsurf --local\n\n ${dim}# Install for Augment globally${reset}\n npx ${pkg.name} --augment --global\n\n ${dim}# Install for Augment locally${reset}\n npx ${pkg.name} --augment --local\n\n ${dim}# Install for Trae globally${reset}\n npx ${pkg.name} --trae --global\n\n ${dim}# Install for Trae locally${reset}\n npx ${pkg.name} --trae --local\n\n ${dim}# Install for Hermes Agent globally${reset}\n npx ${pkg.name} --hermes --global\n\n ${dim}# Install for Hermes Agent locally${reset}\n npx ${pkg.name} --hermes --local\n\n ${dim}# Install for Cline globally${reset}\n npx ${pkg.name} --cline --global\n\n ${dim}# Install for Cline locally${reset}\n npx ${pkg.name} --cline --local\n\n ${dim}# Install for CodeBuddy globally${reset}\n npx ${pkg.name} --codebuddy --global\n\n ${dim}# Install for CodeBuddy locally${reset}\n npx ${pkg.name} --codebuddy --local\n\n ${dim}# Install for all runtimes globally${reset}\n npx ${pkg.name} --all --global\n\n ${dim}# Install to custom config directory${reset}\n npx ${pkg.name} --kilo --global --config-dir ~/.kilo-work\n\n ${dim}# Install to current project only${reset}\n npx ${pkg.name} --claude --local\n\n ${dim}# Uninstall GSD from Cursor globally${reset}\n npx ${pkg.name} --cursor --global --uninstall\n\n ${yellow}Notes:${reset}\n The --config-dir option is useful when you have multiple configurations.\n It takes priority over CLAUDE_CONFIG_DIR / OPENCODE_CONFIG_DIR / GEMINI_CONFIG_DIR / KILO_CONFIG_DIR / CODEX_HOME / COPILOT_CONFIG_DIR / COPILOT_HOME / ANTIGRAVITY_CONFIG_DIR / CURSOR_CONFIG_DIR / WINDSURF_CONFIG_DIR / AUGMENT_CONFIG_DIR / TRAE_CONFIG_DIR / QWEN_CONFIG_DIR / HERMES_HOME / CLINE_CONFIG_DIR / CODEBUDDY_CONFIG_DIR environment variables.\n`);
|
|
596
|
+
console.log(` ${yellow}Usage:${reset} npx ${pkg.name} [options]\n\n ${yellow}Options:${reset}\n ${cyan}-g, --global${reset} Install globally (to config directory)\n ${cyan}-l, --local${reset} Install locally (to current directory)\n ${cyan}--claude${reset} Install for Claude Code only\n ${cyan}--opencode${reset} Install for OpenCode only\n ${cyan}--gemini${reset} Install for Gemini only\n ${cyan}--kilo${reset} Install for Kilo only\n ${cyan}--codex${reset} Install for Codex only\n ${cyan}--kimi${reset} Install for Kimi CLI only\n ${cyan}--copilot${reset} Install for Copilot only\n ${cyan}--antigravity${reset} Install for Antigravity only\n ${cyan}--cursor${reset} Install for Cursor only\n ${cyan}--windsurf${reset} Install for Windsurf only\n ${cyan}--augment${reset} Install for Augment only\n ${cyan}--trae${reset} Install for Trae only\n ${cyan}--qwen${reset} Install for Qwen Code only\n ${cyan}--hermes${reset} Install for Hermes Agent only\n ${cyan}--cline${reset} Install for Cline only\n ${cyan}--codebuddy${reset} Install for CodeBuddy only\n ${cyan}--all${reset} Install for all runtimes\n ${cyan}-u, --uninstall${reset} Uninstall GSD (remove all GSD files)\n ${cyan}-c, --config-dir <path>${reset} Specify custom config directory\n ${cyan}-h, --help${reset} Show this help message\n ${cyan}--force-statusline${reset} Replace existing statusline config\n ${cyan}--portable-hooks${reset} Emit \$HOME-relative hook paths in settings.json\n (for WSL/Docker bind-mount setups; also GSD_PORTABLE_HOOKS=1)\n ${cyan}--profile=<name>${reset} Install a named skill profile. Profiles:\n core — ${PROFILES.core.length} main-loop skills incl. phase (~130 desc tokens)\n standard — ${PROFILES.standard.length} skills incl. phase, review, config (~700)\n full — all skills (default)\n Composable: --profile=core,audit installs union of closures.\n Profile is persisted and respected by \`gsd update\`.\n ${cyan}--minimal${reset} Alias for --profile=core (back-compat).\n Cuts cold-start overhead from ~12k tokens to ~700.\n Alias: --core-only.\n\n ${yellow}Examples:${reset}\n ${dim}# Interactive install (prompts for runtime and location)${reset}\n npx ${pkg.name}\n\n ${dim}# Install for Claude Code globally${reset}\n npx ${pkg.name} --claude --global\n\n ${dim}# Install for Gemini globally${reset}\n npx ${pkg.name} --gemini --global\n\n ${dim}# Install for Kilo globally${reset}\n npx ${pkg.name} --kilo --global\n\n ${dim}# Install for Codex globally${reset}\n npx ${pkg.name} --codex --global\n\n ${dim}# Install for Kimi CLI globally${reset}\n npx ${pkg.name} --kimi --global\n\n ${dim}# Install for Kimi CLI under ~/.kimi-code${reset}\n npx ${pkg.name} --kimi --global --config-dir ~/.kimi-code\n\n ${dim}# Install for Copilot globally${reset}\n npx ${pkg.name} --copilot --global\n\n ${dim}# Install for Copilot locally${reset}\n npx ${pkg.name} --copilot --local\n\n ${dim}# Install for Antigravity globally${reset}\n npx ${pkg.name} --antigravity --global\n\n ${dim}# Install for Antigravity locally${reset}\n npx ${pkg.name} --antigravity --local\n\n ${dim}# Install for Cursor globally${reset}\n npx ${pkg.name} --cursor --global\n\n ${dim}# Install for Cursor locally${reset}\n npx ${pkg.name} --cursor --local\n\n ${dim}# Install for Windsurf globally${reset}\n npx ${pkg.name} --windsurf --global\n\n ${dim}# Install for Windsurf locally${reset}\n npx ${pkg.name} --windsurf --local\n\n ${dim}# Install for Augment globally${reset}\n npx ${pkg.name} --augment --global\n\n ${dim}# Install for Augment locally${reset}\n npx ${pkg.name} --augment --local\n\n ${dim}# Install for Trae globally${reset}\n npx ${pkg.name} --trae --global\n\n ${dim}# Install for Trae locally${reset}\n npx ${pkg.name} --trae --local\n\n ${dim}# Install for Hermes Agent globally${reset}\n npx ${pkg.name} --hermes --global\n\n ${dim}# Install for Hermes Agent locally${reset}\n npx ${pkg.name} --hermes --local\n\n ${dim}# Install for Cline globally${reset}\n npx ${pkg.name} --cline --global\n\n ${dim}# Install for Cline locally${reset}\n npx ${pkg.name} --cline --local\n\n ${dim}# Install for CodeBuddy globally${reset}\n npx ${pkg.name} --codebuddy --global\n\n ${dim}# Install for CodeBuddy locally${reset}\n npx ${pkg.name} --codebuddy --local\n\n ${dim}# Install for all runtimes globally${reset}\n npx ${pkg.name} --all --global\n\n ${dim}# Install to custom config directory${reset}\n npx ${pkg.name} --kilo --global --config-dir ~/.kilo-work\n\n ${dim}# Install to current project only${reset}\n npx ${pkg.name} --claude --local\n\n ${dim}# Uninstall GSD from Cursor globally${reset}\n npx ${pkg.name} --cursor --global --uninstall\n\n ${yellow}Notes:${reset}\n The --config-dir option is useful when you have multiple configurations.\n It takes priority over CLAUDE_CONFIG_DIR / OPENCODE_CONFIG_DIR / GEMINI_CONFIG_DIR / KILO_CONFIG_DIR / CODEX_HOME / KIMI_CONFIG_DIR / COPILOT_CONFIG_DIR / COPILOT_HOME / ANTIGRAVITY_CONFIG_DIR / CURSOR_CONFIG_DIR / WINDSURF_CONFIG_DIR / AUGMENT_CONFIG_DIR / TRAE_CONFIG_DIR / QWEN_CONFIG_DIR / HERMES_HOME / CLINE_CONFIG_DIR / CODEBUDDY_CONFIG_DIR environment variables.\n Kimi CLI defaults to the first existing generic skills root: ${cyan}~/.config/agents/skills${reset}, then ${cyan}~/.agents/skills${reset}; if neither exists, GSD creates ${cyan}~/.config/agents${reset}.\n Use ${cyan}--config-dir ~/.kimi-code${reset} or ${cyan}KIMI_CONFIG_DIR=~/.kimi-code${reset} for brand-specific Kimi installs.\n`);
|
|
584
597
|
process.exit(0);
|
|
585
598
|
}
|
|
586
599
|
|
|
@@ -624,8 +637,37 @@ function computePathPrefix({ isGlobal, isOpencode, isWindowsHost: _isWindowsHost
|
|
|
624
637
|
*
|
|
625
638
|
* Non-Homebrew installs (NVM, system node, Windows, etc.) are returned as-is.
|
|
626
639
|
*/
|
|
627
|
-
function normalizeNodePath(execPath) {
|
|
640
|
+
function normalizeNodePath(execPath, opts) {
|
|
628
641
|
if (!execPath) return execPath;
|
|
642
|
+
const env = (opts && opts.env) || process.env;
|
|
643
|
+
const existsSync = (opts && opts.existsSync) || fs.existsSync;
|
|
644
|
+
|
|
645
|
+
// fnm multishell shim: C:/Users/<u>/AppData/Local/fnm_multishells/<pid>_<ts>/node.exe
|
|
646
|
+
// These are per-shell-session ephemeral directories that fnm cleans up on shell exit.
|
|
647
|
+
// Probe the stable fnm alias paths instead so baked hook commands survive shell restarts.
|
|
648
|
+
// (#977)
|
|
649
|
+
// TODO: Volta (~/.volta/bin/node → ~/.volta/tools/image/node/<ver>/bin/node) and
|
|
650
|
+
// nvm-windows (AppData/Roaming/nvm/<ver>/node.exe) have analogous issues — future work.
|
|
651
|
+
const normalizedForMatch = execPath.replace(/\\/g, '/');
|
|
652
|
+
if (/\/fnm_multishells\/[0-9]+_[0-9]+\/node(\.exe)?$/i.test(normalizedForMatch)) {
|
|
653
|
+
const candidates = [];
|
|
654
|
+
if (env.FNM_DIR) {
|
|
655
|
+
// Preferred: alias installed directly under FNM_DIR/aliases/default/
|
|
656
|
+
candidates.push(`${env.FNM_DIR}/aliases/default/node.exe`);
|
|
657
|
+
// POSIX layout (no .exe)
|
|
658
|
+
candidates.push(`${env.FNM_DIR}/aliases/default/bin/node`);
|
|
659
|
+
}
|
|
660
|
+
if (env.APPDATA) {
|
|
661
|
+
// Fallback: fnm default location on Windows when FNM_DIR is not set
|
|
662
|
+
candidates.push(`${env.APPDATA}/fnm/aliases/default/node.exe`);
|
|
663
|
+
}
|
|
664
|
+
for (const candidate of candidates) {
|
|
665
|
+
if (existsSync(candidate)) return candidate;
|
|
666
|
+
}
|
|
667
|
+
// No stable alias found — return raw path unchanged (graceful fallback)
|
|
668
|
+
return execPath;
|
|
669
|
+
}
|
|
670
|
+
|
|
629
671
|
// Intel Homebrew: /usr/local/Cellar/node/<version>/bin/node
|
|
630
672
|
// or /usr/local/Cellar/node@20/<version>/bin/node
|
|
631
673
|
if (/^\/usr\/local\/Cellar\/node(@\d+)?\/[^/]+\/bin\/node(\.exe)?$/.test(execPath)) {
|
|
@@ -654,11 +696,16 @@ function normalizeNodePath(execPath) {
|
|
|
654
696
|
*
|
|
655
697
|
* When `process.execPath` is a versioned Homebrew Cellar path, the stable
|
|
656
698
|
* Homebrew symlink is returned instead to survive `brew upgrade node` (#3181).
|
|
699
|
+
*
|
|
700
|
+
* When `process.execPath` is an ephemeral fnm multishell shim, the stable fnm
|
|
701
|
+
* alias path is returned instead so managed hook commands survive shell restarts
|
|
702
|
+
* (#977). An optional `opts` bag (`{ env, existsSync }`) is accepted for
|
|
703
|
+
* testability; production callers omit it to get real env / fs.
|
|
657
704
|
*/
|
|
658
|
-
function resolveNodeRunner() {
|
|
705
|
+
function resolveNodeRunner(opts) {
|
|
659
706
|
const execPath = typeof process.execPath === 'string' ? process.execPath : '';
|
|
660
707
|
if (!execPath) return null;
|
|
661
|
-
const stablePath = normalizeNodePath(execPath);
|
|
708
|
+
const stablePath = normalizeNodePath(execPath, opts);
|
|
662
709
|
// JSON.stringify produces a properly escaped double-quoted shell token,
|
|
663
710
|
// safe for paths containing spaces or unusual characters.
|
|
664
711
|
return JSON.stringify(stablePath.replace(/\\/g, '/'));
|
|
@@ -712,6 +759,10 @@ function rewriteLegacyManagedNodeHookCommands(settings, absoluteRunner, opts) {
|
|
|
712
759
|
if (!entry || !Array.isArray(entry.hooks)) continue;
|
|
713
760
|
for (const h of entry.hooks) {
|
|
714
761
|
if (!h || typeof h.command !== 'string') continue;
|
|
762
|
+
// args-form entries have the script path in h.args[] and h.command is
|
|
763
|
+
// the launcher executable (not a managed hook command). These are
|
|
764
|
+
// intentional user wrappers — do not rewrite them. (#976)
|
|
765
|
+
if (Array.isArray(h.args) && h.args.length > 0) continue;
|
|
715
766
|
let trimmed = h.command.trim();
|
|
716
767
|
const hadPowerShellCallOperator = platform === 'win32' && /^&\s+/.test(trimmed);
|
|
717
768
|
if (hadPowerShellCallOperator) {
|
|
@@ -1896,6 +1947,35 @@ const claudeToGeminiTools = {
|
|
|
1896
1947
|
TodoWrite: 'write_todos',
|
|
1897
1948
|
};
|
|
1898
1949
|
|
|
1950
|
+
// Tool name mapping from Claude/GSD agents to Kimi CLI module paths.
|
|
1951
|
+
// Kimi custom agent YAML requires fully-qualified module paths.
|
|
1952
|
+
const claudeToKimiTools = {
|
|
1953
|
+
Read: 'kimi_cli.tools.file:ReadFile',
|
|
1954
|
+
ReadFile: 'kimi_cli.tools.file:ReadFile',
|
|
1955
|
+
Write: 'kimi_cli.tools.file:WriteFile',
|
|
1956
|
+
WriteFile: 'kimi_cli.tools.file:WriteFile',
|
|
1957
|
+
Edit: 'kimi_cli.tools.file:StrReplaceFile',
|
|
1958
|
+
MultiEdit: 'kimi_cli.tools.file:StrReplaceFile',
|
|
1959
|
+
StrReplaceFile: 'kimi_cli.tools.file:StrReplaceFile',
|
|
1960
|
+
Bash: 'kimi_cli.tools.shell:Shell',
|
|
1961
|
+
Shell: 'kimi_cli.tools.shell:Shell',
|
|
1962
|
+
Grep: 'kimi_cli.tools.file:Grep',
|
|
1963
|
+
Glob: 'kimi_cli.tools.file:Glob',
|
|
1964
|
+
Agent: 'kimi_cli.tools.agent:Agent',
|
|
1965
|
+
Task: 'kimi_cli.tools.agent:Agent',
|
|
1966
|
+
AskUserQuestion: 'kimi_cli.tools.ask_user:AskUserQuestion',
|
|
1967
|
+
TodoWrite: 'kimi_cli.tools.todo:SetTodoList',
|
|
1968
|
+
SetTodoList: 'kimi_cli.tools.todo:SetTodoList',
|
|
1969
|
+
WebSearch: 'kimi_cli.tools.web:SearchWeb',
|
|
1970
|
+
SearchWeb: 'kimi_cli.tools.web:SearchWeb',
|
|
1971
|
+
WebFetch: 'kimi_cli.tools.web:FetchURL',
|
|
1972
|
+
FetchURL: 'kimi_cli.tools.web:FetchURL',
|
|
1973
|
+
ReadMediaFile: 'kimi_cli.tools.file:ReadMediaFile',
|
|
1974
|
+
TaskList: 'kimi_cli.tools.background:TaskList',
|
|
1975
|
+
TaskOutput: 'kimi_cli.tools.background:TaskOutput',
|
|
1976
|
+
TaskStop: 'kimi_cli.tools.background:TaskStop',
|
|
1977
|
+
};
|
|
1978
|
+
|
|
1899
1979
|
/**
|
|
1900
1980
|
* Convert a Claude Code tool name to OpenCode format
|
|
1901
1981
|
* - Applies special mappings (AskUserQuestion -> question, etc.)
|
|
@@ -1945,6 +2025,63 @@ function convertGeminiToolName(claudeTool) {
|
|
|
1945
2025
|
return claudeTool.toLowerCase();
|
|
1946
2026
|
}
|
|
1947
2027
|
|
|
2028
|
+
function createKimiToolDiagnostic(reason, tool, source = null) {
|
|
2029
|
+
const isMcp = reason === 'mcp_managed';
|
|
2030
|
+
return {
|
|
2031
|
+
level: 'warning',
|
|
2032
|
+
code: isMcp ? 'kimi_mcp_tool_excluded' : 'kimi_unsupported_tool',
|
|
2033
|
+
reason,
|
|
2034
|
+
message: isMcp
|
|
2035
|
+
? `MCP-managed tool '${tool}' is configured outside Kimi agent YAML.`
|
|
2036
|
+
: `Tool '${tool}' is not supported by the Kimi tool mapper.`,
|
|
2037
|
+
value: tool,
|
|
2038
|
+
source,
|
|
2039
|
+
};
|
|
2040
|
+
}
|
|
2041
|
+
|
|
2042
|
+
/**
|
|
2043
|
+
* Convert a Claude/GSD tool name to a Kimi CLI module path.
|
|
2044
|
+
* @returns {string|null} Kimi module path, or null when excluded/unsupported.
|
|
2045
|
+
*/
|
|
2046
|
+
function convertKimiToolName(claudeTool) {
|
|
2047
|
+
const tool = String(claudeTool || '').trim();
|
|
2048
|
+
if (!tool) return null;
|
|
2049
|
+
if (tool.startsWith('mcp__')) return null;
|
|
2050
|
+
return claudeToKimiTools[tool] || null;
|
|
2051
|
+
}
|
|
2052
|
+
|
|
2053
|
+
function mapClaudeToolsToKimiTools(claudeTools, options = {}) {
|
|
2054
|
+
const diagnostics = [];
|
|
2055
|
+
const tools = [];
|
|
2056
|
+
const seen = new Set();
|
|
2057
|
+
const source = options && Object.prototype.hasOwnProperty.call(options, 'source')
|
|
2058
|
+
? options.source
|
|
2059
|
+
: null;
|
|
2060
|
+
|
|
2061
|
+
for (const rawTool of Array.isArray(claudeTools) ? claudeTools : []) {
|
|
2062
|
+
const tool = String(rawTool || '').trim();
|
|
2063
|
+
if (!tool) continue;
|
|
2064
|
+
|
|
2065
|
+
if (tool.startsWith('mcp__')) {
|
|
2066
|
+
diagnostics.push(createKimiToolDiagnostic('mcp_managed', tool, source));
|
|
2067
|
+
continue;
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
const kimiTool = convertKimiToolName(tool);
|
|
2071
|
+
if (!kimiTool) {
|
|
2072
|
+
diagnostics.push(createKimiToolDiagnostic('unsupported_tool', tool, source));
|
|
2073
|
+
continue;
|
|
2074
|
+
}
|
|
2075
|
+
|
|
2076
|
+
if (!seen.has(kimiTool)) {
|
|
2077
|
+
seen.add(kimiTool);
|
|
2078
|
+
tools.push(kimiTool);
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
|
|
2082
|
+
return { tools, diagnostics };
|
|
2083
|
+
}
|
|
2084
|
+
|
|
1948
2085
|
const claudeToKiloAgentPermissions = {
|
|
1949
2086
|
Read: 'read',
|
|
1950
2087
|
Write: 'edit',
|
|
@@ -2225,6 +2362,283 @@ function convertClaudeCommandToClaudeSkill(content, skillName, runtime = null, c
|
|
|
2225
2362
|
return `${fm}\n${normalizedBody}`;
|
|
2226
2363
|
}
|
|
2227
2364
|
|
|
2365
|
+
function normalizeKimiSkillName(skillName) {
|
|
2366
|
+
let text = String(skillName || '').trim().toLowerCase();
|
|
2367
|
+
if (text.startsWith('/')) text = text.slice(1);
|
|
2368
|
+
if (text.startsWith('$')) text = text.slice(1);
|
|
2369
|
+
text = text.replace(/^gsd:/, 'gsd-');
|
|
2370
|
+
if (!text.startsWith('gsd-')) text = `gsd-${text}`;
|
|
2371
|
+
text = text.replace(/[^a-z0-9-]+/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '');
|
|
2372
|
+
return text || 'gsd-command';
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2375
|
+
function convertGsdCommandReferencesToKimiSkillInvocations(content, cmdNames) {
|
|
2376
|
+
if (!Array.isArray(cmdNames) || cmdNames.length === 0) return content;
|
|
2377
|
+
const commands = [...cmdNames].sort((a, b) => b.length - a.length).map(escapeRegExp);
|
|
2378
|
+
const commandGroup = commands.join('|');
|
|
2379
|
+
const colonPattern = new RegExp(`(?<![A-Za-z0-9_/:.-])/?gsd:(${commandGroup})(?=[^A-Za-z0-9_-]|$)`, 'g');
|
|
2380
|
+
const hyphenPattern = new RegExp(`(?:/|\\$)gsd-(${commandGroup})(?=[^A-Za-z0-9_-]|$)`, 'g');
|
|
2381
|
+
|
|
2382
|
+
return content
|
|
2383
|
+
.replace(colonPattern, (_, cmd) => `/skill:gsd-${cmd}`)
|
|
2384
|
+
.replace(hyphenPattern, (_, cmd) => `/skill:gsd-${cmd}`);
|
|
2385
|
+
}
|
|
2386
|
+
|
|
2387
|
+
function convertClaudeCommandToKimiSkill(content, skillName, _runtime = null, cmdNames = null) {
|
|
2388
|
+
const { frontmatter, body } = extractFrontmatterAndBody(content);
|
|
2389
|
+
const kimiSkillName = normalizeKimiSkillName(skillName);
|
|
2390
|
+
const names = cmdNames || readGsdCommandNames();
|
|
2391
|
+
const description = frontmatter
|
|
2392
|
+
? extractFrontmatterField(frontmatter, 'description') || `Run GSD workflow ${kimiSkillName}.`
|
|
2393
|
+
: `Run GSD workflow ${kimiSkillName}.`;
|
|
2394
|
+
const normalizedBody = convertGsdCommandReferencesToKimiSkillInvocations(
|
|
2395
|
+
frontmatter ? body : content,
|
|
2396
|
+
names
|
|
2397
|
+
);
|
|
2398
|
+
|
|
2399
|
+
return `---\nname: ${kimiSkillName}\ndescription: ${yamlQuote(toSingleLine(description))}\n---\nInvoke this Kimi skill with \`/skill:${kimiSkillName}\`.\n\n${normalizedBody}`;
|
|
2400
|
+
}
|
|
2401
|
+
|
|
2402
|
+
const KIMI_CANONICAL_GSD_AGENT_RE = /^gsd-[a-z0-9-]+$/;
|
|
2403
|
+
|
|
2404
|
+
function parseKimiAgentSource(source) {
|
|
2405
|
+
if (typeof source === 'string') {
|
|
2406
|
+
return {
|
|
2407
|
+
path: null,
|
|
2408
|
+
content: source,
|
|
2409
|
+
};
|
|
2410
|
+
}
|
|
2411
|
+
if (!source || typeof source !== 'object' || typeof source.content !== 'string') {
|
|
2412
|
+
return null;
|
|
2413
|
+
}
|
|
2414
|
+
return {
|
|
2415
|
+
path: typeof source.path === 'string' ? source.path : null,
|
|
2416
|
+
content: source.content,
|
|
2417
|
+
};
|
|
2418
|
+
}
|
|
2419
|
+
|
|
2420
|
+
function parseFrontmatterTools(frontmatter) {
|
|
2421
|
+
if (!frontmatter) return [];
|
|
2422
|
+
const lines = frontmatter.split(/\r?\n/);
|
|
2423
|
+
const tools = [];
|
|
2424
|
+
let collecting = false;
|
|
2425
|
+
|
|
2426
|
+
for (const line of lines) {
|
|
2427
|
+
const trimmed = line.trim();
|
|
2428
|
+
if (!trimmed) continue;
|
|
2429
|
+
|
|
2430
|
+
if (collecting) {
|
|
2431
|
+
if (trimmed.startsWith('- ')) {
|
|
2432
|
+
tools.push(trimmed.slice(2).trim());
|
|
2433
|
+
continue;
|
|
2434
|
+
}
|
|
2435
|
+
collecting = false;
|
|
2436
|
+
}
|
|
2437
|
+
|
|
2438
|
+
if (trimmed === 'tools:' || trimmed === 'allowed-tools:') {
|
|
2439
|
+
collecting = true;
|
|
2440
|
+
continue;
|
|
2441
|
+
}
|
|
2442
|
+
|
|
2443
|
+
if (trimmed.startsWith('tools:') || trimmed.startsWith('allowed-tools:')) {
|
|
2444
|
+
const value = trimmed.slice(trimmed.indexOf(':') + 1).trim();
|
|
2445
|
+
if (value) {
|
|
2446
|
+
for (const tool of value.split(',')) {
|
|
2447
|
+
const name = tool.trim();
|
|
2448
|
+
if (name) tools.push(name);
|
|
2449
|
+
}
|
|
2450
|
+
} else {
|
|
2451
|
+
collecting = true;
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
|
|
2456
|
+
return tools;
|
|
2457
|
+
}
|
|
2458
|
+
|
|
2459
|
+
function addKimiAgentDiagnostic(diagnostics, code, message, value, source = null) {
|
|
2460
|
+
diagnostics.push({
|
|
2461
|
+
level: 'warning',
|
|
2462
|
+
code,
|
|
2463
|
+
message,
|
|
2464
|
+
value,
|
|
2465
|
+
source,
|
|
2466
|
+
});
|
|
2467
|
+
}
|
|
2468
|
+
|
|
2469
|
+
function mapKimiAgentContractTools(toolNames, diagnostics, sourceName) {
|
|
2470
|
+
const result = mapClaudeToolsToKimiTools(toolNames, { source: sourceName });
|
|
2471
|
+
diagnostics.push(...result.diagnostics);
|
|
2472
|
+
return result.tools;
|
|
2473
|
+
}
|
|
2474
|
+
|
|
2475
|
+
function neutralizeKimiAgentPrompt(content) {
|
|
2476
|
+
const { frontmatter, body } = extractFrontmatterAndBody(content);
|
|
2477
|
+
let prompt = frontmatter ? body : content;
|
|
2478
|
+
prompt = neutralizeAgentReferences(prompt, 'AGENTS.md');
|
|
2479
|
+
prompt = prompt.replace(/~\/\.claude\/gsd-core\b/g, 'GSD core');
|
|
2480
|
+
prompt = prompt.replace(/\$HOME\/\.claude\/gsd-core\b/g, 'GSD core');
|
|
2481
|
+
return prompt.replace(/^\s*\r?\n/, '');
|
|
2482
|
+
}
|
|
2483
|
+
|
|
2484
|
+
function pushKimiToolsYaml(lines, indent, tools) {
|
|
2485
|
+
const prefix = ' '.repeat(indent);
|
|
2486
|
+
if (!Array.isArray(tools) || tools.length === 0) {
|
|
2487
|
+
lines.push(`${prefix}tools: []`);
|
|
2488
|
+
return;
|
|
2489
|
+
}
|
|
2490
|
+
lines.push(`${prefix}tools:`);
|
|
2491
|
+
for (const tool of tools) {
|
|
2492
|
+
lines.push(`${prefix} - ${yamlQuote(tool)}`);
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
2495
|
+
|
|
2496
|
+
function buildKimiRootAgentYaml({ description, tools, subagents }) {
|
|
2497
|
+
const lines = [
|
|
2498
|
+
'version: 1',
|
|
2499
|
+
'agent:',
|
|
2500
|
+
' name: gsd',
|
|
2501
|
+
` description: ${yamlQuote(toSingleLine(description || 'Run GSD workflows in Kimi CLI.'))}`,
|
|
2502
|
+
' extend: default',
|
|
2503
|
+
' system_prompt_path: ./gsd.md',
|
|
2504
|
+
];
|
|
2505
|
+
pushKimiToolsYaml(lines, 2, tools);
|
|
2506
|
+
|
|
2507
|
+
if (subagents.length > 0) {
|
|
2508
|
+
lines.push(' subagents:');
|
|
2509
|
+
for (const subagent of subagents) {
|
|
2510
|
+
lines.push(` ${subagent.name}:`);
|
|
2511
|
+
lines.push(` path: ./subagents/${subagent.name}.yaml`);
|
|
2512
|
+
lines.push(` description: ${yamlQuote(toSingleLine(subagent.description))}`);
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
|
|
2516
|
+
return `${lines.join('\n')}\n`;
|
|
2517
|
+
}
|
|
2518
|
+
|
|
2519
|
+
function buildKimiSubagentYaml({ name, description, tools }) {
|
|
2520
|
+
const lines = [
|
|
2521
|
+
'version: 1',
|
|
2522
|
+
'agent:',
|
|
2523
|
+
` name: ${name}`,
|
|
2524
|
+
` description: ${yamlQuote(toSingleLine(description || `Run ${name}.`))}`,
|
|
2525
|
+
` system_prompt_path: ./${name}.md`,
|
|
2526
|
+
];
|
|
2527
|
+
pushKimiToolsYaml(lines, 2, tools);
|
|
2528
|
+
return `${lines.join('\n')}\n`;
|
|
2529
|
+
}
|
|
2530
|
+
|
|
2531
|
+
function buildKimiAgentArtifacts({
|
|
2532
|
+
rootAgent = '',
|
|
2533
|
+
subagents = [],
|
|
2534
|
+
requestedSubagents = null,
|
|
2535
|
+
} = {}) {
|
|
2536
|
+
const diagnostics = [];
|
|
2537
|
+
const rootSource = parseKimiAgentSource(rootAgent) || { path: null, content: '' };
|
|
2538
|
+
const { frontmatter: rootFrontmatter } = extractFrontmatterAndBody(rootSource.content);
|
|
2539
|
+
const rootDescription = rootFrontmatter
|
|
2540
|
+
? extractFrontmatterField(rootFrontmatter, 'description') || 'Run GSD workflows in Kimi CLI.'
|
|
2541
|
+
: 'Run GSD workflows in Kimi CLI.';
|
|
2542
|
+
|
|
2543
|
+
const subagentSources = Array.isArray(subagents) ? subagents : [];
|
|
2544
|
+
if (!Array.isArray(subagents)) {
|
|
2545
|
+
addKimiAgentDiagnostic(
|
|
2546
|
+
diagnostics,
|
|
2547
|
+
'kimi_unsupported_subagents_input',
|
|
2548
|
+
'Subagents input must be an array of Markdown strings or source objects.',
|
|
2549
|
+
typeof subagents,
|
|
2550
|
+
null
|
|
2551
|
+
);
|
|
2552
|
+
}
|
|
2553
|
+
|
|
2554
|
+
const subagentMap = new Map();
|
|
2555
|
+
for (const source of subagentSources) {
|
|
2556
|
+
const parsed = parseKimiAgentSource(source);
|
|
2557
|
+
if (!parsed) {
|
|
2558
|
+
addKimiAgentDiagnostic(
|
|
2559
|
+
diagnostics,
|
|
2560
|
+
'kimi_unsupported_subagent_input',
|
|
2561
|
+
'Subagent source must be a Markdown string or an object with content.',
|
|
2562
|
+
typeof source,
|
|
2563
|
+
null
|
|
2564
|
+
);
|
|
2565
|
+
continue;
|
|
2566
|
+
}
|
|
2567
|
+
|
|
2568
|
+
const { frontmatter } = extractFrontmatterAndBody(parsed.content);
|
|
2569
|
+
const fallbackName = parsed.path ? path.basename(parsed.path, path.extname(parsed.path)) : null;
|
|
2570
|
+
const name = frontmatter
|
|
2571
|
+
? extractFrontmatterField(frontmatter, 'name') || fallbackName
|
|
2572
|
+
: fallbackName;
|
|
2573
|
+
if (!name || !KIMI_CANONICAL_GSD_AGENT_RE.test(name)) {
|
|
2574
|
+
addKimiAgentDiagnostic(
|
|
2575
|
+
diagnostics,
|
|
2576
|
+
'kimi_invalid_subagent_name',
|
|
2577
|
+
'Subagent source does not use a canonical gsd-* Kimi agent name.',
|
|
2578
|
+
name || '(missing)',
|
|
2579
|
+
parsed.path
|
|
2580
|
+
);
|
|
2581
|
+
continue;
|
|
2582
|
+
}
|
|
2583
|
+
|
|
2584
|
+
const description = frontmatter
|
|
2585
|
+
? extractFrontmatterField(frontmatter, 'description') || `Run ${name}.`
|
|
2586
|
+
: `Run ${name}.`;
|
|
2587
|
+
const tools = mapKimiAgentContractTools(parseFrontmatterTools(frontmatter), diagnostics, name);
|
|
2588
|
+
subagentMap.set(name, {
|
|
2589
|
+
name,
|
|
2590
|
+
description,
|
|
2591
|
+
tools,
|
|
2592
|
+
prompt: neutralizeKimiAgentPrompt(parsed.content),
|
|
2593
|
+
});
|
|
2594
|
+
}
|
|
2595
|
+
|
|
2596
|
+
const requested = Array.isArray(requestedSubagents) && requestedSubagents.length > 0
|
|
2597
|
+
? requestedSubagents
|
|
2598
|
+
: [...subagentMap.keys()];
|
|
2599
|
+
const selectedSubagents = [];
|
|
2600
|
+
for (const requestedName of requested) {
|
|
2601
|
+
if (subagentMap.has(requestedName)) {
|
|
2602
|
+
selectedSubagents.push(subagentMap.get(requestedName));
|
|
2603
|
+
continue;
|
|
2604
|
+
}
|
|
2605
|
+
addKimiAgentDiagnostic(
|
|
2606
|
+
diagnostics,
|
|
2607
|
+
'kimi_unknown_subagent',
|
|
2608
|
+
'Requested subagent was not generated and will not be emitted in Kimi YAML.',
|
|
2609
|
+
requestedName,
|
|
2610
|
+
null
|
|
2611
|
+
);
|
|
2612
|
+
}
|
|
2613
|
+
|
|
2614
|
+
const rootTools = mapKimiAgentContractTools(parseFrontmatterTools(rootFrontmatter), diagnostics, 'gsd');
|
|
2615
|
+
if (selectedSubagents.length > 0 && !rootTools.includes('kimi_cli.tools.agent:Agent')) {
|
|
2616
|
+
rootTools.push('kimi_cli.tools.agent:Agent');
|
|
2617
|
+
}
|
|
2618
|
+
|
|
2619
|
+
return {
|
|
2620
|
+
root: {
|
|
2621
|
+
name: 'gsd',
|
|
2622
|
+
yamlPath: 'agents/gsd.yaml',
|
|
2623
|
+
promptPath: 'agents/gsd.md',
|
|
2624
|
+
yaml: buildKimiRootAgentYaml({
|
|
2625
|
+
description: rootDescription,
|
|
2626
|
+
tools: rootTools,
|
|
2627
|
+
subagents: selectedSubagents,
|
|
2628
|
+
}),
|
|
2629
|
+
prompt: neutralizeKimiAgentPrompt(rootSource.content),
|
|
2630
|
+
},
|
|
2631
|
+
subagents: selectedSubagents.map((subagent) => ({
|
|
2632
|
+
name: subagent.name,
|
|
2633
|
+
yamlPath: `agents/subagents/${subagent.name}.yaml`,
|
|
2634
|
+
promptPath: `agents/subagents/${subagent.name}.md`,
|
|
2635
|
+
yaml: buildKimiSubagentYaml(subagent),
|
|
2636
|
+
prompt: subagent.prompt,
|
|
2637
|
+
})),
|
|
2638
|
+
diagnostics,
|
|
2639
|
+
};
|
|
2640
|
+
}
|
|
2641
|
+
|
|
2228
2642
|
/**
|
|
2229
2643
|
* Convert a Claude agent (.md) to a Copilot agent (.agent.md).
|
|
2230
2644
|
* Applies tool mapping + deduplication, formats tools as JSON array.
|
|
@@ -2561,6 +2975,14 @@ function convertClaudeToWindsurfMarkdown(content) {
|
|
|
2561
2975
|
converted = converted.replace(/`CLAUDE\.md`/g, '`.windsurf/rules`');
|
|
2562
2976
|
converted = converted.replace(/\bCLAUDE\.md\b/g, '.windsurf/rules');
|
|
2563
2977
|
converted = converted.replace(/\.claude\/skills\//g, '.windsurf/skills/');
|
|
2978
|
+
converted = converted.replace(/\.\/\.claude\//g, './.windsurf/');
|
|
2979
|
+
converted = converted.replace(/\.claude\//g, '.windsurf/');
|
|
2980
|
+
// Bare forms (no trailing slash) — after slash forms to avoid double-rewrite.
|
|
2981
|
+
// Use negative lookahead (?![\w-]) to preserve .claude-plugin and .claudeignore.
|
|
2982
|
+
converted = converted.replace(/~\/\.claude(?![\w-])/g, '~/.windsurf');
|
|
2983
|
+
converted = converted.replace(/\$HOME\/\.claude(?![\w-])/g, '$HOME/.windsurf');
|
|
2984
|
+
// Environment variable name rewrite
|
|
2985
|
+
converted = converted.replace(/\bCLAUDE_CONFIG_DIR\b/g, 'WINDSURF_CONFIG_DIR');
|
|
2564
2986
|
// Remove Claude Code-specific bug workarounds before brand replacement
|
|
2565
2987
|
converted = converted.replace(/\*\*Known Claude Code bug \(classifyHandoffIfNeeded\):\*\*[^\n]*\n/g, '');
|
|
2566
2988
|
converted = converted.replace(/- \*\*classifyHandoffIfNeeded false failure:\*\*[^\n]*\n/g, '');
|
|
@@ -2782,6 +3204,12 @@ function convertClaudeToTraeMarkdown(content) {
|
|
|
2782
3204
|
converted = converted.replace(/\.claude\/skills\//g, '.trae/skills/');
|
|
2783
3205
|
converted = converted.replace(/\.\/\.claude\//g, './.trae/');
|
|
2784
3206
|
converted = converted.replace(/\.claude\//g, '.trae/');
|
|
3207
|
+
// Bare forms (no trailing slash) — after slash forms to avoid double-rewrite.
|
|
3208
|
+
// Use negative lookahead (?![\w-]) to preserve .claude-plugin and .claudeignore.
|
|
3209
|
+
converted = converted.replace(/~\/\.claude(?![\w-])/g, '~/.trae');
|
|
3210
|
+
converted = converted.replace(/\$HOME\/\.claude(?![\w-])/g, '$HOME/.trae');
|
|
3211
|
+
// Environment variable name rewrite
|
|
3212
|
+
converted = converted.replace(/\bCLAUDE_CONFIG_DIR\b/g, 'TRAE_CONFIG_DIR');
|
|
2785
3213
|
converted = converted.replace(/\*\*Known Claude Code bug \(classifyHandoffIfNeeded\):\*\*[^\n]*\n/g, '');
|
|
2786
3214
|
converted = converted.replace(/- \*\*classifyHandoffIfNeeded false failure:\*\*[^\n]*\n/g, '');
|
|
2787
3215
|
converted = converted.replace(/\bClaude Code\b/g, 'Trae');
|
|
@@ -3009,6 +3437,16 @@ function convertSlashCommandsToCodexSkillMentions(content) {
|
|
|
3009
3437
|
return converted;
|
|
3010
3438
|
}
|
|
3011
3439
|
|
|
3440
|
+
const CODEX_GSD_TOOLS_INVOCATION = 'node "$HOME/.codex/gsd-core/bin/gsd-tools.cjs"';
|
|
3441
|
+
|
|
3442
|
+
function rewriteBareGsdToolsCommandsForCodex(content) {
|
|
3443
|
+
return content
|
|
3444
|
+
.replace(/(^[ \t]*)gsd-tools(?=\s)/gm, `$1${CODEX_GSD_TOOLS_INVOCATION}`)
|
|
3445
|
+
.replace(/(\$\(\s*)gsd-tools(?=\s)/g, `$1${CODEX_GSD_TOOLS_INVOCATION}`)
|
|
3446
|
+
.replace(/(`\s*)gsd-tools(?=\s)/g, `$1${CODEX_GSD_TOOLS_INVOCATION}`)
|
|
3447
|
+
.replace(/((?:&&|\|\||[;|])\s*)gsd-tools(?=\s)/g, `$1${CODEX_GSD_TOOLS_INVOCATION}`);
|
|
3448
|
+
}
|
|
3449
|
+
|
|
3012
3450
|
function convertClaudeToCodexMarkdown(content) {
|
|
3013
3451
|
let converted = convertSlashCommandsToCodexSkillMentions(content);
|
|
3014
3452
|
converted = converted.replace(/\$ARGUMENTS\b/g, '{{GSD_ARGS}}');
|
|
@@ -3034,6 +3472,10 @@ function convertClaudeToCodexMarkdown(content) {
|
|
|
3034
3472
|
// `.claudeignore` → `.codexignore` (#2639). Codex honors its own ignore
|
|
3035
3473
|
// file; leaving the Claude-specific name is misleading in agent prompts.
|
|
3036
3474
|
converted = converted.replace(/\.claudeignore\b/g, '.codexignore');
|
|
3475
|
+
// Codex installs the tools shim under ~/.codex but does not guarantee a
|
|
3476
|
+
// bare `gsd-tools` binary on PATH. Keep resolver probes such as
|
|
3477
|
+
// `command -v gsd-tools` intact; rewrite only command invocations.
|
|
3478
|
+
converted = rewriteBareGsdToolsCommandsForCodex(converted);
|
|
3037
3479
|
// Runtime-neutral agent name replacement (#766)
|
|
3038
3480
|
converted = neutralizeAgentReferences(converted, 'AGENTS.md');
|
|
3039
3481
|
return converted;
|
|
@@ -3073,7 +3515,13 @@ Execute mode fallback:
|
|
|
3073
3515
|
## C. Task() → spawn_agent Mapping
|
|
3074
3516
|
GSD workflows use \`Task(...)\` (Claude Code syntax). Translate to Codex collaboration tools:
|
|
3075
3517
|
|
|
3076
|
-
|
|
3518
|
+
**Schema detection (required first step):** Codex exposes two \`spawn_agent\` schemas:
|
|
3519
|
+
- **agent_type-capable schema** (e.g. \`multi_agent_v2\`): \`spawn_agent\` accepts \`agent_type\`, \`message\`, \`reasoning_effort\`, \`fork_context\`, etc. — typed GSD agent dispatch is available.
|
|
3520
|
+
- **Generic schema** (\`multi_agent_v1\`): \`spawn_agent\` accepts only \`message\`, \`items\`, \`fork_context\` — there is **no \`agent_type\` field**. Typed GSD agent dispatch is unavailable in this session.
|
|
3521
|
+
|
|
3522
|
+
Before spawning, inspect the \`spawn_agent\` tool's visible parameter schema (via \`tool_search\` or the tool list) to determine which form is active.
|
|
3523
|
+
|
|
3524
|
+
Typed mapping (agent_type-capable schema only):
|
|
3077
3525
|
- \`Task(subagent_type="X", prompt="Y")\` → \`spawn_agent(agent_type="X", message="Y")\`
|
|
3078
3526
|
- \`Agent(subagent_type="X", prompt="Y")\` → \`spawn_agent(agent_type="X", message="Y")\`
|
|
3079
3527
|
- \`Task(model="...")\` → omit. \`spawn_agent\` has no inline \`model\` parameter;
|
|
@@ -3090,6 +3538,24 @@ Direct mapping:
|
|
|
3090
3538
|
Workflows that require this isolation must fail closed or use an explicit
|
|
3091
3539
|
manual worktree protocol before spawning (#3360).
|
|
3092
3540
|
|
|
3541
|
+
Generic-agent workaround (multi_agent_v1 schema — NO agent_type field):
|
|
3542
|
+
When only the generic \`multi_agent_v1\` schema is available, typed GSD agent dispatch
|
|
3543
|
+
(\`gsd-planner\`, \`gsd-executor\`, etc.) is NOT possible. This is a known Codex limitation
|
|
3544
|
+
(openai/codex#15250). **This workaround is NOT equivalent to typed gsd-planner/gsd-executor
|
|
3545
|
+
execution** — GSD agents carry project-aware prompts, audit logging, and workflow context
|
|
3546
|
+
that a generic subagent lacks. Use the following fallback:
|
|
3547
|
+
1. Resolve your active Codex config root — the directory that contains your \`config.toml\`.
|
|
3548
|
+
This directory is determined in priority order: \`$CODEX_HOME\` (if set), the path given
|
|
3549
|
+
by \`--config-dir\` (if passed on invocation), a local \`.codex\` directory in the current
|
|
3550
|
+
project (if \`--local\` was used), or the default global config directory. Read
|
|
3551
|
+
\`agents/<agent-name>.toml\` relative to that config root to extract the agent's system
|
|
3552
|
+
instructions.
|
|
3553
|
+
2. Inject those instructions as a role-preamble into a generic \`spawn_agent(message=...)\` call.
|
|
3554
|
+
3. Label results and logs clearly as "generic-agent workaround" so the orchestrator and user
|
|
3555
|
+
know full typed-agent guarantees are not in effect.
|
|
3556
|
+
4. Where typed dispatch is mandatory for correctness (e.g. worktree isolation), fail closed
|
|
3557
|
+
and report the schema limitation rather than silently degrading.
|
|
3558
|
+
|
|
3093
3559
|
Spawn restriction:
|
|
3094
3560
|
- Codex restricts \`spawn_agent\` to cases where the user has explicitly
|
|
3095
3561
|
requested sub-agents. When automatic spawning is not permitted, do the
|
|
@@ -3182,14 +3648,17 @@ function generateCodexAgentToml(agentName, agentContent, modelOverrides = null,
|
|
|
3182
3648
|
// Task() model parameters). See #2256.
|
|
3183
3649
|
// Precedence: per-agent model_overrides > runtime-aware tier resolution (#2517).
|
|
3184
3650
|
const modelOverride = modelOverrides?.[resolvedName] || modelOverrides?.[agentName];
|
|
3651
|
+
let hasPinnedModel = false;
|
|
3185
3652
|
if (modelOverride) {
|
|
3186
3653
|
lines.push(`model = ${JSON.stringify(modelOverride)}`);
|
|
3654
|
+
hasPinnedModel = true;
|
|
3187
3655
|
} else if (runtimeResolver) {
|
|
3188
3656
|
// #2517 — runtime-aware tier resolution. Embeds Codex-native model + reasoning_effort
|
|
3189
3657
|
// from RUNTIME_PROFILE_MAP / model_profile_overrides for the configured tier.
|
|
3190
3658
|
const entry = runtimeResolver.resolve(resolvedName) || runtimeResolver.resolve(agentName);
|
|
3191
3659
|
if (entry?.model) {
|
|
3192
3660
|
lines.push(`model = ${JSON.stringify(entry.model)}`);
|
|
3661
|
+
hasPinnedModel = true;
|
|
3193
3662
|
// model is resolved here; reasoning_effort from catalog tier is REPLACED by the
|
|
3194
3663
|
// unified effort resolver below (#443). Do NOT emit entry.reasoning_effort here.
|
|
3195
3664
|
}
|
|
@@ -3200,9 +3669,15 @@ function generateCodexAgentToml(agentName, agentContent, modelOverrides = null,
|
|
|
3200
3669
|
// from the same effort.agent_overrides / effort.routing_tier_defaults / effort.default
|
|
3201
3670
|
// config source. Codex does not support 'max' → clamped to 'xhigh' by
|
|
3202
3671
|
// gsdRenderEffortForRuntime('codex', ...).
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3672
|
+
// #838 — Do not pin effort when Codex is intentionally inheriting the parent
|
|
3673
|
+
// chat model. A TOML with no `model` but a static `model_reasoning_effort`
|
|
3674
|
+
// creates confusing partial routing: model follows the Codex UI while effort
|
|
3675
|
+
// follows GSD. Keep those knobs coupled unless GSD also pins the model.
|
|
3676
|
+
if (hasPinnedModel) {
|
|
3677
|
+
const _universalEffortCodex = resolveInstallTimeEffort(effortCfg, resolvedName !== agentName ? resolvedName : agentName);
|
|
3678
|
+
const _renderedEffortCodex = _getGsdEffortCatalog().renderEffortForRuntime('codex', _universalEffortCodex).value;
|
|
3679
|
+
lines.push(`model_reasoning_effort = ${JSON.stringify(_renderedEffortCodex)}`);
|
|
3680
|
+
}
|
|
3206
3681
|
|
|
3207
3682
|
// #774 — Emit service_tier and model_verbosity for light-tier agents.
|
|
3208
3683
|
// Light-tier agents (routingTier: "light" in model-catalog.json) are haiku-equivalent
|
|
@@ -7100,7 +7575,7 @@ function applyRuntimeContentRewritesInPlace(stagedDir, runtime, pathPrefix) {
|
|
|
7100
7575
|
const fullPath = path.join(dir, entry.name);
|
|
7101
7576
|
if (entry.isDirectory()) {
|
|
7102
7577
|
walkAndRewrite(fullPath);
|
|
7103
|
-
} else if (entry.name
|
|
7578
|
+
} else if (entry.name.endsWith('.md')) {
|
|
7104
7579
|
let content = fs.readFileSync(fullPath, 'utf8');
|
|
7105
7580
|
content = _applyRuntimeRewrites(content, runtime, pathPrefix);
|
|
7106
7581
|
fs.writeFileSync(fullPath, content);
|
|
@@ -7200,6 +7675,11 @@ function _applyRuntimeRewrites(content, runtime, pathPrefix) {
|
|
|
7200
7675
|
content = content.replace(/~\/\.claude\//g, pathPrefix);
|
|
7201
7676
|
content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
|
|
7202
7677
|
content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
|
|
7678
|
+
// Bare forms (no trailing slash) — use (?![\w-]) instead of \b so that
|
|
7679
|
+
// .claude-plugin / .claudeignore are NOT corrupted (the \b word-boundary
|
|
7680
|
+
// fires between 'e' and '-', which rewrites .claude-plugin → .windsurf-plugin).
|
|
7681
|
+
content = content.replace(/~\/\.claude(?![\w-])/g, normalizedPathPrefix);
|
|
7682
|
+
content = content.replace(/\$HOME\/\.claude(?![\w-])/g, normalizedPathPrefix);
|
|
7203
7683
|
content = content.replace(/~\/\.codeium\/windsurf\//g, pathPrefix);
|
|
7204
7684
|
content = processAttribution(content, getCommitAttribution(runtime));
|
|
7205
7685
|
break;
|
|
@@ -7291,6 +7771,16 @@ function _applyRuntimeRewrites(content, runtime, pathPrefix) {
|
|
|
7291
7771
|
content = processAttribution(content, getCommitAttribution(runtime));
|
|
7292
7772
|
break;
|
|
7293
7773
|
|
|
7774
|
+
case 'kimi':
|
|
7775
|
+
content = content.replace(/~\/\.claude\//g, pathPrefix);
|
|
7776
|
+
content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
|
|
7777
|
+
content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
|
|
7778
|
+
content = content.replace(/~\/\.claude\b/g, normalizedPathPrefix);
|
|
7779
|
+
content = content.replace(/\$HOME\/\.claude\b/g, normalizedPathPrefix);
|
|
7780
|
+
content = content.replace(/\.\/\.claude\b/g, `./${dirName}`);
|
|
7781
|
+
content = processAttribution(content, getCommitAttribution(runtime));
|
|
7782
|
+
break;
|
|
7783
|
+
|
|
7294
7784
|
default:
|
|
7295
7785
|
// Unknown runtime — no rewrites.
|
|
7296
7786
|
// OpenCode/Kilo are intentionally absent: their skills are written by
|
|
@@ -7314,6 +7804,7 @@ function _applyRuntimeRewrites(content, runtime, pathPrefix) {
|
|
|
7314
7804
|
* encodes the GSD namespace as its last segment (e.g. `commands/gsd`), in
|
|
7315
7805
|
* which case write as `${stem}.md` (directory IS the namespace).
|
|
7316
7806
|
* - agents: write as-is (files already carry their own `gsd-` prefix).
|
|
7807
|
+
* For kimi-agents kind: recursively copy generated YAML/prompt files.
|
|
7317
7808
|
*/
|
|
7318
7809
|
function _copyStaged(stagedDir, destDir, kind) {
|
|
7319
7810
|
if (!fs.existsSync(stagedDir)) return;
|
|
@@ -7330,6 +7821,11 @@ function _copyStaged(stagedDir, destDir, kind) {
|
|
|
7330
7821
|
return;
|
|
7331
7822
|
}
|
|
7332
7823
|
|
|
7824
|
+
if (kind.kind === 'kimi-agents') {
|
|
7825
|
+
fs.cpSync(stagedDir, destDir, { recursive: true });
|
|
7826
|
+
return;
|
|
7827
|
+
}
|
|
7828
|
+
|
|
7333
7829
|
// commands or agents
|
|
7334
7830
|
const entries = fs.readdirSync(stagedDir, { withFileTypes: true });
|
|
7335
7831
|
// For commands: apply prefix unless the destSubpath's last segment already
|
|
@@ -7361,11 +7857,27 @@ function _copyStaged(stagedDir, destDir, kind) {
|
|
|
7361
7857
|
|
|
7362
7858
|
/**
|
|
7363
7859
|
* Remove GSD-prefixed entries from destDir matching kind.prefix.
|
|
7364
|
-
* For
|
|
7365
|
-
* (
|
|
7860
|
+
* For the prefix='' case: the destSubpath IS the namespace — remove the entire
|
|
7861
|
+
* destDir. (No current runtime uses prefix='' after #947 reversed Hermes; kept
|
|
7862
|
+
* as a defensive guard for future runtimes.)
|
|
7366
7863
|
*/
|
|
7367
7864
|
function _removeGsdEntries(destDir, kind) {
|
|
7368
7865
|
if (!fs.existsSync(destDir)) return;
|
|
7866
|
+
if (kind.kind === 'kimi-agents') {
|
|
7867
|
+
for (const fileName of ['gsd.yaml', 'gsd.md']) {
|
|
7868
|
+
fs.rmSync(path.join(destDir, fileName), { force: true });
|
|
7869
|
+
}
|
|
7870
|
+
const subagentsDir = path.join(destDir, 'subagents');
|
|
7871
|
+
if (fs.existsSync(subagentsDir)) {
|
|
7872
|
+
for (const entry of fs.readdirSync(subagentsDir, { withFileTypes: true })) {
|
|
7873
|
+
if (!entry.isFile()) continue;
|
|
7874
|
+
if (!entry.name.startsWith('gsd-')) continue;
|
|
7875
|
+
if (!entry.name.endsWith('.yaml') && !entry.name.endsWith('.md')) continue;
|
|
7876
|
+
fs.rmSync(path.join(subagentsDir, entry.name), { force: true });
|
|
7877
|
+
}
|
|
7878
|
+
}
|
|
7879
|
+
return;
|
|
7880
|
+
}
|
|
7369
7881
|
if (kind.prefix === '') {
|
|
7370
7882
|
// Whole-namespace removal (Hermes nested case — destSubpath is skills/gsd)
|
|
7371
7883
|
// The directory itself is the GSD namespace, so remove it entirely.
|
|
@@ -7419,20 +7931,11 @@ function _runLegacyInstallMigrations(runtime, configDir, scope = 'global') {
|
|
|
7419
7931
|
}
|
|
7420
7932
|
}
|
|
7421
7933
|
|
|
7422
|
-
// Hermes:
|
|
7423
|
-
//
|
|
7424
|
-
//
|
|
7425
|
-
//
|
|
7426
|
-
//
|
|
7427
|
-
// directory and any non-gsd- siblings (user content) are preserved.
|
|
7428
|
-
const nestedGsdDir = path.join(configDir, 'skills', 'gsd');
|
|
7429
|
-
if (fs.existsSync(nestedGsdDir)) {
|
|
7430
|
-
for (const entry of fs.readdirSync(nestedGsdDir, { withFileTypes: true })) {
|
|
7431
|
-
if (entry.isDirectory() && entry.name.startsWith('gsd-')) {
|
|
7432
|
-
fs.rmSync(path.join(nestedGsdDir, entry.name), { recursive: true });
|
|
7433
|
-
}
|
|
7434
|
-
}
|
|
7435
|
-
}
|
|
7934
|
+
// Hermes: bare-stem skills/gsd/<stem>/ cleanup is deferred to AFTER the
|
|
7935
|
+
// layout-driven install loop in installRuntimeArtifacts, where the exact set
|
|
7936
|
+
// of staged gsd-<stem>/ dirs is known. Removing here (before staging) would
|
|
7937
|
+
// require readGsdCommandNames() which misses skills like 'dev-preferences'
|
|
7938
|
+
// that are not in the commands directory. See _removeHermesBareStemDirs().
|
|
7436
7939
|
}
|
|
7437
7940
|
|
|
7438
7941
|
// Migrate dev-preferences.md content → runtime-aware SKILL.md location (#2973).
|
|
@@ -7489,6 +7992,18 @@ function _runLegacyUninstallCleanup(runtime, configDir, scope = 'global') {
|
|
|
7489
7992
|
}
|
|
7490
7993
|
}
|
|
7491
7994
|
}
|
|
7995
|
+
|
|
7996
|
+
// Hermes: pre-#947 bare-stem skills/gsd/<stem>/ entries (dirs that do NOT
|
|
7997
|
+
// start with 'gsd-') — the #3664 layout used prefix='' so GSD-owned skills
|
|
7998
|
+
// had bare names (e.g. skills/gsd/help/). These are stale on uninstall.
|
|
7999
|
+
const nestedGsdDirForUninstall = path.join(configDir, 'skills', 'gsd');
|
|
8000
|
+
if (fs.existsSync(nestedGsdDirForUninstall)) {
|
|
8001
|
+
for (const entry of fs.readdirSync(nestedGsdDirForUninstall, { withFileTypes: true })) {
|
|
8002
|
+
if (entry.isDirectory() && !entry.name.startsWith('gsd-')) {
|
|
8003
|
+
fs.rmSync(path.join(nestedGsdDirForUninstall, entry.name), { recursive: true });
|
|
8004
|
+
}
|
|
8005
|
+
}
|
|
8006
|
+
}
|
|
7492
8007
|
}
|
|
7493
8008
|
|
|
7494
8009
|
// Return saved artifacts so the caller can migrate after layout-driven removal.
|
|
@@ -7539,6 +8054,43 @@ function _restoreDir(dir, snapshot) {
|
|
|
7539
8054
|
}
|
|
7540
8055
|
}
|
|
7541
8056
|
|
|
8057
|
+
/**
|
|
8058
|
+
* After the layout-driven install loop writes new gsd-<stem>/ dirs to
|
|
8059
|
+
* skills/gsd/, remove any pre-existing bare-stem dirs (skills/gsd/<stem>/)
|
|
8060
|
+
* that correspond to the newly installed gsd-<stem> entries.
|
|
8061
|
+
*
|
|
8062
|
+
* The removal set is derived from the ACTUAL installed skill dirs (every
|
|
8063
|
+
* entry starting with 'gsd-' that is a directory), so it covers ALL shipped
|
|
8064
|
+
* GSD skills — including 'dev-preferences' and future additions — without
|
|
8065
|
+
* relying on readGsdCommandNames() which only enumerates the commands source
|
|
8066
|
+
* tree and can miss skills that ship outside that directory.
|
|
8067
|
+
*
|
|
8068
|
+
* Safety: a bare dir is ONLY removed when a corresponding gsd-<stem>/ dir was
|
|
8069
|
+
* installed this run. A user-owned dir 'skills/gsd/my-workflow/' that has no
|
|
8070
|
+
* matching 'skills/gsd/gsd-my-workflow/' is never touched.
|
|
8071
|
+
*
|
|
8072
|
+
* @param {string} nestedGsdDir absolute path to skills/gsd/ category dir
|
|
8073
|
+
*/
|
|
8074
|
+
function _removeHermesBareStemDirs(nestedGsdDir) {
|
|
8075
|
+
if (!fs.existsSync(nestedGsdDir)) return;
|
|
8076
|
+
const entries = fs.readdirSync(nestedGsdDir, { withFileTypes: true });
|
|
8077
|
+
|
|
8078
|
+
// Collect the set of stems that were installed as gsd-<stem>/ this run.
|
|
8079
|
+
const installedStems = new Set();
|
|
8080
|
+
for (const entry of entries) {
|
|
8081
|
+
if (entry.isDirectory() && entry.name.startsWith('gsd-')) {
|
|
8082
|
+
installedStems.add(entry.name.slice('gsd-'.length)); // e.g. 'quick', 'dev-preferences'
|
|
8083
|
+
}
|
|
8084
|
+
}
|
|
8085
|
+
|
|
8086
|
+
// Remove any bare <stem>/ dir for which gsd-<stem>/ was just installed.
|
|
8087
|
+
for (const entry of entries) {
|
|
8088
|
+
if (entry.isDirectory() && !entry.name.startsWith('gsd-') && installedStems.has(entry.name)) {
|
|
8089
|
+
fs.rmSync(path.join(nestedGsdDir, entry.name), { recursive: true });
|
|
8090
|
+
}
|
|
8091
|
+
}
|
|
8092
|
+
}
|
|
8093
|
+
|
|
7542
8094
|
function installRuntimeArtifacts(runtime, configDir, scope, resolvedProfile) {
|
|
7543
8095
|
// Legacy cleanup before layout-driven writes
|
|
7544
8096
|
_runLegacyInstallMigrations(runtime, configDir, scope);
|
|
@@ -7562,7 +8114,7 @@ function installRuntimeArtifacts(runtime, configDir, scope, resolvedProfile) {
|
|
|
7562
8114
|
// stagedForCopy: the directory to copy from (may differ from staged if rewrites
|
|
7563
8115
|
// produce a temp copy — see applyRuntimeContentRewritesForCommandsInPlace).
|
|
7564
8116
|
let stagedForCopy = staged;
|
|
7565
|
-
if (kind.kind === 'skills') {
|
|
8117
|
+
if (kind.kind === 'skills' || kind.kind === 'kimi-agents') {
|
|
7566
8118
|
applyRuntimeContentRewritesInPlace(staged, runtime, pathPrefix);
|
|
7567
8119
|
} else if (kind.kind === 'commands') {
|
|
7568
8120
|
// Returns a temp dir with rewritten content so source files are never mutated.
|
|
@@ -7579,29 +8131,15 @@ function installRuntimeArtifacts(runtime, configDir, scope, resolvedProfile) {
|
|
|
7579
8131
|
// then restore after. This preserves user dirs across a wipe-and-replace
|
|
7580
8132
|
// install (#2973 / #3664).
|
|
7581
8133
|
//
|
|
7582
|
-
//
|
|
7583
|
-
//
|
|
7584
|
-
//
|
|
7585
|
-
//
|
|
7586
|
-
//
|
|
7587
|
-
// Non-gsd-* user dirs (e.g. my-custom-skill/) are untouched. Only preserve the
|
|
7588
|
-
// explicit user-owned GSD-prefixed skill gsd-dev-preferences, which GSD does not
|
|
7589
|
-
// reinstall from source but must survive the prune (#2973).
|
|
8134
|
+
// All runtimes (incl. Hermes after #947) use prefix='gsd-'.
|
|
8135
|
+
// _removeGsdEntries removes only gsd-* entries; non-gsd-* user dirs are
|
|
8136
|
+
// untouched. Preserve the explicit user-owned GSD-prefixed skill
|
|
8137
|
+
// gsd-dev-preferences, which GSD does not reinstall from source but must
|
|
8138
|
+
// survive the prune (#2973).
|
|
7590
8139
|
const toPreserve = new Map(); // dirName -> Map<relPath, Buffer>
|
|
7591
8140
|
|
|
7592
|
-
|
|
7593
|
-
//
|
|
7594
|
-
const stagedNames = fs.existsSync(stagedForCopy)
|
|
7595
|
-
? new Set(fs.readdirSync(stagedForCopy, { withFileTypes: true })
|
|
7596
|
-
.filter(e => e.isDirectory()).map(e => e.name))
|
|
7597
|
-
: new Set();
|
|
7598
|
-
for (const entry of fs.readdirSync(dest, { withFileTypes: true })) {
|
|
7599
|
-
if (!entry.isDirectory() || stagedNames.has(entry.name)) continue;
|
|
7600
|
-
const snap = _snapshotDir(path.join(dest, entry.name));
|
|
7601
|
-
if (snap.size > 0) toPreserve.set(entry.name, snap);
|
|
7602
|
-
}
|
|
7603
|
-
} else {
|
|
7604
|
-
// Non-Hermes: only preserve explicitly user-owned GSD-prefixed skill dirs.
|
|
8141
|
+
{
|
|
8142
|
+
// Preserve explicitly user-owned GSD-prefixed skill dirs.
|
|
7605
8143
|
// gsd-dev-preferences is the sole user-customisable skill in this category.
|
|
7606
8144
|
const USER_OWNED_SKILL_DIRS = ['gsd-dev-preferences'];
|
|
7607
8145
|
for (const dirName of USER_OWNED_SKILL_DIRS) {
|
|
@@ -7631,6 +8169,21 @@ function installRuntimeArtifacts(runtime, configDir, scope, resolvedProfile) {
|
|
|
7631
8169
|
}
|
|
7632
8170
|
}
|
|
7633
8171
|
}
|
|
8172
|
+
|
|
8173
|
+
// Hermes: after the install loop has written all gsd-<stem>/ dirs to
|
|
8174
|
+
// skills/gsd/, remove any stale bare-stem dirs (skills/gsd/<stem>/) that
|
|
8175
|
+
// correspond to the newly installed gsd-<stem> entries. This is the robust
|
|
8176
|
+
// replacement for the readGsdCommandNames()-based pre-install cleanup that
|
|
8177
|
+
// missed skills like 'dev-preferences' (#947 adversarial review).
|
|
8178
|
+
//
|
|
8179
|
+
// We run this AFTER the install loop so the installed set is authoritative:
|
|
8180
|
+
// every gsd-<stem>/ present now was written this run (or was there before
|
|
8181
|
+
// with the same prefix). User-owned bare dirs with no gsd-<stem> counterpart
|
|
8182
|
+
// are untouched.
|
|
8183
|
+
if (runtime === 'hermes') {
|
|
8184
|
+
const nestedGsdDirForCleanup = path.join(configDir, 'skills', 'gsd');
|
|
8185
|
+
_removeHermesBareStemDirs(nestedGsdDirForCleanup);
|
|
8186
|
+
}
|
|
7634
8187
|
}
|
|
7635
8188
|
|
|
7636
8189
|
/**
|
|
@@ -7736,6 +8289,23 @@ function uninstallRuntimeArtifacts(runtime, configDir, scope) {
|
|
|
7736
8289
|
_removeGsdEntries(dest, kind);
|
|
7737
8290
|
}
|
|
7738
8291
|
|
|
8292
|
+
// Hermes: after removing gsd-* skill dirs from skills/gsd/, also remove
|
|
8293
|
+
// the GSD-managed DESCRIPTION.md and then the category dir itself if it
|
|
8294
|
+
// contains no user content (#947). _removeGsdEntries removed gsd-* dirs
|
|
8295
|
+
// but left the category container and DESCRIPTION.md intact.
|
|
8296
|
+
if (runtime === 'hermes') {
|
|
8297
|
+
const nestedGsdDir = path.join(configDir, 'skills', 'gsd');
|
|
8298
|
+
if (fs.existsSync(nestedGsdDir)) {
|
|
8299
|
+
// Remove GSD-owned DESCRIPTION.md (written by writeHermesCategoryDescription)
|
|
8300
|
+
fs.rmSync(path.join(nestedGsdDir, 'DESCRIPTION.md'), { force: true });
|
|
8301
|
+
// Remove the category dir if empty (no user content remaining)
|
|
8302
|
+
const remaining = fs.readdirSync(nestedGsdDir, { withFileTypes: true });
|
|
8303
|
+
if (remaining.length === 0) {
|
|
8304
|
+
fs.rmSync(nestedGsdDir, { recursive: true, force: true });
|
|
8305
|
+
}
|
|
8306
|
+
}
|
|
8307
|
+
}
|
|
8308
|
+
|
|
7739
8309
|
// #2973 / Codex review (bd1f06c9): migrate dev-preferences.md to the
|
|
7740
8310
|
// runtime-aware SKILL.md location after all layout-driven removal is
|
|
7741
8311
|
// complete. Do NOT restore to commands/gsd/ — the user is uninstalling.
|
|
@@ -7795,6 +8365,9 @@ function copyWithPathReplacement(srcDir, destDir, pathPrefix, runtime, isCommand
|
|
|
7795
8365
|
content = content.replace(globalClaudeRegex, pathPrefix);
|
|
7796
8366
|
content = content.replace(globalClaudeHomeRegex, pathPrefix);
|
|
7797
8367
|
content = content.replace(localClaudeRegex, `./${dirName}/`);
|
|
8368
|
+
content = content.replace(/~\/\.claude\b/g, pathPrefix.replace(/\/$/, ''));
|
|
8369
|
+
content = content.replace(/\$HOME\/\.claude\b/g, pathPrefix.replace(/\/$/, ''));
|
|
8370
|
+
content = content.replace(/\.\/\.claude\b/g, `./${dirName}`);
|
|
7798
8371
|
content = content.replace(/~\/\.qwen\//g, pathPrefix);
|
|
7799
8372
|
content = content.replace(/\$HOME\/\.qwen\//g, pathPrefix);
|
|
7800
8373
|
content = content.replace(/\.\/\.qwen\//g, `./${dirName}/`);
|
|
@@ -8130,6 +8703,7 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
8130
8703
|
if (runtime === 'trae') runtimeLabel = 'Trae';
|
|
8131
8704
|
if (runtime === 'qwen') runtimeLabel = 'Qwen Code';
|
|
8132
8705
|
if (runtime === 'hermes') runtimeLabel = 'Hermes Agent';
|
|
8706
|
+
if (runtime === 'kimi') runtimeLabel = 'Kimi CLI';
|
|
8133
8707
|
if (runtime === 'codebuddy') runtimeLabel = 'CodeBuddy';
|
|
8134
8708
|
|
|
8135
8709
|
console.log(` Uninstalling GSD from ${cyan}${runtimeLabel}${reset} at ${cyan}${locationLabel}${reset}\n`);
|
|
@@ -8496,6 +9070,52 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
8496
9070
|
}
|
|
8497
9071
|
}
|
|
8498
9072
|
|
|
9073
|
+
// 4a. Remove scripts/changeset/ and scripts/lib/ (#935)
|
|
9074
|
+
// GSD-managed files only: enumerate the exact set the installer writes.
|
|
9075
|
+
// Any file NOT in this set is user-owned and must survive uninstall.
|
|
9076
|
+
// After removing GSD files, attempt to rmdir — if the directory is still
|
|
9077
|
+
// non-empty (user has custom helpers) it stays; otherwise it goes cleanly.
|
|
9078
|
+
const GSD_CHANGESET_FILES = [
|
|
9079
|
+
'cli.cjs', 'parse.cjs', 'render.cjs', 'serialize.cjs',
|
|
9080
|
+
'github-release-notes.cjs', 'lint.cjs', 'new.cjs',
|
|
9081
|
+
'README.md', // documentation only — not user-authored
|
|
9082
|
+
];
|
|
9083
|
+
const GSD_SCRIPTS_LIB_FILES = ['cli-exit.cjs', 'allowlist-ratchet.cjs'];
|
|
9084
|
+
|
|
9085
|
+
const changesetUninstallDir = path.join(targetDir, 'scripts', 'changeset');
|
|
9086
|
+
if (fs.existsSync(changesetUninstallDir)) {
|
|
9087
|
+
let removedChangeset = 0;
|
|
9088
|
+
for (const file of GSD_CHANGESET_FILES) {
|
|
9089
|
+
const fp = path.join(changesetUninstallDir, file);
|
|
9090
|
+
try { fs.unlinkSync(fp); removedChangeset++; } catch (_) { /* best-effort */ }
|
|
9091
|
+
}
|
|
9092
|
+
// Remove directory if empty after our cleanup
|
|
9093
|
+
try { fs.rmdirSync(changesetUninstallDir); } catch (_) { /* Not empty — user content present */ }
|
|
9094
|
+
if (removedChangeset > 0) {
|
|
9095
|
+
removedCount++;
|
|
9096
|
+
console.log(` ${green}✓${reset} Removed scripts/changeset/ GSD files`);
|
|
9097
|
+
}
|
|
9098
|
+
}
|
|
9099
|
+
const scriptsLibUninstallDir = path.join(targetDir, 'scripts', 'lib');
|
|
9100
|
+
if (fs.existsSync(scriptsLibUninstallDir)) {
|
|
9101
|
+
let removedScriptsLib = 0;
|
|
9102
|
+
for (const file of GSD_SCRIPTS_LIB_FILES) {
|
|
9103
|
+
const fp = path.join(scriptsLibUninstallDir, file);
|
|
9104
|
+
try { fs.unlinkSync(fp); removedScriptsLib++; } catch (_) { /* best-effort */ }
|
|
9105
|
+
}
|
|
9106
|
+
// Remove directory if empty after our cleanup
|
|
9107
|
+
try { fs.rmdirSync(scriptsLibUninstallDir); } catch (_) { /* Not empty — user content present */ }
|
|
9108
|
+
if (removedScriptsLib > 0) {
|
|
9109
|
+
removedCount++;
|
|
9110
|
+
console.log(` ${green}✓${reset} Removed scripts/lib/ GSD files`);
|
|
9111
|
+
}
|
|
9112
|
+
}
|
|
9113
|
+
// If scripts/ dir is now empty, remove it too
|
|
9114
|
+
const scriptsUninstallDir = path.join(targetDir, 'scripts');
|
|
9115
|
+
if (fs.existsSync(scriptsUninstallDir)) {
|
|
9116
|
+
try { fs.rmdirSync(scriptsUninstallDir); } catch (_) { /* Not empty — leave it */ }
|
|
9117
|
+
}
|
|
9118
|
+
|
|
8499
9119
|
// 5. Remove GSD package.json (CommonJS mode marker)
|
|
8500
9120
|
const pkgJsonPath = path.join(targetDir, 'package.json');
|
|
8501
9121
|
if (fs.existsSync(pkgJsonPath)) {
|
|
@@ -9063,6 +9683,7 @@ function writeManifest(configDir, runtime = 'claude', options = {}) {
|
|
|
9063
9683
|
const isWindsurf = runtime === 'windsurf';
|
|
9064
9684
|
const isTrae = runtime === 'trae';
|
|
9065
9685
|
const isCline = runtime === 'cline';
|
|
9686
|
+
const isKimi = runtime === 'kimi';
|
|
9066
9687
|
const isHermes = runtime === 'hermes';
|
|
9067
9688
|
const gsdDir = path.join(configDir, 'gsd-core');
|
|
9068
9689
|
const commandsDir = path.join(configDir, 'commands', 'gsd');
|
|
@@ -9110,8 +9731,8 @@ function writeManifest(configDir, runtime = 'claude', options = {}) {
|
|
|
9110
9731
|
}
|
|
9111
9732
|
}
|
|
9112
9733
|
if ((isCodex || isCopilot || isAntigravity || isCursor || isWindsurf || isTrae || (!isOpencode && !isGemini)) && fs.existsSync(codexSkillsDir)) {
|
|
9113
|
-
//
|
|
9114
|
-
const skillListPrefix =
|
|
9734
|
+
// All runtimes (including Hermes post-#947) use the canonical 'gsd-' prefix.
|
|
9735
|
+
const skillListPrefix = 'gsd-';
|
|
9115
9736
|
for (const skillName of listCodexSkillNames(codexSkillsDir, skillListPrefix)) {
|
|
9116
9737
|
const skillRoot = path.join(codexSkillsDir, skillName);
|
|
9117
9738
|
const skillHashes = generateManifest(skillRoot);
|
|
@@ -9127,7 +9748,16 @@ function writeManifest(configDir, runtime = 'claude', options = {}) {
|
|
|
9127
9748
|
}
|
|
9128
9749
|
}
|
|
9129
9750
|
}
|
|
9130
|
-
if (fs.existsSync(agentsDir)) {
|
|
9751
|
+
if (isKimi && fs.existsSync(agentsDir)) {
|
|
9752
|
+
const agentHashes = generateManifest(agentsDir);
|
|
9753
|
+
for (const [rel, hash] of Object.entries(agentHashes)) {
|
|
9754
|
+
const isRootAgent = rel === 'gsd.yaml' || rel === 'gsd.md';
|
|
9755
|
+
const isSubagent = /^subagents\/gsd-[^/]+\.(yaml|md)$/.test(rel);
|
|
9756
|
+
if (isRootAgent || isSubagent) {
|
|
9757
|
+
manifest.files['agents/' + rel] = hash;
|
|
9758
|
+
}
|
|
9759
|
+
}
|
|
9760
|
+
} else if (fs.existsSync(agentsDir)) {
|
|
9131
9761
|
for (const file of fs.readdirSync(agentsDir)) {
|
|
9132
9762
|
if (file.startsWith('gsd-') && (file.endsWith('.md') || file.endsWith('.toml'))) {
|
|
9133
9763
|
manifest.files['agents/' + file] = fileHash(path.join(agentsDir, file));
|
|
@@ -9148,12 +9778,20 @@ function writeManifest(configDir, runtime = 'claude', options = {}) {
|
|
|
9148
9778
|
|
|
9149
9779
|
// Track hook files so saveLocalPatches() can detect user modifications
|
|
9150
9780
|
// Hooks are only installed for runtimes that use settings.json (not Codex/Copilot/Cline)
|
|
9151
|
-
if (!isCodex && !isCopilot && !isCline) {
|
|
9781
|
+
if (!isCodex && !isCopilot && !isCline && !isKimi) {
|
|
9152
9782
|
const hooksDir = path.join(configDir, 'hooks');
|
|
9153
9783
|
if (fs.existsSync(hooksDir)) {
|
|
9154
|
-
|
|
9155
|
-
|
|
9156
|
-
|
|
9784
|
+
// Drive from INSTALLED_HOOK_FILES (the canonical HOOKS_TO_COPY set from
|
|
9785
|
+
// scripts/build-hooks.js) rather than a prefix/extension regex, so the
|
|
9786
|
+
// manifest set is structurally identical to the build set. The old regex
|
|
9787
|
+
// `file.startsWith('gsd-') && (file.endsWith('.js') || file.endsWith('.sh'))`
|
|
9788
|
+
// missed managed-hooks-registry.cjs (wrong prefix, .cjs extension), causing
|
|
9789
|
+
// detect-custom-files to flag it as a perpetual false-positive custom file
|
|
9790
|
+
// on every /gsd-update. See #941.
|
|
9791
|
+
for (const hook of INSTALLED_HOOK_FILES) {
|
|
9792
|
+
const hookPath = path.join(hooksDir, hook);
|
|
9793
|
+
if (fs.existsSync(hookPath)) {
|
|
9794
|
+
manifest.files['hooks/' + hook] = fileHash(hookPath);
|
|
9157
9795
|
}
|
|
9158
9796
|
}
|
|
9159
9797
|
// Track hooks/lib/ helpers so saveLocalPatches() can back up user edits
|
|
@@ -9169,6 +9807,24 @@ function writeManifest(configDir, runtime = 'claude', options = {}) {
|
|
|
9169
9807
|
}
|
|
9170
9808
|
}
|
|
9171
9809
|
|
|
9810
|
+
// Track scripts/changeset/ and scripts/lib/ so saveLocalPatches() can detect drift
|
|
9811
|
+
const changesetInstallDir = path.join(configDir, 'scripts', 'changeset');
|
|
9812
|
+
if (fs.existsSync(changesetInstallDir)) {
|
|
9813
|
+
for (const file of fs.readdirSync(changesetInstallDir)) {
|
|
9814
|
+
if (file.endsWith('.cjs')) {
|
|
9815
|
+
manifest.files['scripts/changeset/' + file] = fileHash(path.join(changesetInstallDir, file));
|
|
9816
|
+
}
|
|
9817
|
+
}
|
|
9818
|
+
}
|
|
9819
|
+
const scriptsLibInstallDir = path.join(configDir, 'scripts', 'lib');
|
|
9820
|
+
if (fs.existsSync(scriptsLibInstallDir)) {
|
|
9821
|
+
for (const file of fs.readdirSync(scriptsLibInstallDir)) {
|
|
9822
|
+
if (file.endsWith('.cjs')) {
|
|
9823
|
+
manifest.files['scripts/lib/' + file] = fileHash(path.join(scriptsLibInstallDir, file));
|
|
9824
|
+
}
|
|
9825
|
+
}
|
|
9826
|
+
}
|
|
9827
|
+
|
|
9172
9828
|
fs.writeFileSync(path.join(configDir, MANIFEST_NAME), JSON.stringify(manifest, null, 2));
|
|
9173
9829
|
return manifest;
|
|
9174
9830
|
}
|
|
@@ -9466,6 +10122,8 @@ function reportLocalPatches(configDir, runtime = 'claude') {
|
|
|
9466
10122
|
? '$gsd-update --reapply'
|
|
9467
10123
|
: runtime === 'cursor'
|
|
9468
10124
|
? 'gsd-update --reapply (mention the skill name)'
|
|
10125
|
+
: runtime === 'kimi'
|
|
10126
|
+
? '/skill:gsd-update --reapply'
|
|
9469
10127
|
: '/gsd-update --reapply';
|
|
9470
10128
|
console.log('');
|
|
9471
10129
|
console.log(' ' + yellow + 'Local patches detected' + reset + ' (from v' + meta.from_version + '):');
|
|
@@ -9496,6 +10154,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
9496
10154
|
const isOpencode = runtime === 'opencode';
|
|
9497
10155
|
const isGemini = runtime === 'gemini';
|
|
9498
10156
|
const isKilo = runtime === 'kilo';
|
|
10157
|
+
const isKimi = runtime === 'kimi';
|
|
9499
10158
|
const isCodex = runtime === 'codex';
|
|
9500
10159
|
const isCopilot = runtime === 'copilot';
|
|
9501
10160
|
const isAntigravity = runtime === 'antigravity';
|
|
@@ -9511,6 +10170,23 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
9511
10170
|
const dirName = getDirName(runtime);
|
|
9512
10171
|
const src = path.join(__dirname, '..');
|
|
9513
10172
|
|
|
10173
|
+
if (isKimi && !isGlobal) {
|
|
10174
|
+
console.log(` ${yellow}⚠${reset} Kimi local install is deferred for Phase 2.`);
|
|
10175
|
+
console.log(` No .kimi-code/skills or .agents/skills project artifacts were written.`);
|
|
10176
|
+
console.log(` Project-level Kimi install semantics remain deferred.`);
|
|
10177
|
+
return {
|
|
10178
|
+
runtime,
|
|
10179
|
+
skipped: true,
|
|
10180
|
+
reason: 'kimi_local_deferred',
|
|
10181
|
+
configDir: null,
|
|
10182
|
+
settingsPath: null,
|
|
10183
|
+
settings: null,
|
|
10184
|
+
statuslineCommand: null,
|
|
10185
|
+
updateBannerCommand: null,
|
|
10186
|
+
rollbackInstallerMigrations: () => {},
|
|
10187
|
+
};
|
|
10188
|
+
}
|
|
10189
|
+
|
|
9514
10190
|
// Reusable helper to copy hooks/lib/ (git-cmd.js + gsd-graphify-rebuild.sh).
|
|
9515
10191
|
// Defined early so it is visible to both the main and Codex code paths.
|
|
9516
10192
|
// `allowlist` (when non-empty) restricts copying to the named top-level entries,
|
|
@@ -9565,7 +10241,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
9565
10241
|
// @-references resolve correctly (#2376 Windows, #2831 macOS/Linux).
|
|
9566
10242
|
// gsd update marker re-application (ADR-0010 Deviation 2):
|
|
9567
10243
|
// Resolve which profile to use for this runtime's install:
|
|
9568
|
-
// 1. --minimal / --core-only → back-compat
|
|
10244
|
+
// 1. --minimal / --core-only → back-compat alias for the core profile
|
|
9569
10245
|
// 2. Explicit --profile=<name> → use it (overrides any marker)
|
|
9570
10246
|
// 3. Marker exists in targetDir → honor it (prevents silent expansion on update)
|
|
9571
10247
|
// 4. Else → 'full' (back-compat for fresh non-interactive installs)
|
|
@@ -9574,8 +10250,16 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
9574
10250
|
// differ, the caller may use mostRestrictiveProfile() across the per-runtime
|
|
9575
10251
|
// results — here we resolve each runtime independently.
|
|
9576
10252
|
//
|
|
9577
|
-
//
|
|
9578
|
-
//
|
|
10253
|
+
// ADR-857 phase 4c: ALL profiles (including core/minimal) use stageSkillsForProfile
|
|
10254
|
+
// with the registry-aware _resolvedProfile so future tier:core capabilities are
|
|
10255
|
+
// staged on core installs. The 'minimal' back-compat distinction is now ONLY the
|
|
10256
|
+
// empty manifest (core profile has no transitive deps); the registry IS consulted.
|
|
10257
|
+
// MINIMAL is intentionally the same skill set as the 'core' profile
|
|
10258
|
+
// (MINIMAL_ALLOWLIST_SET === Set(PROFILES.core)) — it is NOT a separately curated
|
|
10259
|
+
// subset. Any future tier:core capability therefore DOES belong in a minimal/core
|
|
10260
|
+
// install. Using stageSkillsForProfile(_resolvedProfile) honors the registry while
|
|
10261
|
+
// keeping the effective skill set identical to the prior stageSkillsForMode path
|
|
10262
|
+
// until a tier:core capability is registered.
|
|
9579
10263
|
const _activeProfileName = hasMinimal
|
|
9580
10264
|
? 'core' // --minimal is a back-compat alias for the core profile; marker records 'core'
|
|
9581
10265
|
: resolveEffectiveProfile({
|
|
@@ -9586,19 +10270,18 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
9586
10270
|
const _effectiveInstallMode = _isCoreProfileAlias ? 'minimal' : 'full';
|
|
9587
10271
|
// Load the manifest and compute resolved profile for named profiles.
|
|
9588
10272
|
// For --minimal/core: use an empty manifest (core profile has no transitive
|
|
9589
|
-
// deps) to produce a resolvedProfile with the core skill set.
|
|
9590
|
-
//
|
|
9591
|
-
// profile modes without a null sentinel.
|
|
10273
|
+
// deps) to produce a resolvedProfile with the core skill set. Registry IS
|
|
10274
|
+
// consulted so tier:core capability skills are included when registered.
|
|
9592
10275
|
const _commandsDir = path.join(src, 'commands', 'gsd');
|
|
9593
10276
|
const _skillsManifest = _isCoreProfileAlias ? new Map() : loadSkillsManifest(_commandsDir);
|
|
9594
10277
|
const _resolvedProfile = resolveProfile({
|
|
9595
10278
|
modes: [_activeProfileName],
|
|
9596
10279
|
manifest: _skillsManifest,
|
|
10280
|
+
registry: _capabilityRegistry,
|
|
9597
10281
|
});
|
|
9598
|
-
// Unified staging function:
|
|
9599
|
-
//
|
|
10282
|
+
// Unified staging function: all profiles use stageSkillsForProfile with the
|
|
10283
|
+
// registry-aware _resolvedProfile (ADR-857 phase 4c cutover).
|
|
9600
10284
|
function _stageSkills(commandsGsdDir) {
|
|
9601
|
-
if (_isCoreProfileAlias) return stageSkillsForMode(commandsGsdDir, _effectiveInstallMode);
|
|
9602
10285
|
return stageSkillsForProfile(commandsGsdDir, _resolvedProfile);
|
|
9603
10286
|
}
|
|
9604
10287
|
function _stageAgents(agentsDir) {
|
|
@@ -9637,6 +10320,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
9637
10320
|
if (isTrae) runtimeLabel = 'Trae';
|
|
9638
10321
|
if (isQwen) runtimeLabel = 'Qwen Code';
|
|
9639
10322
|
if (isHermes) runtimeLabel = 'Hermes Agent';
|
|
10323
|
+
if (isKimi) runtimeLabel = 'Kimi';
|
|
9640
10324
|
if (isCodebuddy) runtimeLabel = 'CodeBuddy';
|
|
9641
10325
|
if (isCline) runtimeLabel = 'Cline';
|
|
9642
10326
|
|
|
@@ -9926,6 +10610,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
9926
10610
|
// Cline global: emit skills to ~/.cline/skills/ (Cline >= v3.48.0 — #782).
|
|
9927
10611
|
const _isSkillsRuntime = isCodex || isCopilot || isAntigravity || isCursor || isWindsurf ||
|
|
9928
10612
|
isAugment || isTrae || isCodebuddy || isQwen || isHermes ||
|
|
10613
|
+
isKimi ||
|
|
9929
10614
|
(runtime === 'claude' && isGlobal) ||
|
|
9930
10615
|
(isCline && isGlobal);
|
|
9931
10616
|
|
|
@@ -9953,9 +10638,9 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
9953
10638
|
if (isHermes) {
|
|
9954
10639
|
const hermesSkillsDir = path.join(targetDir, 'skills', 'gsd');
|
|
9955
10640
|
if (fs.existsSync(hermesSkillsDir)) {
|
|
9956
|
-
// Hermes layout uses prefix: '' — skill dirs have
|
|
10641
|
+
// Hermes layout uses prefix: 'gsd-' (#947) — skill dirs have gsd-<stem> names
|
|
9957
10642
|
const count = fs.readdirSync(hermesSkillsDir, { withFileTypes: true })
|
|
9958
|
-
.filter(e => e.isDirectory()).length;
|
|
10643
|
+
.filter(e => e.isDirectory() && e.name.startsWith('gsd-')).length;
|
|
9959
10644
|
if (count > 0) {
|
|
9960
10645
|
console.log(` ${green}✓${reset} Installed ${count} skills to skills/gsd/`);
|
|
9961
10646
|
} else {
|
|
@@ -9964,6 +10649,26 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
9964
10649
|
} else {
|
|
9965
10650
|
failures.push('skills/gsd/*');
|
|
9966
10651
|
}
|
|
10652
|
+
} else if (isKimi) {
|
|
10653
|
+
const skillsDir = path.join(targetDir, 'skills');
|
|
10654
|
+
const rootAgentPath = path.join(targetDir, 'agents', 'gsd.yaml');
|
|
10655
|
+
if (fs.existsSync(skillsDir)) {
|
|
10656
|
+
const count = fs.readdirSync(skillsDir, { withFileTypes: true })
|
|
10657
|
+
.filter(e => e.isDirectory() && e.name.startsWith('gsd-')).length;
|
|
10658
|
+
if (count > 0) {
|
|
10659
|
+
console.log(` ${green}✓${reset} Installed ${count} Kimi skills to skills/`);
|
|
10660
|
+
} else {
|
|
10661
|
+
failures.push('skills/gsd-*');
|
|
10662
|
+
}
|
|
10663
|
+
} else {
|
|
10664
|
+
failures.push('skills/gsd-*');
|
|
10665
|
+
}
|
|
10666
|
+
if (fs.existsSync(rootAgentPath)) {
|
|
10667
|
+
console.log(` ${green}✓${reset} Generated Kimi root agent: ${rootAgentPath}`);
|
|
10668
|
+
console.log(` Launch with: kimi --agent-file ${rootAgentPath}`);
|
|
10669
|
+
} else {
|
|
10670
|
+
failures.push('agents/gsd.yaml');
|
|
10671
|
+
}
|
|
9967
10672
|
} else {
|
|
9968
10673
|
const skillsDir = path.join(targetDir, 'skills');
|
|
9969
10674
|
if (fs.existsSync(skillsDir)) {
|
|
@@ -10200,7 +10905,9 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
10200
10905
|
}
|
|
10201
10906
|
}
|
|
10202
10907
|
|
|
10203
|
-
if (
|
|
10908
|
+
if (isKimi) {
|
|
10909
|
+
console.log(` ${dim}↳${reset} Kimi custom agent YAML/prompt artifacts were installed via runtime artifact layout`);
|
|
10910
|
+
} else if (isMinimalMode(_effectiveInstallMode)) {
|
|
10204
10911
|
// Codex registers agents in `config.toml` via `[agents.gsd-*]` sections.
|
|
10205
10912
|
// Without stripping them here, a full → minimal reinstall would leave the
|
|
10206
10913
|
// runtime advertising the old full agent surface even though the agent
|
|
@@ -10343,7 +11050,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
10343
11050
|
failures.push('VERSION');
|
|
10344
11051
|
}
|
|
10345
11052
|
|
|
10346
|
-
if (!isCodex && !isCopilot && !isCursor && !isWindsurf && !isTrae && !isCline) {
|
|
11053
|
+
if (!isCodex && !isCopilot && !isCursor && !isWindsurf && !isTrae && !isCline && !isKimi) {
|
|
10347
11054
|
// Write package.json to force CommonJS mode for GSD scripts
|
|
10348
11055
|
// Prevents "require is not defined" errors when project has "type": "module"
|
|
10349
11056
|
// Node.js walks up looking for package.json - this stops inheritance from project
|
|
@@ -10439,13 +11146,71 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
10439
11146
|
// the hooks/lib/ helpers — otherwise the Codex comment downstream
|
|
10440
11147
|
// ("we deliberately do *not* copy hooks/lib/ for Codex") is contradicted in practice.
|
|
10441
11148
|
const hooksLibSrc = path.join(src, 'hooks', 'lib');
|
|
10442
|
-
if (!isCodex && !isCopilot && !isCursor && !isWindsurf && !isTrae && !isCline && fs.existsSync(hooksLibSrc)) {
|
|
11149
|
+
if (!isCodex && !isCopilot && !isCursor && !isWindsurf && !isTrae && !isCline && !isKimi && fs.existsSync(hooksLibSrc)) {
|
|
10443
11150
|
const hooksLibDest = path.join(targetDir, 'hooks', 'lib');
|
|
10444
11151
|
fs.mkdirSync(hooksLibDest, { recursive: true });
|
|
10445
11152
|
copyLibDir(hooksLibSrc, hooksLibDest, GSD_HOOK_LIB_FILES);
|
|
10446
11153
|
console.log(` ${green}✓${reset} Installed hooks/lib/ helpers (git-cmd, graphify-rebuild, ...)`);
|
|
10447
11154
|
}
|
|
10448
11155
|
|
|
11156
|
+
// Install scripts/changeset/ and scripts/lib/ into <configDir>/scripts/
|
|
11157
|
+
// so that `node "$GSD_DIR/scripts/changeset/cli.cjs"` resolves at runtime.
|
|
11158
|
+
//
|
|
11159
|
+
// The changeset CLI (scripts/changeset/cli.cjs) is invoked by the update
|
|
11160
|
+
// workflow (gsd-core/workflows/update.md) to extract changelog ranges for
|
|
11161
|
+
// the /gsd-update preview step. It was previously only present in the npm
|
|
11162
|
+
// tarball root but never copied to the runtime config dir, causing the
|
|
11163
|
+
// preview to always silently fail (#935).
|
|
11164
|
+
//
|
|
11165
|
+
// cli.cjs requires:
|
|
11166
|
+
// - sibling files in scripts/changeset/ (parse/render/serialize/github-release-notes)
|
|
11167
|
+
// - ../lib/cli-exit.cjs → scripts/lib/cli-exit.cjs
|
|
11168
|
+
// - ../../gsd-core/bin/lib/semver-compare.cjs (already installed under gsd-core/)
|
|
11169
|
+
// - ../../gsd-core/bin/lib/package-identity.cjs (already installed under gsd-core/)
|
|
11170
|
+
//
|
|
11171
|
+
// All runtimes that use the update workflow need this, so we copy unconditionally
|
|
11172
|
+
// (same scope as gsd-core/ itself — every runtime that installs workflows gets it).
|
|
11173
|
+
const changesetSrc = path.join(src, 'scripts', 'changeset');
|
|
11174
|
+
const scriptsLibSrc = path.join(src, 'scripts', 'lib');
|
|
11175
|
+
if (!fs.existsSync(changesetSrc)) {
|
|
11176
|
+
// The changeset CLI source is missing from the package — mark as a hard failure
|
|
11177
|
+
// so the user knows the changelog preview will not work rather than silently degrading.
|
|
11178
|
+
failures.push('scripts/changeset/ (source missing from package — reinstall from npm)');
|
|
11179
|
+
} else {
|
|
11180
|
+
const changesetDest = path.join(targetDir, 'scripts', 'changeset');
|
|
11181
|
+
const scriptsLibDest = path.join(targetDir, 'scripts', 'lib');
|
|
11182
|
+
fs.mkdirSync(changesetDest, { recursive: true });
|
|
11183
|
+
fs.mkdirSync(scriptsLibDest, { recursive: true });
|
|
11184
|
+
// Copy scripts/changeset/ — all .cjs and .md files
|
|
11185
|
+
for (const entry of fs.readdirSync(changesetSrc)) {
|
|
11186
|
+
const srcFile = path.join(changesetSrc, entry);
|
|
11187
|
+
if (fs.statSync(srcFile).isFile()) {
|
|
11188
|
+
fs.copyFileSync(srcFile, path.join(changesetDest, entry));
|
|
11189
|
+
}
|
|
11190
|
+
}
|
|
11191
|
+
// Copy scripts/lib/ — cli-exit.cjs (required by cli.cjs) and any future lib helpers.
|
|
11192
|
+
// Hard-fail if missing: without cli-exit.cjs the installed CLI throws MODULE_NOT_FOUND.
|
|
11193
|
+
if (!fs.existsSync(scriptsLibSrc)) {
|
|
11194
|
+
failures.push('scripts/lib/ (source missing from package — reinstall from npm)');
|
|
11195
|
+
} else {
|
|
11196
|
+
for (const entry of fs.readdirSync(scriptsLibSrc)) {
|
|
11197
|
+
const srcFile = path.join(scriptsLibSrc, entry);
|
|
11198
|
+
if (fs.statSync(srcFile).isFile()) {
|
|
11199
|
+
fs.copyFileSync(srcFile, path.join(scriptsLibDest, entry));
|
|
11200
|
+
}
|
|
11201
|
+
}
|
|
11202
|
+
// Verify the critical dep cli-exit.cjs landed
|
|
11203
|
+
if (!verifyFileInstalled(path.join(scriptsLibDest, 'cli-exit.cjs'), 'scripts/lib/cli-exit.cjs')) {
|
|
11204
|
+
failures.push('scripts/lib/cli-exit.cjs');
|
|
11205
|
+
}
|
|
11206
|
+
}
|
|
11207
|
+
if (verifyFileInstalled(path.join(changesetDest, 'cli.cjs'), 'scripts/changeset/cli.cjs')) {
|
|
11208
|
+
console.log(` ${green}✓${reset} Installed scripts/changeset/ (changelog preview CLI)`);
|
|
11209
|
+
} else {
|
|
11210
|
+
failures.push('scripts/changeset/cli.cjs');
|
|
11211
|
+
}
|
|
11212
|
+
}
|
|
11213
|
+
|
|
10449
11214
|
// Remove legacy get-shit-done-cc artifacts and stale update caches (#607).
|
|
10450
11215
|
// cleanupLegacyGsdCc handles both the legacy shared cache and the per-package
|
|
10451
11216
|
// cache (formerly an inline unlinkSync here). A cleanup failure must never
|
|
@@ -10967,7 +11732,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
10967
11732
|
}
|
|
10968
11733
|
|
|
10969
11734
|
if (configIntent.installSurface === 'profile-marker-only') {
|
|
10970
|
-
// Windsurf/Trae use
|
|
11735
|
+
// Windsurf/Trae/Kimi use artifact-only surfaces — no config.toml or settings.json hooks needed.
|
|
10971
11736
|
persistActiveProfileMarker();
|
|
10972
11737
|
return { settingsPath: null, settings: null, statuslineCommand: null, updateBannerCommand: null, runtime, configDir: targetDir };
|
|
10973
11738
|
}
|
|
@@ -11160,6 +11925,18 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
11160
11925
|
}
|
|
11161
11926
|
}
|
|
11162
11927
|
|
|
11928
|
+
// Helper: detect whether a hook entry references a managed hook by name.
|
|
11929
|
+
// Checks both the plain command string (standard form) and the args array
|
|
11930
|
+
// (command+args / wrapped-launcher form used by windowless launchers on
|
|
11931
|
+
// Windows and some custom PATH-less environments). Without this check the
|
|
11932
|
+
// presence guards below only inspect h.command, so an args-form wrapper is
|
|
11933
|
+
// invisible and a stock string-command entry is appended on every
|
|
11934
|
+
// install/update, running the hook twice. (#976)
|
|
11935
|
+
function referencesHook(h, hookName) {
|
|
11936
|
+
return (typeof h.command === 'string' && h.command.includes(hookName)) ||
|
|
11937
|
+
(Array.isArray(h.args) && h.args.some(a => typeof a === 'string' && a.includes(hookName)));
|
|
11938
|
+
}
|
|
11939
|
+
|
|
11163
11940
|
// Configure SessionStart hook for update checking (skip for opencode)
|
|
11164
11941
|
if (!isOpencode && !isKilo) {
|
|
11165
11942
|
if (!settings.hooks) {
|
|
@@ -11170,7 +11947,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
11170
11947
|
}
|
|
11171
11948
|
|
|
11172
11949
|
const hasGsdUpdateHook = settings.hooks.SessionStart.some(entry =>
|
|
11173
|
-
entry.hooks && entry.hooks.some(h => h
|
|
11950
|
+
entry.hooks && entry.hooks.some(h => referencesHook(h, 'gsd-check-update'))
|
|
11174
11951
|
);
|
|
11175
11952
|
|
|
11176
11953
|
// Guard: only register if the hook file was actually installed (#1754).
|
|
@@ -11198,7 +11975,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
11198
11975
|
}
|
|
11199
11976
|
|
|
11200
11977
|
const hasContextMonitorHook = settings.hooks[postToolEvent].some(entry =>
|
|
11201
|
-
entry.hooks && entry.hooks.some(h => h
|
|
11978
|
+
entry.hooks && entry.hooks.some(h => referencesHook(h, 'gsd-context-monitor'))
|
|
11202
11979
|
);
|
|
11203
11980
|
|
|
11204
11981
|
const contextMonitorFile = path.join(targetDir, 'hooks', 'gsd-context-monitor.js');
|
|
@@ -11219,14 +11996,14 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
11219
11996
|
} else {
|
|
11220
11997
|
// Migrate existing context monitor hooks: add matcher and timeout if missing
|
|
11221
11998
|
for (const entry of settings.hooks[postToolEvent]) {
|
|
11222
|
-
if (entry.hooks && entry.hooks.some(h => h
|
|
11999
|
+
if (entry.hooks && entry.hooks.some(h => referencesHook(h, 'gsd-context-monitor'))) {
|
|
11223
12000
|
let migrated = false;
|
|
11224
12001
|
if (!entry.matcher) {
|
|
11225
12002
|
entry.matcher = 'Bash|Edit|Write|MultiEdit|Agent|Task';
|
|
11226
12003
|
migrated = true;
|
|
11227
12004
|
}
|
|
11228
12005
|
for (const h of entry.hooks) {
|
|
11229
|
-
if (h
|
|
12006
|
+
if (referencesHook(h, 'gsd-context-monitor') && !h.timeout) {
|
|
11230
12007
|
h.timeout = 10;
|
|
11231
12008
|
migrated = true;
|
|
11232
12009
|
}
|
|
@@ -11246,7 +12023,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
11246
12023
|
}
|
|
11247
12024
|
|
|
11248
12025
|
const hasPromptGuardHook = settings.hooks[preToolEvent].some(entry =>
|
|
11249
|
-
entry.hooks && entry.hooks.some(h => h
|
|
12026
|
+
entry.hooks && entry.hooks.some(h => referencesHook(h, 'gsd-prompt-guard'))
|
|
11250
12027
|
);
|
|
11251
12028
|
|
|
11252
12029
|
const promptGuardFile = path.join(targetDir, 'hooks', 'gsd-prompt-guard.js');
|
|
@@ -11270,7 +12047,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
11270
12047
|
// Prevents infinite retry loops when non-Claude models attempt to edit
|
|
11271
12048
|
// files without reading them first. Advisory-only — does not block.
|
|
11272
12049
|
const hasReadGuardHook = settings.hooks[preToolEvent].some(entry =>
|
|
11273
|
-
entry.hooks && entry.hooks.some(h => h
|
|
12050
|
+
entry.hooks && entry.hooks.some(h => referencesHook(h, 'gsd-read-guard'))
|
|
11274
12051
|
);
|
|
11275
12052
|
|
|
11276
12053
|
const readGuardFile = path.join(targetDir, 'hooks', 'gsd-read-guard.js');
|
|
@@ -11294,7 +12071,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
11294
12071
|
// Scans content returned by the Read tool for injection patterns, including
|
|
11295
12072
|
// summarisation-specific patterns that survive context compression.
|
|
11296
12073
|
const hasReadInjectionScannerHook = settings.hooks[postToolEvent].some(entry =>
|
|
11297
|
-
entry.hooks && entry.hooks.some(h => h
|
|
12074
|
+
entry.hooks && entry.hooks.some(h => referencesHook(h, 'gsd-read-injection-scanner'))
|
|
11298
12075
|
);
|
|
11299
12076
|
|
|
11300
12077
|
const readInjectionScannerFile = path.join(targetDir, 'hooks', 'gsd-read-injection-scanner.js');
|
|
@@ -11328,7 +12105,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
11328
12105
|
: localCmd('gsd-workflow-guard.js');
|
|
11329
12106
|
const workflowGuardMatcher = 'Bash|Edit|Write|MultiEdit';
|
|
11330
12107
|
const workflowGuardHookEntry = settings.hooks[preToolEvent].find(entry =>
|
|
11331
|
-
entry.hooks && entry.hooks.some(h => h
|
|
12108
|
+
entry.hooks && entry.hooks.some(h => referencesHook(h, 'gsd-workflow-guard'))
|
|
11332
12109
|
);
|
|
11333
12110
|
const hasWorkflowGuardHook = Boolean(workflowGuardHookEntry);
|
|
11334
12111
|
|
|
@@ -11360,7 +12137,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
11360
12137
|
? buildHookCommand(targetDir, 'gsd-worktree-path-guard.js', hookOpts)
|
|
11361
12138
|
: localCmd('gsd-worktree-path-guard.js');
|
|
11362
12139
|
const hasWorktreePathGuardHook = settings.hooks[preToolEvent].some(entry =>
|
|
11363
|
-
entry.hooks && entry.hooks.some(h => h
|
|
12140
|
+
entry.hooks && entry.hooks.some(h => referencesHook(h, 'gsd-worktree-path-guard'))
|
|
11364
12141
|
);
|
|
11365
12142
|
const worktreePathGuardFile = path.join(targetDir, 'hooks', 'gsd-worktree-path-guard.js');
|
|
11366
12143
|
if (!hasWorktreePathGuardHook && fs.existsSync(worktreePathGuardFile) && worktreePathGuardCommand) {
|
|
@@ -11384,7 +12161,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
11384
12161
|
? buildHookCommand(targetDir, 'gsd-validate-commit.sh', hookOpts)
|
|
11385
12162
|
: localShellCmd('gsd-validate-commit.sh');
|
|
11386
12163
|
const hasValidateCommitHook = settings.hooks[preToolEvent].some(entry =>
|
|
11387
|
-
entry.hooks && entry.hooks.some(h => h
|
|
12164
|
+
entry.hooks && entry.hooks.some(h => referencesHook(h, 'gsd-validate-commit'))
|
|
11388
12165
|
);
|
|
11389
12166
|
// Guard: only register if the .sh file was actually installed. If the npm package
|
|
11390
12167
|
// omitted the file (as happened in v1.32.0, bug #1817), registering a missing hook
|
|
@@ -11416,7 +12193,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
11416
12193
|
? buildHookCommand(targetDir, 'gsd-graphify-update.sh', hookOpts)
|
|
11417
12194
|
: localShellCmd('gsd-graphify-update.sh');
|
|
11418
12195
|
const hasGraphifyUpdateHook = settings.hooks[postToolEvent].some(entry =>
|
|
11419
|
-
entry.hooks && entry.hooks.some(h => h
|
|
12196
|
+
entry.hooks && entry.hooks.some(h => referencesHook(h, 'gsd-graphify-update'))
|
|
11420
12197
|
);
|
|
11421
12198
|
const graphifyUpdateFile = path.join(targetDir, 'hooks', 'gsd-graphify-update.sh');
|
|
11422
12199
|
if (!hasGraphifyUpdateHook && fs.existsSync(graphifyUpdateFile) && graphifyUpdateCommand) {
|
|
@@ -11442,7 +12219,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
11442
12219
|
? buildHookCommand(targetDir, 'gsd-session-state.sh', hookOpts)
|
|
11443
12220
|
: localShellCmd('gsd-session-state.sh');
|
|
11444
12221
|
const hasSessionStateHook = settings.hooks.SessionStart.some(entry =>
|
|
11445
|
-
entry.hooks && entry.hooks.some(h => h
|
|
12222
|
+
entry.hooks && entry.hooks.some(h => referencesHook(h, 'gsd-session-state'))
|
|
11446
12223
|
);
|
|
11447
12224
|
const sessionStateFile = path.join(targetDir, 'hooks', 'gsd-session-state.sh');
|
|
11448
12225
|
if (!hasSessionStateHook && fs.existsSync(sessionStateFile) && sessionStateCommand) {
|
|
@@ -11466,7 +12243,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
11466
12243
|
? buildHookCommand(targetDir, 'gsd-phase-boundary.sh', hookOpts)
|
|
11467
12244
|
: localShellCmd('gsd-phase-boundary.sh');
|
|
11468
12245
|
const hasPhaseBoundaryHook = settings.hooks[postToolEvent].some(entry =>
|
|
11469
|
-
entry.hooks && entry.hooks.some(h => h
|
|
12246
|
+
entry.hooks && entry.hooks.some(h => referencesHook(h, 'gsd-phase-boundary'))
|
|
11470
12247
|
);
|
|
11471
12248
|
const phaseBoundaryFile = path.join(targetDir, 'hooks', 'gsd-phase-boundary.sh');
|
|
11472
12249
|
if (!hasPhaseBoundaryHook && fs.existsSync(phaseBoundaryFile) && phaseBoundaryCommand) {
|
|
@@ -11510,7 +12287,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
11510
12287
|
settings.hooks[event] = [];
|
|
11511
12288
|
}
|
|
11512
12289
|
const alreadyHasContextMonitor = settings.hooks[event].some(entry =>
|
|
11513
|
-
entry.hooks && entry.hooks.some(h => h
|
|
12290
|
+
entry.hooks && entry.hooks.some(h => referencesHook(h, 'gsd-context-monitor'))
|
|
11514
12291
|
);
|
|
11515
12292
|
if (!alreadyHasContextMonitor && fs.existsSync(contextMonitorFile) && contextMonitorCommand) {
|
|
11516
12293
|
settings.hooks[event].push({
|
|
@@ -11559,7 +12336,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
11559
12336
|
settings.hooks[geminiEvent] = [];
|
|
11560
12337
|
}
|
|
11561
12338
|
const alreadyHasContextMonitor = settings.hooks[geminiEvent].some(entry =>
|
|
11562
|
-
entry.hooks && entry.hooks.some(h => h
|
|
12339
|
+
entry.hooks && entry.hooks.some(h => referencesHook(h, 'gsd-context-monitor'))
|
|
11563
12340
|
);
|
|
11564
12341
|
if (!alreadyHasContextMonitor && fs.existsSync(contextMonitorFile) && contextMonitorCommand) {
|
|
11565
12342
|
settings.hooks[geminiEvent].push({
|
|
@@ -11596,7 +12373,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
11596
12373
|
}
|
|
11597
12374
|
const configReloadFile = path.join(targetDir, 'hooks', 'gsd-config-reload.js');
|
|
11598
12375
|
const alreadyHasConfigReload = settings.hooks.FileChanged.some(entry =>
|
|
11599
|
-
entry.hooks && entry.hooks.some(h => h
|
|
12376
|
+
entry.hooks && entry.hooks.some(h => referencesHook(h, 'gsd-config-reload'))
|
|
11600
12377
|
);
|
|
11601
12378
|
if (!alreadyHasConfigReload && fs.existsSync(configReloadFile) && configReloadCommand) {
|
|
11602
12379
|
settings.hooks.FileChanged.push({
|
|
@@ -11734,7 +12511,7 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
|
|
|
11734
12511
|
const isCline = runtime === 'cline';
|
|
11735
12512
|
const configIntent = resolveRuntimeConfigIntent(runtime);
|
|
11736
12513
|
|
|
11737
|
-
if (shouldInstallStatusline &&
|
|
12514
|
+
if (shouldInstallStatusline && configIntent.writesSharedSettings && !isOpencode) {
|
|
11738
12515
|
if (!isGlobal && !forceStatusline) {
|
|
11739
12516
|
// Local installs skip statusLine by default: repo settings.json takes precedence over
|
|
11740
12517
|
// profile-level settings.json in Claude Code, so writing here would silently clobber
|
|
@@ -11760,14 +12537,14 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
|
|
|
11760
12537
|
// settings.json hooks block — opencode/kilo/codex/cursor/windsurf/trae/
|
|
11761
12538
|
// cline either lack the surface or use a different config schema.
|
|
11762
12539
|
const { shouldInstallBanner, bannerCommand } = bannerOpts;
|
|
11763
|
-
if (shouldInstallBanner && settings &&
|
|
12540
|
+
if (shouldInstallBanner && settings && configIntent.writesSharedSettings && !isOpencode) {
|
|
11764
12541
|
if (!bannerCommand) {
|
|
11765
12542
|
console.warn(` ${yellow}⚠${reset} Skipped update banner registration — Node executable path unavailable. See #2979 / #3002.`);
|
|
11766
12543
|
} else {
|
|
11767
12544
|
if (!settings.hooks) settings.hooks = {};
|
|
11768
12545
|
if (!settings.hooks.SessionStart) settings.hooks.SessionStart = [];
|
|
11769
12546
|
const alreadyRegistered = settings.hooks.SessionStart.some(entry =>
|
|
11770
|
-
entry && entry.hooks && entry.hooks.some(h => h && h
|
|
12547
|
+
entry && entry.hooks && entry.hooks.some(h => h && referencesHook(h, 'gsd-update-banner'))
|
|
11771
12548
|
);
|
|
11772
12549
|
const bannerHookFile = configDir ? path.join(configDir, 'hooks', 'gsd-update-banner.js') : null;
|
|
11773
12550
|
const bannerInstalled = bannerHookFile ? fs.existsSync(bannerHookFile) : false;
|
|
@@ -11800,7 +12577,7 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
|
|
|
11800
12577
|
// {type: 'command', command: null} items that the runtime hook schema
|
|
11801
12578
|
// rejects at parse time. validateHookFields filters those out so the file
|
|
11802
12579
|
// we write is always schema-valid.
|
|
11803
|
-
if (configIntent.writesSharedSettings) {
|
|
12580
|
+
if (settingsPath && settings && configIntent.writesSharedSettings) {
|
|
11804
12581
|
writeSettings(settingsPath, validateHookFields(settings));
|
|
11805
12582
|
}
|
|
11806
12583
|
|
|
@@ -11849,6 +12626,7 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
|
|
|
11849
12626
|
if (runtime === 'cline') program = 'Cline';
|
|
11850
12627
|
if (runtime === 'qwen') program = 'Qwen Code';
|
|
11851
12628
|
if (runtime === 'hermes') program = 'Hermes Agent';
|
|
12629
|
+
if (runtime === 'kimi') program = 'Kimi CLI';
|
|
11852
12630
|
|
|
11853
12631
|
let command = '/gsd-new-project';
|
|
11854
12632
|
if (runtime === 'opencode') command = '/gsd-new-project';
|
|
@@ -11864,6 +12642,7 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
|
|
|
11864
12642
|
if (runtime === 'cline') command = '/gsd-new-project';
|
|
11865
12643
|
if (runtime === 'qwen') command = '/gsd-new-project';
|
|
11866
12644
|
if (runtime === 'hermes') command = '/gsd-new-project';
|
|
12645
|
+
if (runtime === 'kimi') command = '/skill:gsd-new-project';
|
|
11867
12646
|
|
|
11868
12647
|
// Claude Code global installs use the skills/ format (CC 2.1.88+).
|
|
11869
12648
|
// Restart is required for CC to pick up newly-installed skills, and the
|
|
@@ -11878,6 +12657,16 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
|
|
|
11878
12657
|
return;
|
|
11879
12658
|
}
|
|
11880
12659
|
|
|
12660
|
+
if (runtime === 'kimi') {
|
|
12661
|
+
const agentPath = configDir ? path.join(configDir, 'agents', 'gsd.yaml') : 'agents/gsd.yaml';
|
|
12662
|
+
console.log(`
|
|
12663
|
+
${green}Done!${reset} Start ${program} with ${cyan}kimi --agent-file ${agentPath}${reset}, then run ${cyan}${command}${reset}.
|
|
12664
|
+
|
|
12665
|
+
${cyan}Join the community:${reset} https://discord.gg/mYgfVNfA2r
|
|
12666
|
+
`);
|
|
12667
|
+
return;
|
|
12668
|
+
}
|
|
12669
|
+
|
|
11881
12670
|
console.log(`
|
|
11882
12671
|
${green}Done!${reset} Open a blank directory in ${program} and run ${cyan}${command}${reset}.
|
|
11883
12672
|
|
|
@@ -11954,14 +12743,15 @@ const runtimeMap = {
|
|
|
11954
12743
|
'8': 'cursor',
|
|
11955
12744
|
'9': 'gemini',
|
|
11956
12745
|
'10': 'hermes',
|
|
11957
|
-
'11': '
|
|
11958
|
-
'12': '
|
|
11959
|
-
'13': '
|
|
11960
|
-
'14': '
|
|
11961
|
-
'15': '
|
|
12746
|
+
'11': 'kimi',
|
|
12747
|
+
'12': 'kilo',
|
|
12748
|
+
'13': 'opencode',
|
|
12749
|
+
'14': 'qwen',
|
|
12750
|
+
'15': 'trae',
|
|
12751
|
+
'16': 'windsurf'
|
|
11962
12752
|
};
|
|
11963
|
-
const allRuntimes = ['claude', 'antigravity', 'augment', 'cline', 'codebuddy', 'codex', 'copilot', 'cursor', 'gemini', 'hermes', 'kilo', 'opencode', 'qwen', 'trae', 'windsurf'];
|
|
11964
|
-
const ALL_RUNTIMES_OPTION = '
|
|
12753
|
+
const allRuntimes = ['claude', 'antigravity', 'augment', 'cline', 'codebuddy', 'codex', 'copilot', 'cursor', 'gemini', 'hermes', 'kimi', 'kilo', 'opencode', 'qwen', 'trae', 'windsurf'];
|
|
12754
|
+
const ALL_RUNTIMES_OPTION = '17';
|
|
11965
12755
|
|
|
11966
12756
|
/**
|
|
11967
12757
|
* Build the runtime-selection prompt text shown by the interactive installer.
|
|
@@ -11979,12 +12769,13 @@ function buildRuntimePromptText() {
|
|
|
11979
12769
|
${cyan}8${reset}) Cursor ${dim}(~/.cursor)${reset}
|
|
11980
12770
|
${cyan}9${reset}) Gemini ${dim}(~/.gemini)${reset}
|
|
11981
12771
|
${cyan}10${reset}) Hermes Agent ${dim}(~/.hermes)${reset}
|
|
11982
|
-
${cyan}11${reset})
|
|
11983
|
-
${cyan}12${reset})
|
|
11984
|
-
${cyan}13${reset})
|
|
11985
|
-
${cyan}14${reset})
|
|
11986
|
-
${cyan}15${reset})
|
|
11987
|
-
${cyan}16${reset})
|
|
12772
|
+
${cyan}11${reset}) Kimi ${dim}(~/.config/agents, then ~/.agents if existing)${reset}
|
|
12773
|
+
${cyan}12${reset}) Kilo ${dim}(~/.config/kilo)${reset}
|
|
12774
|
+
${cyan}13${reset}) OpenCode ${dim}(~/.config/opencode)${reset}
|
|
12775
|
+
${cyan}14${reset}) Qwen Code ${dim}(~/.qwen)${reset}
|
|
12776
|
+
${cyan}15${reset}) Trae ${dim}(~/.trae)${reset}
|
|
12777
|
+
${cyan}16${reset}) Windsurf ${dim}(~/.codeium/windsurf)${reset}
|
|
12778
|
+
${cyan}17${reset}) All
|
|
11988
12779
|
|
|
11989
12780
|
${dim}Select multiple: 1,2,6 or 1 2 6${reset}
|
|
11990
12781
|
`;
|
|
@@ -11995,7 +12786,7 @@ function buildRuntimePromptText() {
|
|
|
11995
12786
|
* Pure function — exported so tests can verify split/dedupe/fallback behavior.
|
|
11996
12787
|
* - Accepts comma- and/or whitespace-separated choices
|
|
11997
12788
|
* - Deduplicates while preserving order
|
|
11998
|
-
* - Maps option
|
|
12789
|
+
* - Maps option 17 ("All") to every runtime
|
|
11999
12790
|
* - Falls back to ['claude'] when nothing valid is selected
|
|
12000
12791
|
*/
|
|
12001
12792
|
function parseRuntimeInput(answer) {
|
|
@@ -12447,6 +13238,8 @@ function installAllRuntimes(runtimes, isGlobal, isInteractive) {
|
|
|
12447
13238
|
try {
|
|
12448
13239
|
const printSummaries = () => {
|
|
12449
13240
|
for (const result of results) {
|
|
13241
|
+
if (result && result.skipped) continue;
|
|
13242
|
+
if (!result) continue;
|
|
12450
13243
|
const useStatusline = statuslineRuntimes.includes(result.runtime) && shouldInstallStatusline;
|
|
12451
13244
|
finishInstall(
|
|
12452
13245
|
result.settingsPath,
|
|
@@ -12560,6 +13353,10 @@ module.exports = {
|
|
|
12560
13353
|
uninstall,
|
|
12561
13354
|
convertSlashCommandsToCodexSkillMentions,
|
|
12562
13355
|
convertClaudeCommandToCodexSkill,
|
|
13356
|
+
convertClaudeCommandToKimiSkill,
|
|
13357
|
+
convertKimiToolName,
|
|
13358
|
+
mapClaudeToolsToKimiTools,
|
|
13359
|
+
buildKimiAgentArtifacts,
|
|
12563
13360
|
convertClaudeToOpencodeFrontmatter,
|
|
12564
13361
|
convertClaudeToKiloFrontmatter,
|
|
12565
13362
|
convertClaudeCommandToOpencodeSkill,
|
|
@@ -12573,6 +13370,7 @@ module.exports = {
|
|
|
12573
13370
|
GSD_CODEX_MARKER,
|
|
12574
13371
|
CODEX_AGENT_SANDBOX,
|
|
12575
13372
|
getDirName,
|
|
13373
|
+
getGlobalDir,
|
|
12576
13374
|
getConfigDirFromHome,
|
|
12577
13375
|
resolveKiloConfigPath,
|
|
12578
13376
|
configureKiloPermissions,
|
|
@@ -12639,6 +13437,7 @@ module.exports = {
|
|
|
12639
13437
|
maybeSuggestPathExport,
|
|
12640
13438
|
runtimeMap,
|
|
12641
13439
|
allRuntimes,
|
|
13440
|
+
selectRuntimesFromArgs,
|
|
12642
13441
|
GSD_UNINSTALL_HOOKS,
|
|
12643
13442
|
parseRuntimeInput,
|
|
12644
13443
|
buildRuntimePromptText,
|
|
@@ -12689,12 +13488,11 @@ if (require.main === module && !process.env.GSD_TEST_MODE) {
|
|
|
12689
13488
|
console.error('Usage: node install.js --skills-root <runtime>');
|
|
12690
13489
|
process.exit(1);
|
|
12691
13490
|
}
|
|
12692
|
-
const
|
|
12693
|
-
|
|
12694
|
-
|
|
12695
|
-
|
|
12696
|
-
|
|
12697
|
-
: path.join(globalDir, 'skills');
|
|
13491
|
+
const skillsRoot = getGlobalSkillsBase(runtimeArg);
|
|
13492
|
+
if (skillsRoot === null) {
|
|
13493
|
+
console.error(`${runtimeArg} does not use a skills directory`);
|
|
13494
|
+
process.exit(1);
|
|
13495
|
+
}
|
|
12698
13496
|
console.log(skillsRoot);
|
|
12699
13497
|
} else if (hasGlobal && hasLocal) {
|
|
12700
13498
|
console.error(` ${yellow}Cannot specify both --global and --local${reset}`);
|