@specship/specship-win32-arm64 0.25.0 → 0.28.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.
Files changed (274) hide show
  1. package/bin/specship.cmd +31 -1
  2. package/lib/dist/activation/cheatsheet.d.ts +17 -1
  3. package/lib/dist/activation/cheatsheet.d.ts.map +1 -1
  4. package/lib/dist/activation/cheatsheet.js +47 -3
  5. package/lib/dist/activation/cheatsheet.js.map +1 -1
  6. package/lib/dist/activation/hook-output.d.ts +80 -0
  7. package/lib/dist/activation/hook-output.d.ts.map +1 -0
  8. package/lib/dist/activation/hook-output.js +103 -0
  9. package/lib/dist/activation/hook-output.js.map +1 -0
  10. package/lib/dist/activation/search-intercept.d.ts +60 -29
  11. package/lib/dist/activation/search-intercept.d.ts.map +1 -1
  12. package/lib/dist/activation/search-intercept.js +159 -55
  13. package/lib/dist/activation/search-intercept.js.map +1 -1
  14. package/lib/dist/activation/steering.d.ts +8 -10
  15. package/lib/dist/activation/steering.d.ts.map +1 -1
  16. package/lib/dist/activation/steering.js +21 -6
  17. package/lib/dist/activation/steering.js.map +1 -1
  18. package/lib/dist/behaviour/behaviour-surface.d.ts +47 -0
  19. package/lib/dist/behaviour/behaviour-surface.d.ts.map +1 -1
  20. package/lib/dist/behaviour/behaviour-surface.js +108 -2
  21. package/lib/dist/behaviour/behaviour-surface.js.map +1 -1
  22. package/lib/dist/bin/node-version-check.d.ts +22 -7
  23. package/lib/dist/bin/node-version-check.d.ts.map +1 -1
  24. package/lib/dist/bin/node-version-check.js +39 -9
  25. package/lib/dist/bin/node-version-check.js.map +1 -1
  26. package/lib/dist/bin/specship.js +762 -81
  27. package/lib/dist/bin/specship.js.map +1 -1
  28. package/lib/dist/commands/specship/learn.md +22 -0
  29. package/lib/dist/commands/specship/spec.md +12 -0
  30. package/lib/dist/db/sqlite-adapter.d.ts +7 -5
  31. package/lib/dist/db/sqlite-adapter.d.ts.map +1 -1
  32. package/lib/dist/db/sqlite-adapter.js +11 -9
  33. package/lib/dist/db/sqlite-adapter.js.map +1 -1
  34. package/lib/dist/enforce/changed-files.d.ts +11 -0
  35. package/lib/dist/enforce/changed-files.d.ts.map +1 -1
  36. package/lib/dist/enforce/changed-files.js +42 -0
  37. package/lib/dist/enforce/changed-files.js.map +1 -1
  38. package/lib/dist/enforce/enforce.d.ts +25 -1
  39. package/lib/dist/enforce/enforce.d.ts.map +1 -1
  40. package/lib/dist/enforce/enforce.js +58 -3
  41. package/lib/dist/enforce/enforce.js.map +1 -1
  42. package/lib/dist/enforce/mutation.d.ts +46 -0
  43. package/lib/dist/enforce/mutation.d.ts.map +1 -0
  44. package/lib/dist/enforce/mutation.js +104 -0
  45. package/lib/dist/enforce/mutation.js.map +1 -0
  46. package/lib/dist/enforce/suppressions.d.ts +33 -0
  47. package/lib/dist/enforce/suppressions.d.ts.map +1 -0
  48. package/lib/dist/enforce/suppressions.js +159 -0
  49. package/lib/dist/enforce/suppressions.js.map +1 -0
  50. package/lib/dist/extraction/specs/markdown-spec-extractor.d.ts.map +1 -1
  51. package/lib/dist/extraction/specs/markdown-spec-extractor.js +16 -2
  52. package/lib/dist/extraction/specs/markdown-spec-extractor.js.map +1 -1
  53. package/lib/dist/graph/spec-coverage.d.ts +57 -1
  54. package/lib/dist/graph/spec-coverage.d.ts.map +1 -1
  55. package/lib/dist/graph/spec-coverage.js +129 -5
  56. package/lib/dist/graph/spec-coverage.js.map +1 -1
  57. package/lib/dist/health/smoke-check.js +2 -2
  58. package/lib/dist/health/smoke-check.js.map +1 -1
  59. package/lib/dist/index.d.ts +8 -3
  60. package/lib/dist/index.d.ts.map +1 -1
  61. package/lib/dist/index.js +65 -9
  62. package/lib/dist/index.js.map +1 -1
  63. package/lib/dist/installer/gemini-commands.d.ts +107 -0
  64. package/lib/dist/installer/gemini-commands.d.ts.map +1 -0
  65. package/lib/dist/installer/gemini-commands.js +264 -0
  66. package/lib/dist/installer/gemini-commands.js.map +1 -0
  67. package/lib/dist/installer/index.d.ts +23 -1
  68. package/lib/dist/installer/index.d.ts.map +1 -1
  69. package/lib/dist/installer/index.js +22 -3
  70. package/lib/dist/installer/index.js.map +1 -1
  71. package/lib/dist/installer/install-defaults.d.ts +19 -0
  72. package/lib/dist/installer/install-defaults.d.ts.map +1 -1
  73. package/lib/dist/installer/install-defaults.js +28 -0
  74. package/lib/dist/installer/install-defaults.js.map +1 -1
  75. package/lib/dist/installer/instructions-template.d.ts +22 -0
  76. package/lib/dist/installer/instructions-template.d.ts.map +1 -1
  77. package/lib/dist/installer/instructions-template.js +37 -1
  78. package/lib/dist/installer/instructions-template.js.map +1 -1
  79. package/lib/dist/installer/targets/claude.d.ts +4 -0
  80. package/lib/dist/installer/targets/claude.d.ts.map +1 -1
  81. package/lib/dist/installer/targets/claude.js +8 -25
  82. package/lib/dist/installer/targets/claude.js.map +1 -1
  83. package/lib/dist/installer/targets/gemini.d.ts +85 -12
  84. package/lib/dist/installer/targets/gemini.d.ts.map +1 -1
  85. package/lib/dist/installer/targets/gemini.js +527 -30
  86. package/lib/dist/installer/targets/gemini.js.map +1 -1
  87. package/lib/dist/installer/targets/shared.d.ts +13 -0
  88. package/lib/dist/installer/targets/shared.d.ts.map +1 -1
  89. package/lib/dist/installer/targets/shared.js +18 -0
  90. package/lib/dist/installer/targets/shared.js.map +1 -1
  91. package/lib/dist/installer/targets/types.d.ts +12 -0
  92. package/lib/dist/installer/targets/types.d.ts.map +1 -1
  93. package/lib/dist/jira/auto-publish.d.ts +71 -4
  94. package/lib/dist/jira/auto-publish.d.ts.map +1 -1
  95. package/lib/dist/jira/auto-publish.js +186 -17
  96. package/lib/dist/jira/auto-publish.js.map +1 -1
  97. package/lib/dist/jira/reconcile.d.ts +24 -1
  98. package/lib/dist/jira/reconcile.d.ts.map +1 -1
  99. package/lib/dist/jira/reconcile.js +46 -2
  100. package/lib/dist/jira/reconcile.js.map +1 -1
  101. package/lib/dist/jira/repo-config.d.ts +9 -0
  102. package/lib/dist/jira/repo-config.d.ts.map +1 -1
  103. package/lib/dist/jira/repo-config.js +11 -0
  104. package/lib/dist/jira/repo-config.js.map +1 -1
  105. package/lib/dist/jira/types.d.ts +7 -0
  106. package/lib/dist/jira/types.d.ts.map +1 -1
  107. package/lib/dist/jira/types.js.map +1 -1
  108. package/lib/dist/knowledge/enforce.d.ts +75 -0
  109. package/lib/dist/knowledge/enforce.d.ts.map +1 -0
  110. package/lib/dist/knowledge/enforce.js +250 -0
  111. package/lib/dist/knowledge/enforce.js.map +1 -0
  112. package/lib/dist/knowledge/facts.d.ts +33 -0
  113. package/lib/dist/knowledge/facts.d.ts.map +1 -0
  114. package/lib/dist/knowledge/facts.js +117 -0
  115. package/lib/dist/knowledge/facts.js.map +1 -0
  116. package/lib/dist/knowledge/proposals.d.ts +88 -0
  117. package/lib/dist/knowledge/proposals.d.ts.map +1 -0
  118. package/lib/dist/knowledge/proposals.js +360 -0
  119. package/lib/dist/knowledge/proposals.js.map +1 -0
  120. package/lib/dist/knowledge/stale.d.ts +64 -0
  121. package/lib/dist/knowledge/stale.d.ts.map +1 -0
  122. package/lib/dist/knowledge/stale.js +174 -0
  123. package/lib/dist/knowledge/stale.js.map +1 -0
  124. package/lib/dist/mcp/gemini-session.d.ts +50 -0
  125. package/lib/dist/mcp/gemini-session.d.ts.map +1 -0
  126. package/lib/dist/mcp/gemini-session.js +159 -0
  127. package/lib/dist/mcp/gemini-session.js.map +1 -0
  128. package/lib/dist/mcp/jira-tools.d.ts.map +1 -1
  129. package/lib/dist/mcp/jira-tools.js +25 -0
  130. package/lib/dist/mcp/jira-tools.js.map +1 -1
  131. package/lib/dist/mcp/model-context.d.ts +22 -9
  132. package/lib/dist/mcp/model-context.d.ts.map +1 -1
  133. package/lib/dist/mcp/model-context.js +35 -13
  134. package/lib/dist/mcp/model-context.js.map +1 -1
  135. package/lib/dist/mcp/proxy.js +1 -1
  136. package/lib/dist/mcp/proxy.js.map +1 -1
  137. package/lib/dist/mcp/server-instructions.d.ts +16 -15
  138. package/lib/dist/mcp/server-instructions.d.ts.map +1 -1
  139. package/lib/dist/mcp/server-instructions.js +67 -1
  140. package/lib/dist/mcp/server-instructions.js.map +1 -1
  141. package/lib/dist/mcp/session.js +1 -1
  142. package/lib/dist/mcp/session.js.map +1 -1
  143. package/lib/dist/mcp/spec-tools.d.ts +9 -1
  144. package/lib/dist/mcp/spec-tools.d.ts.map +1 -1
  145. package/lib/dist/mcp/spec-tools.js +69 -21
  146. package/lib/dist/mcp/spec-tools.js.map +1 -1
  147. package/lib/dist/mcp/tools.d.ts +37 -1
  148. package/lib/dist/mcp/tools.d.ts.map +1 -1
  149. package/lib/dist/mcp/tools.js +120 -9
  150. package/lib/dist/mcp/tools.js.map +1 -1
  151. package/lib/dist/pulse/pulse.d.ts +93 -0
  152. package/lib/dist/pulse/pulse.d.ts.map +1 -0
  153. package/lib/dist/pulse/pulse.js +401 -0
  154. package/lib/dist/pulse/pulse.js.map +1 -0
  155. package/lib/dist/reflect/apply.d.ts.map +1 -1
  156. package/lib/dist/reflect/apply.js +38 -3
  157. package/lib/dist/reflect/apply.js.map +1 -1
  158. package/lib/dist/reflect/auto-sweep.d.ts +26 -0
  159. package/lib/dist/reflect/auto-sweep.d.ts.map +1 -0
  160. package/lib/dist/reflect/auto-sweep.js +90 -0
  161. package/lib/dist/reflect/auto-sweep.js.map +1 -0
  162. package/lib/dist/reflect/cli.d.ts +62 -0
  163. package/lib/dist/reflect/cli.d.ts.map +1 -0
  164. package/lib/dist/reflect/cli.js +163 -0
  165. package/lib/dist/reflect/cli.js.map +1 -0
  166. package/lib/dist/reflect/miner.d.ts.map +1 -1
  167. package/lib/dist/reflect/miner.js +119 -0
  168. package/lib/dist/reflect/miner.js.map +1 -1
  169. package/lib/dist/reflect/sweep.d.ts +1 -0
  170. package/lib/dist/reflect/sweep.d.ts.map +1 -1
  171. package/lib/dist/reflect/sweep.js +62 -13
  172. package/lib/dist/reflect/sweep.js.map +1 -1
  173. package/lib/dist/reflect/targets.d.ts +2 -0
  174. package/lib/dist/reflect/targets.d.ts.map +1 -1
  175. package/lib/dist/reflect/targets.js +33 -0
  176. package/lib/dist/reflect/targets.js.map +1 -1
  177. package/lib/dist/reflect/types.d.ts +19 -2
  178. package/lib/dist/reflect/types.d.ts.map +1 -1
  179. package/lib/dist/resolution/test-title-links.d.ts.map +1 -1
  180. package/lib/dist/resolution/test-title-links.js +3 -14
  181. package/lib/dist/resolution/test-title-links.js.map +1 -1
  182. package/lib/dist/server/routes/claude.js +15 -7
  183. package/lib/dist/server/routes/knowledge.js +118 -0
  184. package/lib/dist/server/server.js +2 -0
  185. package/lib/dist/spec-lint/lint.d.ts +1 -1
  186. package/lib/dist/spec-lint/lint.d.ts.map +1 -1
  187. package/lib/dist/spec-lint/lint.js +63 -2
  188. package/lib/dist/spec-lint/lint.js.map +1 -1
  189. package/lib/dist/spec-quality/criterion-ids.d.ts +13 -0
  190. package/lib/dist/spec-quality/criterion-ids.d.ts.map +1 -0
  191. package/lib/dist/spec-quality/criterion-ids.js +38 -0
  192. package/lib/dist/spec-quality/criterion-ids.js.map +1 -0
  193. package/lib/dist/spec-quality/evidence-kind.d.ts +54 -0
  194. package/lib/dist/spec-quality/evidence-kind.d.ts.map +1 -0
  195. package/lib/dist/spec-quality/evidence-kind.js +183 -0
  196. package/lib/dist/spec-quality/evidence-kind.js.map +1 -0
  197. package/lib/dist/spec-quality/link-persist.d.ts +65 -0
  198. package/lib/dist/spec-quality/link-persist.d.ts.map +1 -0
  199. package/lib/dist/spec-quality/link-persist.js +249 -0
  200. package/lib/dist/spec-quality/link-persist.js.map +1 -0
  201. package/lib/dist/spec-quality/plan-scope.d.ts +38 -0
  202. package/lib/dist/spec-quality/plan-scope.d.ts.map +1 -0
  203. package/lib/dist/spec-quality/plan-scope.js +129 -0
  204. package/lib/dist/spec-quality/plan-scope.js.map +1 -0
  205. package/lib/dist/spec-quality/postcheck.d.ts +85 -0
  206. package/lib/dist/spec-quality/postcheck.d.ts.map +1 -0
  207. package/lib/dist/spec-quality/postcheck.js +390 -0
  208. package/lib/dist/spec-quality/postcheck.js.map +1 -0
  209. package/lib/dist/spec-quality/readiness.d.ts +72 -0
  210. package/lib/dist/spec-quality/readiness.d.ts.map +1 -0
  211. package/lib/dist/spec-quality/readiness.js +226 -0
  212. package/lib/dist/spec-quality/readiness.js.map +1 -0
  213. package/lib/dist/workflows/agents/claude.d.ts +16 -0
  214. package/lib/dist/workflows/agents/claude.d.ts.map +1 -0
  215. package/lib/dist/workflows/agents/claude.js +82 -0
  216. package/lib/dist/workflows/agents/claude.js.map +1 -0
  217. package/lib/dist/workflows/agents/gemini.d.ts +21 -0
  218. package/lib/dist/workflows/agents/gemini.d.ts.map +1 -0
  219. package/lib/dist/workflows/agents/gemini.js +124 -0
  220. package/lib/dist/workflows/agents/gemini.js.map +1 -0
  221. package/lib/dist/workflows/agents/index.d.ts +28 -0
  222. package/lib/dist/workflows/agents/index.d.ts.map +1 -0
  223. package/lib/dist/workflows/agents/index.js +104 -0
  224. package/lib/dist/workflows/agents/index.js.map +1 -0
  225. package/lib/dist/workflows/agents/types.d.ts +55 -0
  226. package/lib/dist/workflows/agents/types.d.ts.map +1 -0
  227. package/lib/dist/workflows/agents/types.js +46 -0
  228. package/lib/dist/workflows/agents/types.js.map +1 -0
  229. package/lib/dist/workflows/batch.d.ts +63 -0
  230. package/lib/dist/workflows/batch.d.ts.map +1 -0
  231. package/lib/dist/workflows/batch.js +151 -0
  232. package/lib/dist/workflows/batch.js.map +1 -0
  233. package/lib/dist/workflows/defaults/claude-design-implement.yaml +3 -4
  234. package/lib/dist/workflows/defaults/spec-author.yaml +11 -9
  235. package/lib/dist/workflows/defaults/spec-implement-mixed.yaml +21 -436
  236. package/lib/dist/workflows/defaults/spec-implement.yaml +465 -46
  237. package/lib/dist/workflows/discovery.d.ts +13 -0
  238. package/lib/dist/workflows/discovery.d.ts.map +1 -1
  239. package/lib/dist/workflows/discovery.js +79 -17
  240. package/lib/dist/workflows/discovery.js.map +1 -1
  241. package/lib/dist/workflows/executor.d.ts +18 -0
  242. package/lib/dist/workflows/executor.d.ts.map +1 -1
  243. package/lib/dist/workflows/executor.js +130 -33
  244. package/lib/dist/workflows/executor.js.map +1 -1
  245. package/lib/dist/workflows/gc.d.ts +50 -0
  246. package/lib/dist/workflows/gc.d.ts.map +1 -0
  247. package/lib/dist/workflows/gc.js +120 -0
  248. package/lib/dist/workflows/gc.js.map +1 -0
  249. package/lib/dist/workflows/merge-queue.d.ts +64 -0
  250. package/lib/dist/workflows/merge-queue.d.ts.map +1 -0
  251. package/lib/dist/workflows/merge-queue.js +162 -0
  252. package/lib/dist/workflows/merge-queue.js.map +1 -0
  253. package/lib/dist/workflows/runners/approval.d.ts +1 -1
  254. package/lib/dist/workflows/runners/approval.d.ts.map +1 -1
  255. package/lib/dist/workflows/runners/approval.js +11 -2
  256. package/lib/dist/workflows/runners/approval.js.map +1 -1
  257. package/lib/dist/workflows/runners/bash.d.ts +15 -0
  258. package/lib/dist/workflows/runners/bash.d.ts.map +1 -1
  259. package/lib/dist/workflows/runners/bash.js +50 -1
  260. package/lib/dist/workflows/runners/bash.js.map +1 -1
  261. package/lib/dist/workflows/runners/prompt.d.ts +16 -41
  262. package/lib/dist/workflows/runners/prompt.d.ts.map +1 -1
  263. package/lib/dist/workflows/runners/prompt.js +55 -120
  264. package/lib/dist/workflows/runners/prompt.js.map +1 -1
  265. package/lib/dist/workflows/runners/types.d.ts +12 -0
  266. package/lib/dist/workflows/runners/types.d.ts.map +1 -1
  267. package/lib/dist/workflows/schemas/workflow.d.ts +17 -0
  268. package/lib/dist/workflows/schemas/workflow.d.ts.map +1 -1
  269. package/lib/dist/workflows/schemas/workflow.js +27 -0
  270. package/lib/dist/workflows/schemas/workflow.js.map +1 -1
  271. package/lib/node_modules/.package-lock.json +1 -1
  272. package/lib/package.json +2 -2
  273. package/package.json +5 -3
  274. package/node.exe +0 -4
