specrails-desktop 2.6.0 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/client/dist/assets/{ActivityFeedPage-CoWwVcty.js → ActivityFeedPage-LKqd18-G.js} +1 -1
  2. package/client/dist/assets/{AgentsPage-CgPvynWc.js → AgentsPage-Cb-b-6Ot.js} +1 -1
  3. package/client/dist/assets/AnalyticsPage-HVxQQ1wy.js +1 -0
  4. package/client/dist/assets/{BarChart-BKXQPcoW.js → BarChart-BOyHB0dw.js} +1 -1
  5. package/client/dist/assets/{CodePage-CYhXRKiI.js → CodePage-DnOnwKGB.js} +1 -1
  6. package/client/dist/assets/{DesktopAnalyticsPage-CBfPCT3q.js → DesktopAnalyticsPage-D2auU39x.js} +1 -1
  7. package/client/dist/assets/{DocsDialog-uRTBV-3T.js → DocsDialog-CTuDX3GK.js} +1 -1
  8. package/client/dist/assets/{DocsPage-gH0Lc54I.js → DocsPage-DRyMmu0Z.js} +1 -1
  9. package/client/dist/assets/{ExportDropdown-DAp7zWib.js → ExportDropdown-DO-GGiMh.js} +1 -1
  10. package/client/dist/assets/{IntegrationsPage-D40Si_7s.js → IntegrationsPage-BhbO4jFT.js} +1 -1
  11. package/client/dist/assets/{JobDetailPage-DSxAvB1n.js → JobDetailPage-DJooEg1s.js} +1 -1
  12. package/client/dist/assets/{JobsPage-ZMBc1BHE.js → JobsPage-BbaC-YOg.js} +1 -1
  13. package/client/dist/assets/{addspec-DeDOztDr.js → addspec-B-BKlvDj.js} +1 -1
  14. package/client/dist/assets/{addspec-v8j6A7CD.js → addspec-BErjOdNK.js} +1 -1
  15. package/client/dist/assets/{addspec-B1FTtI2a.js → addspec-CIGb34PS.js} +1 -1
  16. package/client/dist/assets/{addspec-GWm4ffKl.js → addspec-C_3NBarY.js} +1 -1
  17. package/client/dist/assets/{addspec-Dw-0Dg-4.js → addspec-DDvvnE6N.js} +1 -1
  18. package/client/dist/assets/{addspec-DpRgmfmx.js → addspec-RuL8Zd7w.js} +1 -1
  19. package/client/dist/assets/{addspec-rp496P_F.js → addspec-rmhOaH7N.js} +1 -1
  20. package/client/dist/assets/{addspec-BCT9vm_c.js → addspec-xjDbYZWL.js} +1 -1
  21. package/client/dist/assets/{dist-js-CKqmDyXR.js → dist-js-CiIVMsx3.js} +1 -1
  22. package/client/dist/assets/{dist-js-bTZuok_W.js → dist-js-Xc2lRKp2.js} +1 -1
  23. package/client/dist/assets/{index-B9IKK_QQ.js → index-DK214dak.js} +34 -34
  24. package/client/dist/assets/index-DgKfQFcf.css +2 -0
  25. package/client/dist/assets/{lib-B5mjOeEi.js → lib-Bo5s6xpe.js} +1 -1
  26. package/client/dist/assets/{useProjectCache-Cf83MBQh.js → useProjectCache-DVNypkmR.js} +1 -1
  27. package/client/dist/index.html +3 -3
  28. package/docs/adding-a-provider.md +107 -0
  29. package/docs/agy-cli-provider-study.md +179 -0
  30. package/docs/gemini-cli-provider-study.md +301 -0
  31. package/docs/gemini-core-support-evaluation.md +160 -0
  32. package/docs/gemini.md +106 -0
  33. package/package.json +1 -1
  34. package/server/dist/chat-manager.js +1 -1
  35. package/server/dist/core-package.js +6 -1
  36. package/server/dist/desktop-router.js +27 -8
  37. package/server/dist/explore-cwd-manager.js +1 -1
  38. package/server/dist/mobile/mobile-datachannel.js +65 -4
  39. package/server/dist/pricing.js +13 -0
  40. package/server/dist/project-router-tickets.js +63 -18
  41. package/server/dist/providers/gemini-adapter.js +234 -0
  42. package/server/dist/providers/index.js +4 -1
  43. package/server/dist/setup-manager.js +13 -7
  44. package/server/dist/setup-prerequisites.js +4 -0
  45. package/server/dist/spec-models.js +17 -3
  46. package/server/dist/util/cli-prompt.js +17 -1
  47. package/client/dist/assets/AnalyticsPage-ioz3Ub2D.js +0 -1
  48. package/client/dist/assets/index-BqAXaTbC.css +0 -2
