chati-dev 3.2.0 → 3.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -80,12 +80,16 @@ The wizard guides you through 4 steps:
80
80
 
81
81
  Open your AI CLI in the same project directory, then type:
82
82
 
83
- ```
84
- /chati
85
- ```
83
+ | CLI | Command | Notes |
84
+ |-----|---------|-------|
85
+ | **Claude Code** | `/chati` | Slash command (native) |
86
+ | **Gemini CLI** | `/chati` | TOML custom command |
87
+ | **Codex CLI** | `$chati` | Skill invocation (Codex uses `$` for skills, not `/`) |
86
88
 
87
89
  The orchestrator loads your session, detects where you left off, and routes you to the right agent. You stay inside the system until you explicitly exit.
88
90
 
91
+ > **Codex CLI note:** You can also just describe what you want (e.g., "start chati" or "plan my project") and Codex will auto-invoke the skill based on its description.
92
+
89
93
  ### 3. Follow the pipeline
90
94
 
91
95
  The agents guide you through each phase:
@@ -108,10 +112,13 @@ npx chati-dev status --watch # Auto-refresh every 5s
108
112
 
109
113
  ### Exit & Resume
110
114
 
111
- ```
112
- /chati exit # Save session and exit
113
- /chati # Resume exactly where you left off
114
- ```
115
+ | CLI | Exit | Resume |
116
+ |-----|------|--------|
117
+ | **Claude Code** | `/chati exit` | `/chati` |
118
+ | **Gemini CLI** | `/chati exit` | `/chati` |
119
+ | **Codex CLI** | `$chati exit` | `$chati` |
120
+
121
+ The system saves your full session state — pipeline position, current agent, decisions, and context. You resume exactly where you left off, even across days or machines.
115
122
 
116
123
  ---
117
124
 
@@ -127,7 +134,7 @@ npx chati-dev status --watch # Auto-refresh every 5s
127
134
  | **Multi-Terminal** | Autonomous agents run in parallel in separate terminals. Detail, Architect, and UX agents work simultaneously |
128
135
  | **Memory System** | The system learns from mistakes. Gotchas are captured automatically and recalled when relevant |
129
136
  | **Execution Profiles** | Three profiles — explore (read-only), guided (default), autonomous (gate >= 90%) — with safety net and circuit breaker |
130
- | **IDE-Agnostic** | Works with Claude Code, VS Code, Cursor, Gemini CLI, and AntiGravity |
137
+ | **IDE-Agnostic** | Works with Claude Code, VS Code, Cursor, Gemini CLI, Codex CLI, and AntiGravity |
131
138
  | **4 Languages** | Interface supports English, Portuguese, Spanish, and French. Artifacts are always generated in English |
132
139
  | **Supply Chain Security** | Every file is cryptographically signed (Ed25519). Tampered packages are blocked on install |
133
140
 
@@ -203,7 +210,7 @@ The system starts in `guided` mode. Transition to `autonomous` requires both QA
203
210
  ### How the Pipeline Works
204
211
 
205
212
  ```
206
- You type /chati
213
+ You activate the orchestrator (/chati or $chati)
207
214
 
208
215
 
209
216
  ┌──────────────────────────────────────────────┐
@@ -280,13 +287,14 @@ The system is governed by a **19-article Constitution** that enforces agent beha
280
287
 
281
288
  ## Supported IDEs
282
289
 
283
- | IDE | How it connects |
284
- |-----|----------------|
285
- | **Claude Code** | `.claude/commands/chati.md` → orchestrator |
286
- | **VS Code** | `.vscode/chati.md` → orchestrator |
287
- | **Cursor** | `.cursor/rules/chati.md` → orchestrator |
288
- | **Gemini CLI** | `.gemini/commands/chati.toml` → orchestrator |
289
- | **AntiGravity** | Platform agent config → orchestrator |
290
+ | IDE | How it connects | Activation |
291
+ |-----|----------------|------------|
292
+ | **Claude Code** | `.claude/commands/chati.md` → orchestrator | `/chati` |
293
+ | **Gemini CLI** | `.gemini/commands/chati.toml` → orchestrator | `/chati` |
294
+ | **Codex CLI** | `.agents/skills/chati/SKILL.md` → orchestrator | `$chati` |
295
+ | **VS Code** | `.vscode/chati.md` → orchestrator | Extension-dependent |
296
+ | **Cursor** | `.cursor/rules/chati.md` → orchestrator | Extension-dependent |
297
+ | **AntiGravity** | Platform agent config → orchestrator | Platform-dependent |
290
298
 
291
299
  All IDEs use a thin router file that points to the same orchestrator. Your project works the same regardless of which IDE you use.
292
300
 
@@ -323,23 +331,27 @@ All IDEs use a thin router file that points to the same orchestrator. Your proje
323
331
 
324
332
  ### Inside an Active Session
325
333
 
326
- | Command | Description |
327
- |---------|-------------|
328
- | `/chati` | Start or resume session |
329
- | `/chati status` | Show pipeline progress |
330
- | `/chati help` | Show available commands |
331
- | `/chati resume` | Resume from continuation file |
332
- | `/chati exit` | Save session and exit |
334
+ | Action | Claude Code / Gemini | Codex CLI |
335
+ |--------|---------------------|-----------|
336
+ | Start or resume session | `/chati` | `$chati` |
337
+ | Show pipeline progress | `/chati status` | `$chati status` |
338
+ | Show available commands | `/chati help` | `$chati help` |
339
+ | Resume from continuation | `/chati resume` | `$chati resume` |
340
+ | Save session and exit | `/chati exit` | `$chati exit` |
333
341
 
334
342
  ---
335
343
 
336
344
  ## Project Structure
337
345
 
346
+ The structure below shows the Claude Code layout (default). Gemini and Codex get equivalent files in their native locations.
347
+
338
348
  ```
339
349
  your-project/
340
350
  ├── .chati/
341
351
  │ ├── session.yaml # Session state (auto-managed, gitignored)
342
352
  │ └── memories/ # Memory storage (gitignored)
353
+
354
+ │── # ─── Claude Code ───────────────────
343
355
  ├── .claude/
344
356
  │ ├── commands/
345
357
  │ │ └── chati.md # Thin router → orchestrator
@@ -350,7 +362,26 @@ your-project/
350
362
  │ ├── protocols.md # Universal protocols
351
363
  │ └── quality.md # Quality standards