@@ -2,14 +2,15 @@
2
2
  /**
3
3
  * Google Gemini CLI target — Phase 1 (GEMINI-TARGET-DOC, REQ-GEMINI-002/003).
4
4
  *
5
- * Implements the full `AgentTarget` contract over exactly ONE surface:
6
- * Gemini CLI's `mcpServers` settings block. Everything the Claude target
7
- * additionally writes (permissions, auto-sync + steering hooks, slash
8
- * commands, the `specship-explorer` subagent, the status line, the CLAUDE.md
9
- * steering block) has no Gemini equivalent, so `install` writes none of it and
10
- * says so in `notes` rather than half-installing it (REQ-GEMINI-006).
11
- * GEMINI.md steering (REQ-GEMINI-004) and TOML commands (REQ-GEMINI-005) are
12
- * still unimplemented — deliberately absent, not faked.
5
+ * Implements the full `AgentTarget` contract over Gemini CLI's `mcpServers`
6
+ * settings block, GEMINI.md steering (REQ-GEMINI-004), settings hooks
7
+ * (REQ-GEMINI-010/011), the opt-in `--trust` policy (REQ-GEMINI-009) and the
8
+ * slash commands rendered into Gemini TOML from the same
9
+ * `commands/specship/*.md` sources the Claude target copies (REQ-GEMINI-005,
10
+ * see `../gemini-commands.ts` for the verified format + per-command
11
+ * decisions). What still has no Gemini equivalent (the `specship-explorer`
12
+ * subagent, the status line, the permissions list) is left unwritten and
13
+ * named in `notes` rather than half-installed (REQ-GEMINI-006).
13
14
  *
14
15
  * The target IS registered in `targets/registry.ts`, but a plain
15
16
  * `specship install` stays Claude-only: target selection is opt-in through
@@ -66,17 +67,77 @@ var __importStar = (this && this.__importStar) || (function () {
66
67
  Object.defineProperty(exports, "__esModule", { value: true });
67
68
  exports.geminiTarget = exports.GeminiCliTarget = void 0;
68
69
  exports.settingsPath = settingsPath;
70
+ exports.writeGeminiInstructionsEntry = writeGeminiInstructionsEntry;
71
+ exports.removeGeminiInstructionsEntry = removeGeminiInstructionsEntry;
72
+ exports.writeGeminiSddEntry = writeGeminiSddEntry;
73
+ exports.removeGeminiSddEntry = removeGeminiSddEntry;
69
74
  exports.writeGeminiMcpEntry = writeGeminiMcpEntry;
75
+ exports.geminiCommandsDir = geminiCommandsDir;
76
+ exports.writeGeminiCommandsEntries = writeGeminiCommandsEntries;
77
+ exports.removeGeminiCommandsEntries = removeGeminiCommandsEntries;
78
+ exports.writeGeminiHooksEntry = writeGeminiHooksEntry;
79
+ exports.writeGeminiSddHooksEntry = writeGeminiSddHooksEntry;
80
+ exports.removeGeminiHooksEntry = removeGeminiHooksEntry;
81
+ exports.removeGeminiSddHooksEntry = removeGeminiSddHooksEntry;
82
+ exports.renderGeminiPolicyToml = renderGeminiPolicyToml;
83
+ exports.writeGeminiPolicyEntry = writeGeminiPolicyEntry;
84
+ exports.removeGeminiPolicyEntry = removeGeminiPolicyEntry;
70
85
  const fs = __importStar(require("fs"));
71
86
  const os = __importStar(require("os"));
72
87
  const path = __importStar(require("path"));
73
88
  const shared_1 = require("./shared");
89
+ const instructions_template_1 = require("../instructions-template");
90
+ const claude_1 = require("./claude");
91
+ const gemini_commands_1 = require("../gemini-commands");
74
92
  /** Gemini CLI's settings file for this location. */