@@ -0,0 +1,301 @@
1
+ # Estudio de implementación — Gemini CLI como tercer proveedor de specrails-desktop
2
+
3
+ > Documento de planificación (no contrato final). Generado 2026-06-16 mediante auditoría multi-agente del código real + investigación de fuentes primarias de `github.com/google-gemini/gemini-cli` (HEAD `5624a3b`, v0.48.0-nightly). Listón de referencia: el adapter de Codex (no-nativo con puentes). Las citas `file:line` provienen de la auditoría del código; las flags/campos de Gemini en `verbatim` de la investigación. Lo marcado `[UNCERTAIN]` se señala con su plan de resolución.
4
+
5
+ ---
6
+
7
+ ## 1. Resumen ejecutivo y alcance
8
+
9
+ **Veredicto: viable y de bajo riesgo arquitectónico.** El core de spawn/detect/cost/registry ya está generalizado por id de proveedor (`server/providers/registry.ts` es un `Map<ProviderId,adapter>`, `core-compat.ts` recorre `listAdapters()`, `pricing.ts` usa claves `'<id>:<model>'`, `result-event.ts` `finaliseInvocationResult` y `provider-selection.ts` son membership-based). El contrato promete *"un fichero + una entrada de registro"* y eso se cumple para el camino de compilación mínimo. El resto del trabajo es **ensanchar uniones literales `'claude'|'codex'`** y **rellenar branches `=== 'codex'` que no tienen hermano gemini**.
10
+
11
+ Gemini supera a Codex en dos capacidades clave verificadas:
12
+ - **OTEL nativo** (`[confirmed]`): emite OTLP por env (`GEMINI_TELEMETRY_*`), igual que claude → `nativeOtelEnv: true`, **no necesita bridge sintético**.
13
+ - **System-prompt nativo** (`[confirmed]`): `GEMINI_SYSTEM_MD=<path>` reemplaza el system prompt por env de proceso → soporte de system-prompt (con matiz, ver §3).
14
+
15
+ **Entra en v1 (PR-B beta-gated):** jobs/rails básicos, Explore Spec multi-turno (spawn-per-turn con `--resume`), Quick spec, Analytics/coste vía rate-card, OTEL nativo, terminal CLI launch, detección + prerequisitos, selección de modelo, multi-provider per project.
16
+
17
+ **NO entra en v1 (paridad Codex, se ocultan por intersección de capacidades):** Agent Profiles, SMASH, Contract Refine, Ultracode (+interactivo), plugins/Serena, persistent-stdin de Explore. Gemini se comporta byte-idéntico a Codex en estas superficies **siempre que declare las capacidades correctas y se ensanchen los tipos** — cero código nuevo en esas superficies.
18
+
19
+ **Diferido a follow-up explícito (decisiones tomadas, no implementadas en v1):** generalizar `provider-capabilities.ts` para *otorgar* a Gemini features hoy claude-only; bridge Windows multi-line argv; traducción slash-command para rails funcionales en Gemini (esto último es **bloqueante para rails reales**, ver §6).
20
+
21
+ ---
22
+
23
+ ## 2. Modelos a ofrecer
24
+
25
+ Decisión: **pinear ids GA concretos, no alias ni ids preview**, porque los ids `gemini-3-*-preview` rotan (la API ya movió Pro a `gemini-3.1-pro-preview`) y el CLI acepta ids inválidos sin validar (`issue #21391`). `ai_invocations.model` y la clave de pricing necesitan un id concreto y estable → el `modelCatalog()` ofrece ids GA concretos como `value`, que son los que se pasan a `--model` y se almacenan; se evita el churn de los preview.
26
+
27
+ **Catálogo curado (4 entradas):**
28
+
29
+ | value (`--model` / `ai_invocations.model`) | label | default |
30
+ |---|---|---|
31
+ | `gemini-2.5-pro` | Gemini 2.5 Pro | **sí** |
32
+ | `gemini-2.5-flash` | Gemini 2.5 Flash | no |
33
+ | `gemini-2.5-flash-lite` | Gemini 2.5 Flash Lite | no |
34
+ | `gemini-3.1-pro-preview` | Gemini 3 Pro (preview) | no |
35
+
36
+ - **Default = `gemini-2.5-pro`**: GA, no-preview, es el `DEFAULT_GEMINI_MODEL` hardcoded del propio CLI, pricing estable. Evita anclar el producto a un id preview que rotará.
37
+ - **Se ofrece `gemini-3.1-pro-preview`** como cuarta opción de calidad (el único Pro de Gemini-3 *con precio publicado*; `gemini-3-pro` no existe como modelo con precio). Etiquetado "preview" para señalar volatilidad.
38
+ - **Se omiten `gemini-3-flash-preview` / `gemini-3.5-flash` / `gemini-3.1-flash-lite`**: preview de precio movible; con `flash`/`flash-lite` 2.5 ya cubrimos el tier rápido/barato con GA estable. Añadirlos luego es trivial (solo filas de pricing + catálogo).
39
+ - **Se omite pasar alias `auto`/`pro`/`flash`**: el routing `auto` impide stampear un id concreto en `ai_invocations.model`, rompiendo la atribución de coste. Pinneamos ids.
40
+
41
+ `[UNCERTAIN]`: el mapping alias→id-concreto no está byte-documentado; se resuelve NO usando alias.
42
+
43
+ **Filas para `server/pricing.ts` (clave `'gemini:<model>'`, USD por 1M tokens, Standard paid tier):**
44
+
45
+ ```ts
46
+ // Gemini (Google). Ref: ai.google.dev/gemini-api/docs/pricing (fetched 2026-06-16).
47
+ // nativeCostUsd:false → estas tarifas son la ÚNICA fuente de coste.
48
+ 'gemini:gemini-2.5-pro': { inputPer1M: 1.25, outputPer1M: 10.00, cacheReadPer1M: 0.125, lastReviewedAt: '2026-06-16' },
49
+ 'gemini:gemini-2.5-flash': { inputPer1M: 0.30, outputPer1M: 2.50, cacheReadPer1M: 0.03, lastReviewedAt: '2026-06-16' },
50
+ 'gemini:gemini-2.5-flash-lite': { inputPer1M: 0.10, outputPer1M: 0.40, cacheReadPer1M: 0.01, lastReviewedAt: '2026-06-16' },
51
+ 'gemini:gemini-3.1-pro-preview': { inputPer1M: 2.00, outputPer1M: 12.00, cacheReadPer1M: 0.20, lastReviewedAt: '2026-06-16' },
52
+ ```
53
+
54
+ Notas de tiering y caché (decisiones explícitas):
55
+ - **`gemini-2.5-pro` y `gemini-3.1-pro-preview` son context-tiered** (>200k tokens duplica precio: pro $2.50/$15.00; 3.1-pro $4.00/$18.00). `PriceEntry` no soporta tiering por tamaño de prompt. **Decisión v1: usar el tier ≤200k** (la mayoría de jobs caen ahí). El coste de prompts >200k se **infra-estima** — aceptable y honesto (Analytics ya marca `estimated=true`). Fidelidad = follow-up que ensancha `PriceEntry` con tiering (toca también codex pro). No bloqueante.
56
+ - **Free tier**: existe (OAuth 60 RPM/1000 RPD; API key free 10 RPM/250 RPD, solo Flash). **No se modela en pricing** — el rate-card es para coste estimado, no para gating de cuota (coherente con codex).
57
+ - **Caché**: `cacheReadPer1M` asume (como OpenAI/codex) que los cached tokens son un *subconjunto* de `tokens_in`. Gemini reporta `cachedContentTokenCount` separado → `[UNCERTAIN]` **resolución**: en `extractResult` mapear `cached` a `tokens_cache_read` y NO restarlo de `tokens_in` (mismo patrón que codex `cached_input_tokens`); el test de pricing fija el contrato.
58
+
59
+ ---
60
+
61
+ ## 3. El adapter `server/providers/gemini-adapter.ts`
62
+
63
+ Estructura espejo de `codex-adapter.ts`. Constantes `GEMINI_MODELS`, `GEMINI_MIN_VERSION`, helper `fold()` (igual que codex-adapter.ts:49-52), `WHICH_CMD`, `compareSemver`.
64
+
65
+ ### Propiedades readonly
66
+
67
+ | Miembro | Valor Gemini | Razón |
68
+ |---|---|---|
69
+ | `id` | `'gemini'` | clave de registro; igual a `projects.providers[]` y al param `aiEngine`. |
70
+ | `displayName` | `'Gemini CLI'` | etiqueta UI pura. |
71
+ | `binary` | `'gemini'` | ejecutable en PATH. |
72
+ | `minCliVersion` | `'0.11.0'` | piso donde stream-json (PR #10883) + resume están validados. <0.6 no tiene `--output-format`; <0.11 no tiene stream-json. |
73
+ | `projectDirName` | `'.gemini'` | dir de settings del CLI (`.gemini/settings.json`). |
74
+ | `instructionsFilename` | `'GEMINI.md'` | el CLI lee GEMINI.md por defecto; plugin/explore-cwd escriben aquí. **Mejor que codex**: nativo, sin config extra. |
75
+ | `mcpRegistration` | `'project-json'` | Gemini registra MCP en `.gemini/settings.json` `mcpServers`. `gemini mcp add -s project` escribe al settings.json. |
76
+
77
+ ### `capabilities` (8 campos)
78
+
79
+ | Campo | Valor | Por qué |
80
+ |---|---|---|
81
+ | `nativeResume` | `true` | `--resume <uuid\|index\|latest>` headless wired en non-interactive (`nonInteractiveCli.ts:236-242`, `resumeChat`). El host pre-asigna UUID con `--session-id` y resume con `--resume <uuid>`. |
82
+ | `nativeStreamJson` | `true` | `--output-format stream-json` emite NDJSON (`init/message/tool_use/tool_result/error/result`). |
83
+ | `nativeCostUsd` | `false` | NO hay campo cost/usd en ningún evento (grep confirmado). → rate-card en pricing.ts (igual codex). |
84
+ | `nativeOtelEnv` | `true` | **MEJOR que codex**: emite OTLP nativo por `GEMINI_TELEMETRY_*` env. No necesita bridge sintético (ver §4). |
85
+ | `profileEnvSupport` | `true` (vestigial) | Igual que codex: rails fuerzan profile=null para non-claude (`rails-router.ts:250`); valor irrelevante funcionalmente; `true` por paridad. |
86
+ | `systemPromptArg` | `false` (v1) | **Matiz**: Gemini SÍ soporta override de system-prompt, pero por **env `GEMINI_SYSTEM_MD=<path>`**, NO por flag de argv. El contrato `systemPromptArg` significa "¿hay un *flag* `--system-prompt`?" → no lo hay. **v1: `false`** y foldear systemPrompt en el prompt (como codex, fold()). El path env es follow-up. |
87
+ | `persistentStdin` | omitido (`false`) | El CLI requiere el follow-up prompt vía `-p`/`--prompt` en resume headless (issue #14180), no soporta stdin stream-json long-lived documentado. → Explore cae al spawn-per-turn legacy (sin pérdida vs default, que es OFF). |
88
+
89
+ **Nota sobre `systemPromptArg=false` pese a soporte nativo**: el contrato solo modela "flag de argv". Como Gemini usa env (`GEMINI_SYSTEM_MD`), la vía limpia v1 es foldear (codex-parity). Un follow-up puede inyectar `GEMINI_SYSTEM_MD` en el env de spawn — pero eso es un reemplazo *completo* del system prompt (no merge), y los managers asumen append; foldear es más seguro en v1.
90
+
91
+ ### `buildArgs(action, opts)` — switch de los 9 SpawnActions
92
+
93
+ Flags base: `--output-format stream-json`, `--model <opts.model>`. Headless se dispara con `-p <prompt>`. Jobs autónomos: `--approval-mode yolo`. Acciones read-only de spec/explore: `--approval-mode plan`. `opts.extraArgs` siempre se appendea verbatim.
94
+
95
+ ```
96
+ chat-turn (Explore): gemini -p <fold(sys,prompt)> --model <m> --output-format stream-json --session-id <hostUUID>
97
+ chat-resume: gemini -p <fold(sys,prompt)> --model <m> --output-format stream-json --resume <opts.sessionId>
98
+ // throw si !opts.sessionId (igual codex)
99
+ chat-stream: throw // persistentStdin no soportado (igual codex)
100
+ rail-job: gemini -p <command> --model <m> --output-format stream-json --approval-mode yolo --session-id <hostUUID>
101
+ // NB: el command slash /specrails:X NO lo entiende Gemini → §6 (bloqueante rails reales)
102
+ spec-gen: gemini -p <fold(sys,prompt)> --model <m> --output-format stream-json --approval-mode plan
103
+ agent-refine: gemini -p <fold(sys,prompt)> --model <m> --output-format stream-json --approval-mode yolo
104
+ setup-enrich: gemini -p <fold(sys,prompt)> --model <m> --output-format stream-json
105
+ setup-enrich-resume: gemini -p <fold(sys,prompt)> --model <m> --output-format stream-json --resume <opts.sessionId>
106
+ // throw si !opts.sessionId
107
+ auto-title: gemini -p <fold(sys,prompt)> --model <m> --output-format stream-json
108
+ ```
109
+
110
+ Detalles:
111
+ - **`--session-id <hostUUID>` en turno 1** (chat-turn, rail-job): el host mintea un UUID propio y lo pre-asigna, así no depende de parsear `init.session_id` (que es post-2025-12-04). Resume luego con `--resume <eseUUID>`. Robustez de versión.
112
+ - **Resume requiere prompt vía `-p`** (no stdin/positional) — issue #14180, workaround estable documentado.
113
+ - **`opts.maxTurns`**: Gemini lo mapea a `maxSessionTurns` (settings.json, default 100), NO hay flag `--max-turns`. Ignorar `maxTurns` en buildArgs (como codex); el cap sale por exit code 53.
114
+ - **cwd**: todos los spawns desde `project.path` (o explore-cwd) — el resume está scopeado por hash de cwd (`getProjectHash(process.cwd())`). El host ya fija `cwd=project.path` por manager.
115
+
116
+ ### `parseStreamLine(line)` — mapeo evento-gemini → AdapterEvent[]
117
+
118
+ `JSON.parse` por línea; `null` en línea vacía o parse-fail (igual codex):
119
+
120
+ | Evento Gemini | AdapterEvent | Mapeo |
121
+ |---|---|---|
122
+ | `init` `{session_id,model}` | `session-started{sessionId: e.session_id}` | **session_id sale del evento `init`** (único sitio). |
123
+ | `message` `{role:'assistant',content,delta}` | `text-delta{text: e.content}` | acumular chunks con `role==='assistant'`. `role:'user'` (echo) → ignorar. |
124
+ | `tool_use` `{tool_name,tool_id,parameters}` | `tool-use{name: e.tool_name, inputPreview: ...slice(0,200)}` | preview 200 chars (igual codex). |
125
+ | `tool_result` `{tool_id,status,output,error}` | `other{type:'tool_result',raw}` | no hay variant dedicado; codex tampoco lo usa. |
126
+ | `result` `{status,stats}` | `result{payload: e}` | terminal; `stats` queda en payload para `extractResult`. |
127
+ | `error` `{severity,message}` | `other{type:'error',raw}` | no-fatal; los fatales van a stderr+exit. |
128
+ | desconocido | `other{type,raw}` | |
129
+
130
+ **session_id**: del evento `init.session_id`. Como además pre-asignamos `--session-id`, el host ya lo conoce sin parsear (doble seguridad).
131
+
132
+ ### `extractResult(events)` — suma de tokens
133
+
134
+ Lee el último `result.payload.stats` (StreamStats, flat):
135
+ - `tokens_in ← stats.input_tokens`
136
+ - `tokens_out ← stats.output_tokens` (Gemini stream-json **descarta** `thoughts`/`tool` tokens; no hay reasoning separado que foldear)
137
+ - `tokens_cache_read ← stats.cached`
138
+ - `tokens_cache_create ← undefined`
139
+ - `num_turns ←` contar eventos `result` (1 por turno)
140
+ - `session_id ←` último `session-started.sessionId`
141
+ - `total_cost_usd ← undefined` → `finaliseInvocationResult` (`result-event.ts:48-89`) aplica `estimateCostUsd('gemini', model, usage)` contra `pricing.ts`. Sin filas → coste NULL + warn (`result-event.ts:81`).
142
+
143
+ ### `detectInstalled()` — espejo exacto de codex (contrato cap 3s, types.ts:131-132)
144
+
145
+ `which/where gemini` → si ausente `{installed:false,executable:false}`. Si presente: `gemini --version` con `timeout:3000`, regex `\d+\.\d+\.\d+`, `compareSemver` vs `GEMINI_MIN_VERSION='0.11.0'` → `meetsMinimum` + string de error/upgrade. Fallo del probe → `{installed:true,executable:false}`. Corre al startup y en `/setup-prerequisites`.
146
+
147
+ ### `baselineAgents()` → `['sr-architect','sr-developer','sr-reviewer']` (vestigial — rails fuerzan profile=null).
148
+
149
+ ### `registration` (`server/providers/index.ts:11-14,17`)
150
+ ```ts
151
+ import { geminiAdapter } from './gemini-adapter'
152
+ register(geminiAdapter)
153
+ export { claudeAdapter, codexAdapter, geminiAdapter }
154
+ ```
155
+
156
+ ---
157
+
158
+ ## 4. OTEL: nativo (NO bridge)
159
+
160
+ **Decisión: `nativeOtelEnv: true` — Gemini va por el path de claude (env-injection), NO por el bridge sintético de codex.** Evidencia `[confirmed]`: `packages/core/src/telemetry/config.ts` (PR #9113) lee de `process.env` con precedencia env>settings, emitiendo OTLP nativo de traces+metrics+logs.
161
+
162
+ **Cómo se inyecta** — en `server/queue-manager.ts`, con `nativeOtelEnv:true` el bloque del bridge (`queue-manager.ts:1197-1205`, gated `!adapter.capabilities.nativeOtelEnv`) **se salta automáticamente**, y Gemini fluye por `buildTelemetryEnv` (`queue-manager.ts:1042,1105`). El adapter/queue inyecta:
163
+ ```
164
+ GEMINI_TELEMETRY_ENABLED=true
165
+ GEMINI_TELEMETRY_TARGET=local
166
+ GEMINI_TELEMETRY_OTLP_ENDPOINT=<app OTLP receiver>
167
+ GEMINI_TELEMETRY_OTLP_PROTOCOL=grpc # grpc:4317, evita el bug OTLP/HTTP #15581
168
+ GEMINI_TELEMETRY_TRACES_ENABLED=true # spans (off por defecto)
169
+ ```
170
+
171
+ **Qué cambia respecto a claude en queue-manager**: `buildTelemetryEnv` hardcodea `CLAUDE_CODE_ENABLE_TELEMETRY:'1'` (`queue-manager.ts:52-60`), claude-específico. Gemini necesita SU enable-flag. **Cambio requerido (solo si telemetry está ON para un job gemini)**: parametrizar el enable-var por adapter — p.ej. `adapter.nativeOtelEnvVars()` que devuelva `{CLAUDE_CODE_ENABLE_TELEMETRY:'1'}` para claude y el bloque `GEMINI_TELEMETRY_*` para gemini. Los `OTEL_EXPORTER_OTLP_*` estándar siguen neutrales. **No bloqueante** salvo que se active telemetry.
172
+
173
+ **Caveat de correlación `[UNCERTAIN]`**: el receptor OTLP del app rutea por `specrails.job_id` + `specrails.project_id` en `resource.attributes`. Gemini emite sus propios `session.id`/`installation.id`, NO los de specrails. Si Gemini honra `OTEL_RESOURCE_ATTRIBUTES` (estándar OTEL SDK, **no documentado en telemetry.md**), inyectar `OTEL_RESOURCE_ATTRIBUTES=specrails.job_id=<id>,specrails.project_id=<id>` resuelve la correlación. **Resolución**: probar empíricamente antes de confiar; fallback = correlacionar por `session.id` de spawn-time (lo conocemos vía `--session-id`). Telemetry es opt-in OFF por defecto → no bloquea v1.
174
+
175
+ **El bridge `codex-otel-bridge.ts` NO se toca** para Gemini (se bypassa por capability).
176
+
177
+ ---
178
+
179
+ ## 5. Change-list exacto por fichero (dos PRs)
180
+
181
+ ### PR-A — Generalización previa (ensanchar tipos, desramificar; sin Gemini todavía)
182
+
183
+ Mergeable independiente, no cambia comportamiento observable.
184
+
185
+ | Fichero:línea | Cambio | Bloq |
186
+ |---|---|---|
187
+ | `server/desktop-db.ts:10,50,167,268,388` | `CliProvider = 'claude'\|'codex'` → ensanchar a incluir `'gemini'` (o alias a `ProviderId`/string). Tipo DB central; todos heredan. `DEFAULT 'claude'` se mantiene. | **sí** |
188
+ | `server/spec-models.ts:1-37` | `SpecProvider` union + reemplazar ternario `provider==='codex'?CODEX:CLAUDE` en `getModelsForProvider` por **lookup `Record<provider, SpecModelOption[]>`** (fallback `adapter.defaultModel()`/`[]`). `PROVIDER_DEFAULT_MODEL` → record extensible. | **sí** |
189
+ | `server/desktop-router.ts:158-168` (`GET /available-providers`) | Dejar de destructurar `{claude,codex}` fijo; `detectAvailableCLIs()` ya devuelve `Record<string,boolean>` → **devolver el map entero (spread)**. `if(providers.claude\|\|providers.codex)` → `Object.values(providers).some(Boolean)`. | **sí** |
190
+ | `server/desktop-router.ts:265-266` | casts `as 'claude'\|'codex'` en addProject → ensanchar (registry valida con `hasAdapter`). | **sí** |
191
+ | `server/project-registry.ts:116` | `AddProjectInput.providers?: ('claude'\|'codex')[]` → `ProviderId[]`/`string[]`. | **sí** |
192
+ | `client/src/hooks/useDesktop.tsx:29,35,48,172` | 4 anotaciones `('claude'\|'codex')[]` → `string[]`/`ProviderId`. | **sí** |
193
+ | `client/src/lib/provider-capabilities.ts:11` | `ProviderId = 'claude'\|'codex'` → añadir `'gemini'`/string. | **sí** |
194
+ | `client/src/components/ModelSelector.tsx:20-55,80` | ternario `provider==='claude'?CLAUDE:CODEX` → map por id; `PRESET_DEFAULTS`/`MAX_OVERRIDES` literal → `Record<string,string>`; ensanchar prop union. **Bloqueante funcional**: sin esto Gemini mostraría modelos de Codex. | **sí** |
195
+ | `client/src/components/ChatInput.tsx:7-18,25` | selección por id-keyed map; ensanchar `provider?: 'claude'\|'codex'`. | **sí** |
196
+ | `client/src/components/AddProjectDialog.tsx:28,31,37,41,67-79,96,141,235-236` | `availableProviders` de `{claude;codex}` fijo → `Record<string,boolean>` iterado sobre `/available-providers`; `!claude && !codex` → iterar keys. | **sí** |
197
+ | `client/src/components/{AiEngineSelector:40,53, RailEngineSelector:33,38, CliLaunchMenu:52,55, explore-spec/SpecModelPicker:23,81, SetupWizard:563}` | casts inline `as 'claude'\|'codex'` → ensanchar union. Listas data-driven, Gemini aparece solo. | **sí** |
198
+ | `server/util/cli-prompt.ts:178-191` | `spawnAiCli` ya cae a `spawnCli` genérico para binarios desconocidos → **POSIX funciona sin cambio**. Windows multi-line argv (`transformClaudeArgsForWindows`/`transformCodexArgsForWindows`) NO cubre gemini. | cosmético (POSIX) / bloq Windows |
199
+
200
+ ### PR-B — El adapter Gemini + branches y catálogos
201
+
202
+ | Fichero:línea | Cambio | Bloq |
203
+ |---|---|---|
204
+ | `server/providers/gemini-adapter.ts` (nuevo) | El adapter completo (§3). | **sí** |
205
+ | `server/providers/index.ts:11-14,17` | import + `register(geminiAdapter)` + re-export. | **sí** |
206
+ | `server/pricing.ts:44-50` | 4 filas `'gemini:<model>'` (§2). Sin ellas coste=NULL. | **sí** |
207
+ | `server/project-router-tickets.ts:1540-1556` (ai-edit) | dispatch hardcoded `if(provider==='codex'){binary='codex'...} else {binary='claude'...}` → **un proyecto gemini spawnearía 'claude' (binario equivocado)**. Reemplazar por `adapter.binary` + `adapter.buildArgs` (patrón ya usado en quick-spec línea 341). | **sí** |
208
+ | `server/project-router-tickets.ts:420,610,1022,1148` | bloques `if(provider==='codex')` sin hermano gemini → generalizar o añadir arm gemini. | **sí** |
209
+ | `server/setup-manager.ts:697-700,1306-1307` | regex allow-list `m[1]==='claude'\|\|m[1]==='codex'` **descarta 'gemini' silenciosamente** → install-config cae a claude. Añadir `'gemini'`. **Fallo silencioso de alto riesgo.** | **sí** |
210
+ | `server/setup-manager.ts:495-503` | `computeSummary` branch codex → hermano gemini si el summary difiere. | menor |
211
+ | `server/setup-prerequisites.ts:405-423` | switch de install-hint: añadir `case 'gemini':` (URL/comando install Gemini CLI). | menor |
212
+ | `server/agent-refine-manager.ts:73,79` | ensanchar `provider?: 'claude'\|'codex'`. (validateAgentBody ya skip non-claude). | **sí** (compile) |
213
+ | `server/chat-manager.ts:124,132,332` | ensanchar `provider?: 'claude'\|'codex'` ctor + cast persistido. Gates `adapter.id==='claude'` (scope/userMcp) ya rutean gemini como codex (OK). | **sí** (compile) |
214
+ | `server/result-event.ts:98-117` | `normaliseResultEvent` legacy shim: gemini cae al branch codex `else` (mis-parsea usage). **Preferir `adapter.extractResult`** (path moderno). Ensanchar union por si se usa. | menor |
215
+ | `client/src/types/context-scope.ts:62-67` | añadir filas de coste gemini (cliente). | menor |
216
+ | `client/src/components/analytics/ProviderBreakdownCard.tsx:9-17` | `PROVIDER_LABEL`/`PROVIDER_ACCENT`: añadir `gemini` (label + accent, p.ej. `bg-accent-success`). | menor |
217
+ | `client/src/components/Navbar.tsx:26-43` | badge: añadir `=== 'gemini'` (label/color), sino cae a 'no CLI' rojo. | menor |
218
+ | `client/src/components/SetupWizard.tsx:63-76` | heurísticas modelId sonnet/haiku/opus son claude-specific → branch gemini si el configure debe mostrar modelo. | menor |
219
+ | `server/desktop-router.ts:28-35,155-157,229-234` | gate beta: añadir `SPECRAILS_GEMINI_BETA` paralelo a `SPECRAILS_CODEX_BETA`. Generalizar el refuse hardcoded `providers.includes('codex')` (línea 229). | menor (necesario para el gate) |
220
+ | `docs/adding-a-provider.md` (**no existe en disco**) | **Crearlo** — CLAUDE.md lo referencia pero falta. Documentar el patrón "un fichero + una entrada" usando gemini como ejemplo. | menor |
221
+
222
+ **NO tocar (verificado, evitar over-edit):** `core-compat.ts` (registry-driven), `provider-selection.ts` (membership; solo necesita `CliProvider` ensanchado), `spawn-lifecycle.ts` (adapter-driven, cero literales), `result-event.ts` `finaliseInvocationResult` (id-driven), `registry.ts`, `queue-manager._resolveJobAdapter`, `COALESCE(provider,'claude')` en ai-invocations/spending/db (solo backfill de NULLs legacy), `contract-refine-runner.ts`/`smash-runner.ts` (claude-only intencional).
223
+
224
+ ---
225
+
226
+ ## 6. Matriz de feature-parity
227
+
228
+ | Feature | ¿Gemini lo soporta? | Qué haría falta para paridad |
229
+ |---|---|---|
230
+ | **Jobs/rails (compila + corre)** | **Degradado/Bloqueado** | Compila vía adapter. **BLOQUEANTE para rails reales**: `queue-manager.ts:976-981` pasa el slash `/specrails:implement #N` crudo a Gemini en el `else` final — Gemini NO entiende slash-commands de Claude (Codex los traduce a `$skill`). Paridad: branch gemini que mapee slash→forma que entiendan las skills de specrails-core en Gemini. |
231
+ | **Explore Spec (multi-turno)** | **Nativo (degradado)** | `--resume` headless + `--session-id` pre-asignado funcionan. Spawn-per-turn con `-p`. Sin pérdida básica. |
232
+ | **— persistent-stdin Explore** | **Bloqueado** | `persistentStdin:false` → spawn-per-turn (default OFF de todas formas). Requiere stdin stream-json long-lived que Gemini no documenta. |
233
+ | **— tool-gating (Explore/Quick scope)** | **Degradado (default-safe)** | Gates `adapter.id==='claude'` → gemini recibe args vacíos (sin restricción; usa su propio `--approval-mode plan/yolo`). Paridad: branch gemini en `toolFlagsForScope` (`context-scope.ts`) mapeando ContextScope → flags Gemini. |
234
+ | **— MCP per-spec (`.mcp.json`)** | **Degradado** | Gemini usa `.gemini/settings.json` `mcpServers`, NO el `.mcp.json` de claude ni `--mcp-config` inline. Paridad: escribir `.gemini/settings.json` en la cwd o `--allowed-mcp-server-names`. |
235
+ | **— user-MCP (My approved MCPs)** | **Degradado (default-safe)** | `buildUserMcpArgs` devuelve `[]` para non-claude. Gemini lee su MCP global desde `~/.gemini/settings.json` (como codex con `~/.codex`) → no-op correcto, sin pérdida. |
236
+ | **Quick spec** | **Nativo** | spawn one-shot vía adapter; coste por rate-card. |
237
+ | **Agent profiles** | **Bloqueado (paridad codex)** | `CLAUDE_ONLY_SECTIONS=['agents']`, `profileEnvSupport` vestigial. Oculto por intersección. Paridad: soporte por sección en `provider-capabilities.ts:44-71` + `projectSupportsProfiles` por-provider. |
238
+ | **Integrations/plugins (MCP/Serena)** | **Bloqueado (paridad codex)** | Manifest Serena omite `providerSupport` → claude-only. Paridad: añadir `'gemini'` a `providerSupport` + entry MCP bajo `.gemini/settings.json`. |
239
+ | **SMASH** | **Bloqueado (paridad codex)** | `isSmashCapable→'claude'`. `smash-runner.ts` spawnea `claude` directo sin adapter. Paridad: reescribir runner sobre `getAdapter()` + prompt/parser Gemini + flip del gate. |
240
+ | **Contract Refine** | **Bloqueado (inherentemente anthropic en su forma actual)** | `--resume` a sesión Claude + slash `/specrails:contract-refine`. Paridad parcial vía el path no-resume (re-seed por system prompt, como `runContractRefineForQuick`) + de-hardcodear `getAdapter('claude')`. |
241
+ | **Ultracode interactivo** | **Bloqueado (paridad codex)** | `mode==='ultracode' && provider!=='claude'→400`; `isUltracode=adapter.id==='claude'`. Paridad: quitar el 400 para gemini, generalizar el branch ultracode-prompt, `persistentStdin:true` para el interactivo. Mecanismo no-anthropic; factible. |
242
+ | **Analytics/coste** | **Nativo (estimado)** | `nativeCostUsd:false` + filas pricing → coste estimado, `estimated=true`. Filtros engine ya data-driven. |
243
+ | **OTEL/telemetry** | **Nativo (MEJOR que codex)** | `nativeOtelEnv:true` → env-injection (no bridge). Solo parametrizar el enable-var en `buildTelemetryEnv`. Caveat correlación (§4). |
244
+ | **Terminal CLI launch** | **Nativo** | `CliLaunchMenu` itera providers; aparece tras ensanchar union + wiring binario. |
245
+
246
+ **Problema de la intersección binaria `=== 'claude'`**: `sectionVisibleForProviders` **oculta una sección si CUALQUIER proveedor instalado no la soporta**. En un proyecto mixto claude+gemini, Agents/SMASH/etc. desaparecen porque gemini no las declara — igual que codex hoy. **Si se generalizara `provider-capabilities.ts`** (soporte por-sección por-proveedor en vez del `Set` claude-only), Gemini *ganaría*: Agent Profiles (solo env `SPECRAILS_PROFILE_PATH`, agnóstico), tool-gating, user-MCP, file-summary cheap-model — todas mecanismo-dependientes. Solo Contract Refine es inherentemente anthropic. **Decisión v1**: paridad-codex (ocultar), generalización diferida a follow-up.
247
+
248
+ ---
249
+
250
+ ## 7. Riesgos y mitigaciones
251
+
252
+ | Riesgo | Mitigación |
253
+ |---|---|
254
+ | **Pin de versión pre-1.0** (cambia a diario; nightly 0.48) | `minCliVersion='0.11.0'` (piso validado de stream-json). `detectInstalled` surface meetsMinimum + upgrade hint. Documentar que los ids preview rotan. |
255
+ | **Drift del stream-json** (bugs #9281/#11184/#9009) | Esos bugs son del `--output-format json` (single-object `response`), **NO del stream-json** (usa message deltas, sin `response`). #9009 (flag desconocida) cerrado, afecta <0.6 — el pin 0.11 lo evita. **Golden-snapshot test**: fixtures NDJSON bajo `__fixtures__/` verbatim del `stream-json-formatter.test.ts` upstream, aserción `toEqual` sobre el AdapterEvent[]. |
256
+ | **Scope por cwd-hash en resume** | Sesiones scopeadas por `getProjectHash(cwd)`. **Siempre spawn desde `project.path`** (ya lo hace el host). Resume cross-cwd no encuentra la sesión → host-controllable. Pre-asignar `--session-id <UUID>` para no depender de parsear init en builds <2025-12-04. |
257
+ | **Exit code 53** (turn limit, no quota) | Mapear 53 = "cap de turnos `maxSessionTurns`" distinto de error general. 42 = bad input/args. Resto non-zero → tratar como exit 1 (general/API). Quota/429/403 colapsan a exit 1. |
258
+ | **Auth headless** | **Presetear `GEMINI_API_KEY`** en el env de spawn → `getAuthTypeFromEnv()` devuelve `USE_GEMINI`, `validateAuthMethod` pasa, sin browser. Alt CI: `GOOGLE_GENAI_USE_VERTEXAI=true`+`GOOGLE_CLOUD_PROJECT`+`GOOGLE_CLOUD_LOCATION`+`GOOGLE_APPLICATION_CREDENTIALS`. **No setear `security.auth.enforcedType`** conflictivo. Sin var de auth → `process.exit(FATAL_AUTHENTICATION_ERROR)` (no browser). El host necesita un flujo para que el usuario provea la API key (Settings/AddProject). |
259
+ | **Drift OTEL** (correlación `session.id` vs `specrails.job_id`) | Telemetry OFF por defecto → no bloquea v1. Al activar: validar si Gemini propaga `OTEL_RESOURCE_ATTRIBUTES`; fallback correlación por `--session-id` de spawn-time. |
260
+ | **Bug OTLP/HTTP #15581** (POST a `/` no `/v1/{signal}`) | Usar `GEMINI_TELEMETRY_OTLP_PROTOCOL=grpc` (default :4317, well-tested), evitar HTTP. |
261
+ | **Coste >200k tokens infra-estimado** | Aceptado en v1 (tier ≤200k). Analytics marca `estimated`. Follow-up: tiering en `PriceEntry`. |
262
+
263
+ ---
264
+
265
+ ## 8. Plan de tests + coverage
266
+
267
+ CI exige **80% server** (lines/functions/statements, 70% branches) y **80% client** (lines/statements, 70% functions). Cada fichero tocado debe llegar.
268
+
269
+ **Tests nuevos server:**
270
+ - `server/providers/gemini-adapter.test.ts` (espejo de `codex-adapter.test.ts`):
271
+ - `capabilities` exactos (8 campos).
272
+ - `buildArgs` por cada uno de los 9 SpawnActions (línea exacta; resume throw si `!sessionId`; chat-stream throw; fold cuando `systemPromptArg=false`; `extraArgs` appendeado).
273
+ - `parseStreamLine`: fixtures NDJSON bajo `server/providers/__fixtures__/gemini-*.ndjson` (init/message/tool_use/tool_result/error/result/línea-vacía/parse-fail), aserción del AdapterEvent[].
274
+ - `extractResult`: stats → tokens_in/out/cache_read, num_turns, session_id, `total_cost_usd` undefined.
275
+ - `detectInstalled`: mock de `execSync` (no instalado / version OK / version < min / probe-fail / timeout >3s → installed:false).
276
+ - `server/pricing.test.ts`: 4 filas `gemini:*`, `estimateCostUsd('gemini',model,usage)` correcto; clave ausente → null; semántica cached (no doble-conteo).
277
+ - `server/provider-selection.test.ts`: `isProviderEnabled`/`resolveProvider`/`validateRequestedProvider` aceptan `'gemini'`; multi-provider claude+gemini.
278
+ - `server/providers/registry.test.ts`: `getAdapter('gemini')`/`hasAdapter`/`listAdapters` incluye gemini tras import de index.
279
+
280
+ **Tests nuevos client:**
281
+ - `ModelSelector`/`ChatInput`: provider `'gemini'` muestra `GEMINI_MODELS` (no Codex).
282
+ - `AddProjectDialog`: checkbox gemini cuando `/available-providers` lo devuelve; `noProviderAvailable` iterado.
283
+ - `provider-capabilities.test.ts`: `providerLabel('gemini')`, `sectionVisibleForProviders` oculta Agents/SMASH en proyecto con gemini (paridad codex).
284
+ - `ProviderBreakdownCard`/`Navbar`: label+accent gemini.
285
+
286
+ **Gate `SPECRAILS_GEMINI_BETA`**: test en `desktop-router.test.ts` — con `=0`, `/available-providers` devuelve `gemini:false` y `POST /projects` con `providers:['gemini']` se rechaza; default/`1` → habilitado. Espejo de `SPECRAILS_CODEX_BETA`.
287
+
288
+ **Cumplir 80%**: el adapter es lógica pura (sin I/O salvo `detectInstalled` mockeado) → fácil 100%. Cubrir los 9 cases de `buildArgs` garantiza branch coverage. Las filas pricing y los branches `=== 'codex'` generalizados necesitan un test que ejercite el path gemini (ai-edit con provider gemini → `adapter.binary='gemini'`). Excluir solo lo Tauri-only inalcanzable en jsdom, documentando inline; **nunca para enmascarar tests faltantes**.
289
+
290
+ ---
291
+
292
+ ## 9. Rollout por fases
293
+
294
+ 1. **PR-A (generalización previa)** — ensanchar `CliProvider`/`ProviderId`/las ~12 uniones `('claude'|'codex')[]`, desramificar `/available-providers` y `spec-models.ts`/`ModelSelector`/`ChatInput` a lookups por id. **Sin Gemini todavía**; comportamiento idéntico. Mergeable y testeable solo. Reduce el blast-radius del PR-B.
295
+ 2. **PR-B (el adapter)** — `gemini-adapter.ts` + `register` + filas pricing + branches gemini (ai-edit dispatch, setup-manager regex, install-hint) + labels/accents + `SPECRAILS_GEMINI_BETA`. **Gemini detrás del gate beta**, default ON con kill-switch.
296
+ 3. **Beta-gated validation** — probar end-to-end: detect, add-project con gemini, Quick spec, Explore multi-turno (resume), Analytics coste, terminal launch. Validar exit 53/42, auth `GEMINI_API_KEY`. **NO** habilitar rails reales hasta resolver la traducción slash-command (§6, bloqueante) — gemini sirve para spec/explore/quick en esta fase.
297
+ 4. **Docs** — crear `docs/adding-a-provider.md` (falta) con gemini como ejemplo, y `docs/gemini.md` (espejo de `docs/codex.md`) con setup de API key.
298
+
299
+ **Primer commit recomendado:** PR-A paso atómico — ensanchar `server/desktop-db.ts:10` `CliProvider` para incluir `'gemini'` y convertir el ternario de `server/spec-models.ts:1-37` `getModelsForProvider` en lookup `Record<provider, SpecModelOption[]>`. Desbloquea la cadena de tipos sin tocar comportamiento y deja la base lista para registrar el adapter.
300
+
301
+ **Puntos `[UNCERTAIN]` pendientes (no inventados):** (a) `OTEL_RESOURCE_ATTRIBUTES` propagation en Gemini — validar empíricamente antes de confiar para correlación job/project; (b) semántica de `cachedContentTokenCount` como subconjunto de input — fijar por test de pricing; (c) `mcpRegistration` exacto en la versión bundleada — verificado que escribe a settings.json (`project-json`), confirmar en build. Ninguno bloquea v1 (spec/explore/quick); todos tienen fallback.
@@ -0,0 +1,160 @@
1
+ > Documento de planificación. Generado 2026-06-17 mediante auditoría multi-agente del repo real `/Users/javi/repos/specrails-core` + verificación de fuentes primarias de gemini-cli. Complementa `docs/gemini-cli-provider-study.md` (desktop) — esta evalúa el trabajo en **specrails-core** para habilitar rails/implement en Gemini.
2
+
3
+ ---
4
+
5
+ # Evaluación: ¿Hay que tocar specrails-core para Gemini CLI?
6
+
7
+ > **Veredicto en una línea:** Sí, **obligatoriamente hay que tocar core** para que `implement`/`batch-implement` (rails) funcionen en Gemini. El desktop por sí solo (PR-A/PR-B + `gemini-adapter.ts`) cubre **solo** spec/explore/quick. El pipeline architect→developer→reviewer **es expresable sin rediseño de fases** en el modelo plano de Gemini, pero **sin un target gemini en core no hay agentes ni comandos ni skills que ejecutar**, así que los rails en gemini hoy son inejecutables.
8
+
9
+ ---
10
+
11
+ ## 1. Respuesta directa: ¿hay que tocar core?
12
+
13
+ **Sí, para los rails. No, para spec/explore/quick.** El corte es exacto y limpio:
14
+
15
+ ### Ya funciona HOY sin tocar core (solo desktop adapter)
16
+ El desktop tiene `server/providers/gemini-adapter.ts` registrado (`server/providers/index.ts:12,16`), con `projectDirName: '.gemini'`, `instructionsFilename: 'GEMINI.md'`, `nativeCostUsd: false` (gemini-adapter.ts:225-235). Esto cubre las superficies que **NO dependen de artefactos instalados en el repo**:
17
+ - **Explore Spec** — spawnea gemini desde `explore-cwd/` con un `CLAUDE.md`/system-prompt embebido por el desktop; no lee `.gemini/*`.
18
+ - **Quick spec** (`POST /tickets/generate-spec`) — turno suelto, system prompt inyectado por el desktop.
19
+ - **Sidebar chat** — idem.
20
+
21
+ Estas tres superficies inyectan su propio prompt y no resuelven slash-commands ni subagentes del proyecto. Por eso el adapter desktop basta.
22
+
23
+ ### EXIGE core (sin esto, los rails en gemini no arrancan)
24
+ `implement` y `batch-implement` **no son prompts** que el desktop inyecte: son **artefactos instalados en el repo** que el orquestador resuelve nativamente. El desktop solo pasa el comando resuelto al binario (`queue-manager.ts` `buildArgs('rail-job', …)`), y el binario espera encontrar en disco:
25
+ 1. **Comandos** `implement` / `batch-implement` (en formato gemini: `.gemini/commands/*.toml`).
26
+ 2. **Agentes** `sr-architect`/`sr-developer`/`sr-reviewer` (en `.gemini/agents/*.md` con frontmatter).
27
+ 3. **Skills/comandos OpenSpec** (`opsx:*`) instalados por `openspec init --tools gemini`.
28
+ 4. **`GEMINI.md`** (instrucciones de proyecto) + settings.
29
+
30
+ **Core no emite NADA de esto para gemini** — verificado: `grep -rni gemini` sobre `src/`, `templates/`, `integration-contract.json` devuelve **0 matches**. El tipo `Provider` es un set cerrado `'claude' | 'codex'` que **rechaza** cualquier otro valor en `install-config.ts:96` (`unsupported provider '...'`). Por tanto, hoy un proyecto gemini no puede ni siquiera instalarse: el handshake desktop→core (desktop escribe `provider:` en `install-config.yaml`, `setup-manager.ts:906`; core valida en `install-config.ts:96` e `init.ts:86`) **falla en duro** con "unsupported provider 'gemini'".
31
+
32
+ **Conclusión inequívoca:** spec/explore/quick = solo desktop, ya hecho. Rails (implement/batch-implement + agentes + skills + OpenSpec) = **bloqueado en core**, requiere un target gemini.
33
+
34
+ ---
35
+
36
+ ## 2. Cómo emite core hoy por proveedor
37
+
38
+ El flujo real de instalación: `init.ts` → `provider-detect.derivedPaths(provider)` → `scaffold.scaffoldInstallation` → `installOpenSpecProject`.
39
+
40
+ ### Detección/selección de proveedor (todo cerrado a 2 literales)
41
+ - **Tipo:** `Provider = 'claude' | 'codex'` declarado en DOS sitios que deben coincidir: `provider-detect.ts:15` e `install-config.ts:13`.
42
+ - **Detección:** `detectAvailability()` solo sondea claude/codex en PATH — verificado verbatim:
43
+ ```ts
44
+ const [claude, codex] = await Promise.all([commandExists('claude'), commandExists('codex')])
45
+ return { claude, codex }
46
+ ```
47
+ (`provider-detect.ts:33-36`).
48
+ - **Validación dura:** `install-config.ts:96` rechaza ≠ claude/codex; `init.ts:86` rechaza el flag `--provider`; `bin/tui-installer.mjs:114/118`.
49
+
50
+ ### Dónde divergen claude vs codex
51
+ Toda la divergencia es **imperativa, `if (input.provider === 'codex')`**, no hay registry/adapter (contraste con el `ProviderAdapter` del desktop). Sitios clave en `scaffold.ts`:
52
+ - **Convención de paths** centralizada en `derivedPaths` — verificado verbatim: codex → `{ '.codex', 'AGENTS.md' }`, else → `{ '.claude', 'CLAUDE.md' }` (`provider-detect.ts:87-92`).
53
+ - Esqueleto de directorios (`scaffold.ts:174-184`): claude `.claude/commands/specrails` + `.claude/skills`; codex `.codex/skills/{enrich,doctor,rails}`.
54
+ - Colocación de comandos (`copyBundledCommands`, `scaffold.ts:278-311`): claude copia `.md` verbatim; codex porta cada comando a `.codex/skills/<name>/SKILL.md` vía `writeCodexSkillFromCommand` (rewrite `.claude/`→`.codex/`, `/specrails:x`→`$x`) salvo que exista override en `templates/codex-skills/<name>/`.
55
+ - Agentes (`scaffold.ts:493-648`): claude coloca `.claude/agents/sr-*.md` + memory dirs; codex **salta agentes** y usa rail-skills en `.codex/skills/rails/`.
56
+ - Skills (`placeSkills`, `scaffold.ts:762-830`): claude **genera** `sr-*` skills desde el cuerpo del comando (`SKILL_FROM_COMMAND`, `scaffold.ts:46-82` + `writeClaudeSkillFromCommand`); codex copia `templates/codex-skills/rails/`.
57
+ - Settings (`scaffold.ts:244-250`): codex-only `applyCodexSettings` escribe `.codex/config.toml` + `AGENTS.md` con bloque sentinel-managed (`renderInitialAgentsMd`, `scaffold.ts:666-747`). Claude **no** tiene settings-writer aquí (su `CLAUDE.md` no lo escribe el instalador, solo lo chequea `doctor.ts:91`).
58
+
59
+ ### Cómo se instala OpenSpec (esto SÍ es ya per-provider)
60
+ `installOpenSpecProject(repoRoot, provider)` (llamado solo en `init.ts:128`, **no** en update) shellea — verificado verbatim:
61
+ ```ts
62
+ args: ['--yes', '-p', `@fission-ai/openspec@${pinnedVersion}`, '--',
63
+ 'openspec', 'init', '--tools', provider, repoRoot]
64
+ ```
65
+ (`init.ts:204-218`, pin `1.3.1` desde `pinned-versions.json:3`). **Pasa `provider` DIRECTO a `--tools`.** Esto significa que la capa OpenSpec **no es el bloqueante**: `openspec init --tools gemini` es nativamente soportado por openspec 1.3.1 (genera `.gemini/commands/opsx` + `.gemini/skills/openspec-*` + el dir agnóstico `openspec/{specs,changes}`). En cuanto core propague el literal `gemini` a esta llamada (pasos 1+3 abajo), OpenSpec hace lo correcto **sin cambios de código**.
66
+
67
+ ---
68
+
69
+ ## 3. El target gemini en core: qué emitir, transform vs autoría nueva, additividad
70
+
71
+ ### Qué debe emitir
72
+ | Artefacto | Formato gemini | Origen |
73
+ |---|---|---|
74
+ | `implement`, `batch-implement` (+ resto comandos) | `.gemini/commands/specrails/*.toml` — **solo `prompt` + `description`**, sin `tools`/`model` | **Transform** de `templates/commands/specrails/*.md` (reusar el cuerpo, re-envolver en TOML) |
75
+ | `sr-architect/developer/reviewer` (+ opcionales) | `.gemini/agents/sr-*.md` con **YAML frontmatter** que SÍ lleva `model` + `tools` | **Transform** de `templates/agents/sr-*.md` (markdown ya compatible; ajustar frontmatter + `MEMORY_PATH`→`.gemini/agent-memory/`) |
76
+ | `GEMINI.md` + settings | `GEMINI.md` con bloque sentinel + `.gemini/settings.json` (con `experimental.enableAgents`) | **Autoría nueva** — clon de `applyCodexSettings`/`renderInitialAgentsMd` (`scaffold.ts:666-747`) |
77
+ | OpenSpec (`opsx:*`) | `.gemini/commands/opsx/*` + `.gemini/skills/openspec-*` | **Sin cambios** — lo emite `openspec init --tools gemini` |
78
+
79
+ ### Transform vs autoría — la asimetría es **decisiva** y la dicta el formato gemini
80
+ El veredicto verificado fija la regla: **commands TOML solo aceptan `prompt`+`description`** (sin `tools`/`model` por comando), pero **subagents `.md` SÍ aceptan `tools`+`model` en frontmatter**. Consecuencia de arquitectura para el scaffold:
81
+
82
+ > El **model-routing y el tool-gating** que hoy viven embebidos en `implement.md` (bloque ORCHESTRATOR_MODEL ~líneas 160-171 + overrides por agente) **deben migrar al frontmatter de los `.gemini/agents/sr-*.md`**, NO al comando TOML.
83
+
84
+ Mapeo correcto: orquestador → `.gemini/commands/specrails/{implement,batch-implement}.toml` (solo prompt+description); roles → `.gemini/agents/sr-*.md` (con `model:`, `tools:`). Esto **espeja el split de codex** (comando-vs-skill) pero con el primitivo de subagente nativo de gemini en vez de `spawn_agent`.
85
+
86
+ **Neto:** es una **mezcla** — cuerpos de comandos y agentes son **transforms mecánicos** de los templates claude existentes; el **emisor TOML** (`writeGeminiCommandFromCommand`, análogo a `writeCodexSkillFromCommand`), el **escritor `GEMINI.md`/settings**, y el **plumbing de tipo/detección** son **autoría nueva**. Lo más limpio: añadir `templates/gemini-skills/` (o `templates/gemini-*`) como dir de override para lo que el transform mecánico produzca mal, igual que `templates/codex-skills/`.
87
+
88
+ ### Cómo se añade de forma aditiva sin romper claude/codex
89
+ Es aditivo en **semántica** (cada branch es `=== 'codex'` / else-claude; añadir un brazo `=== 'gemini'` deja los paths existentes byte-idénticos; el allow-list de validación solo se ensancha, nunca rechaza configs previas válidas), pero **NO aditivo en código** (no hay abstracción → hay que editar cada sitio). Cambios concretos (~7 archivos + nuevo árbol de templates):
90
+ 1. Ensanchar `Provider` en `provider-detect.ts:15` **y** `install-config.ts:13` (deben quedar idénticos); relajar validadores `init.ts:86`, `install-config.ts:96`, `bin/tui-installer.mjs:114/118`.
91
+ 2. `detectAvailability` (`provider-detect.ts:33-36`): añadir `commandExists('gemini')`; extender `resolveProvider` (61-80) + el mensaje de error que hoy solo nombra Claude+Codex.
92
+ 3. `derivedPaths` (`provider-detect.ts:87-92`): brazo gemini → `{ '.gemini', 'GEMINI.md' }`.
93
+ 4. `scaffold.ts`: brazos gemini en cada `=== 'codex'` (dir skeleton 174-184, colocación comandos 278-325, quick-tier 493-648, `placeSkills` 762-830, prune 454-462) + `applyGeminiSettings` clon de `applyCodexSettings`.
94
+ 5. `prereqs.ts:91-104`: gatear el bloque claude-only o dar a gemini un path mínimo (sin auth-assert, como codex).
95
+ 6. `doctor.ts:96/160/167` + `update.ts:196-210` (`resolveExistingProvider` probe `.gemini`).
96
+ 7. `integration-contract.json:3-26`: bloque `providers.gemini` (la superficie casi-declarativa que lee el desktop; la de menor fricción).
97
+ 8. OpenSpec: **cero cambios de código** más allá de propagar `gemini` (pasos 1+3 → ya llega a `init.ts:215`).
98
+
99
+ ---
100
+
101
+ ## 4. El bloqueante de orquestación: ¿plano de gemini lo soporta?
102
+
103
+ **Veredicto honesto (verificado contra fuentes Google primarias): el pipeline ES expresable en el modelo plano de gemini SIN rediseño de fases.** No es un blocker de capacidad; es trabajo imperativo en core.
104
+
105
+ Razonamiento (todo confirmado):
106
+ - El pipeline es **recursivo pero SHALLOW**: el orquestador spawnea architect→developer→reviewer a **profundidad 1**; las hojas **nunca re-spawnean** (un developer no llama a un reviewer; lo único "hacia abajo" es un `Skill("opsx:ff/apply/archive")` **in-context**, que shellea al CLI `openspec`, **no es un subagente**). Contrato verbatim: `implement.md:5` "delegate to the agents"; `implement.md:547` spawn de sr-architect; codex `implement/SKILL.md:24` "Each phase MUST be a real spawn_agent call".
107
+ - El modelo de gemini es exactamente eso: orquestador delega depth-1, subagentes **FLAT** (`docs/core/subagents.md` verbatim: "subagents cannot call other subagents"). La restricción plana (las hojas no spawnean) **nunca se viola** porque todo el spawning requerido ocurre en el orquestador a depth-1.
108
+ - **El único rediseño** es `batch-implement`, que conceptualmente anida (batch→implement→roles = depth 2), ilegal en plano. **Pero el proyecto YA lo resolvió para codex**: `codex-skills/batch-implement/SKILL.md:5` "Drives architect/developer/reviewer spawns at the ROOT agent level — does NOT spawn a nested $implement sub-agent per ticket". El batch-implement gemini **reutiliza esa misma disciplina de aplanamiento ya probada**.
109
+
110
+ **Por tanto: NO hay rediseño del pipeline de fases. La viabilidad de rails en gemini está gateada por el trabajo imperativo en core, no por un gap de capacidad de gemini.**
111
+
112
+ Matiz honesto (no inventado): subagents en gemini son **experimentales** (namespace `experimental.enableAgents`, PR #14371, públicos desde v0.38.1 — ahora habilitados por defecto pero aún bajo `experimental.*`). Y queda un **unknown externo** que estos repos no pueden resolver: si el orquestador gemini puede **iniciar** spawns en headless/non-interactivo o si la delegación `@name` está garantizada sin TTY (ver riesgos §6).
113
+
114
+ ---
115
+
116
+ ## 5. Esfuerzo + secuenciación + versión + reutilización agy/Antigravity
117
+
118
+ **Tamaño del trabajo en core:** moderado, no trivial. ~7 archivos editados (`provider-detect`, `install-config`, `scaffold`, `prereqs`, `doctor`, `init`, `update`, `bin/tui-installer.mjs`) + `integration-contract.json` + **un árbol nuevo `templates/gemini-skills/`** (o `gemini-*`) + tests (incluido el contrato `reserved-paths.test.ts`, que debe seguir verde para init+update gemini). La falta de abstracción installer-side significa **N ediciones inline, no un descriptor**. La mayor parte del esfuerzo de autoría es: el emisor TOML, el escritor `GEMINI.md`/settings, y **validar empíricamente** que la orquestación plana de gemini ejecuta las fases en headless.
119
+
120
+ **Secuenciación con el beta desktop:**
121
+ 1. **PR-A #396** (generaliza tipos) y **PR-B #397** (adapter beta-gated) habilitan spec/explore/quick en gemini — **independientes de core**, se mergean primero.
122
+ 2. El target gemini en core es **secuencialmente posterior** y desbloquea rails. Mientras tanto, el desktop debería **ocultar rails/implement para gemini** (capability-intersection: igual que codex fuerza `profile=null` y oculta Agents/Integrations) hasta que core publique el target.
123
+
124
+ **Versión de core:** feature aditiva → conventional-commit `feat:` → release-please **MINOR**. Desde 4.7.1 actual → **4.8.0**. El profile-gate `>= 4.1.0` (`queue-manager.ts:77`, `profiles-router.ts:233`) **no se toca**: proyectos gemini en 4.8.0 ya lo satisfacen. Que gemini **participe** en profiles es una decisión de política del desktop (probablemente como codex: `profile=null`), no un cambio de gate.
125
+
126
+ **¿Puramente aditivo?** En contrato/runtime: **sí** (no rompe claude/codex). En código: **no** (cada branch imperativo gana un brazo gemini). Reserved-paths sin cambios.
127
+
128
+ **Reutilización agy/Antigravity (no es trabajo tirado):** Antigravity comparte el harness `~/.gemini` (mismas convenciones `.gemini/commands/*.toml`, `.gemini/agents/*.md`, `GEMINI.md`). Un target gemini en core que emita en formato `.gemini/*` **sirve de base directa para agy** más adelante — el scaffold gemini es reutilizable casi tal cual. Esto sube el ROI del trabajo de core.
129
+
130
+ ---
131
+
132
+ ## 6. Recomendación
133
+
134
+ ### Decisión: **HACER, pero DESPUÉS del beta desktop de spec/explore/quick (no en paralelo, no bloqueante del beta).**
135
+
136
+ Justificación: el beta desktop (PR-A/PR-B) entrega valor real de gemini (spec/explore/quick) **sin tocar core** y sin riesgo. Los rails en gemini son un trabajo de core mayor, con un unknown empírico (headless), y **no deben retrasar el beta**. El desktop oculta rails para gemini mientras tanto.
137
+
138
+ ### Ownership
139
+ - **Desktop owna:** el adapter gemini (ya hecho), capability-intersection para ocultar rails/Agents/Integrations en gemini hasta que core publique, command-syntax translation en `queue-manager` (hoy claude=verbatim, codex=`/x`→`$x`; gemini necesita su forma), y `profile=null` para rails gemini.
140
+ - **Core owna:** el target de instalación gemini completo (tipos/detección, `.gemini/commands/*.toml`, `.gemini/agents/sr-*.md`, `GEMINI.md`/settings, `templates/gemini-skills/`, `integration-contract.json` gemini block, propagar `gemini` a `openspec init --tools`), y los tests.
141
+
142
+ ### Primer paso concreto en core
143
+ Un **spike de validación de orquestación**, ANTES de escribir el scaffold completo: instalar manualmente a mano un `.gemini/commands/specrails/implement.toml` (prompt mínimo) + `.gemini/agents/sr-{architect,developer,reviewer}.md` (frontmatter con `model`/`tools`) + `enableAgents`, correr `openspec init --tools gemini`, y ejecutar el binario `gemini` en modo headless/exec (como lo spawnearía el desktop) para **confirmar empíricamente** que el orquestador inicia los spawns depth-1 y completa architect→developer→reviewer sin TTY. Si pasa, se procede al scaffold; si no, el riesgo se materializa antes de invertir en los 7 archivos.
144
+
145
+ ### Riesgos honestos
146
+ 1. **TOML sin `tools`/`model` por comando** — confirmado: los `.gemini/commands/*.toml` no pueden restringir tools ni fijar modelo. El routing/gating **debe** vivir en el frontmatter de los `.gemini/agents/*.md`. Si algún comando dependía de gating a nivel comando, hay que re-arquitecturarlo a nivel agente.
147
+ 2. **Subagentes experimentales** — `experimental.enableAgents`; comportamiento puede cambiar entre versiones de gemini-cli. Pinear/probar contra una versión concreta.
148
+ 3. **Shell-injection con confirm interactivo en headless** — los agentes shellean al CLI `openspec` vía Skill in-context; si gemini exige confirmación interactiva de tool-calls de shell y el desktop lo spawnea sin TTY, las fases podrían colgarse. **Riesgo no verificado, alto impacto** — es lo que el spike del primer paso debe descartar.
149
+ 4. **`@`-routing headless no garantizado** — la delegación forzada `@sr-architect` puede no funcionar sin interactividad; quizás haya que apoyarse en auto-delegación, menos determinista para un pipeline que exige las tres fases.
150
+ 5. **OpenSpec install adaptado a gemini** — `openspec init --tools gemini` está confirmado nativo en 1.3.1, pero **no verifiqué empíricamente en esta sesión** que los `.gemini/skills/openspec-*` que genera sean **invocables** por el orquestador gemini en el flujo real (solo que el `--tools` lo acepta). Validar en el spike.
151
+
152
+ ### Lo que NO está cierto (declarado explícitamente)
153
+ - Si el orquestador gemini puede **iniciar** spawns en headless/exec, o si solo el harness puede pre-definir un set fijo de agentes. Si fuera lo segundo, el orquestador `implement` habría que re-expresarlo como agentes paralelos definidos por harness en vez de spawns iniciados por el orquestador — **lift mayor**. Esto **decide la viabilidad real** y no es deducible de estos repos.
154
+ - El comportamiento exacto de confirm de shell-calls de gemini en headless (riesgo #3).
155
+
156
+ **Archivos clave (absolutos):**
157
+ - Core gap: `/Users/javi/repos/specrails-core/src/installer/phases/provider-detect.ts` (15, 33-36, 87-92), `/Users/javi/repos/specrails-core/src/installer/phases/install-config.ts` (13, 96), `/Users/javi/repos/specrails-core/src/installer/phases/scaffold.ts` (174-184, 244-250, 278-311, 666-747, 762-830), `/Users/javi/repos/specrails-core/src/installer/commands/init.ts` (86, 128, 204-218), `/Users/javi/repos/specrails-core/integration-contract.json` (3-26), `/Users/javi/repos/specrails-core/pinned-versions.json` (3).
158
+ - Templates a transformar: `/Users/javi/repos/specrails-core/templates/commands/specrails/implement.md`, `/Users/javi/repos/specrails-core/templates/commands/specrails/batch-implement.md`, `/Users/javi/repos/specrails-core/templates/agents/sr-{architect,developer,reviewer}.md`.
159
+ - Precedente de aplanamiento codex: `/Users/javi/repos/specrails-core/templates/codex-skills/batch-implement/SKILL.md` (5, 19-32), `/Users/javi/repos/specrails-core/templates/codex-skills/implement/SKILL.md` (24).
160
+ - Desktop (ya hecho): `/Users/javi/repos/specrails-desktop/server/providers/gemini-adapter.ts` (225-235), `/Users/javi/repos/specrails-desktop/server/providers/index.ts` (12, 16); a tocar: `/Users/javi/repos/specrails-desktop/server/queue-manager.ts` (command translation, `profile=null`), `/Users/javi/repos/specrails-desktop/client/src/lib/provider-capabilities.ts` (ocultar rails gemini).
package/docs/gemini.md ADDED
@@ -0,0 +1,106 @@
1
+ # Using Specrails with the Gemini CLI
2
+
3
+ Specrails supports Google's [Gemini CLI](https://github.com/google-gemini/gemini-cli)
4
+ as a third AI provider alongside Claude Code and the Codex CLI.
5
+
6
+ > **Beta — opt-in.** Gemini is gated behind `SPECRAILS_GEMINI_BETA` and is **off by
7
+ > default**. Until you set it, Gemini is invisible everywhere (it won't show in Add
8
+ > Project and can't be selected). See [Enabling the beta](#enabling-the-beta).
9
+ >
10
+ > **Scope today.** With the beta on, Gemini powers the *non-pipeline* surfaces —
11
+ > Explore Spec, Quick spec, AI Edit, the terminal launcher, and cost analytics.
12
+ > The full **rails pipeline** (`/specrails:implement`, `batch-implement`) needs a
13
+ > Gemini artifact target in `specrails-core` (`.gemini/commands/*.toml` +
14
+ > `.gemini/agents/sr-*.md`), which ships separately. Rails are hidden for Gemini
15
+ > projects until then.
16
+
17
+ ## Prerequisites
18
+
19
+ - **Gemini CLI ≥ 0.11.0** on your `PATH` (`npm i -g @google/gemini-cli`; `gemini --version`).
20
+ Older versions lack `--output-format stream-json` and headless `--resume`.
21
+ - **A Gemini API key.** Specrails spawns Gemini headlessly, so it needs
22
+ non-interactive auth: set `GEMINI_API_KEY` (a paid Gemini Developer API key from
23
+ [Google AI Studio](https://aistudio.google.com/apikey)). The free OAuth
24
+ "Login with Google" tier exists but is being wound down for the CLI and is not a
25
+ reliable unattended path — use an API key.
26
+
27
+ ## Enabling the beta
28
+
29
+ Set the env var where the Specrails server runs, then restart the app:
30
+
31
+ ```bash
32
+ SPECRAILS_GEMINI_BETA=1 # or 'true'
33
+ ```
34
+
35
+ With it set, `GET /api/available-providers` reports Gemini's real install status,
36
+ Add Project shows a **Gemini** checkbox, and projects can select it.
37
+
38
+ ## Adding a Gemini project
39
+
40
+ Add Project → tick **Gemini** (visible once the beta is on and `gemini` is on PATH).
41
+ A project can install Gemini alongside Claude/Codex; the first provider selected is
42
+ the primary/default, and per-invocation engine pickers let you choose per spec/rail.
43
+
44
+ ## What's different vs Claude
45
+
46
+ - **Cost is estimated, not native.** Gemini does not report a USD cost in its
47
+ stream, so Specrails estimates it from a rate card (`server/pricing.ts`,
48
+ keys `gemini:<model>`). Token counts (incl. cached) come straight from the run.
49
+ - **No per-agent profiles / SMASH / Contract Refine.** These are Claude-only; on a
50
+ mixed project the capability intersection hides them (same as Codex).
51
+ - **System prompt is folded.** Gemini has no `--system-prompt` flag, so for
52
+ non-Explore actions the system prompt is folded into the user prompt. Explore
53
+ turns stay user-only and trust the app-managed `GEMINI.md` in the explore cwd.
54
+ - **OTEL is native.** Unlike Codex, Gemini emits OTLP via `GEMINI_TELEMETRY_*`, so
55
+ pipeline telemetry needs no synthetic bridge.
56
+
57
+ ## Models
58
+
59
+ Curated catalog (`server/providers/gemini-adapter.ts`): `gemini-2.5-pro` (default),
60
+ `gemini-2.5-flash`, `gemini-2.5-flash-lite`, `gemini-3.1-pro-preview`. Concrete GA
61
+ ids are pinned (preview ids rotate). Free-tier API keys serve Flash; Pro/preview
62
+ need billing.
63
+
64
+ ## Trusted folders (headless)
65
+
66
+ Gemini's "trusted folders" gate silently overrides `--yolo` back to `default` —
67
+ blocking *every* tool call — in a directory it doesn't trust. Specrails injects
68
+ `GEMINI_CLI_TRUST_WORKSPACE=true` into every Gemini spawn (`server/util/cli-prompt.ts`
69
+ `spawnGemini`) so headless rails/explore can actually run tools. No action needed
70
+ from you.
71
+
72
+ ## Troubleshooting
73
+
74
+ - **"Gemini" never appears in Add Project** → the beta is off (`SPECRAILS_GEMINI_BETA`
75
+ unset), or `gemini` isn't on PATH. Check `GET /api/available-providers`.
76
+ - **Tools never run / job does nothing** → almost always auth or trust. Confirm
77
+ `GEMINI_API_KEY` is set in the server env; the trust var is handled automatically.
78
+ - **`limit: 0` / quota errors on Pro** → free-tier API keys don't serve Pro models;
79
+ use a Flash model or enable billing.
80
+ - **Cost shows `—`** → no `gemini:<model>` pricing row for the model used; add one to
81
+ `server/pricing.ts`.
82
+
83
+ ## Emergency rollback
84
+
85
+ Unset `SPECRAILS_GEMINI_BETA` (or set it to `0`). Gemini disappears from the UI and
86
+ becomes unselectable immediately; existing non-Gemini projects are unaffected. The
87
+ adapter stays registered but dormant.
88
+
89
+ ## Architecture pointers (for specrails-desktop developers)
90
+
91
+ - Adapter: `server/providers/gemini-adapter.ts` (`nativeCostUsd:false`,
92
+ `nativeOtelEnv:true`, `systemPromptArg:false`, `instructionsFilename:'GEMINI.md'`,
93
+ `mcpRegistration:'project-json'`). Registered in `server/providers/index.ts`.
94
+ - Stream schema (validated against gemini 0.46): `init{session_id,model}` /
95
+ `message{role,content,delta}` / `tool_use{tool_name,tool_id,parameters}` /
96
+ `tool_result` / `result{stats}`. `stats.input_tokens` includes `stats.cached`.
97
+ - Beta gate + provider list: `server/desktop-router.ts` (`isGeminiBetaEnabled`,
98
+ `/available-providers`, `POST /projects`).
99
+ - Trust-folder env: `server/util/cli-prompt.ts` `spawnGemini`.
100
+
101
+ ## See also
102
+
103
+ - [`docs/codex.md`](./codex.md) — the Codex provider (the other non-native provider).
104
+ - [`docs/adding-a-provider.md`](./adding-a-provider.md) — how to add a provider.
105
+ - [`docs/gemini-cli-provider-study.md`](./gemini-cli-provider-study.md) — the design study.
106
+ - [`docs/gemini-core-support-evaluation.md`](./gemini-core-support-evaluation.md) — the rails/core work.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specrails-desktop",
3
- "version": "2.6.0",
3
+ "version": "2.7.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -278,7 +278,7 @@ class ChatManager {
278
278
  slug: this._projectSlug,
279
279
  projectPath: this._cwd,
280
280
  projectName: this._projectName,
281
- provider: (providerId ?? this._adapter.id),
281
+ provider: providerId ?? this._adapter.id,
282
282
  });
283
283
  console.log(`[chat-manager] explore spawn cwd=${cwd} (mcp=off)`);
284
284
  return cwd;