352
364
  ├── CLAUDE.md # Project context (auto-generated)
353
- ├── CLAUDE.local.md # Runtime state (gitignored)
365
+ ├── CLAUDE.local.md # Runtime state / session lock (gitignored)
366
+
367
+ │── # ─── Gemini CLI (when selected) ────
368
+ ├── .gemini/
369
+ │ ├── commands/
370
+ │ │ └── chati.toml # TOML command → orchestrator
371
+ │ ├── context/ # 4 framework context files (@imported by GEMINI.md)
372
+ │ ├── hooks/ # 6 hooks (BeforeModel, BeforeTool, PreCompress)
373
+ │ ├── settings.json # Hook configuration
374
+ │ └── session-lock.md # Runtime state / session lock (gitignored)
375
+ ├── GEMINI.md # Project context with @import chain
376
+
377
+ │── # ─── Codex CLI (when selected) ─────
378
+ ├── .agents/skills/chati/
379
+ │ └── SKILL.md # Skill definition → orchestrator
380
+ ├── .codex/rules/ # Starlark execution policies (constitution-guard, read-protection)
381
+ ├── AGENTS.md # Project context with inline governance
382
+ ├── AGENTS.override.md # Runtime state / session lock (gitignored)
383
+
384
+ │── # ─── Framework ─────────────────────
354
385
  ├── chati.dev/
355
386
  │ ├── orchestrator/ # Main orchestrator
356
387
  │ ├── agents/ # 13 agent definitions
@@ -364,8 +395,8 @@ your-project/
364
395
  │ ├── schemas/ # 5 JSON schemas
365
396
  │ ├── intelligence/ # PRISM, RECALL, COMPASS specs
366
397
  │ ├── domains/ # Per-agent and per-workflow configs
367
- │ ├── hooks/ # 6 Claude Code hooks
368
- │ ├── context/ # Context files (deployed to .claude/rules/)
398
+ │ ├── hooks/ # 6 shared hooks (used by Claude + Gemini)
399
+ │ ├── context/ # Context files (deployed per provider)
369
400
  │ ├── frameworks/ # Decision heuristics
370
401
  │ ├── quality-gates/ # Planning & implementation gates
371
402
  │ ├── patterns/ # Elicitation patterns
@@ -1,8 +1,8 @@
1
1
  # chati.dev Configuration
2
- version: "3.2.0"
2
+ version: "3.2.2"
3
3
  installed_at: "2026-02-07T10:00:00Z"
4
- updated_at: "2026-02-19T00:00:00Z"
5
- installer_version: "3.2.0"
4
+ updated_at: "2026-02-20T00:00:00Z"
5
+ installer_version: "3.2.2"
6
6
  project_type: greenfield
7
7
  language: en
8
8
  ides: [claude-code]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chati-dev",
3
- "version": "3.2.0",
3
+ "version": "3.2.2",
4
4
  "description": "AI-Powered Multi-Agent Orchestration System — Structured vibe coding for Full Stack Development",
5
5
  "type": "module",