75
93
  function settingsPath(loc) {
76
94
  return loc === 'global'
77
95
  ? path.join(os.homedir(), '.gemini', 'settings.json')
78
96
  : path.join(process.cwd(), '.gemini', 'settings.json');
79
97
  }
98
+ /**
99
+ * Gemini CLI's project / user context file. Global → `~/.gemini/GEMINI.md`
100
+ * (user memory, loaded in every session); local → `<cwd>/GEMINI.md` (project
101
+ * memory, loaded when Gemini CLI starts in this repo). Mirror of the Claude
102
+ * target's `claudeMdPath` for CLAUDE.md.
103
+ */
104
+ function geminiMdPath(loc) {
105
+ return loc === 'global'
106
+ ? path.join(os.homedir(), '.gemini', 'GEMINI.md')
107
+ : path.join(process.cwd(), 'GEMINI.md');
108
+ }
109
+ /**
110
+ * Write the retrieval-preference block into GEMINI.md (REQ-GEMINI-004).
111
+ * Marker-delimited and idempotent — content outside the markers is never
112
+ * touched, and a byte-identical re-run returns `unchanged`.
113
+ */
114
+ function writeGeminiInstructionsEntry(loc) {
115
+ const file = geminiMdPath(loc);
116
+ const action = (0, shared_1.upsertMarkedSection)(file, instructions_template_1.SPECSHIP_GEMINI_RETRIEVAL_START, instructions_template_1.SPECSHIP_GEMINI_RETRIEVAL_END, (0, instructions_template_1.getGeminiRetrievalBlock)());
117
+ return { path: file, action };
118
+ }
119
+ /** Inverse of `writeGeminiInstructionsEntry`: strip the retrieval block. */
120
+ function removeGeminiInstructionsEntry(loc) {
121
+ const file = geminiMdPath(loc);
122
+ const action = (0, shared_1.removeMarkedSection)(file, instructions_template_1.SPECSHIP_GEMINI_RETRIEVAL_START, instructions_template_1.SPECSHIP_GEMINI_RETRIEVAL_END);
123
+ return { path: file, action };
124
+ }
125
+ /**
126
+ * Write the spec-driven-development steering block into GEMINI.md
127
+ * (REQ-GEMINI-004.A4). Generated from the SAME `getSddRuleBlock` the Claude
128
+ * target writes, so the two surfaces cannot drift byte-wise.
129
+ */
130
+ function writeGeminiSddEntry(loc) {
131
+ const file = geminiMdPath(loc);
132
+ const action = (0, shared_1.upsertMarkedSection)(file, instructions_template_1.SPECSHIP_SDD_SECTION_START, instructions_template_1.SPECSHIP_SDD_SECTION_END, (0, instructions_template_1.getSddRuleBlock)());
133
+ return { path: file, action };
134
+ }
135
+ /** Inverse of `writeGeminiSddEntry`: strip the SDD rule block. */
136
+ function removeGeminiSddEntry(loc) {
137
+ const file = geminiMdPath(loc);
138
+ const action = (0, shared_1.removeMarkedSection)(file, instructions_template_1.SPECSHIP_SDD_SECTION_START, instructions_template_1.SPECSHIP_SDD_SECTION_END);
139
+ return { path: file, action };
140
+ }
80
141
  /**
81
142
  * The `mcpServers.specship` entry as Gemini CLI wants it: the Claude entry
82
143
  * minus `type`, which Gemini restricts to `'sse' | 'http'` (a stdio server is
@@ -86,7 +147,15 @@ function settingsPath(loc) {
86
147
  */
