@specship/specship-darwin-arm64 0.29.0 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/specship +1 -1
- package/lib/dist/.claude-plugin/plugin.json +15 -3
- package/lib/dist/activation/cheatsheet.d.ts +10 -7
- package/lib/dist/activation/cheatsheet.d.ts.map +1 -1
- package/lib/dist/activation/cheatsheet.js +21 -5
- package/lib/dist/activation/cheatsheet.js.map +1 -1
- package/lib/dist/activation/hook-output.d.ts +75 -2
- package/lib/dist/activation/hook-output.d.ts.map +1 -1
- package/lib/dist/activation/hook-output.js +114 -3
- package/lib/dist/activation/hook-output.js.map +1 -1
- package/lib/dist/activation/search-intercept.d.ts +20 -7
- package/lib/dist/activation/search-intercept.d.ts.map +1 -1
- package/lib/dist/activation/search-intercept.js +27 -5
- package/lib/dist/activation/search-intercept.js.map +1 -1
- package/lib/dist/activation/steering.d.ts +7 -4
- package/lib/dist/activation/steering.d.ts.map +1 -1
- package/lib/dist/activation/steering.js +10 -8
- package/lib/dist/activation/steering.js.map +1 -1
- package/lib/dist/agents/detect.d.ts +32 -0
- package/lib/dist/agents/detect.d.ts.map +1 -0
- package/lib/dist/agents/detect.js +54 -0
- package/lib/dist/agents/detect.js.map +1 -0
- package/lib/dist/agents/recent-project.d.ts +18 -0
- package/lib/dist/agents/recent-project.d.ts.map +1 -0
- package/lib/dist/agents/recent-project.js +92 -0
- package/lib/dist/agents/recent-project.js.map +1 -0
- package/lib/dist/analytics/canonical-tools.d.ts +23 -0
- package/lib/dist/analytics/canonical-tools.d.ts.map +1 -0
- package/lib/dist/analytics/canonical-tools.js +36 -0
- package/lib/dist/analytics/canonical-tools.js.map +1 -0
- package/lib/dist/analytics/canonical-tools.json +16 -0
- package/lib/dist/analytics/specship-impact.d.ts +3 -2
- package/lib/dist/analytics/specship-impact.d.ts.map +1 -1
- package/lib/dist/analytics/specship-impact.js +6 -4
- package/lib/dist/analytics/specship-impact.js.map +1 -1
- package/lib/dist/bin/specship.js +215 -170
- package/lib/dist/bin/specship.js.map +1 -1
- package/lib/dist/bin/uninstall.js +2 -2
- package/lib/dist/bin/uninstall.js.map +1 -1
- package/lib/dist/claudemd/audit.d.ts +12 -3
- package/lib/dist/claudemd/audit.d.ts.map +1 -1
- package/lib/dist/claudemd/audit.js +65 -22
- package/lib/dist/claudemd/audit.js.map +1 -1
- package/lib/dist/commands/specship/spec.md +9 -0
- package/lib/dist/db/migrations.d.ts +1 -1
- package/lib/dist/db/migrations.d.ts.map +1 -1
- package/lib/dist/db/migrations.js +18 -4
- package/lib/dist/db/migrations.js.map +1 -1
- package/lib/dist/db/schema.sql +8 -0
- package/lib/dist/db/spec-queries.d.ts +13 -1
- package/lib/dist/db/spec-queries.d.ts.map +1 -1
- package/lib/dist/db/spec-queries.js +21 -0
- package/lib/dist/db/spec-queries.js.map +1 -1
- package/lib/dist/enforce/changed-files.d.ts +7 -0
- package/lib/dist/enforce/changed-files.d.ts.map +1 -1
- package/lib/dist/enforce/changed-files.js +17 -0
- package/lib/dist/enforce/changed-files.js.map +1 -1
- package/lib/dist/enforce/enforce.d.ts +35 -1
- package/lib/dist/enforce/enforce.d.ts.map +1 -1
- package/lib/dist/enforce/enforce.js +66 -2
- package/lib/dist/enforce/enforce.js.map +1 -1
- package/lib/dist/extraction/index.d.ts +50 -4
- package/lib/dist/extraction/index.d.ts.map +1 -1
- package/lib/dist/extraction/index.js +105 -13
- package/lib/dist/extraction/index.js.map +1 -1
- package/lib/dist/extraction/parse-warnings.d.ts +16 -0
- package/lib/dist/extraction/parse-warnings.d.ts.map +1 -0
- package/lib/dist/extraction/parse-warnings.js +30 -0
- package/lib/dist/extraction/parse-warnings.js.map +1 -0
- package/lib/dist/extraction/tree-sitter.d.ts +27 -0
- package/lib/dist/extraction/tree-sitter.d.ts.map +1 -1
- package/lib/dist/extraction/tree-sitter.js +168 -1
- package/lib/dist/extraction/tree-sitter.js.map +1 -1
- package/lib/dist/health/smoke-check.js +1 -1
- package/lib/dist/health/smoke-check.js.map +1 -1
- package/lib/dist/hooks/hooks.json +16 -1
- package/lib/dist/index.d.ts +63 -3
- package/lib/dist/index.d.ts.map +1 -1
- package/lib/dist/index.js +235 -9
- package/lib/dist/index.js.map +1 -1
- package/lib/dist/installer/agent-record.d.ts +80 -0
- package/lib/dist/installer/agent-record.d.ts.map +1 -0
- package/lib/dist/installer/agent-record.js +351 -0
- package/lib/dist/installer/agent-record.js.map +1 -0
- package/lib/dist/installer/auto-migrate.d.ts +56 -0
- package/lib/dist/installer/auto-migrate.d.ts.map +1 -0
- package/lib/dist/installer/auto-migrate.js +328 -0
- package/lib/dist/installer/auto-migrate.js.map +1 -0
- package/lib/dist/installer/index.d.ts +37 -8
- package/lib/dist/installer/index.d.ts.map +1 -1
- package/lib/dist/installer/index.js +156 -46
- package/lib/dist/installer/index.js.map +1 -1
- package/lib/dist/installer/install-defaults.d.ts +4 -3
- package/lib/dist/installer/install-defaults.d.ts.map +1 -1
- package/lib/dist/installer/install-defaults.js +6 -5
- package/lib/dist/installer/install-defaults.js.map +1 -1
- package/lib/dist/installer/{config-writer.d.ts → targets/claude-config-writer.d.ts} +1 -1
- package/lib/dist/installer/targets/claude-config-writer.d.ts.map +1 -0
- package/lib/dist/installer/{config-writer.js → targets/claude-config-writer.js} +7 -6
- package/lib/dist/installer/targets/claude-config-writer.js.map +1 -0
- package/lib/dist/installer/targets/claude-projects.d.ts +9 -0
- package/lib/dist/installer/targets/claude-projects.d.ts.map +1 -0
- package/lib/dist/installer/targets/claude-projects.js +151 -0
- package/lib/dist/installer/targets/claude-projects.js.map +1 -0
- package/lib/dist/installer/targets/claude.d.ts +28 -1
- package/lib/dist/installer/targets/claude.d.ts.map +1 -1
- package/lib/dist/installer/targets/claude.js +39 -20
- package/lib/dist/installer/targets/claude.js.map +1 -1
- package/lib/dist/installer/targets/feature-parity.d.ts +28 -0
- package/lib/dist/installer/targets/feature-parity.d.ts.map +1 -0
- package/lib/dist/installer/targets/feature-parity.js +32 -0
- package/lib/dist/installer/targets/feature-parity.js.map +1 -0
- package/lib/dist/installer/{gemini-commands.d.ts → targets/gemini-commands.d.ts} +7 -3
- package/lib/dist/installer/targets/gemini-commands.d.ts.map +1 -0
- package/lib/dist/installer/{gemini-commands.js → targets/gemini-commands.js} +35 -14
- package/lib/dist/installer/targets/gemini-commands.js.map +1 -0
- package/lib/dist/installer/targets/gemini-projects.d.ts +11 -0
- package/lib/dist/installer/targets/gemini-projects.d.ts.map +1 -0
- package/lib/dist/installer/targets/gemini-projects.js +84 -0
- package/lib/dist/installer/targets/gemini-projects.js.map +1 -0
- package/lib/dist/installer/targets/gemini.d.ts +94 -8
- package/lib/dist/installer/targets/gemini.d.ts.map +1 -1
- package/lib/dist/installer/targets/gemini.js +392 -33
- package/lib/dist/installer/targets/gemini.js.map +1 -1
- package/lib/dist/installer/targets/registry.d.ts +8 -1
- package/lib/dist/installer/targets/registry.d.ts.map +1 -1
- package/lib/dist/installer/targets/registry.js +14 -5
- package/lib/dist/installer/targets/registry.js.map +1 -1
- package/lib/dist/installer/targets/shared.d.ts +44 -1
- package/lib/dist/installer/targets/shared.d.ts.map +1 -1
- package/lib/dist/installer/targets/shared.js +130 -3
- package/lib/dist/installer/targets/shared.js.map +1 -1
- package/lib/dist/installer/targets/types.d.ts +23 -1
- package/lib/dist/installer/targets/types.d.ts.map +1 -1
- package/lib/dist/jira/auto-publish.d.ts +22 -2
- package/lib/dist/jira/auto-publish.d.ts.map +1 -1
- package/lib/dist/jira/auto-publish.js +54 -9
- package/lib/dist/jira/auto-publish.js.map +1 -1
- package/lib/dist/jira/board-first.d.ts +15 -2
- package/lib/dist/jira/board-first.d.ts.map +1 -1
- package/lib/dist/jira/board-first.js +16 -0
- package/lib/dist/jira/board-first.js.map +1 -1
- package/lib/dist/jira/ci-publish.d.ts +72 -0
- package/lib/dist/jira/ci-publish.d.ts.map +1 -0
- package/lib/dist/jira/ci-publish.js +162 -0
- package/lib/dist/jira/ci-publish.js.map +1 -0
- package/lib/dist/jira/publish.d.ts +23 -0
- package/lib/dist/jira/publish.d.ts.map +1 -1
- package/lib/dist/jira/publish.js +0 -0
- package/lib/dist/jira/publish.js.map +1 -1
- package/lib/dist/jira/regression-pack.d.ts.map +1 -1
- package/lib/dist/jira/regression-pack.js +7 -1
- package/lib/dist/jira/regression-pack.js.map +1 -1
- package/lib/dist/jira/repo-config.d.ts.map +1 -1
- package/lib/dist/jira/repo-config.js +8 -0
- package/lib/dist/jira/repo-config.js.map +1 -1
- package/lib/dist/jira/types.d.ts +8 -0
- package/lib/dist/jira/types.d.ts.map +1 -1
- package/lib/dist/jira/types.js.map +1 -1
- package/lib/dist/knowledge/enforce.d.ts.map +1 -1
- package/lib/dist/knowledge/enforce.js +6 -1
- package/lib/dist/knowledge/enforce.js.map +1 -1
- package/lib/dist/mcp/jira-tools.d.ts +5 -0
- package/lib/dist/mcp/jira-tools.d.ts.map +1 -1
- package/lib/dist/mcp/jira-tools.js +59 -4
- package/lib/dist/mcp/jira-tools.js.map +1 -1
- package/lib/dist/mcp/proxy.d.ts.map +1 -1
- package/lib/dist/mcp/proxy.js +2 -1
- package/lib/dist/mcp/proxy.js.map +1 -1
- package/lib/dist/mcp/server-instructions.d.ts +16 -10
- package/lib/dist/mcp/server-instructions.d.ts.map +1 -1
- package/lib/dist/mcp/server-instructions.js +69 -20
- package/lib/dist/mcp/server-instructions.js.map +1 -1
- package/lib/dist/mcp/session.d.ts +11 -0
- package/lib/dist/mcp/session.d.ts.map +1 -1
- package/lib/dist/mcp/session.js +43 -1
- package/lib/dist/mcp/session.js.map +1 -1
- package/lib/dist/mcp/tools.d.ts.map +1 -1
- package/lib/dist/mcp/tools.js +8 -0
- package/lib/dist/mcp/tools.js.map +1 -1
- package/lib/dist/reflect/apply.d.ts +2 -2
- package/lib/dist/reflect/apply.d.ts.map +1 -1
- package/lib/dist/reflect/apply.js +6 -5
- package/lib/dist/reflect/apply.js.map +1 -1
- package/lib/dist/reflect/miner.d.ts.map +1 -1
- package/lib/dist/reflect/miner.js +28 -15
- package/lib/dist/reflect/miner.js.map +1 -1
- package/lib/dist/reflect/session-outcomes.d.ts.map +1 -1
- package/lib/dist/reflect/session-outcomes.js +8 -3
- package/lib/dist/reflect/session-outcomes.js.map +1 -1
- package/lib/dist/reflect/targets.d.ts.map +1 -1
- package/lib/dist/reflect/targets.js +25 -5
- package/lib/dist/reflect/targets.js.map +1 -1
- package/lib/dist/reflect/types.d.ts +7 -0
- package/lib/dist/reflect/types.d.ts.map +1 -1
- package/lib/dist/resolution/spec-link-resolver.d.ts +14 -2
- package/lib/dist/resolution/spec-link-resolver.d.ts.map +1 -1
- package/lib/dist/resolution/spec-link-resolver.js +13 -3
- package/lib/dist/resolution/spec-link-resolver.js.map +1 -1
- package/lib/dist/server/canonical-tools.js +50 -0
- package/lib/dist/server/cli.js +2 -2
- package/lib/dist/server/host-profiles.js +39 -0
- package/lib/dist/server/ingest/index.js +2 -1
- package/lib/dist/server/ingest/ingestor.js +195 -76
- package/lib/dist/server/ingest/project-paths.js +8 -2
- package/lib/dist/server/ingest/recorded-projects.js +43 -0
- package/lib/dist/server/ingest/specship-classify.js +7 -2
- package/lib/dist/server/ingest/transcripts/claude-jsonl.js +54 -0
- package/lib/dist/server/ingest/transcripts/claude.js +64 -0
- package/lib/dist/server/ingest/transcripts/gemini.js +469 -0
- package/lib/dist/server/ingest/transcripts/index.js +14 -0
- package/lib/dist/server/ingest/{parser.js → transcripts/shared.js} +3 -49
- package/lib/dist/server/ingest/transcripts/types.js +4 -0
- package/lib/dist/server/ingest/watcher.js +31 -30
- package/lib/dist/server/routes/claude.js +27 -10
- package/lib/dist/server/routes/graph.js +64 -3
- package/lib/dist/server/routes/mcp.js +57 -15
- package/lib/dist/server/routes/memory.js +54 -35
- package/lib/dist/server/routes/projects.js +78 -7
- package/lib/dist/server/routes/spec.js +125 -0
- package/lib/dist/server/server.js +8 -2
- package/lib/dist/skills/spec-author/SKILL.md +1 -1
- package/lib/dist/skills/spec-author/references/gap-questions.md +1 -1
- package/lib/dist/spec-lint/lint.d.ts +26 -1
- package/lib/dist/spec-lint/lint.d.ts.map +1 -1
- package/lib/dist/spec-lint/lint.js +96 -7
- package/lib/dist/spec-lint/lint.js.map +1 -1
- package/lib/dist/sync/worktree.d.ts +48 -0
- package/lib/dist/sync/worktree.d.ts.map +1 -1
- package/lib/dist/sync/worktree.js +109 -0
- package/lib/dist/sync/worktree.js.map +1 -1
- package/lib/dist/ui/assets/bricolage-latin-C23fTOVi.woff2 +0 -0
- package/lib/dist/ui/assets/bricolage-latin-ext-B82pxkPf.woff2 +0 -0
- package/lib/dist/ui/assets/index-54vorjyq.css +1 -0
- package/lib/dist/ui/assets/index-BZQonCVb.js +52 -0
- package/lib/dist/ui/assets/space-grotesk-latin-DPT1xrvW.woff2 +0 -0
- package/lib/dist/ui/assets/space-grotesk-latin-ext-wJGaI8U0.woff2 +0 -0
- package/lib/dist/ui/index.html +2 -2
- package/lib/dist/update/updater.d.ts +6 -0
- package/lib/dist/update/updater.d.ts.map +1 -1
- package/lib/dist/update/updater.js +13 -1
- package/lib/dist/update/updater.js.map +1 -1
- package/lib/dist/workflows/agents/claude.d.ts +9 -0
- package/lib/dist/workflows/agents/claude.d.ts.map +1 -1
- package/lib/dist/workflows/agents/claude.js +22 -3
- package/lib/dist/workflows/agents/claude.js.map +1 -1
- package/lib/dist/workflows/agents/gemini-version.d.ts +18 -0
- package/lib/dist/workflows/agents/gemini-version.d.ts.map +1 -0
- package/lib/dist/workflows/agents/gemini-version.js +81 -0
- package/lib/dist/workflows/agents/gemini-version.js.map +1 -0
- package/lib/dist/workflows/agents/gemini.d.ts +1 -1
- package/lib/dist/workflows/agents/gemini.d.ts.map +1 -1
- package/lib/dist/workflows/agents/gemini.js +12 -2
- package/lib/dist/workflows/agents/gemini.js.map +1 -1
- package/lib/dist/workflows/agents/index.d.ts +2 -0
- package/lib/dist/workflows/agents/index.d.ts.map +1 -1
- package/lib/dist/workflows/agents/index.js +5 -0
- package/lib/dist/workflows/agents/index.js.map +1 -1
- package/lib/dist/workflows/agents/types.d.ts +13 -0
- package/lib/dist/workflows/agents/types.d.ts.map +1 -1
- package/lib/dist/workflows/agents/types.js.map +1 -1
- package/lib/dist/workflows/condition-evaluator.d.ts +8 -0
- package/lib/dist/workflows/condition-evaluator.d.ts.map +1 -1
- package/lib/dist/workflows/condition-evaluator.js +7 -0
- package/lib/dist/workflows/condition-evaluator.js.map +1 -1
- package/lib/dist/workflows/defaults/claude-design-implement.yaml +11 -11
- package/lib/dist/workflows/defaults/spec-author.yaml +9 -9
- package/lib/dist/workflows/defaults/spec-fix.yaml +8 -8
- package/lib/dist/workflows/defaults/spec-implement.yaml +38 -23
- package/lib/dist/workflows/defaults/spec-relink.yaml +5 -5
- package/lib/dist/workflows/defaults/spec-verify.yaml +3 -3
- package/lib/dist/workflows/executor.d.ts +7 -0
- package/lib/dist/workflows/executor.d.ts.map +1 -1
- package/lib/dist/workflows/executor.js +22 -5
- package/lib/dist/workflows/executor.js.map +1 -1
- package/lib/node_modules/.package-lock.json +1 -1
- package/lib/package.json +3 -2
- package/package.json +1 -1
- package/lib/dist/installer/config-writer.d.ts.map +0 -1
- package/lib/dist/installer/config-writer.js.map +0 -1
- package/lib/dist/installer/gemini-commands.d.ts.map +0 -1
- package/lib/dist/installer/gemini-commands.js.map +0 -1
- package/lib/dist/ui/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
- package/lib/dist/ui/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
- package/lib/dist/ui/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
- package/lib/dist/ui/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
- package/lib/dist/ui/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
- package/lib/dist/ui/assets/index-BzuUWUAE.js +0 -50
- package/lib/dist/ui/assets/index-ezwX2EfG.css +0 -1
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
* slash commands rendered into Gemini TOML from the same
|
|
9
9
|
* `commands/specship/*.md` sources the Claude target copies (REQ-GEMINI-005,
|
|
10
10
|
* see `../gemini-commands.ts` for the verified format + per-command
|
|
11
|
-
* decisions)
|
|
12
|
-
*
|
|
11
|
+
* decisions), plus the skills and the explorer subagent when the installed
|
|
12
|
+
* Gemini CLI supports them (REQ-AGENTSWITCH-005). What still has no Gemini
|
|
13
|
+
* equivalent (the status line, the permissions list) is left unwritten and
|
|
13
14
|
* named in `notes` rather than half-installed (REQ-GEMINI-006).
|
|
14
15
|
*
|
|
15
16
|
* The target IS registered in `targets/registry.ts`, but a plain
|
|
@@ -65,13 +66,15 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
65
66
|
};
|
|
66
67
|
})();
|
|
67
68
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
68
|
-
exports.geminiTarget = exports.GeminiCliTarget = void 0;
|
|
69
|
+
exports.geminiTarget = exports.GeminiCliTarget = exports.GEMINI_AGENT_MARKER = exports.GEMINI_SKILL_MARKER_FILE = exports.GEMINI_AGENTS_ON_BY_DEFAULT = exports.GEMINI_MIN_AGENTS = exports.GEMINI_MIN_SKILLS = exports.probeGeminiVersion = exports.geminiVersionSupports = exports.geminiMcpToolName = exports.GEMINI_FOLDER_TRUST_NOTE = void 0;
|
|
69
70
|
exports.settingsPath = settingsPath;
|
|
70
71
|
exports.writeGeminiInstructionsEntry = writeGeminiInstructionsEntry;
|
|
71
72
|
exports.removeGeminiInstructionsEntry = removeGeminiInstructionsEntry;
|
|
72
73
|
exports.writeGeminiSddEntry = writeGeminiSddEntry;
|
|
73
74
|
exports.removeGeminiSddEntry = removeGeminiSddEntry;
|
|
74
75
|
exports.writeGeminiMcpEntry = writeGeminiMcpEntry;
|
|
76
|
+
exports.writeGeminiContextFilesEntry = writeGeminiContextFilesEntry;
|
|
77
|
+
exports.removeGeminiContextFilesEntry = removeGeminiContextFilesEntry;
|
|
75
78
|
exports.geminiCommandsDir = geminiCommandsDir;
|
|
76
79
|
exports.writeGeminiCommandsEntries = writeGeminiCommandsEntries;
|
|
77
80
|
exports.removeGeminiCommandsEntries = removeGeminiCommandsEntries;
|
|
@@ -82,13 +85,28 @@ exports.removeGeminiSddHooksEntry = removeGeminiSddHooksEntry;
|
|
|
82
85
|
exports.renderGeminiPolicyToml = renderGeminiPolicyToml;
|
|
83
86
|
exports.writeGeminiPolicyEntry = writeGeminiPolicyEntry;
|
|
84
87
|
exports.removeGeminiPolicyEntry = removeGeminiPolicyEntry;
|
|
88
|
+
exports.geminiSkillsDir = geminiSkillsDir;
|
|
89
|
+
exports.geminiAgentsDir = geminiAgentsDir;
|
|
90
|
+
exports.writeGeminiSkillsEntries = writeGeminiSkillsEntries;
|
|
91
|
+
exports.removeGeminiSkillsEntries = removeGeminiSkillsEntries;
|
|
92
|
+
exports.renderGeminiExplorerAgent = renderGeminiExplorerAgent;
|
|
93
|
+
exports.writeGeminiAgentsEntry = writeGeminiAgentsEntry;
|
|
94
|
+
exports.removeGeminiAgentsEntry = removeGeminiAgentsEntry;
|
|
95
|
+
const gemini_projects_1 = require("./gemini-projects");
|
|
85
96
|
const fs = __importStar(require("fs"));
|
|
97
|
+
const gemini_version_1 = require("../../workflows/agents/gemini-version");
|
|
98
|
+
Object.defineProperty(exports, "geminiMcpToolName", { enumerable: true, get: function () { return gemini_version_1.geminiMcpToolName; } });
|
|
99
|
+
Object.defineProperty(exports, "geminiVersionSupports", { enumerable: true, get: function () { return gemini_version_1.geminiVersionSupports; } });
|
|
100
|
+
Object.defineProperty(exports, "probeGeminiVersion", { enumerable: true, get: function () { return gemini_version_1.probeGeminiVersion; } });
|
|
101
|
+
const hook_output_1 = require("../../activation/hook-output");
|
|
86
102
|
const os = __importStar(require("os"));
|
|
87
103
|
const path = __importStar(require("path"));
|
|
88
104
|
const shared_1 = require("./shared");
|
|
89
105
|
const instructions_template_1 = require("../instructions-template");
|
|
90
106
|
const claude_1 = require("./claude");
|
|
91
|
-
const
|
|
107
|
+
const agent_record_1 = require("../agent-record");
|
|
108
|
+
const feature_parity_1 = require("./feature-parity");
|
|
109
|
+
const gemini_commands_1 = require("./gemini-commands");
|
|
92
110
|
/** Gemini CLI's settings file for this location. */
|
|
93
111
|
function settingsPath(loc) {
|
|
94
112
|
return loc === 'global'
|
|
@@ -187,20 +205,126 @@ function writeGeminiMcpEntry(loc, integrations = []) {
|
|
|
187
205
|
(0, shared_1.writeJsonFile)(file, existing);
|
|
188
206
|
return { path: file, action };
|
|
189
207
|
}
|
|
208
|
+
/**
|
|
209
|
+
* Context file names as Gemini CLI reads them (REQ-AGENTSWITCH-009).
|
|
210
|
+
* Verified against gemini-cli 0.28.0 `settingsSchema` / `getCurrentGeminiMdFilename`:
|
|
211
|
+
* `context.fileName` is a string or a string array; when unset Gemini loads
|
|
212
|
+
* only its default (`GEMINI.md`). The legacy top-level `contextFileName` is
|
|
213
|
+
* the pre-v2 spelling of the same setting — read as the user's list, never
|
|
214
|
+
* written or removed by us.
|
|
215
|
+
*/
|
|
216
|
+
function contextFileNames(value) {
|
|
217
|
+
if (typeof value === 'string' && value.length > 0)
|
|
218
|
+
return [value];
|
|
219
|
+
if (Array.isArray(value)) {
|
|
220
|
+
const names = value.filter((v) => typeof v === 'string' && v.length > 0);
|
|
221
|
+
return names.length > 0 ? names : null;
|
|
222
|
+
}
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
/** The scope's project root as the record keys it; absent for global. */
|
|
226
|
+
function recordRoot(loc) {
|
|
227
|
+
return loc === 'local' ? process.cwd() : undefined;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Point Gemini CLI at the user's own CLAUDE.md instead of copying it
|
|
231
|
+
* (REQ-AGENTSWITCH-009): add its file name to `context.fileName`, keeping
|
|
232
|
+
* every name the user already listed in their order and keeping Gemini's
|
|
233
|
+
* default (`GEMINI.md`) when the setting was unset. The CLAUDE.md file itself
|
|
234
|
+
* is never opened for writing.
|
|
235
|
+
*
|
|
236
|
+
* Local scope writes nothing — and records nothing — when the project has
|
|
237
|
+
* no CLAUDE.md. Global scope always adds the name: Gemini CLI resolves a
|
|
238
|
+
* bare context file name in `~/.gemini/` and in each project's directories
|
|
239
|
+
* (gemini-cli-core `memoryDiscovery`, verified against 0.28.0), so the
|
|
240
|
+
* user-wide setting is what makes Gemini read every project's own CLAUDE.md.
|
|
241
|
+
* It never reaches `~/.claude/CLAUDE.md` — the capability note says so.
|
|
242
|
+
* Idempotent: a re-run that finds the name already listed reports
|
|
243
|
+
* `unchanged`.
|
|
244
|
+
*
|
|
245
|
+
* Returns the file result (null when there was nothing to point at) and the
|
|
246
|
+
* names this call put into `context.fileName` that were not in it before.
|
|
247
|
+
*/
|
|
248
|
+
function writeGeminiContextFilesEntry(loc) {
|
|
249
|
+
const claudeMd = (0, claude_1.claudeMdPath)(loc);
|
|
250
|
+
if (loc === 'local' && !fs.existsSync(claudeMd))
|
|
251
|
+
return { file: null, added: [] };
|
|
252
|
+
const wanted = path.basename(claudeMd);
|
|
253
|
+
const geminiDefault = path.basename(geminiMdPath(loc));
|
|
254
|
+
const file = settingsPath(loc);
|
|
255
|
+
const existed = fs.existsSync(file);
|
|
256
|
+
const settings = (0, shared_1.readJsonFile)(file);
|
|
257
|
+
const context = settings.context && typeof settings.context === 'object' && !Array.isArray(settings.context)
|
|
258
|
+
? settings.context
|
|
259
|
+
: undefined;
|
|
260
|
+
const explicit = contextFileNames(context?.fileName);
|
|
261
|
+
const base = explicit ?? contextFileNames(settings.contextFileName) ?? [geminiDefault];
|
|
262
|
+
const next = base.includes(wanted) ? [...base] : [...base, wanted];
|
|
263
|
+
if (explicit && (0, shared_1.jsonDeepEqual)(explicit, next)) {
|
|
264
|
+
return { file: { path: file, action: 'unchanged' }, added: [] };
|
|
265
|
+
}
|
|
266
|
+
const nextContext = context ? { ...context } : {};
|
|
267
|
+
nextContext.fileName = next;
|
|
268
|
+
settings.context = nextContext;
|
|
269
|
+
(0, shared_1.writeJsonFile)(file, settings);
|
|
270
|
+
const added = next.filter((n) => !(explicit ?? []).includes(n));
|
|
271
|
+
return { file: { path: file, action: existed ? 'updated' : 'created' }, added };
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Inverse of `writeGeminiContextFilesEntry` (REQ-AGENTSWITCH-009.A4): drop
|
|
275
|
+
* only the names SpecShip recorded as added; every name the user listed
|
|
276
|
+
* stays. With no recorded list (record lost or rebuilt) the setting is left
|
|
277
|
+
* untouched — conservative by design. Removing the last name deletes
|
|
278
|
+
* `context.fileName`, and `context` when that leaves it empty.
|
|
279
|
+
*/
|
|
280
|
+
function removeGeminiContextFilesEntry(loc, added) {
|
|
281
|
+
if (!added || added.length === 0)
|
|
282
|
+
return null;
|
|
283
|
+
const file = settingsPath(loc);
|
|
284
|
+
if (!fs.existsSync(file))
|
|
285
|
+
return null;
|
|
286
|
+
const settings = (0, shared_1.readJsonFile)(file);
|
|
287
|
+
const context = settings.context;
|
|
288
|
+
if (!context || typeof context !== 'object' || Array.isArray(context))
|
|
289
|
+
return null;
|
|
290
|
+
const current = contextFileNames(context.fileName);
|
|
291
|
+
if (!current)
|
|
292
|
+
return null;
|
|
293
|
+
const remaining = current.filter((n) => !added.includes(n));
|
|
294
|
+
if (remaining.length === current.length)
|
|
295
|
+
return null;
|
|
296
|
+
if (remaining.length === 0) {
|
|
297
|
+
delete context.fileName;
|
|
298
|
+
if (Object.keys(context).length === 0)
|
|
299
|
+
delete settings.context;
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
context.fileName = remaining;
|
|
303
|
+
}
|
|
304
|
+
(0, shared_1.writeJsonFile)(file, settings);
|
|
305
|
+
return { path: file, action: 'removed' };
|
|
306
|
+
}
|
|
190
307
|
/**
|
|
191
308
|
* What a Claude install provides that this one does not (REQ-GEMINI-006.A1).
|
|
192
309
|
* One note, naming every still-unsupported surface. As of REQ-GEMINI-010 the
|
|
193
310
|
* prompt / session / tool hooks DO have Gemini equivalents and are installed,
|
|
194
311
|
* so the note no longer lists them; nor slash commands, which REQ-GEMINI-005
|
|
195
|
-
* renders as Gemini TOML
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
312
|
+
* renders as Gemini TOML; nor skills and the explorer subagent, which
|
|
313
|
+
* REQ-AGENTSWITCH-005 installs when the Gemini version supports them (with its
|
|
314
|
+
* own skip / experimental notes). Auto-allow is the policy file
|
|
315
|
+
* (REQ-GEMINI-009). Surfaces Gemini has no equivalent for (the status line)
|
|
316
|
+
* each get their own line from `noSurfaceNotes()` (REQ-AGENTSWITCH-010.A4).
|
|
317
|
+
* A command the renderer had to exclude gets its own note from
|
|
318
|
+
* `writeGeminiCommandsEntries`.
|
|
199
319
|
*/
|
|
200
320
|
const CAPABILITY_NOTE = 'Gemini CLI install wires the MCP server, writes a GEMINI.md steering ' +
|
|
201
|
-
'block, installs prompt/session/tool hooks,
|
|
202
|
-
'slash commands as Gemini TOML
|
|
203
|
-
'
|
|
321
|
+
'block, installs prompt/session/tool hooks, renders the /specship:* ' +
|
|
322
|
+
'slash commands as Gemini TOML, and adds CLAUDE.md to Gemini\'s context ' +
|
|
323
|
+
'files so Gemini reads each project\'s CLAUDE.md in place (a project install ' +
|
|
324
|
+
'does this only when the project has one). Gemini never reads ' +
|
|
325
|
+
'~/.claude/CLAUDE.md — copy anything you need from it into ~/.gemini/GEMINI.md. ' +
|
|
326
|
+
'Skills and the specship-explorer subagent are installed when the Gemini ' +
|
|
327
|
+
'CLI version supports them (see the notes below). The tool playbook ' +
|
|
204
328
|
'itself still lives in the MCP server instructions.';
|
|
205
329
|
/**
|
|
206
330
|
* Gemini CLI's custom-commands dir for this location (verified against
|
|
@@ -239,17 +363,17 @@ function isOurCommandFile(file) {
|
|
|
239
363
|
* `commandsSourceDir` defaults to the packaged `commands/` dir — the same
|
|
240
364
|
* one the Claude target copies from.
|
|
241
365
|
*/
|
|
242
|
-
function writeGeminiCommandsEntries(loc, includeGovernance = true, commandsSourceDir = (0, shared_1.packageAssetPath)('commands')) {
|
|
366
|
+
function writeGeminiCommandsEntries(loc, includeGovernance = true, commandsSourceDir = (0, shared_1.packageAssetPath)('commands'), skills = []) {
|
|
243
367
|
const names = includeGovernance ? ALL_COMMAND_SOURCES : [...claude_1.RETRIEVAL_TIER_COMMANDS];
|
|
244
368
|
const dir = geminiCommandsDir(loc);
|
|
245
369
|
const files = [];
|
|
246
370
|
const notes = [];
|
|
247
|
-
for (const cmd of (0, gemini_commands_1.geminiCommandSet)(commandsSourceDir, names)) {
|
|
371
|
+
for (const cmd of (0, gemini_commands_1.geminiCommandSet)(commandsSourceDir, names, { skills })) {
|
|
248
372
|
const dest = path.join(dir, cmd.target);
|
|
249
373
|
if (cmd.toml === undefined) {
|
|
250
374
|
notes.push(`Gemini CLI: skipped /${cmd.target.replace(/\.toml$/, '').replace(/\//g, ':')} — ${cmd.excluded}.`);
|
|
251
375
|
if (isOurCommandFile(dest)) {
|
|
252
|
-
|
|
376
|
+
(0, shared_1.removeFileSync)(dest);
|
|
253
377
|
files.push({ path: dest, action: 'removed' });
|
|
254
378
|
}
|
|
255
379
|
continue;
|
|
@@ -266,8 +390,7 @@ function writeGeminiCommandsEntries(loc, includeGovernance = true, commandsSourc
|
|
|
266
390
|
continue;
|
|
267
391
|
}
|
|
268
392
|
}
|
|
269
|
-
|
|
270
|
-
fs.writeFileSync(dest, cmd.toml);
|
|
393
|
+
(0, shared_1.atomicWriteFileSync)(dest, cmd.toml);
|
|
271
394
|
files.push({ path: dest, action: existed ? 'updated' : 'created' });
|
|
272
395
|
}
|
|
273
396
|
return { files, notes };
|
|
@@ -314,13 +437,13 @@ const TRUST_POLICY_NOTE = 'Wrote ~/.gemini/policies/specship.toml allowing SpecS
|
|
|
314
437
|
'tools. An administrator policy still overrides this file, and ' +
|
|
315
438
|
'run_shell_command confirmations are unaffected.';
|
|
316
439
|
/**
|
|
317
|
-
* Gemini
|
|
318
|
-
*
|
|
319
|
-
*
|
|
440
|
+
* Gemini CLI loads a project's GEMINI.md, hooks, skills, subagents and MCP
|
|
441
|
+
* servers only in a folder the user has trusted — on by default from 0.33.0.
|
|
442
|
+
* Every Gemini install and switch names that gate in one line, and SpecShip
|
|
443
|
+
* never writes Gemini's trustedFolders.json itself (REQ-AGENTSWITCH-018.A4).
|
|
320
444
|
*/
|
|
321
|
-
|
|
322
|
-
'
|
|
323
|
-
'Gemini CLI and accept the trust prompt.';
|
|
445
|
+
exports.GEMINI_FOLDER_TRUST_NOTE = 'Gemini CLI: trust this folder (accept the prompt when you open it, or run /permissions) ' +
|
|
446
|
+
'before Gemini loads SpecShip\'s project files — GEMINI.md, hooks, skills and the MCP server.';
|
|
324
447
|
const GEMINI_HOOKS = [
|
|
325
448
|
// Prompt-level retrieval nudge — mirror of Claude's UserPromptSubmit.
|
|
326
449
|
{
|
|
@@ -550,8 +673,7 @@ function writeGeminiPolicyEntry() {
|
|
|
550
673
|
if (!current.startsWith(GEMINI_POLICY_HEADER_MARKER))
|
|
551
674
|
return { path: file, action: 'kept' };
|
|
552
675
|
}
|
|
553
|
-
|
|
554
|
-
fs.writeFileSync(file, content);
|
|
676
|
+
(0, shared_1.atomicWriteFileSync)(file, content);
|
|
555
677
|
return { path: file, action: existed ? 'updated' : 'created' };
|
|
556
678
|
}
|
|
557
679
|
/**
|
|
@@ -571,9 +693,197 @@ function removeGeminiPolicyEntry() {
|
|
|
571
693
|
fs.unlinkSync(file);
|
|
572
694
|
return { path: file, action: 'removed' };
|
|
573
695
|
}
|
|
696
|
+
/** The Gemini CLI version these surfaces were verified against. */
|
|
697
|
+
exports.GEMINI_MIN_SKILLS = '0.28.0';
|
|
698
|
+
exports.GEMINI_MIN_AGENTS = '0.28.0';
|
|
699
|
+
/** First Gemini CLI release with subagents on by default (`experimental.enableAgents`, REQ-AGENTSWITCH-018.A6). */
|
|
700
|
+
exports.GEMINI_AGENTS_ON_BY_DEFAULT = '0.36.0';
|
|
701
|
+
/** Gemini CLI's skills dir for this location (same scope as `settingsPath`). */
|
|
702
|
+
function geminiSkillsDir(loc) {
|
|
703
|
+
return path.join(path.dirname(settingsPath(loc)), 'skills');
|
|
704
|
+
}
|
|
705
|
+
/** Gemini CLI's subagents dir for this location. */
|
|
706
|
+
function geminiAgentsDir(loc) {
|
|
707
|
+
return path.join(path.dirname(settingsPath(loc)), 'agents');
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* Rewrite Claude Code's `mcp__specship__<tool>` names in prose to the bare
|
|
711
|
+
* name the Gemini model is shown (DOM-SPECSHIP-007), so the skill and agent
|
|
712
|
+
* bodies name tools the way Gemini does.
|
|
713
|
+
*/
|
|
714
|
+
/**
|
|
715
|
+
* Render one shipped skill file for Gemini (SKILL.md format is shared),
|
|
716
|
+
* through the same rewrites as the commands (REQ-AGENTSWITCH-014): Gemini's
|
|
717
|
+
* tool names, `gemini mcp add`, and Gemini's memory file.
|
|
718
|
+
*/
|
|
719
|
+
function renderGeminiSkillFile(name) {
|
|
720
|
+
return (0, gemini_commands_1.rewriteForGemini)(fs.readFileSync((0, shared_1.packageAssetPath)('skills', name), 'utf-8'), { skills: shippedSkillRoots() });
|
|
721
|
+
}
|
|
722
|
+
/** Write `content` to `dest` with the installer's idempotency contract. */
|
|
723
|
+
function writeIfChanged(dest, content) {
|
|
724
|
+
const existed = fs.existsSync(dest);
|
|
725
|
+
if (existed && fs.readFileSync(dest, 'utf-8') === content)
|
|
726
|
+
return { path: dest, action: 'unchanged' };
|
|
727
|
+
(0, shared_1.atomicWriteFileSync)(dest, content);
|
|
728
|
+
return { path: dest, action: existed ? 'updated' : 'created' };
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* Write SpecShip's skills (`spec-author`, `design-md`) under Gemini's skills
|
|
732
|
+
* dir (REQ-AGENTSWITCH-005.A1). Same file list as the Claude target, so the
|
|
733
|
+
* two installs cannot diverge. Sibling user skills are never touched.
|
|
734
|
+
*/
|
|
735
|
+
/** Marks a skill directory as written by SpecShip, so install and uninstall touch only their own. */
|
|
736
|
+
exports.GEMINI_SKILL_MARKER_FILE = '.specship-installed';
|
|
737
|
+
/** The top-level skill directories we ship (`spec-author`, `design-md`). */
|
|
738
|
+
function shippedSkillRoots() {
|
|
739
|
+
return [...new Set(claude_1.SHIPPED_SKILLS.map((f) => f.split('/')[0]))];
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* A skill directory is SpecShip's to write when it does not exist yet, already
|
|
743
|
+
* carries our marker, or holds no SKILL.md (an empty leftover). A directory a
|
|
744
|
+
* user authored — a SKILL.md and no marker — is theirs and is never touched.
|
|
745
|
+
*/
|
|
746
|
+
function ownsSkillDir(skillDir) {
|
|
747
|
+
if (!fs.existsSync(skillDir))
|
|
748
|
+
return true;
|
|
749
|
+
if (fs.existsSync(path.join(skillDir, exports.GEMINI_SKILL_MARKER_FILE)))
|
|
750
|
+
return true;
|
|
751
|
+
return !fs.existsSync(path.join(skillDir, 'SKILL.md'));
|
|
752
|
+
}
|
|
753
|
+
function writeGeminiSkillsEntries(loc) {
|
|
754
|
+
const dir = geminiSkillsDir(loc);
|
|
755
|
+
const files = [];
|
|
756
|
+
for (const root of shippedSkillRoots()) {
|
|
757
|
+
const skillDir = path.join(dir, root);
|
|
758
|
+
const ours = claude_1.SHIPPED_SKILLS.filter((f) => f.startsWith(root + '/'));
|
|
759
|
+
if (!ownsSkillDir(skillDir)) {
|
|
760
|
+
for (const f of ours)
|
|
761
|
+
files.push({ path: path.join(dir, f), action: 'kept' });
|
|
762
|
+
continue;
|
|
763
|
+
}
|
|
764
|
+
for (const f of ours)
|
|
765
|
+
files.push(writeIfChanged(path.join(dir, f), renderGeminiSkillFile(f)));
|
|
766
|
+
writeIfChanged(path.join(skillDir, exports.GEMINI_SKILL_MARKER_FILE), 'Written by specship install (REQ-AGENTSWITCH-005).\n');
|
|
767
|
+
}
|
|
768
|
+
return files;
|
|
769
|
+
}
|
|
770
|
+
/**
|
|
771
|
+
* Inverse of `writeGeminiSkillsEntries`: delete exactly the files we ship,
|
|
772
|
+
* then prune the per-skill directories left empty. A user skill — or a user
|
|
773
|
+
* file inside one of our skill dirs — survives.
|
|
774
|
+
*/
|
|
775
|
+
function removeGeminiSkillsEntries(loc) {
|
|
776
|
+
const dir = geminiSkillsDir(loc);
|
|
777
|
+
const files = [];
|
|
778
|
+
for (const root of shippedSkillRoots()) {
|
|
779
|
+
const skillDir = path.join(dir, root);
|
|
780
|
+
const marker = path.join(skillDir, exports.GEMINI_SKILL_MARKER_FILE);
|
|
781
|
+
const ours = claude_1.SHIPPED_SKILLS.filter((f) => f.startsWith(root + '/'));
|
|
782
|
+
// Only a directory SpecShip marked is removed; a user's own skill stays.
|
|
783
|
+
if (!fs.existsSync(marker)) {
|
|
784
|
+
for (const f of ours) {
|
|
785
|
+
const dest = path.join(dir, f);
|
|
786
|
+
files.push({ path: dest, action: fs.existsSync(dest) ? 'kept' : 'not-found' });
|
|
787
|
+
}
|
|
788
|
+
continue;
|
|
789
|
+
}
|
|
790
|
+
for (const f of ours) {
|
|
791
|
+
const dest = path.join(dir, f);
|
|
792
|
+
if (!fs.existsSync(dest)) {
|
|
793
|
+
files.push({ path: dest, action: 'not-found' });
|
|
794
|
+
continue;
|
|
795
|
+
}
|
|
796
|
+
(0, shared_1.removeFileSync)(dest);
|
|
797
|
+
files.push({ path: dest, action: 'removed' });
|
|
798
|
+
}
|
|
799
|
+
(0, shared_1.removeFileSync)(marker);
|
|
800
|
+
}
|
|
801
|
+
(0, shared_1.pruneEmptySkillDirs)(dir, claude_1.SHIPPED_SKILLS);
|
|
802
|
+
return files;
|
|
803
|
+
}
|
|
804
|
+
/** Body marker identifying an agent file a SpecShip install wrote. */
|
|
805
|
+
exports.GEMINI_AGENT_MARKER = '<!-- Written by specship install — REQ-AGENTSWITCH-005 -->';
|
|
806
|
+
const GEMINI_AGENT_FILE = 'specship-explorer.md';
|
|
807
|
+
/**
|
|
808
|
+
* Render the shipped `agents/specship-explorer.md` as a Gemini subagent
|
|
809
|
+
* (REQ-AGENTSWITCH-005.A2). Frontmatter is rebuilt with only keys Gemini's
|
|
810
|
+
* strict schema accepts — `model: inherit` is dropped (Gemini would read it as
|
|
811
|
+
* a model id) — and `tools` becomes a YAML array of the installed Gemini CLI's
|
|
812
|
+
* MCP tool names (REQ-AGENTSWITCH-018).
|
|
813
|
+
* The body names tools by their bare Gemini name. No `mcp__specship__` survives.
|
|
814
|
+
*/
|
|
815
|
+
function renderGeminiExplorerAgent(source) {
|
|
816
|
+
const raw = source ?? fs.readFileSync((0, shared_1.packageAssetPath)('agents', GEMINI_AGENT_FILE), 'utf-8');
|
|
817
|
+
const m = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/.exec(raw);
|
|
818
|
+
if (!m)
|
|
819
|
+
throw new Error('specship-explorer agent asset has no frontmatter');
|
|
820
|
+
const front = m[1];
|
|
821
|
+
const body = m[2];
|
|
822
|
+
const field = (key) => {
|
|
823
|
+
const line = front.split(/\r?\n/).find((l) => l.startsWith(`${key}:`));
|
|
824
|
+
return line?.slice(key.length + 1).trim();
|
|
825
|
+
};
|
|
826
|
+
const name = field('name') ?? 'specship-explorer';
|
|
827
|
+
const description = field('description') ?? '';
|
|
828
|
+
const tools = (field('tools') ?? '')
|
|
829
|
+
.split(',')
|
|
830
|
+
.map((t) => t.trim())
|
|
831
|
+
.filter(Boolean)
|
|
832
|
+
.map((t) => (0, gemini_version_1.geminiMcpToolName)('specship', t.replace(/^mcp__specship__/, ''), (0, gemini_version_1.probeGeminiVersion)()));
|
|
833
|
+
return [
|
|
834
|
+
'---',
|
|
835
|
+
`name: ${name}`,
|
|
836
|
+
// JSON string = valid YAML double-quoted scalar; the description carries
|
|
837
|
+
// quotes and dashes a plain scalar would mis-parse.
|
|
838
|
+
`description: ${JSON.stringify(description)}`,
|
|
839
|
+
'tools:',
|
|
840
|
+
...tools.map((t) => ` - ${t}`),
|
|
841
|
+
'---',
|
|
842
|
+
exports.GEMINI_AGENT_MARKER,
|
|
843
|
+
'',
|
|
844
|
+
(0, gemini_commands_1.rewriteForGemini)(body, { skills: shippedSkillRoots() }).replace(/^\s+/, ''),
|
|
845
|
+
].join('\n');
|
|
846
|
+
}
|
|
847
|
+
/** Write the explorer subagent under Gemini's agents dir. Never overwrites a user file. */
|
|
848
|
+
function writeGeminiAgentsEntry(loc) {
|
|
849
|
+
const dest = path.join(geminiAgentsDir(loc), GEMINI_AGENT_FILE);
|
|
850
|
+
const content = renderGeminiExplorerAgent();
|
|
851
|
+
if (fs.existsSync(dest)) {
|
|
852
|
+
const current = fs.readFileSync(dest, 'utf-8');
|
|
853
|
+
if (current === content)
|
|
854
|
+
return { path: dest, action: 'unchanged' };
|
|
855
|
+
if (!current.includes(exports.GEMINI_AGENT_MARKER))
|
|
856
|
+
return { path: dest, action: 'kept' };
|
|
857
|
+
(0, shared_1.atomicWriteFileSync)(dest, content);
|
|
858
|
+
return { path: dest, action: 'updated' };
|
|
859
|
+
}
|
|
860
|
+
(0, shared_1.atomicWriteFileSync)(dest, content);
|
|
861
|
+
return { path: dest, action: 'created' };
|
|
862
|
+
}
|
|
863
|
+
/** Inverse of `writeGeminiAgentsEntry`: removes the file only if it carries our marker. */
|
|
864
|
+
function removeGeminiAgentsEntry(loc) {
|
|
865
|
+
const dest = path.join(geminiAgentsDir(loc), GEMINI_AGENT_FILE);
|
|
866
|
+
if (!fs.existsSync(dest))
|
|
867
|
+
return { path: dest, action: 'not-found' };
|
|
868
|
+
if (!fs.readFileSync(dest, 'utf-8').includes(exports.GEMINI_AGENT_MARKER))
|
|
869
|
+
return { path: dest, action: 'kept' };
|
|
870
|
+
(0, shared_1.removeFileSync)(dest);
|
|
871
|
+
return { path: dest, action: 'removed' };
|
|
872
|
+
}
|
|
873
|
+
const AGENTS_EXPERIMENTAL_NOTE = 'Gemini CLI: installed the specship-explorer subagent, but Gemini subagents are ' +
|
|
874
|
+
'experimental and off by default — set experimental.enableAgents to true in ' +
|
|
875
|
+
'Gemini settings to use it (SpecShip does not change that setting).';
|
|
876
|
+
function foundLabel(version) {
|
|
877
|
+
return version ? `found ${version}` : 'Gemini CLI version not found';
|
|
878
|
+
}
|
|
574
879
|
class GeminiCliTarget {
|
|
575
880
|
id = 'gemini';
|
|
576
|
-
|
|
881
|
+
traits = hook_output_1.GEMINI_CLI_TRAITS;
|
|
882
|
+
displayName = hook_output_1.GEMINI_CLI_TRAITS.displayName;
|
|
883
|
+
recentProjects(home) {
|
|
884
|
+
return (0, gemini_projects_1.geminiRecentProjects)(home);
|
|
885
|
+
}
|
|
886
|
+
switchNote = exports.GEMINI_FOLDER_TRUST_NOTE;
|
|
577
887
|
docsUrl = 'https://google-gemini.github.io/gemini-cli/docs/cli/configuration.html';
|
|
578
888
|
supportsLocation(_loc) {
|
|
579
889
|
return true;
|
|
@@ -594,11 +904,13 @@ class GeminiCliTarget {
|
|
|
594
904
|
}
|
|
595
905
|
/**
|
|
596
906
|
* Writes the MCP entry, GEMINI.md steering, hooks, the rendered slash
|
|
597
|
-
* commands and (with
|
|
907
|
+
* commands and (with `trust`) the policy TOML. `autoAllow` and
|
|
598
908
|
* `installStatusLine` are accepted and IGNORED — Gemini CLI has no
|
|
599
|
-
* permissions list or status line to write them to
|
|
600
|
-
*
|
|
601
|
-
*
|
|
909
|
+
* permissions list or status line to write them to; auto-allow arrives as
|
|
910
|
+
* `trust` (from `--trust`, or a switch carrying a Claude install's
|
|
911
|
+
* auto-allow over, REQ-AGENTSWITCH-010.A3), and the status line is named
|
|
912
|
+
* as unavailable in the notes (REQ-GEMINI-006, REQ-AGENTSWITCH-010.A4).
|
|
913
|
+
* Nothing under `.claude/` is ever touched (REQ-GEMINI-006.A2).
|
|
602
914
|
*/
|
|
603
915
|
install(loc, opts) {
|
|
604
916
|
const integrations = [
|
|
@@ -606,6 +918,16 @@ class GeminiCliTarget {
|
|
|
606
918
|
...(opts.withDesigner ? ['designer'] : []),
|
|
607
919
|
];
|
|
608
920
|
const files = [writeGeminiMcpEntry(loc, integrations)];
|
|
921
|
+
// REQ-AGENTSWITCH-009: Gemini reads the user's CLAUDE.md in place (never
|
|
922
|
+
// copied). Remember the names we added so uninstall removes only those;
|
|
923
|
+
// names an earlier install added stay remembered across a re-run.
|
|
924
|
+
const contextFiles = writeGeminiContextFilesEntry(loc);
|
|
925
|
+
if (contextFiles.file)
|
|
926
|
+
files.push(contextFiles.file);
|
|
927
|
+
if (contextFiles.added.length > 0) {
|
|
928
|
+
const prior = (0, agent_record_1.readAddedContextFiles)(this.id, loc, recordRoot(loc)) ?? [];
|
|
929
|
+
(0, agent_record_1.setAddedContextFiles)(this.id, loc, recordRoot(loc), [...new Set([...prior, ...contextFiles.added])]);
|
|
930
|
+
}
|
|
609
931
|
// GEMINI.md retrieval preference — always written (REQ-GEMINI-004).
|
|
610
932
|
files.push(writeGeminiInstructionsEntry(loc));
|
|
611
933
|
// SDD rule — default-on, matching the Claude target's governance tier.
|
|
@@ -620,18 +942,40 @@ class GeminiCliTarget {
|
|
|
620
942
|
files.push(includeGovernance ? writeGeminiSddHooksEntry(loc) : removeGeminiSddHooksEntry(loc));
|
|
621
943
|
// Slash commands (REQ-GEMINI-005), rendered from the Claude sources.
|
|
622
944
|
// Same tiering as the Claude target: governance doors ride `--sdd`.
|
|
623
|
-
|
|
945
|
+
// The version gate comes first: commands keep references to the skills
|
|
946
|
+
// this install provides (REQ-AGENTSWITCH-014.A2).
|
|
947
|
+
const version = (0, gemini_version_1.probeGeminiVersion)();
|
|
948
|
+
const skillsSupported = (0, gemini_version_1.geminiVersionSupports)(version, exports.GEMINI_MIN_SKILLS);
|
|
949
|
+
const commands = writeGeminiCommandsEntries(loc, includeGovernance, undefined, skillsSupported ? shippedSkillRoots() : []);
|
|
624
950
|
files.push(...commands.files);
|
|
625
951
|
// REQ-GEMINI-009: strictly opt-in. Omitting `--trust` NEVER touches the
|
|
626
952
|
// policy file — a prior `--trust` install is preserved byte-identical
|
|
627
953
|
// (parity with REQ-INTEG-001.A3).
|
|
628
|
-
|
|
954
|
+
// REQ-AGENTSWITCH-010.A4: every feature Gemini has no surface for is
|
|
955
|
+
// named on its own line (from GEMINI_PARITY), never dropped silently.
|
|
956
|
+
const notes = [CAPABILITY_NOTE, ...(0, feature_parity_1.noSurfaceNotes)(), ...commands.notes];
|
|
957
|
+
// Skills + explorer subagent (REQ-AGENTSWITCH-005), gated on the installed
|
|
958
|
+
// Gemini CLI version. An unknown or older version is skipped WITH a note —
|
|
959
|
+
// never guessed, never fatal (the install still exits 0).
|
|
960
|
+
if (skillsSupported) {
|
|
961
|
+
files.push(...writeGeminiSkillsEntries(loc));
|
|
962
|
+
}
|
|
963
|
+
else {
|
|
964
|
+
notes.push(`Gemini CLI: skipped skills spec-author, design-md — needs Gemini CLI >= ${exports.GEMINI_MIN_SKILLS} (${foundLabel(version)}).`);
|
|
965
|
+
}
|
|
966
|
+
if ((0, gemini_version_1.geminiVersionSupports)(version, exports.GEMINI_MIN_AGENTS)) {
|
|
967
|
+
files.push(writeGeminiAgentsEntry(loc));
|
|
968
|
+
if (!(0, gemini_version_1.geminiVersionSupports)(version, exports.GEMINI_AGENTS_ON_BY_DEFAULT))
|
|
969
|
+
notes.push(AGENTS_EXPERIMENTAL_NOTE);
|
|
970
|
+
}
|
|
971
|
+
else {
|
|
972
|
+
notes.push(`Gemini CLI: skipped subagent specship-explorer — needs Gemini CLI >= ${exports.GEMINI_MIN_AGENTS} (${foundLabel(version)}).`);
|
|
973
|
+
}
|
|
629
974
|
if (opts.trust === true) {
|
|
630
975
|
files.push(writeGeminiPolicyEntry());
|
|
631
976
|
notes.push(TRUST_POLICY_NOTE);
|
|
632
977
|
}
|
|
633
|
-
|
|
634
|
-
notes.push(LOCAL_TRUST_NOTE);
|
|
978
|
+
notes.push(exports.GEMINI_FOLDER_TRUST_NOTE);
|
|
635
979
|
return { files, notes };
|
|
636
980
|
}
|
|
637
981
|
/**
|
|
@@ -654,6 +998,10 @@ class GeminiCliTarget {
|
|
|
654
998
|
(0, shared_1.writeJsonFile)(file, config);
|
|
655
999
|
files.push({ path: file, action: 'removed' });
|
|
656
1000
|
}
|
|
1001
|
+
// REQ-AGENTSWITCH-009.A4: drop only the context file names we added.
|
|
1002
|
+
const contextCleanup = removeGeminiContextFilesEntry(loc, (0, agent_record_1.readAddedContextFiles)(this.id, loc, recordRoot(loc)));
|
|
1003
|
+
if (contextCleanup)
|
|
1004
|
+
files.push(contextCleanup);
|
|
657
1005
|
// Hooks (REQ-GEMINI-010.A4) — strip both tiers so uninstall reverses
|
|
658
1006
|
// either install path. Each helper is surgical: user-written sibling
|
|
659
1007
|
// hooks survive byte-identical.
|
|
@@ -678,6 +1026,12 @@ class GeminiCliTarget {
|
|
|
678
1026
|
const policyCleanup = removeGeminiPolicyEntry();
|
|
679
1027
|
if (policyCleanup.action !== 'not-found')
|
|
680
1028
|
files.push(policyCleanup);
|
|
1029
|
+
// REQ-AGENTSWITCH-005: always strip skills + subagent, whatever version is
|
|
1030
|
+
// installed now, so uninstall reverses any earlier install.
|
|
1031
|
+
files.push(...removeGeminiSkillsEntries(loc).filter((f) => f.action === 'removed'));
|
|
1032
|
+
const agentCleanup = removeGeminiAgentsEntry(loc);
|
|
1033
|
+
if (agentCleanup.action !== 'not-found')
|
|
1034
|
+
files.push(agentCleanup);
|
|
681
1035
|
return { files };
|
|
682
1036
|
}
|
|
683
1037
|
/**
|
|
@@ -688,6 +1042,11 @@ class GeminiCliTarget {
|
|
|
688
1042
|
const snippet = JSON.stringify({ mcpServers: { specship: geminiEntry() } }, null, 2);
|
|
689
1043
|
return `# Add to ${settingsPath(loc)}\n\n${snippet}\n`;
|
|
690
1044
|
}
|
|
1045
|
+
locationHint(loc) {
|
|
1046
|
+
const first = this.describePaths(loc)[0] ?? '';
|
|
1047
|
+
const home = os.homedir();
|
|
1048
|
+
return first.startsWith(home + path.sep) ? '~' + first.slice(home.length) : first;
|
|
1049
|
+
}
|
|
691
1050
|
describePaths(loc) {
|
|
692
1051
|
return [settingsPath(loc), geminiMdPath(loc), geminiCommandsDir(loc)];
|
|
693
1052
|
}
|