6
6
  "bin": {
@@ -29,11 +29,12 @@ const GEMINI_REPLACEMENTS = [
29
29
  ['CLAUDE.md', 'GEMINI.md'],
30
30
  ['Claude.md', 'GEMINI.md'],
31
31
  ['.claude/commands/', '.gemini/commands/'],
32
- ['.claude/rules/', 'chati.dev/context/'],
32
+ ['.claude/rules/chati/', '.gemini/context/'],
33
+ ['.claude/rules/', '.gemini/context/'],
33
34
  ['.claude/mcp.json', '.gemini/settings.json'],
34
35
  ['claude --print', 'gemini --prompt'],
35
36
  ['claude -p', 'gemini --prompt'],
36
- ['CLAUDE.local.md', 'GEMINI.local.md'],
37
+ ['CLAUDE.local.md', '.gemini/session-lock.md'],
37
38
  ];
38
39
 
39
40
  /**
@@ -56,22 +57,38 @@ const CODEX_REPLACEMENTS = [
56
57
  ['CLAUDE.md', 'AGENTS.md'],
57
58
  ['Claude.md', 'AGENTS.md'],
58
59
  ['.claude/commands/', 'chati.dev/orchestrator/'],
60
+ ['.claude/rules/chati/', 'chati.dev/context/'],
59
61
  ['.claude/rules/', 'chati.dev/context/'],
60
62
  ['.claude/mcp.json', '.codex/config.toml'],
61
63
  ['claude --print', 'codex exec'],
62
64
  ['claude -p', 'codex exec'],
63
- ['CLAUDE.local.md', 'AGENTS.local.md'],
65
+ ['CLAUDE.local.md', 'AGENTS.override.md'],
66
+ ['/chati', '$chati'],
64
67
  ];
65
68
 
66
69
  // ---------------------------------------------------------------------------
67
70
  // Generators
68
71
  // ---------------------------------------------------------------------------
69
72
 
73
+ /**
74
+ * @import directives appended to GEMINI.md.
75
+ * Gemini CLI resolves @import automatically, loading the full governance
76
+ * context chain + session lock — equivalent to Claude Code's rules/ + CLAUDE.local.md.
77
+ */
78
+ const GEMINI_IMPORTS = [
79
+ '@import .gemini/context/root.md',
80
+ '@import .gemini/context/governance.md',
81
+ '@import .gemini/context/protocols.md',
82
+ '@import .gemini/context/quality.md',
83
+ '@import .gemini/session-lock.md',
84
+ ];
85
+
70
86
  /**
71
87
  * Generate GEMINI.md content from CLAUDE.md content.
72
88
  *
73
89
  * Adapts the content by replacing Claude Code-specific references with
74
- * Gemini CLI equivalents. Preserves structure and formatting.
90
+ * Gemini CLI equivalents, then appends @import directives so Gemini CLI
91
+ * auto-loads governance rules and session lock (context parity with Claude).
75
92
  *
76
93
  * @param {string} content - Raw CLAUDE.md content
77
94
  * @returns {string} Adapted content for GEMINI.md
@@ -89,7 +106,10 @@ export function generateGeminiMd(content) {
89
106
  '',
90
107
  ].join('\n');
91
108
 
92
- return header + result;
109
+ // Append @import directives for context parity
110
+ const imports = '\n' + GEMINI_IMPORTS.join('\n') + '\n';
111
+
112
+ return header + result + imports;
93
113
  }
94
114
 
95
115
  /**
@@ -97,12 +117,14 @@ export function generateGeminiMd(content) {
97
117
  *
98
118
  * Simplifies the content for Codex CLI: strips hook references,
99
119
  * replaces CLI-specific paths, and produces a leaner context file
100
- * focused on code execution.
120
+ * focused on code execution. When contextFiles are provided, inlines
121
+ * governance/protocols/quality for full context parity with Claude Code.
101
122
  *
102
123
  * @param {string} content - Raw CLAUDE.md content
124
+ * @param {object} [contextFiles] - Optional inline context { root, governance, protocols, quality }
103
125
  * @returns {string} Adapted content for AGENTS.md
104
126
  */
105
- export function generateAgentsMd(content) {
127
+ export function generateAgentsMd(content, contextFiles = null) {
106
128
  let result = content;
107
129
 
108
130
  // Strip hook-related sections (Codex has no hooks support)
@@ -124,13 +146,39 @@ export function generateAgentsMd(content) {
124
146
  '',
125
147
  ].join('\n');
126
148
 
127
- return header + result;
149
+ result = header + result;
150
+
151
+ // Inline context files for full governance parity (Codex has no @import)
152
+ if (contextFiles) {
153
+ const sections = [];
154
+ for (const [name, fileContent] of Object.entries(contextFiles)) {
155
+ if (fileContent) {
156
+ // Adapt context for Codex (replace Claude-specific refs)
157
+ let adapted = fileContent;
158
+ for (const [search, replace] of CODEX_REPLACEMENTS) {
159
+ adapted = adapted.replaceAll(search, replace);
160
+ }
161
+ sections.push(adapted.trim());
162
+ }
163
+ }
164
+ if (sections.length > 0) {
165
+ result += '\n---\n\n' + sections.join('\n\n---\n\n') + '\n';
166
+ }
167
+ }
168
+
169
+ return result;
128
170
  }
129
171
 
130
172
  // ---------------------------------------------------------------------------
131
173
  // Orchestrator
132
174
  // ---------------------------------------------------------------------------
133
175
 
176
+ /**
177
+ * Context file names that provide governance parity across CLIs.
178
+ * These are read from chati.dev/context/ and inlined into AGENTS.md for Codex.
179
+ */
180
+ const CONTEXT_FILES = ['root.md', 'governance.md', 'protocols.md', 'quality.md'];
181
+
134
182
  /**
135
183
  * Generate context files for all enabled alternative providers.
136
184
  *
@@ -138,6 +186,8 @@ export function generateAgentsMd(content) {
138
186
  * CLAUDE.md from the project root, and writes the appropriate context
139
187
  * files (GEMINI.md, AGENTS.md) when their providers are active.
140
188
  *
189
+ * For Codex, governance context files are inlined into AGENTS.md to provide
190
+ * the same rules awareness that Claude gets from .claude/rules/chati/.
141
191
  *
142
192
  * @param {string} projectDir - Project root directory
143
193
  * @param {string} [baseContent] - Optional base content (used when CLAUDE.md doesn't exist on disk)
@@ -160,15 +210,18 @@ export function generateContextFiles(projectDir, baseContent = null) {
160
210
  // Determine enabled providers from config.yaml
161
211
  const enabledProviders = resolveEnabledProviders(projectDir);
162
212
 
213
+ // Read context files for inline injection (Codex)
214
+ const contextFiles = readContextFilesFromDisk(projectDir);
215
+
163
216
  // Provider-to-generator mapping
164
217
  const generators = {
165
218
  gemini: {
166
219
  filename: 'GEMINI.md',
167
- generate: generateGeminiMd,
220
+ generate: (content) => generateGeminiMd(content),
168
221
  },
169
222
  codex: {
170
223
  filename: 'AGENTS.md',
171
- generate: generateAgentsMd,
224
+ generate: (content) => generateAgentsMd(content, contextFiles),
172
225
  },
173
226
  };
174
227
 
@@ -186,6 +239,30 @@ export function generateContextFiles(projectDir, baseContent = null) {
186
239
  return result;
187
240
  }
188
241
 
242
+ /**
243
+ * Read context files from chati.dev/context/ for inline injection.
244
+ * Returns null if no context files found (backward compat).
245
+ *
246
+ * @param {string} projectDir - Project root directory
247
+ * @returns {object|null} { root, governance, protocols, quality } or null
248
+ */
249
+ function readContextFilesFromDisk(projectDir) {
250
+ const contextDir = join(projectDir, 'chati.dev', 'context');
251
+ if (!existsSync(contextDir)) return null;
252
+
253
+ const files = {};
254
+ let found = false;
255
+ for (const file of CONTEXT_FILES) {
256
+ const filePath = join(contextDir, file);
257
+ if (existsSync(filePath)) {
258
+ const key = file.replace('.md', '');
259
+ files[key] = readFileSync(filePath, 'utf-8');
260
+ found = true;
261
+ }
262
+ }
263
+ return found ? files : null;
264
+ }
265
+
189
266
  // ---------------------------------------------------------------------------
190
267
  // Helpers
191
268
  // ---------------------------------------------------------------------------
@@ -50,12 +50,12 @@ const PROVIDER_META = {
50
50
  gemini: {
51
51
  cliName: 'Gemini CLI',
52
52
  contextFile: 'GEMINI.md',
53
- localFile: '.chati/session.yaml',
53
+ localFile: '.gemini/session-lock.md',
54
54
  },
55
55
  codex: {
56
56
  cliName: 'Codex CLI',
57
57
  contextFile: 'AGENTS.md',
58
- localFile: '.chati/session.yaml',
58
+ localFile: 'AGENTS.override.md',
59
59
  },
60
60
  };
61
61
 
@@ -103,6 +103,11 @@ function buildReplacementConfig(provider) {
103
103
  ['/model haiku', `/model ${minimal}`],
104
104
  ];
105
105
 
106
+ // Codex CLI uses $chati for skill invocation, not /chati
107
+ if (provider === 'codex') {
108
+ strings.push(['/chati', '$chati']);
109
+ }
110
+
106
111
  // --- Regex patterns for model names in structured contexts ---
107
112
  const patterns = [
108
113
  // Identity section: **Model**: opus | ...
@@ -0,0 +1,494 @@
1
+ /**
2
+ * @fileoverview Gemini CLI hooks generator.
3
+ *
4
+ * Generates thin wrapper hooks for Gemini CLI that import shared logic
5
+ * from chati.dev/hooks/. Each hook translates between Gemini CLI event
6
+ * format and the existing hook logic, providing governance parity with
7
+ * Claude Code.
8
+ *
9
+ * Constitution Article XIX — hooks are generated at install time, zero runtime overhead.
10
+ *
11
+ * Gemini CLI Hook Events (used by chati.dev):
12
+ * - BeforeModel: runs before model inference (PRISM injection, model advisory)
13
+ * - BeforeTool: runs before tool execution (mode governance, constitution guard, read protection)
14
+ * - PreCompress: runs before context compression (session digest)
15
+ */
16
+
17
+ // ---------------------------------------------------------------------------
18
+ // Hook Map — Claude Hook → Gemini Event
19
+ // ---------------------------------------------------------------------------
20
+
21
+ /**
22
+ * Maps each chati.dev hook to its Gemini CLI equivalent event.
23
+ */
24
+ export const HOOK_MAP = {
25
+ 'prism-engine': { event: 'BeforeModel', description: 'Inject PRISM context into model prompt' },
26
+ 'model-governance': { event: 'BeforeModel', description: 'Advisory: recommended model per agent' },
27
+ 'mode-governance': { event: 'BeforeTool', description: 'Block writes outside current mode scope' },
28
+ 'constitution-guard': { event: 'BeforeTool', description: 'Block destructive commands and secret writes' },
29
+ 'read-protection': { event: 'BeforeTool', description: 'Block reads of sensitive files' },
30
+ 'session-digest': { event: 'PreCompress', description: 'Save session state before context compression' },
31
+ };
32
+
33
+ // ---------------------------------------------------------------------------
34
+ // Hook Templates
35
+ // ---------------------------------------------------------------------------
36
+
37
+ /**
38
+ * Shared header for all generated hooks.
39
+ */
40
+ const HOOK_HEADER = `#!/usr/bin/env node
41
+ /**
42
+ * Auto-generated by chati.dev — Gemini CLI hook wrapper.
43
+ * Imports shared logic from chati.dev/hooks/ for governance parity.
44
+ * Do not edit manually — regenerate with \`npx chati-dev init\`.
45
+ */
46
+
47
+ import { existsSync, readFileSync } from 'fs';
48
+ import { join } from 'path';
49
+ import { fileURLToPath } from 'url';
50
+
51
+ const __filename = fileURLToPath(import.meta.url);
52
+ const __dirname = join(__filename, '..');
53
+ const projectRoot = join(__dirname, '..', '..');
54
+ `;
55
+
56
+ /**
57
+ * Generate the PRISM engine hook for Gemini CLI.
58
+ * BeforeModel event — injects PRISM context XML into the model prompt.
59
+ */
60
+ function generatePrismEngine() {
61
+ return `${HOOK_HEADER}
62
+ /**
63
+ * PRISM Engine — BeforeModel
64
+ * Reads session state and injects PRISM context block.
65
+ */
66
+ async function main() {
67
+ let input = '';
68
+ for await (const chunk of process.stdin) {
69
+ input += chunk;
70
+ }
71
+
72
+ try {
73
+ const event = JSON.parse(input);
74
+ const cwd = event.cwd || process.cwd();
75
+
76
+ const sessionPath = join(cwd, '.chati', 'session.yaml');
77
+ if (!existsSync(sessionPath)) {
78
+ console.log(JSON.stringify({}));
79
+ return;
80
+ }
81
+
82
+ // Delegate to shared PRISM engine logic
83
+ const hookPath = join(cwd, 'chati.dev', 'hooks', 'prism-engine.js');
84
+ if (existsSync(hookPath)) {
85
+ const mod = await import(hookPath);
86
+ if (typeof mod.buildPrismContext === 'function') {
87
+ const context = await mod.buildPrismContext(cwd);
88
+ if (context) {
89
+ console.log(JSON.stringify({ additionalContext: context }));
90
+ return;
91
+ }
92
+ }
93
+ }
94
+
95
+ console.log(JSON.stringify({}));
96
+ } catch {
97
+ console.log(JSON.stringify({}));
98
+ }
99
+ }
100
+
101
+ main();
102
+ `;
103
+ }
104
+
105
+ /**
106
+ * Generate the model governance hook for Gemini CLI.
107
+ * BeforeModel event — advisory about recommended model per agent.
108
+ */
109
+ function generateModelGovernance() {
110
+ return `${HOOK_HEADER}
111
+ /**
112
+ * Model Governance — BeforeModel
113
+ * Advisory: logs recommended model for current agent.
114
+ */
115
+ async function main() {
116
+ let input = '';
117
+ for await (const chunk of process.stdin) {
118
+ input += chunk;
119
+ }
120
+
121
+ try {
122
+ const event = JSON.parse(input);
123
+ const cwd = event.cwd || process.cwd();
124
+
125
+ const sessionPath = join(cwd, '.chati', 'session.yaml');
126
+ if (!existsSync(sessionPath)) {
127
+ console.log(JSON.stringify({}));
128
+ return;
129
+ }
130
+
131
+ // Read current agent from session
132
+ const raw = readFileSync(sessionPath, 'utf-8');
133
+ const agentMatch = raw.match(/^\\s*current_agent:\\s*(.+)$/m);
134
+ const agent = agentMatch ? agentMatch[1].trim().replace(/^["']|["']$/g, '') : null;
135
+
136
+ if (agent) {
137
+ // Delegate to shared model governance logic
138
+ const hookPath = join(cwd, 'chati.dev', 'hooks', 'model-governance.js');
139
+ if (existsSync(hookPath)) {
140
+ const mod = await import(hookPath);
141
+ if (typeof mod.getRecommendedModel === 'function') {
142
+ const recommendation = mod.getRecommendedModel(cwd, agent);
143
+ if (recommendation) {
144
+ console.log(JSON.stringify({ advisory: \`Recommended model: \${recommendation}\` }));
145
+ return;
146
+ }
147
+ }
148
+ }
149
+ }
150
+
151
+ console.log(JSON.stringify({}));
152
+ } catch {
153
+ console.log(JSON.stringify({}));
154
+ }
155
+ }
156
+
157
+ main();
158
+ `;
159
+ }
160
+
161
+ /**
162
+ * Generate the mode governance hook for Gemini CLI.
163
+ * BeforeTool event — blocks writes outside the scope of current governance mode.
164
+ */
165
+ function generateModeGovernance() {
166
+ return `${HOOK_HEADER}
167
+ /**
168
+ * Mode Governance — BeforeTool
169
+ * Blocks write operations outside current mode scope.
170
+ * Constitution Article XI enforcement.
171
+ */
172
+ const MODE_SCOPES = {
173
+ planning: { allowed: ['chati.dev/', '.chati/', 'chati.dev/artifacts/'] },
174
+ build: { allowed: ['*'] },
175
+ deploy: { allowed: ['*'] },
176
+ };
177
+
178
+ const STATE_TO_MODE = {
179
+ discover: 'planning', plan: 'planning', planning: 'planning',
180
+ build: 'build', validate: 'build',
181
+ deploy: 'deploy', completed: 'deploy',
182
+ };
183
+
184
+ function getCurrentMode(cwd) {
185
+ const sessionPath = join(cwd, '.chati', 'session.yaml');
186
+ if (!existsSync(sessionPath)) return 'planning';
187
+ const raw = readFileSync(sessionPath, 'utf-8');
188
+ const match = raw.match(/^\\s*state:\\s*(.+)$/m);
189
+ const state = match ? match[1].trim().replace(/^["']|["']$/g, '') : 'discover';
190
+ return STATE_TO_MODE[state] || 'planning';
191
+ }
192
+
193
+ function isPathAllowed(filePath, cwd, mode) {
194
+ const scope = MODE_SCOPES[mode];
195
+ if (!scope) return false;
196
+ if (scope.allowed.includes('*')) return true;
197
+
198
+ // Normalize to relative path
199
+ const rel = filePath.startsWith('/') ? filePath.slice(cwd.length + 1) : filePath;
200
+
201
+ // Check path escape
202
+ if (rel.startsWith('..') || rel.startsWith('/')) return false;
203
+
204
+ return scope.allowed.some(prefix => rel.startsWith(prefix));
205
+ }
206
+
207
+ async function main() {
208
+ let input = '';
209
+ for await (const chunk of process.stdin) {
210
+ input += chunk;
211
+ }
212
+
213
+ try {
214
+ const event = JSON.parse(input);
215
+ const cwd = event.cwd || process.cwd();
216
+ const toolName = event.tool_name || '';
217
+ const toolInput = event.tool_input || {};
218
+
219
+ // Only intercept write/edit operations
220
+ if (!['WriteFile', 'EditFile', 'Write', 'Edit'].includes(toolName)) {
221
+ console.log(JSON.stringify({}));
222
+ return;
223
+ }
224
+
225
+ const filePath = toolInput.path || toolInput.file_path || '';
226
+ if (!filePath) {
227
+ console.log(JSON.stringify({}));
228
+ return;
229
+ }
230
+
231
+ const mode = getCurrentMode(cwd);
232
+ if (isPathAllowed(filePath, cwd, mode)) {
233
+ console.log(JSON.stringify({}));
234
+ } else {
235
+ console.log(JSON.stringify({
236
+ error: \`[Article XI] Cannot write "\${filePath}" in \${mode} mode. Allowed: \${MODE_SCOPES[mode].allowed.join(', ')}\`
237
+ }));
238
+ }
239
+ } catch {
240
+ console.log(JSON.stringify({}));
241
+ }
242
+ }
243
+
244
+ main();
245
+ `;
246
+ }
247
+
248
+ /**
249
+ * Generate the constitution guard hook for Gemini CLI.
250
+ * BeforeTool event — blocks destructive commands and secret writes.
251
+ */
252
+ function generateConstitutionGuard() {
253
+ return `${HOOK_HEADER}
254
+ /**
255
+ * Constitution Guard — BeforeTool
256
+ * Blocks destructive commands and secret writes.
257
+ * Constitution Article XI enforcement.
258
+ */
259
+
260
+ const DESTRUCTIVE_PATTERNS = [
261
+ /rm\\s+-rf\\s+\\//,
262
+ /git\\s+reset\\s+--hard/,
263
+ /git\\s+push\\s+--force/,
264
+ /git\\s+push\\s+-f/,
265
+ /git\\s+clean\\s+-fd/,
266
+ /DROP\\s+TABLE/i,
267
+ /DROP\\s+DATABASE/i,
268
+ /TRUNCATE\\s+TABLE/i,
269
+ /chmod\\s+777/,
270
+ ];
271
+
272
+ const SECRET_PATTERNS = [
273
+ /API_KEY\\s*=/,
274
+ /SECRET_KEY\\s*=/,
275
+ /PASSWORD\\s*=/,
276
+ /PRIVATE_KEY\\s*=/,
277
+ /aws_secret_access_key/i,
278
+ /-----BEGIN.*PRIVATE KEY-----/,
279
+ ];
280
+
281
+ const PROTECTED_WRITE_FILES = ['.env', '.env.local', '.env.production', 'credentials.json', 'service-account.json'];
282
+ const SAFE_PATTERNS = ['.env.example', '.env.template', '.env.sample'];
283
+
284
+ async function main() {
285
+ let input = '';
286
+ for await (const chunk of process.stdin) {
287
+ input += chunk;
288
+ }
289
+
290
+ try {
291
+ const event = JSON.parse(input);
292
+ const toolName = event.tool_name || '';
293
+ const toolInput = event.tool_input || {};
294
+
295
+ // Check Bash commands for destructive patterns
296
+ if (['Bash', 'RunCommand'].includes(toolName)) {
297
+ const cmd = toolInput.command || '';
298
+ for (const pattern of DESTRUCTIVE_PATTERNS) {
299
+ if (pattern.test(cmd)) {
300
+ console.log(JSON.stringify({
301
+ error: \`[Constitution Guard] Blocked destructive command: \${cmd.slice(0, 80)}\`
302
+ }));
303
+ return;
304
+ }
305
+ }
306
+ }
307
+
308
+ // Check Write/Edit for secret patterns and protected files
309
+ if (['WriteFile', 'EditFile', 'Write', 'Edit'].includes(toolName)) {
310
+ const filePath = toolInput.path || toolInput.file_path || '';
311
+ const content = toolInput.content || toolInput.new_string || '';
312
+
313
+ // Check if writing to a protected file
314
+ if (PROTECTED_WRITE_FILES.some(p => filePath.endsWith(p)) &&
315
+ !SAFE_PATTERNS.some(s => filePath.endsWith(s))) {
316
+ console.log(JSON.stringify({
317
+ error: \`[Constitution Guard] Cannot write to protected file: \${filePath}\`
318
+ }));
319
+ return;
320
+ }
321
+
322
+ // Check content for secret patterns
323
+ for (const pattern of SECRET_PATTERNS) {
324
+ if (pattern.test(content)) {
325
+ console.log(JSON.stringify({
326
+ error: '[Constitution Guard] Blocked write containing secrets/credentials'
327
+ }));
328
+ return;
329
+ }
330
+ }
331
+ }
332
+
333
+ console.log(JSON.stringify({}));
334
+ } catch {
335
+ console.log(JSON.stringify({}));
336
+ }
337
+ }
338
+
339
+ main();
340
+ `;
341
+ }
342
+
343
+ /**
344
+ * Generate the read protection hook for Gemini CLI.
345
+ * BeforeTool event — blocks reads of sensitive files.
346
+ */
347
+ function generateReadProtection() {
348
+ return `${HOOK_HEADER}
349
+ /**
350
+ * Read Protection — BeforeTool
351
+ * Blocks reading sensitive files (.env, .pem, credentials).
352
+ * Constitution Article XI enforcement.
353
+ */
354
+
355
+ const PROTECTED_FILES = ['.env', '.env.local', '.env.production', '.env.staging'];
356
+ const PROTECTED_EXTENSIONS = ['.pem', '.key', '.p12', '.pfx', '.jks'];
357
+ const PROTECTED_PATHS = ['credentials.json', 'service-account.json', '.git/config', '.aws/credentials', '.npmrc'];
358
+ const SAFE_FILES = ['.env.example', '.env.template', '.env.sample'];
359
+
360
+ async function main() {
361
+ let input = '';
362
+ for await (const chunk of process.stdin) {
363
+ input += chunk;
364
+ }
365
+
366
+ try {
367
+ const event = JSON.parse(input);
368
+ const toolName = event.tool_name || '';
369
+ const toolInput = event.tool_input || {};
370
+
371
+ // Only intercept read operations
372
+ if (!['ReadFile', 'Read'].includes(toolName)) {
373
+ console.log(JSON.stringify({}));
374
+ return;
375
+ }
376
+
377
+ const filePath = toolInput.path || toolInput.file_path || '';
378
+ if (!filePath) {
379
+ console.log(JSON.stringify({}));
380
+ return;
381
+ }
382
+
383
+ // Allow safe patterns first
384
+ if (SAFE_FILES.some(s => filePath.endsWith(s))) {
385
+ console.log(JSON.stringify({}));
386
+ return;
387
+ }
388
+
389
+ // Block protected files
390
+ if (PROTECTED_FILES.some(p => filePath.endsWith(p))) {
391
+ console.log(JSON.stringify({
392
+ error: \`[Read Protection] Cannot read protected file: \${filePath}\`
393
+ }));
394
+ return;
395
+ }
396
+
397
+ // Block protected extensions
398
+ if (PROTECTED_EXTENSIONS.some(ext => filePath.endsWith(ext))) {
399
+ console.log(JSON.stringify({
400
+ error: \`[Read Protection] Cannot read sensitive file type: \${filePath}\`
401
+ }));
402
+ return;
403
+ }
404
+
405
+ // Block protected paths
406
+ if (PROTECTED_PATHS.some(p => filePath.includes(p))) {
407
+ console.log(JSON.stringify({
408
+ error: \`[Read Protection] Cannot read protected path: \${filePath}\`
409
+ }));
410
+ return;
411
+ }
412
+
413
+ console.log(JSON.stringify({}));
414
+ } catch {
415
+ console.log(JSON.stringify({}));
416
+ }
417
+ }
418
+
419
+ main();
420
+ `;
421
+ }
422
+
423
+ /**
424
+ * Generate the session digest hook for Gemini CLI.
425
+ * PreCompress event — saves session state before context compression.
426
+ */
427
+ function generateSessionDigest() {
428
+ return `${HOOK_HEADER}
429
+ /**
430
+ * Session Digest — PreCompress
431
+ * Saves session state before context compression.
432
+ */
433
+ async function main() {
434
+ let input = '';
435
+ for await (const chunk of process.stdin) {
436
+ input += chunk;
437
+ }
438
+
439
+ try {
440
+ const event = JSON.parse(input);
441
+ const cwd = event.cwd || process.cwd();
442
+
443
+ // Delegate to shared session digest logic
444
+ const hookPath = join(cwd, 'chati.dev', 'hooks', 'session-digest.js');
445
+ if (existsSync(hookPath)) {
446
+ const mod = await import(hookPath);
447
+ if (typeof mod.saveDigest === 'function') {
448
+ await mod.saveDigest(cwd);
449
+ }
450
+ }
451
+
452
+ console.log(JSON.stringify({}));
453
+ } catch {
454
+ console.log(JSON.stringify({}));
455
+ }
456
+ }
457
+
458
+ main();
459
+ `;
460
+ }
461
+
462
+ // ---------------------------------------------------------------------------
463
+ // Public API
464
+ // ---------------------------------------------------------------------------
465
+
466
+ /**
467
+ * Generate all 6 Gemini CLI hook files.
468
+ *
469
+ * @returns {Record<string, string>} Map of filename → file content
470
+ */
471
+ export function generateAllGeminiHooks() {
472
+ return {
473
+ 'prism-engine.js': generatePrismEngine(),
474
+ 'model-governance.js': generateModelGovernance(),
475
+ 'mode-governance.js': generateModeGovernance(),
476
+ 'constitution-guard.js': generateConstitutionGuard(),
477
+ 'read-protection.js': generateReadProtection(),
478
+ 'session-digest.js': generateSessionDigest(),
479
+ };
480
+ }
481
+
482
+ /**
483
+ * Generate .gemini/settings.json content with hook configuration.
484
+ *
485
+ * @returns {string} JSON string for .gemini/settings.json
486
+ */
487
+ export function generateGeminiSettings() {
488
+ const hooks = Object.entries(HOOK_MAP).map(([name, config]) => ({
489
+ path: `.gemini/hooks/${name}.js`,
490
+ event: config.event,
491
+ }));
492
+
493
+ return JSON.stringify({ hooks }, null, 2) + '\n';
494
+ }
@@ -47,15 +47,19 @@ export const IDE_CONFIGS = {
47
47
  rulesFile: 'AGENTS.md',
48
48
  mcpConfigFile: null,
49
49
  formatNotes: 'Codex skill format (SKILL.md with YAML frontmatter)',
50
+ rulesPath: '.codex/rules/',
51
+ overrideFile: 'AGENTS.override.md',
50
52
  },
51
53
  'gemini-cli': {
52
54
  name: 'Gemini CLI',
53
55
  description: 'Google AI terminal agent',
54
56
  recommended: false,
55
57
  configPath: '.gemini/commands/',
56
- rulesFile: null,
58
+ rulesFile: 'GEMINI.md',
57
59
  mcpConfigFile: '.gemini/settings.json',
58
60
  formatNotes: 'TOML command format',
61
+ contextPath: '.gemini/context/',
62
+ hooksPath: '.gemini/hooks/',
59
63
  },
60
64
  };
61
65
 
@@ -3,7 +3,7 @@ import { join, dirname } from 'path';
3
3
  import { fileURLToPath } from 'url';
4
4
  import { IDE_CONFIGS } from '../config/ide-configs.js';
5
5
  import { generateClaudeMCPConfig } from '../config/mcp-configs.js';
6
- import { generateSessionYaml, generateConfigYaml, generateClaudeMd, generateClaudeLocalMd, generateCodexSkill, generateGeminiRouter } from './templates.js';
6
+ import { generateSessionYaml, generateConfigYaml, generateClaudeMd, generateClaudeLocalMd, generateCodexSkill, generateGeminiRouter, generateGeminiSessionLock, generateAgentsOverrideMd, generateCodexConstitutionGuardRules, generateCodexReadProtectionRules } from './templates.js';
7
7
  import { generateContextFiles } from '../config/context-file-generator.js';
8
8
  import { adaptFrameworkFile, ADAPTABLE_FILES } from '../config/framework-adapter.js';
9
9
  import { verifyManifest } from './manifest.js';
@@ -128,6 +128,9 @@ export async function installFramework(config) {
128
128
  if (hasNonClaude) {
129
129
  generateContextFiles(targetDir, baseContent);
130
130
  }
131
+
132
+ // 7. Update .gitignore with runtime session lock files
133
+ updateGitignore(targetDir, selectedIDEs);
131
134
  }
132
135
 
133
136
  /**
@@ -314,9 +317,42 @@ Pass through all context: session state, handoffs, artifacts, and user input.
314
317
  // Codex CLI: chati skill via .agents/skills/chati/SKILL.md (invoke with $chati)
315
318
  createDir(join(targetDir, '.agents', 'skills', 'chati'));
316
319
  writeFileSync(join(targetDir, '.agents', 'skills', 'chati', 'SKILL.md'), generateCodexSkill(), 'utf-8');
320
+
321
+ // Session lock override file (equivalent to CLAUDE.local.md)
322
+ writeFileSync(join(targetDir, 'AGENTS.override.md'), generateAgentsOverrideMd(), 'utf-8');
323
+
324
+ // Starlark execution policies (equivalent to Claude hooks for constitution guard + read protection)
325
+ createDir(join(targetDir, '.codex', 'rules'));
326
+ writeFileSync(join(targetDir, '.codex', 'rules', 'constitution-guard.rules'), generateCodexConstitutionGuardRules(), 'utf-8');
327
+ writeFileSync(join(targetDir, '.codex', 'rules', 'read-protection.rules'), generateCodexReadProtectionRules(), 'utf-8');
317
328
  } else if (ideKey === 'gemini-cli') {
318
329
  // Gemini CLI: TOML command file (native format for /chati command)
319
330
  writeFileSync(join(targetDir, '.gemini', 'commands', 'chati.toml'), generateGeminiRouter(), 'utf-8');
331
+
332
+ // Context files via @import (equivalent to .claude/rules/chati/)
333
+ const geminiContextDir = join(targetDir, '.gemini', 'context');
334
+ createDir(geminiContextDir);
335
+ const contextFileNames = ['root.md', 'governance.md', 'protocols.md', 'quality.md'];
336
+ for (const file of contextFileNames) {
337
+ const src = join(FRAMEWORK_SOURCE, 'context', file);
338
+ if (existsSync(src)) {
339
+ const content = readFileSync(src, 'utf-8');
340
+ writeFileSync(join(geminiContextDir, file), adaptFrameworkFile(content, `context/${file}`, 'gemini'), 'utf-8');
341
+ }
342
+ }
343
+
344
+ // Session lock (equivalent to CLAUDE.local.md, imported via @import in GEMINI.md)
345
+ writeFileSync(join(targetDir, '.gemini', 'session-lock.md'), generateGeminiSessionLock(), 'utf-8');
346
+
347
+ // Hooks (6 governance hooks — equivalent to Claude Code hooks)
348
+ const geminiHooksDir = join(targetDir, '.gemini', 'hooks');
349
+ createDir(geminiHooksDir);
350
+ const { generateAllGeminiHooks, generateGeminiSettings } = await import('../config/gemini-hooks-generator.js');
351
+ const hooks = generateAllGeminiHooks();
352
+ for (const [filename, hookContent] of Object.entries(hooks)) {
353
+ writeFileSync(join(geminiHooksDir, filename), hookContent, 'utf-8');
354
+ }
355
+ writeFileSync(join(targetDir, '.gemini', 'settings.json'), generateGeminiSettings(), 'utf-8');
320
356
  } else {
321
357
  // VS Code, Cursor, AntiGravity — generic rules file
322
358
  if (config.rulesFile) {
@@ -364,6 +400,42 @@ DEPLOY: DevOps
364
400
  `;
365
401
  }
366
402
 
403
+ /**
404
+ * Append chati.dev runtime entries to .gitignore.
405
+ * Session lock files are runtime-only and should never be committed.
406
+ */
407
+ function updateGitignore(targetDir, selectedIDEs) {
408
+ const entries = [
409
+ '',
410
+ '# Chati.dev runtime files (session lock — not committed)',
411
+ '.chati/memories/*/session/',
412
+ ];
413
+
414
+ if (selectedIDEs.includes('claude-code')) {
415
+ entries.push('CLAUDE.local.md');
416
+ }
417
+ if (selectedIDEs.includes('gemini-cli')) {
418
+ entries.push('.gemini/session-lock.md');
419
+ }
420
+ if (selectedIDEs.includes('codex-cli')) {
421
+ entries.push('AGENTS.override.md');
422
+ }
423
+
424
+ entries.push('');
425
+
426
+ const gitignorePath = join(targetDir, '.gitignore');
427
+ const marker = '# Chati.dev runtime files';
428
+
429
+ if (existsSync(gitignorePath)) {
430
+ const existing = readFileSync(gitignorePath, 'utf-8');
431
+ // Don't duplicate if already present
432
+ if (existing.includes(marker)) return;
433
+ writeFileSync(gitignorePath, existing.trimEnd() + '\n' + entries.join('\n'), 'utf-8');
434
+ } else {
435
+ writeFileSync(gitignorePath, entries.join('\n'), 'utf-8');
436
+ }
437
+ }
438
+
367
439
  /**
368
440
  * Recursively create directory if it doesn't exist
369
441
  */
@@ -243,3 +243,148 @@ _No decisions yet. Start with /chati._
243
243
  _Auto-updated by Chati.dev orchestrator_
244
244
  `;
245
245
  }
246
+
247
+ /**
248
+ * Generate Gemini CLI session lock (.gemini/session-lock.md)
249
+ * Equivalent to CLAUDE.local.md — imported via @import in GEMINI.md.
250
+ */
251
+ export function generateGeminiSessionLock() {
252
+ return `# Chati.dev Runtime State
253
+
254
+ ## Session Lock
255
+ **Status: INACTIVE** — Type \`/chati\` to activate.
256
+
257
+ <!-- SESSION-LOCK:INACTIVE -->
258
+
259
+ ## Current State
260
+ - **Agent**: None (ready to start)
261
+ - **Pipeline**: Pre-start
262
+ - **Mode**: interactive
263
+
264
+ ## Recent Decisions
265
+ _No decisions yet. Start with /chati._
266
+
267
+ ---
268
+ _Auto-updated by Chati.dev orchestrator_
269
+ `;
270
+ }
271
+
272
+ /**
273
+ * Generate Codex CLI session lock (AGENTS.override.md)
274
+ * Codex auto-loads this as an override to AGENTS.md.
275
+ */
276
+ export function generateAgentsOverrideMd() {
277
+ return `# Chati.dev Runtime State
278
+
279
+ ## Session Lock
280
+ **Status: INACTIVE** — Type \`$chati\` to activate.
281
+
282
+ <!-- SESSION-LOCK:INACTIVE -->
283
+
284
+ ## Current State
285
+ - **Agent**: None (ready to start)
286
+ - **Pipeline**: Pre-start
287
+ - **Mode**: interactive
288
+
289
+ ## Recent Decisions
290
+ _No decisions yet. Start with $chati._
291
+
292
+ ---
293
+ _Auto-updated by Chati.dev orchestrator_
294
+ `;
295
+ }
296
+
297
+ /**
298
+ * Generate Codex CLI constitution guard rules (.codex/rules/constitution-guard.rules)
299
+ * Starlark execution policy that blocks destructive commands and secret writes.
300
+ */
301
+ export function generateCodexConstitutionGuardRules() {
302
+ return `# Chati.dev Constitution Guard — Codex CLI Execution Policy
303
+ # Article XI: Block destructive commands and secret writes
304
+
305
+ # Destructive commands that require explicit user approval
306
+ deny_commands = [
307
+ "rm -rf",
308
+ "git reset --hard",
309
+ "git push --force",
310
+ "git push -f",
311
+ "git clean -fd",
312
+ "DROP TABLE",
313
+ "DROP DATABASE",
314
+ "TRUNCATE TABLE",
315
+ "chmod 777",
316
+ ]
317
+
318
+ # Patterns that indicate secret/credential writes
319
+ deny_write_patterns = [
320
+ "API_KEY=",
321
+ "SECRET_KEY=",
322
+ "PASSWORD=",
323
+ "TOKEN=",
324
+ "PRIVATE_KEY=",
325
+ "aws_secret_access_key",
326
+ "-----BEGIN RSA PRIVATE KEY-----",
327
+ "-----BEGIN OPENSSH PRIVATE KEY-----",
328
+ ]
329
+
330
+ # Files that should never be written to
331
+ deny_write_files = [
332
+ ".env",
333
+ ".env.local",
334
+ ".env.production",
335
+ "credentials.json",
336
+ "service-account.json",
337
+ ]
338
+
339
+ # Allow list — these are safe even though they match patterns
340
+ allow_write_files = [
341
+ ".env.example",
342
+ ".env.template",
343
+ ".env.sample",
344
+ ]
345
+ `;
346
+ }
347
+
348
+ /**
349
+ * Generate Codex CLI read protection rules (.codex/rules/read-protection.rules)
350
+ * Starlark execution policy that blocks reading sensitive files.
351
+ */
352
+ export function generateCodexReadProtectionRules() {
353
+ return `# Chati.dev Read Protection — Codex CLI Execution Policy
354
+ # Article XI: Protect sensitive files from being read
355
+
356
+ # Files that should never be read
357
+ deny_read_files = [
358
+ ".env",
359
+ ".env.local",
360
+ ".env.production",
361
+ ".env.staging",
362
+ ]
363
+
364
+ # File extensions that indicate sensitive content
365
+ deny_read_extensions = [
366
+ ".pem",
367
+ ".key",
368
+ ".p12",
369
+ ".pfx",
370
+ ".jks",
371
+ ]
372
+
373
+ # Paths that should never be read
374
+ deny_read_paths = [
375
+ "credentials.json",
376
+ "service-account.json",
377
+ ".git/config",
378
+ ".ssh/",
379
+ ".aws/credentials",
380
+ ".npmrc",
381
+ ]
382
+
383
+ # Allow list — these are safe even though they match patterns
384
+ allow_read_files = [
385
+ ".env.example",
386
+ ".env.template",
387
+ ".env.sample",
388
+ ]
389
+ `;
390
+ }
@@ -51,7 +51,7 @@ const PROVIDERS = {
51
51
  baseArgs: [],
52
52
  modelFlag: '--model',
53
53
  stdinSupport: true,
54
- hooksSupport: false,
54
+ hooksSupport: true,
55
55
  mcpSupport: true,
56
56
  contextFile: 'GEMINI.md',
57
57
  modelMap: {