87
148
  function geminiEntry(integrations = []) {
88
149
  const { type: _stdioIsImplied, ...server } = (0, shared_1.getMcpServerConfig)(integrations);
89
- return server;
150
+ // Host marker (REQ-LOWEND-001): tier auto-detection's Gemini leg only runs
151
+ // when the server was launched by Gemini CLI. This env is that evidence —
152
+ // present in both the written entry and the printable snippet by
153
+ // construction, absent from every Claude Code launch.
154
+ const env = {
155
+ ...server.env,
156
+ SPECSHIP_HOST: 'gemini',
157
+ };
158
+ return { ...server, env };
90
159
  }
91
160
  /**
92
161
  * Write the specship server into Gemini CLI's `mcpServers` block
@@ -120,13 +189,388 @@ function writeGeminiMcpEntry(loc, integrations = []) {
120
189
  }
121
190
  /**
122
191
  * What a Claude install provides that this one does not (REQ-GEMINI-006.A1).
123
- * One note, naming every unsupported surface, so the gap is explicit at
124
- * install time instead of discovered as a missing feature later.
192
+ * One note, naming every still-unsupported surface. As of REQ-GEMINI-010 the
193
+ * prompt / session / tool hooks DO have Gemini equivalents and are installed,
194
+ * so the note no longer lists them; nor slash commands, which REQ-GEMINI-005
195
+ * renders as Gemini TOML. What remains is the specship-explorer subagent, the
196
+ * status line, and the permissions auto-allow list (REQ-GEMINI-009 covers the
197
+ * opt-in policy file). A command the renderer had to exclude gets its own
198
+ * note from `writeGeminiCommandsEntries`.
125
199
  */
126
- const CAPABILITY_NOTE = 'Gemini CLI install wires the MCP server only. No auto-sync/steering hooks, ' +
127
- 'no slash commands, no specship-explorer subagent, no status line, and no ' +
128
- 'permissions auto-allow — Gemini CLI has no equivalent surface. Retrieval ' +
129
- 'steering therefore relies solely on the MCP server instructions.';
200
+ const CAPABILITY_NOTE = 'Gemini CLI install wires the MCP server, writes a GEMINI.md steering ' +
201
+ 'block, installs prompt/session/tool hooks, and renders the /specship:* ' +
202
+ 'slash commands as Gemini TOML. No specship-explorer subagent and no ' +
203
+ 'status line — Gemini CLI has no equivalent surface. The tool playbook ' +
204
+ 'itself still lives in the MCP server instructions.';
205
+ /**
206
+ * Gemini CLI's custom-commands dir for this location (verified against
207
+ * gemini-cli 0.28.0 `Storage.getUserCommandsDir` / `getProjectCommandsDir`):
208
+ * global `~/.gemini/commands`, local `<cwd>/.gemini/commands`. Same scope as
209
+ * the MCP entry written by `settingsPath`.
210
+ */
211
+ function geminiCommandsDir(loc) {
212
+ return path.join(path.dirname(settingsPath(loc)), 'commands');
213
+ }
214
+ /**
215
+ * Every command the Claude installer ships, both tiers — the Gemini set is
216
+ * derived from the same list so the surfaces can't diverge (REQ-GEMINI-005).
217
+ */
218
+ const ALL_COMMAND_SOURCES = [
219
+ ...claude_1.RETRIEVAL_TIER_COMMANDS,
220
+ ...claude_1.GOVERNANCE_TIER_COMMANDS,
221
+ ];
222
+ /** True when `file` is a TOML a SpecShip install wrote (carries our header). */
223
+ function isOurCommandFile(file) {
224
+ try {
225
+ return fs.readFileSync(file, 'utf-8').startsWith(gemini_commands_1.GEMINI_COMMAND_HEADER_MARKER);
226
+ }
227
+ catch {
228
+ return false;
229
+ }
230
+ }
231
+ /**
232
+ * Render and write the slash commands (REQ-GEMINI-005). Same tiering as the
233
+ * Claude target: the retrieval door always, the governance doors unless
234
+ * `--no-sdd`. Per-file idempotent (byte-equal → `unchanged`); a user-authored
235
+ * file at one of our paths (no header marker) is never overwritten (`kept`).
236
+ * A command the renderer excluded is NOT written, a stale copy an earlier
237
+ * install wrote is removed, and the exclusion is returned as a note.
238
+ *
239
+ * `commandsSourceDir` defaults to the packaged `commands/` dir — the same
240
+ * one the Claude target copies from.
241
+ */
242
+ function writeGeminiCommandsEntries(loc, includeGovernance = true, commandsSourceDir = (0, shared_1.packageAssetPath)('commands')) {
243
+ const names = includeGovernance ? ALL_COMMAND_SOURCES : [...claude_1.RETRIEVAL_TIER_COMMANDS];
244
+ const dir = geminiCommandsDir(loc);
245
+ const files = [];
246
+ const notes = [];
247
+ for (const cmd of (0, gemini_commands_1.geminiCommandSet)(commandsSourceDir, names)) {
248
+ const dest = path.join(dir, cmd.target);
249
+ if (cmd.toml === undefined) {
250
+ notes.push(`Gemini CLI: skipped /${cmd.target.replace(/\.toml$/, '').replace(/\//g, ':')} — ${cmd.excluded}.`);
251
+ if (isOurCommandFile(dest)) {
252
+ fs.unlinkSync(dest);
253
+ files.push({ path: dest, action: 'removed' });
254
+ }
255
+ continue;
256
+ }
257
+ const existed = fs.existsSync(dest);
258
+ if (existed) {
259
+ const current = fs.readFileSync(dest, 'utf-8');
260
+ if (current === cmd.toml) {
261
+ files.push({ path: dest, action: 'unchanged' });
262
+ continue;
263
+ }
264
+ if (!current.startsWith(gemini_commands_1.GEMINI_COMMAND_HEADER_MARKER)) {
265
+ files.push({ path: dest, action: 'kept' });
266
+ continue;
267
+ }
268
+ }
269
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
270
+ fs.writeFileSync(dest, cmd.toml);
271
+ files.push({ path: dest, action: existed ? 'updated' : 'created' });
272
+ }
273
+ return { files, notes };
274
+ }
275
+ /**
276
+ * Inverse of `writeGeminiCommandsEntries` (REQ-GEMINI-005.A2). Removes only
277
+ * files at our command paths that carry our header marker — user-authored
278
+ * commands, in the same `specship/` subdir or anywhere else in the commands
279
+ * dir, survive byte-identical. The `specship/` subdir is dropped only when
280
+ * that leaves it empty.
281
+ */
282
+ function removeGeminiCommandsEntries(loc) {
283
+ const dir = geminiCommandsDir(loc);
284
+ const files = [];
285
+ for (const source of ALL_COMMAND_SOURCES) {
286
+ const dest = path.join(dir, (0, gemini_commands_1.geminiCommandTarget)(source));
287
+ if (!fs.existsSync(dest))
288
+ continue;
289
+ if (!isOurCommandFile(dest)) {
290
+ files.push({ path: dest, action: 'kept' });
291
+ continue;
292
+ }
293
+ fs.unlinkSync(dest);
294
+ files.push({ path: dest, action: 'removed' });
295
+ }
296
+ const parents = new Set(ALL_COMMAND_SOURCES.map((s) => path.dirname(path.join(dir, s))));
297
+ for (const parent of parents) {
298
+ try {
299
+ if (parent !== dir && fs.existsSync(parent) && fs.readdirSync(parent).length === 0) {
300
+ fs.rmdirSync(parent);
301
+ }
302
+ }
303
+ catch {
304
+ // Leave the directory in place if it can't be removed — never fatal.
305
+ }
306
+ }
307
+ return files;
308
+ }
309
+ /**
310
+ * Opt-in `--trust` only (REQ-GEMINI-009.A5): shown alongside the policy
311
+ * writer so a user who opted in knows the limits of what the file does.
312
+ */
313
+ const TRUST_POLICY_NOTE = 'Wrote ~/.gemini/policies/specship.toml allowing SpecShip\'s read-only ' +
314
+ 'tools. An administrator policy still overrides this file, and ' +
315
+ 'run_shell_command confirmations are unaffected.';
316
+ /**
317
+ * Gemini's project-level hooks only run when the user has marked the folder
318
+ * as trusted — a local install points the user at that gate so hooks silently
319
+ * failing to run isn't the first evidence something's off.
320
+ */
321
+ const LOCAL_TRUST_NOTE = 'Gemini CLI runs project-level hooks only in folders you have marked as ' +
322
+ 'trusted. If the SpecShip hooks seem inert in this project, open it once in ' +
323
+ 'Gemini CLI and accept the trust prompt.';
324
+ const GEMINI_HOOKS = [
325
+ // Prompt-level retrieval nudge — mirror of Claude's UserPromptSubmit.
326
+ {
327
+ event: 'BeforeAgent',
328
+ matcher: '',
329
+ hook: { type: 'command', command: 'specship steer-nudge' },
330
+ },
331
+ // SessionStart cheat-sheet (human-facing, systemMessage).
332
+ {
333
+ event: 'SessionStart',
334
+ matcher: '',
335
+ hook: { type: 'command', command: 'specship cheatsheet' },
336
+ },
337
+ // SessionStart sync + drift summary — one-shot catch-up at session start.
338
+ {
339
+ event: 'SessionStart',
340
+ matcher: '',
341
+ hook: { type: 'command', command: 'specship sync --quiet --drift-summary' },
342
+ },
343
+ // Post-edit sync — Gemini's `write_file` / `replace` tools are the write
344
+ // surface, so matcher is scoped there (parity with Claude's Edit|Write).
345
+ {
346
+ event: 'AfterTool',
347
+ matcher: 'write_file|replace',
348
+ hook: { type: 'command', command: 'specship sync --quiet' },
349
+ },
350
+ // Search-intercept — REQ-GEMINI-011. Advisory redirect on built-in
351
+ // grep_search / glob, plus run_shell_command (which gets classified
352
+ // search-vs-other at hook time; non-search shell commands pass through
353
+ // silently). In strict mode (`SPECSHIP_GEMINI_STRICT=1`) grep_search and
354
+ // glob are denied; run_shell_command NEVER denies.
355
+ {
356
+ event: 'BeforeTool',
357
+ matcher: 'grep_search',
358
+ hook: { type: 'command', command: 'specship search-intercept' },
359
+ },
360
+ {
361
+ event: 'BeforeTool',
362
+ matcher: 'glob',
363
+ hook: { type: 'command', command: 'specship search-intercept' },
364
+ },
365
+ {
366
+ event: 'BeforeTool',
367
+ matcher: 'run_shell_command',
368
+ hook: { type: 'command', command: 'specship search-intercept' },
369
+ },
370
+ ];
371
+ /** SDD governance tier — opt-out via `--no-sdd`, matching the Claude install. */
372
+ const GEMINI_SDD_HOOKS = [
373
+ {
374
+ event: 'BeforeAgent',
375
+ matcher: '',
376
+ hook: { type: 'command', command: 'specship spec-nudge' },
377
+ },
378
+ ];
379
+ /**
380
+ * Idempotently merge Gemini hook entries into `settings.hooks`. Mirrors the
381
+ * Claude target's `writeHooksFor`: a matcher group sharing our exact matcher
382
+ * string is reused, sibling matchers / events are untouched, a byte-equal
383
+ * command is skipped. Returns `unchanged` when nothing was added.
384
+ */
385
+ function writeGeminiHooksFor(loc, hooks) {
386
+ const file = settingsPath(loc);
387
+ const created = !fs.existsSync(file);
388
+ const settings = (0, shared_1.readJsonFile)(file);
389
+ const beforeJson = JSON.stringify(settings);
390
+ if (!settings.hooks || typeof settings.hooks !== 'object' || Array.isArray(settings.hooks)) {
391
+ settings.hooks = {};
392
+ }
393
+ for (const { event, matcher, hook } of hooks) {
394
+ if (!Array.isArray(settings.hooks[event]))
395
+ settings.hooks[event] = [];
396
+ let group = settings.hooks[event].find((g) => g && g.matcher === matcher);
397
+ if (!group) {
398
+ group = { matcher, hooks: [] };
399
+ settings.hooks[event].push(group);
400
+ }
401
+ if (!Array.isArray(group.hooks))
402
+ group.hooks = [];
403
+ if (!group.hooks.some((h) => h && h.command === hook.command)) {
404
+ group.hooks.push({ ...hook });
405
+ }
406
+ }
407
+ const afterJson = JSON.stringify(settings);
408
+ if (beforeJson === afterJson && !created) {
409
+ return { path: file, action: 'unchanged' };
410
+ }
411
+ (0, shared_1.writeJsonFile)(file, settings);
412
+ return { path: file, action: created ? 'created' : 'updated' };
413
+ }
414
+ /**
415
+ * Surgically remove a predicate-matched set of commands from `settings.hooks`,
416
+ * then prune empty matcher groups / events / the top-level `hooks` map.
417
+ * Mirrors Claude's `stripHooksMatching` so sibling user-written hooks survive
418
+ * byte-identical.
419
+ */
420
+ function stripGeminiHooksMatching(loc, predicate) {
421
+ const file = settingsPath(loc);
422
+ if (!fs.existsSync(file))
423
+ return { path: file, action: 'not-found' };
424
+ const settings = (0, shared_1.readJsonFile)(file);
425
+ const hooks = settings.hooks;
426
+ if (!hooks || typeof hooks !== 'object' || Array.isArray(hooks)) {
427
+ return { path: file, action: 'unchanged' };
428
+ }
429
+ let removedAny = false;
430
+ for (const event of Object.keys(hooks)) {
431
+ const groups = hooks[event];
432
+ if (!Array.isArray(groups))
433
+ continue;
434
+ for (const group of groups) {
435
+ if (!group || !Array.isArray(group.hooks))
436
+ continue;
437
+ const before = group.hooks.length;
438
+ group.hooks = group.hooks.filter((h) => !predicate(h?.command));
439
+ if (group.hooks.length !== before)
440
+ removedAny = true;
441
+ }
442
+ }
443
+ if (!removedAny)
444
+ return { path: file, action: 'unchanged' };
445
+ for (const event of Object.keys(hooks)) {
446
+ const groups = hooks[event];
447
+ if (!Array.isArray(groups))
448
+ continue;
449
+ hooks[event] = groups.filter((g) => !(g && Array.isArray(g.hooks) && g.hooks.length === 0));
450
+ if (hooks[event].length === 0)
451
+ delete hooks[event];
452
+ }
453
+ if (Object.keys(hooks).length === 0)
454
+ delete settings.hooks;
455
+ (0, shared_1.writeJsonFile)(file, settings);
456
+ return { path: file, action: 'removed' };
457
+ }
458
+ /** Write the default (retrieval + sync + cheatsheet) hook set. */
459
+ function writeGeminiHooksEntry(loc) {
460
+ return writeGeminiHooksFor(loc, GEMINI_HOOKS);
461
+ }
462
+ /** Write the governance-tier SDD nudge hook. */
463
+ function writeGeminiSddHooksEntry(loc) {
464
+ return writeGeminiHooksFor(loc, GEMINI_SDD_HOOKS);
465
+ }
466
+ function isGeminiHookCommand(command) {
467
+ if (typeof command !== 'string')
468
+ return false;
469
+ return GEMINI_HOOKS.some(({ hook }) => command === hook.command);
470
+ }
471
+ function isGeminiSddHookCommand(command) {
472
+ if (typeof command !== 'string')
473
+ return false;
474
+ return GEMINI_SDD_HOOKS.some(({ hook }) => command === hook.command);
475
+ }
476
+ /** Inverse of `writeGeminiHooksEntry`. */
477
+ function removeGeminiHooksEntry(loc) {
478
+ return stripGeminiHooksMatching(loc, isGeminiHookCommand);
479
+ }
480
+ /** Inverse of `writeGeminiSddHooksEntry`. */
481
+ function removeGeminiSddHooksEntry(loc) {
482
+ return stripGeminiHooksMatching(loc, isGeminiSddHookCommand);
483
+ }
484
+ /**
485
+ * Policy TOML (REQ-GEMINI-009): auto-allow Gemini's per-tool confirmation
486
+ * for SpecShip's READ-ONLY MCP tools only. User tier by construction — we
487
+ * never write to the admin policies directory, and an admin rule still wins
488
+ * over this one (that is the intended outcome). `run_shell_command` is out
489
+ * of scope: this file removes prompts only for SpecShip's own tools.
490
+ *
491
+ * Verified against Gemini CLI 0.28.0 `loadPoliciesFromToml`: the loader
492
+ * composes `specship__<toolName>` from `mcpName` itself, so every entry here
493
+ * is the BARE server tool name — a prefixed name would match nothing.
494
+ */
495
+ function geminiPolicyPath() {
496
+ return path.join(os.homedir(), '.gemini', 'policies', 'specship.toml');
497
+ }
498
+ const GEMINI_POLICY_HEADER_MARKER = '# Written by specship install --trust — REQ-GEMINI-009';
499
+ const READONLY_TOOLS = Object.freeze([
500
+ 'specship_explore',
501
+ 'specship_search',
502
+ 'specship_node',
503
+ 'specship_callers',
504
+ 'specship_callees',
505
+ 'specship_impact',
506
+ 'specship_files',
507
+ 'specship_status',
508
+ 'specship_health',
509
+ 'specship_spec',
510
+ 'specship_drifted',
511
+ ]);
512
+ /** Mid of Gemini's 0–999 priority range: well below admin tiers. */
513
+ const POLICY_PRIORITY = 100;
514
+ /**
515
+ * Pure string builder for `specship.toml`. Single source for the writer so
516
+ * the on-disk content and any future `printConfig` surface cannot drift.
517
+ */
518
+ function renderGeminiPolicyToml() {
519
+ const toolList = READONLY_TOOLS.map((t) => `"${t}"`).join(', ');
520
+ return [
521
+ GEMINI_POLICY_HEADER_MARKER,
522
+ '# User-tier policy auto-allowing SpecShip\'s read-only MCP tools.',
523
+ '# An administrator policy overrides this file. Shell-command',
524
+ '# confirmations (run_shell_command) are not affected.',
525
+ '',
526
+ // Gemini's loader reads `[[rule]]` tables (0.28.0 PolicyFileSchema.rule);
527
+ // any other table name is silently ignored and the policy does nothing.
528
+ '[[rule]]',
529
+ 'mcpName = "specship"',
530
+ `toolName = [${toolList}]`,
531
+ 'decision = "allow"',
532
+ `priority = ${POLICY_PRIORITY}`,
533
+ '',
534
+ ].join('\n');
535
+ }
536
+ /**
537
+ * Write `~/.gemini/policies/specship.toml`. Idempotent — byte-identical
538
+ * contents report `unchanged`. Never touches the admin policies directory.
539
+ */
540
+ function writeGeminiPolicyEntry() {
541
+ const file = geminiPolicyPath();
542
+ const content = renderGeminiPolicyToml();
543
+ const existed = fs.existsSync(file);
544
+ if (existed) {
545
+ const current = fs.readFileSync(file, 'utf-8');
546
+ if (current === content)
547
+ return { path: file, action: 'unchanged' };
548
+ // Never overwrite a user-authored file of the same name — only one that
549
+ // an earlier `--trust` install wrote (it carries our header marker).
550
+ if (!current.startsWith(GEMINI_POLICY_HEADER_MARKER))
551
+ return { path: file, action: 'kept' };
552
+ }
553
+ fs.mkdirSync(path.dirname(file), { recursive: true });
554
+ fs.writeFileSync(file, content);
555
+ return { path: file, action: existed ? 'updated' : 'created' };
556
+ }
557
+ /**
558
+ * Inverse of `writeGeminiPolicyEntry`. Removes `specship.toml` ONLY if its
559
+ * contents carry our header marker — a user-authored file of the same name
560
+ * is left byte-identical. Sibling files in the policies directory are
561
+ * untouched; the admin policies dir is never written or read.
562
+ */
563
+ function removeGeminiPolicyEntry() {
564
+ const file = geminiPolicyPath();
565
+ if (!fs.existsSync(file))
566
+ return { path: file, action: 'not-found' };
567
+ const current = fs.readFileSync(file, 'utf-8');
568
+ if (!current.startsWith(GEMINI_POLICY_HEADER_MARKER)) {
569
+ return { path: file, action: 'kept' };
570
+ }
571
+ fs.unlinkSync(file);
572
+ return { path: file, action: 'removed' };
573
+ }
130
574
  class GeminiCliTarget {
131
575
  id = 'gemini';
132
576
  displayName = 'Gemini CLI';
@@ -149,21 +593,46 @@ class GeminiCliTarget {
149
593
  };
150
594
  }
151
595
  /**
152
- * Writes exactly one thing: the MCP entry. `autoAllow`, `sdd` and
596
+ * Writes the MCP entry, GEMINI.md steering, hooks, the rendered slash
597
+ * commands and (with `--trust`) the policy TOML. `autoAllow` and
153
598
  * `installStatusLine` are accepted and IGNORED — Gemini CLI has no
154
- * permissions, hooks or commands surface to write them to, and faking one
155
- * would be worse than the note (REQ-GEMINI-006). Nothing under `.claude/`
156
- * is ever touched (REQ-GEMINI-006.A2).
599
+ * permissions list or status line to write them to, and faking one would be
600
+ * worse than the note (REQ-GEMINI-006). Nothing under `.claude/` is ever
601
+ * touched (REQ-GEMINI-006.A2).
157
602
  */
158
603
  install(loc, opts) {
159
604
  const integrations = [
160
605
  ...(opts.withJira ? ['jira'] : []),
161
606
  ...(opts.withDesigner ? ['designer'] : []),
162
607
  ];
163
- return {
164
- files: [writeGeminiMcpEntry(loc, integrations)],
165
- notes: [CAPABILITY_NOTE],
166
- };
608
+ const files = [writeGeminiMcpEntry(loc, integrations)];
609
+ // GEMINI.md retrieval preference — always written (REQ-GEMINI-004).
610
+ files.push(writeGeminiInstructionsEntry(loc));
611
+ // SDD rule — default-on, matching the Claude target's governance tier.
612
+ // `--no-sdd` (opts.sdd === false) self-heals by stripping an earlier
613
+ // governance install.
614
+ const includeGovernance = opts.sdd !== false;
615
+ files.push(includeGovernance ? writeGeminiSddEntry(loc) : removeGeminiSddEntry(loc));
616
+ // Prompt / session / tool hooks (REQ-GEMINI-010). Default retrieval tier
617
+ // is always written; the governance SDD nudge rides `--sdd` and
618
+ // self-heals (strip on `--no-sdd`).
619
+ files.push(writeGeminiHooksEntry(loc));
620
+ files.push(includeGovernance ? writeGeminiSddHooksEntry(loc) : removeGeminiSddHooksEntry(loc));
621
+ // Slash commands (REQ-GEMINI-005), rendered from the Claude sources.
622
+ // Same tiering as the Claude target: governance doors ride `--sdd`.
623
+ const commands = writeGeminiCommandsEntries(loc, includeGovernance);
624
+ files.push(...commands.files);
625
+ // REQ-GEMINI-009: strictly opt-in. Omitting `--trust` NEVER touches the
626
+ // policy file — a prior `--trust` install is preserved byte-identical
627
+ // (parity with REQ-INTEG-001.A3).
628
+ const notes = [CAPABILITY_NOTE, ...commands.notes];
629
+ if (opts.trust === true) {
630
+ files.push(writeGeminiPolicyEntry());
631
+ notes.push(TRUST_POLICY_NOTE);
632
+ }
633
+ if (loc === 'local')
634
+ notes.push(LOCAL_TRUST_NOTE);
635
+ return { files, notes };
167
636
  }
168
637
  /**
169
638
  * Removes only the `mcpServers.specship` key (REQ-GEMINI-002.A2): sibling
@@ -172,16 +641,44 @@ class GeminiCliTarget {
172
641
  * rather than throwing.
173
642
  */
174
643
  uninstall(loc) {
644
+ const files = [];
175
645
  const file = settingsPath(loc);
176
646
  const config = (0, shared_1.readJsonFile)(file);
177
647
  if (!config.mcpServers?.specship) {
178
- return { files: [{ path: file, action: 'not-found' }] };
648
+ files.push({ path: file, action: 'not-found' });
649
+ }
650
+ else {
651
+ delete config.mcpServers.specship;
652
+ if (Object.keys(config.mcpServers).length === 0)
653
+ delete config.mcpServers;
654
+ (0, shared_1.writeJsonFile)(file, config);
655
+ files.push({ path: file, action: 'removed' });
179
656
  }
180
- delete config.mcpServers.specship;
181
- if (Object.keys(config.mcpServers).length === 0)
182
- delete config.mcpServers;
183
- (0, shared_1.writeJsonFile)(file, config);
184
- return { files: [{ path: file, action: 'removed' }] };
657
+ // Hooks (REQ-GEMINI-010.A4) — strip both tiers so uninstall reverses
658
+ // either install path. Each helper is surgical: user-written sibling
659
+ // hooks survive byte-identical.
660
+ const hookCleanup = removeGeminiHooksEntry(loc);
661
+ if (hookCleanup.action === 'removed')
662
+ files.push(hookCleanup);
663
+ const sddHookCleanup = removeGeminiSddHooksEntry(loc);
664
+ if (sddHookCleanup.action === 'removed')
665
+ files.push(sddHookCleanup);
666
+ // Strip both GEMINI.md blocks regardless of which tier was selected on
667
+ // install, so uninstall fully reverses either install path
668
+ // (REQ-GEMINI-004.A2). `removeMarkedSection` deletes the file iff
669
+ // removal leaves it empty, so a user-authored GEMINI.md is preserved.
670
+ files.push(removeGeminiInstructionsEntry(loc));
671
+ files.push(removeGeminiSddEntry(loc));
672
+ // Slash commands (REQ-GEMINI-005.A2) — only our marked TOMLs go.
673
+ files.push(...removeGeminiCommandsEntries(loc));
674
+ // REQ-GEMINI-009.A4: strip our policy TOML iff it carries our header
675
+ // marker — a user-authored specship.toml of the same name is left
676
+ // byte-identical. Sibling files in the policies directory survive, and
677
+ // the admin policies dir is never written or read.
678
+ const policyCleanup = removeGeminiPolicyEntry();
679
+ if (policyCleanup.action !== 'not-found')
680
+ files.push(policyCleanup);
681
+ return { files };
185
682
  }
186
683
  /**
187
684
  * The snippet, verbatim-pasteable into `mcpServers`. Pure string building —
@@ -192,7 +689,7 @@ class GeminiCliTarget {
192
689
  return `# Add to ${settingsPath(loc)}\n\n${snippet}\n`;
193
690
  }
194
691
  describePaths(loc) {
195
- return [settingsPath(loc)];
692
+ return [settingsPath(loc), geminiMdPath(loc), geminiCommandsDir(loc)];
196
693
  }
197
694
  }
198
695
  exports.GeminiCliTarget = GeminiCliTarget;