@nerviq/cli 0.9.2 → 0.9.4

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.
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Claude Code Freshness Operationalization
3
+ *
4
+ * Release gates, recurring probes, propagation checklists,
5
+ * and staleness blocking for Claude Code surfaces.
6
+ *
7
+ * P0 sources from docs.anthropic.com, propagation for CLAUDE.md format changes.
8
+ */
9
+
10
+ const { version } = require('../package.json');
11
+
12
+ /**
13
+ * P0 sources that must be fresh before any Claude Code release claim.
14
+ */
15
+ const P0_SOURCES = [
16
+ {
17
+ key: 'claude-code-docs',
18
+ label: 'Claude Code Official Docs',
19
+ url: 'https://docs.anthropic.com/claude-code',
20
+ stalenessThresholdDays: 30,
21
+ verifiedAt: null,
22
+ },
23
+ {
24
+ key: 'claude-md-format',
25
+ label: 'CLAUDE.md Format Documentation',
26
+ url: 'https://docs.anthropic.com/claude-code/claude-md',
27
+ stalenessThresholdDays: 30,
28
+ verifiedAt: null,
29
+ },
30
+ {
31
+ key: 'claude-mcp-docs',
32
+ label: 'Claude Code MCP Documentation',
33
+ url: 'https://docs.anthropic.com/claude-code/mcp',
34
+ stalenessThresholdDays: 30,
35
+ verifiedAt: null,
36
+ },
37
+ {
38
+ key: 'claude-hooks-docs',
39
+ label: 'Claude Code Hooks Documentation',
40
+ url: 'https://docs.anthropic.com/claude-code/hooks',
41
+ stalenessThresholdDays: 14,
42
+ verifiedAt: null,
43
+ },
44
+ {
45
+ key: 'claude-security-docs',
46
+ label: 'Claude Code Security Documentation',
47
+ url: 'https://docs.anthropic.com/claude-code/security',
48
+ stalenessThresholdDays: 30,
49
+ verifiedAt: null,
50
+ },
51
+ {
52
+ key: 'claude-permissions-docs',
53
+ label: 'Claude Code Permissions Documentation',
54
+ url: 'https://docs.anthropic.com/claude-code/permissions',
55
+ stalenessThresholdDays: 14,
56
+ verifiedAt: null,
57
+ },
58
+ {
59
+ key: 'claude-settings-docs',
60
+ label: 'Claude Code Settings Documentation',
61
+ url: 'https://docs.anthropic.com/claude-code/settings',
62
+ stalenessThresholdDays: 30,
63
+ verifiedAt: null,
64
+ },
65
+ {
66
+ key: 'anthropic-changelog',
67
+ label: 'Anthropic Changelog',
68
+ url: 'https://docs.anthropic.com/changelog',
69
+ stalenessThresholdDays: 14,
70
+ verifiedAt: null,
71
+ },
72
+ ];
73
+
74
+ /**
75
+ * Propagation checklist: when a Claude Code source changes, these must update.
76
+ */
77
+ const PROPAGATION_CHECKLIST = [
78
+ {
79
+ trigger: 'CLAUDE.md format change (new fields, import syntax, hierarchy change)',
80
+ targets: [
81
+ 'src/context.js — update ProjectContext parsing',
82
+ 'src/techniques.js — update memory/context checks',
83
+ 'src/setup.js — update CLAUDE.md template generation',
84
+ ],
85
+ },
86
+ {
87
+ trigger: 'Hooks system change (event types, exit codes, schema)',
88
+ targets: [
89
+ 'src/governance.js — update hookRegistry',
90
+ 'src/techniques.js — update hooks checks',
91
+ ],
92
+ },
93
+ {
94
+ trigger: 'MCP configuration format change',
95
+ targets: [
96
+ 'src/techniques.js — update MCP checks',
97
+ 'src/mcp-packs.js — update pack projections',
98
+ 'src/context.js — update mcpConfig parsing',
99
+ ],
100
+ },
101
+ {
102
+ trigger: 'Permissions model change (allow/deny lists, operator/user split)',
103
+ targets: [
104
+ 'src/governance.js — update permissionProfiles',
105
+ 'src/techniques.js — update permission checks',
106
+ ],
107
+ },
108
+ ];
109
+
110
+ /**
111
+ * Release gate: check if all P0 sources are within staleness threshold.
112
+ */
113
+ function checkReleaseGate(sourceVerifications = {}) {
114
+ const now = new Date();
115
+ const results = P0_SOURCES.map(source => {
116
+ const verifiedAt = sourceVerifications[source.key]
117
+ ? new Date(sourceVerifications[source.key])
118
+ : source.verifiedAt ? new Date(source.verifiedAt) : null;
119
+
120
+ if (!verifiedAt) {
121
+ return { ...source, status: 'unverified', daysStale: null };
122
+ }
123
+
124
+ const daysSince = Math.floor((now - verifiedAt) / (1000 * 60 * 60 * 24));
125
+ const isStale = daysSince > source.stalenessThresholdDays;
126
+
127
+ return {
128
+ ...source,
129
+ verifiedAt: verifiedAt.toISOString(),
130
+ daysStale: daysSince,
131
+ status: isStale ? 'stale' : 'fresh',
132
+ };
133
+ });
134
+
135
+ return {
136
+ ready: results.every(r => r.status === 'fresh'),
137
+ stale: results.filter(r => r.status === 'stale' || r.status === 'unverified'),
138
+ fresh: results.filter(r => r.status === 'fresh'),
139
+ results,
140
+ };
141
+ }
142
+
143
+ function formatReleaseGate(gateResult) {
144
+ const lines = [
145
+ `Claude Code Freshness Gate (nerviq v${version})`,
146
+ '═══════════════════════════════════════',
147
+ '',
148
+ `Status: ${gateResult.ready ? 'READY' : 'BLOCKED'}`,
149
+ `Fresh: ${gateResult.fresh.length}/${gateResult.results.length}`,
150
+ '',
151
+ ];
152
+
153
+ for (const result of gateResult.results) {
154
+ const icon = result.status === 'fresh' ? '✓' : result.status === 'stale' ? '✗' : '?';
155
+ const age = result.daysStale !== null ? ` (${result.daysStale}d ago)` : ' (unverified)';
156
+ lines.push(` ${icon} ${result.label}${age} — threshold: ${result.stalenessThresholdDays}d`);
157
+ }
158
+
159
+ if (!gateResult.ready) {
160
+ lines.push('', 'Action required: verify stale/unverified sources before claiming release freshness.');
161
+ }
162
+
163
+ return lines.join('\n');
164
+ }
165
+
166
+ function getPropagationTargets(triggerKeyword) {
167
+ const keyword = triggerKeyword.toLowerCase();
168
+ return PROPAGATION_CHECKLIST.filter(item => item.trigger.toLowerCase().includes(keyword));
169
+ }
170
+
171
+ module.exports = {
172
+ P0_SOURCES,
173
+ PROPAGATION_CHECKLIST,
174
+ checkReleaseGate,
175
+ formatReleaseGate,
176
+ getPropagationTargets,
177
+ };
@@ -1,10 +1,11 @@
1
1
  /**
2
2
  * Gemini CLI techniques module — CHECK CATALOG
3
3
  *
4
- * 68 checks across 12 categories:
4
+ * 87 checks across 17 categories:
5
5
  * v0.1 (40): A. Instructions, B. Config, C. Trust & Safety, D. Hooks, E. MCP, F. Sandbox & Policy
6
6
  * v0.5 (54): G. Skills & Agents, H. CI & Automation, I. Extensions
7
7
  * v1.0 (68): J. Review & Workflow, K. Quality Deep, L. Commands
8
+ * v1.1 (73): Q. Experiment-Verified Fixes (v0.36.0 findings: --json→-o json, model object format, --yolo in approval, plan mode, --allowed-tools deprecated, eager loading)
8
9
  *
9
10
  * Each check: { id, name, check(ctx), impact, rating, category, fix, template, file(), line() }
10
11
  */
@@ -13,6 +14,7 @@ const os = require('os');
13
14
  const path = require('path');
14
15
  const { GeminiProjectContext } = require('./context');
15
16
  const { EMBEDDED_SECRET_PATTERNS, containsEmbeddedSecret } = require('../secret-patterns');
17
+ const { attachSourceUrls } = require('../source-urls');
16
18
 
17
19
  // ─── Shared helpers ─────────────────────────────────────────────────────────
18
20
 
@@ -357,7 +359,7 @@ const GEMINI_TECHNIQUES = {
357
359
  impact: 'critical',
358
360
  rating: 5,
359
361
  category: 'config',
360
- fix: 'Fix malformed JSON in .gemini/settings.json so Gemini CLI does not silently ignore settings.',
362
+ fix: 'Fix malformed JSON in .gemini/settings.json. Invalid JSON causes exit code 52 — Gemini CLI will not start.',
361
363
  template: null,
362
364
  file: () => '.gemini/settings.json',
363
365
  line: (ctx) => {
@@ -376,16 +378,21 @@ const GEMINI_TECHNIQUES = {
376
378
 
377
379
  geminiModelExplicit: {
378
380
  id: 'GM-B03',
379
- name: 'Model is set explicitly (not relying on default/free tier)',
381
+ name: 'Model is set explicitly in object format (v0.36.0+)',
380
382
  check: (ctx) => {
381
383
  const data = settingsData(ctx);
382
384
  if (!data) return null;
383
- return Boolean(data.model);
385
+ if (!data.model) return false;
386
+ // v0.36.0: model field MUST be an object { name: "..." }, not a string
387
+ // String format causes exit code 41: "Expected object, received string"
388
+ if (typeof data.model === 'string') return false;
389
+ if (typeof data.model === 'object' && data.model.name) return true;
390
+ return false;
384
391
  },
385
- impact: 'medium',
386
- rating: 4,
392
+ impact: 'critical',
393
+ rating: 5,
387
394
  category: 'config',
388
- fix: 'Set "model" explicitly in settings.json so the team knows which Gemini model is used (Flash vs Pro).',
395
+ fix: 'CRITICAL: In v0.36.0+, model must be an object: {"model": {"name": "gemini-2.5-flash"}}. String format ({"model": "gemini-2.5-flash"}) causes exit code 41. Default model is now gemini-3-flash-preview.',
389
396
  template: 'gemini-settings',
390
397
  file: () => '.gemini/settings.json',
391
398
  line: (ctx) => ctx.lineNumber('.gemini/settings.json', /"model"/),
@@ -483,13 +490,17 @@ const GEMINI_TECHNIQUES = {
483
490
 
484
491
  geminiNoYolo: {
485
492
  id: 'GM-C01',
486
- name: 'No --yolo in project settings or scripts',
493
+ name: 'No --yolo in project settings, scripts, or approval field',
487
494
  check: (ctx) => {
488
495
  const raw = settingsRaw(ctx);
489
496
  const gmd = geminiMd(ctx) || '';
490
497
  const combined = `${raw}\n${gmd}`;
491
498
  // Check settings and scripts for --yolo
492
499
  if (/--yolo\b|\byolo\b.*:\s*true/i.test(raw)) return false;
500
+ // CRITICAL: v0.36.0 silently accepts "--yolo" as an approval value in settings.json
501
+ // {"approval": "--yolo"} passes validation without warning
502
+ const data = settingsData(ctx);
503
+ if (data && data.approval && /yolo/i.test(String(data.approval))) return false;
493
504
  // Check package.json scripts
494
505
  const pkg = ctx.jsonFile ? ctx.jsonFile('package.json') : null;
495
506
  if (pkg && pkg.scripts) {
@@ -501,7 +512,7 @@ const GEMINI_TECHNIQUES = {
501
512
  impact: 'critical',
502
513
  rating: 5,
503
514
  category: 'trust',
504
- fix: 'Remove --yolo from project settings and scripts. It bypasses all safety controls and is never safe for shared repos.',
515
+ fix: 'Remove --yolo from project settings and scripts. WARNING: v0.36.0 silently accepts "--yolo" in the approval field without any validation error — this is a security risk.',
505
516
  template: null,
506
517
  file: () => '.gemini/settings.json',
507
518
  line: (ctx) => {
@@ -1429,32 +1440,34 @@ const GEMINI_TECHNIQUES = {
1429
1440
 
1430
1441
  geminiCiJsonOutput: {
1431
1442
  id: 'GM-H04',
1432
- name: 'Headless output is --json for machine parsing',
1443
+ name: 'Headless output uses -o json (not deprecated --json)',
1433
1444
  check: (ctx) => {
1434
1445
  for (const wf of workflowArtifacts(ctx)) {
1435
1446
  if (!/\bgemini\b/i.test(wf.content)) continue;
1436
- // If gemini is used in CI with -p (prompt), check for --json
1447
+ // If gemini is used in CI with -p (prompt), check for -o json (correct) and flag --json (removed in v0.36.0)
1437
1448
  if (/gemini\s+.*-p\b/i.test(wf.content)) {
1438
- return /--json\b/i.test(wf.content);
1449
+ // CRITICAL: --json was removed in v0.36.0. Correct flag is -o json or --output-format json
1450
+ if (/--json\b/i.test(wf.content)) return false; // Using deprecated flag
1451
+ return /-o\s+json\b|--output-format\s+json\b/i.test(wf.content);
1439
1452
  }
1440
1453
  }
1441
1454
  return null; // Not relevant if no headless usage
1442
1455
  },
1443
- impact: 'low',
1444
- rating: 2,
1456
+ impact: 'critical',
1457
+ rating: 5,
1445
1458
  category: 'automation',
1446
- fix: 'Use --json flag when running gemini -p in CI for reliable machine-parseable output.',
1459
+ fix: 'CRITICAL: --json flag was removed in v0.36.0. Use `-o json` or `--output-format json` instead. Three formats available: text, json, stream-json.',
1447
1460
  template: null,
1448
1461
  file: (ctx) => {
1449
1462
  for (const wf of workflowArtifacts(ctx)) {
1450
- if (/gemini\s+.*-p\b/i.test(wf.content) && !/--json\b/i.test(wf.content)) return wf.filePath;
1463
+ if (/gemini\s+.*-p\b/i.test(wf.content) && (/--json\b/i.test(wf.content) || !/-o\s+json\b|--output-format\s+json\b/i.test(wf.content))) return wf.filePath;
1451
1464
  }
1452
1465
  return null;
1453
1466
  },
1454
1467
  line: (ctx) => {
1455
1468
  for (const wf of workflowArtifacts(ctx)) {
1456
1469
  const line = firstLineMatching(wf.content, /gemini\s+.*-p\b/i);
1457
- if (line && !/--json\b/i.test(wf.content)) return line;
1470
+ if (line && (/--json\b/i.test(wf.content) || !/-o\s+json\b|--output-format\s+json\b/i.test(wf.content))) return line;
1458
1471
  }
1459
1472
  return null;
1460
1473
  },
@@ -1772,7 +1785,7 @@ const GEMINI_TECHNIQUES = {
1772
1785
  impact: 'low',
1773
1786
  rating: 2,
1774
1787
  category: 'quality-deep',
1775
- fix: 'For monorepos, add component-level GEMINI.md files in package subdirectories for JIT loading.',
1788
+ fix: 'For monorepos, add component-level GEMINI.md files in package subdirectories. NOTE: v0.36.0 loads ALL subdirectory GEMINI.md files eagerly at startup (not JIT) — watch for token bloat in large monorepos.',
1776
1789
  template: null,
1777
1790
  file: () => 'GEMINI.md',
1778
1791
  line: () => 1,
@@ -1785,7 +1798,9 @@ const GEMINI_TECHNIQUES = {
1785
1798
  const gmd = geminiMd(ctx) || '';
1786
1799
  const data = settingsData(ctx);
1787
1800
  if (!data || !data.model) return null;
1788
- const model = String(data.model).toLowerCase();
1801
+ // v0.36.0: model is an object { name: "..." } or could be a legacy string
1802
+ const modelName = (typeof data.model === 'object' && data.model.name) ? data.model.name : String(data.model);
1803
+ const model = modelName.toLowerCase();
1789
1804
  // If using a specific model, check that implications are documented
1790
1805
  if (/flash|pro/i.test(model)) {
1791
1806
  return /\bflash\b|\bpro\b|\bmodel\b.*\b(fast|cheap|accurate|expensive|quality)\b/i.test(gmd);
@@ -2064,10 +2079,17 @@ const GEMINI_TECHNIQUES = {
2064
2079
  template: 'gemini-md', file: () => 'GEMINI.md', line: () => 1,
2065
2080
  },
2066
2081
  geminiSourceFreshness: {
2067
- id: 'GM-P02', name: 'Config references current Gemini features',
2068
- check: (ctx) => { const s = ctx.settingsJson(); if (!s) return null; const content = JSON.stringify(s); return !/chat_model|notepads|old_format/i.test(content); },
2069
- impact: 'medium', rating: 3, category: 'release-freshness',
2070
- fix: 'Update deprecated config keys to current equivalents.',
2082
+ id: 'GM-P02', name: 'Config and docs reference current Gemini features (no deprecated flags)',
2083
+ check: (ctx) => {
2084
+ const s = ctx.settingsJson();
2085
+ const g = ctx.geminiMdContent() || '';
2086
+ const combined = (s ? JSON.stringify(s) : '') + '\n' + g;
2087
+ if (!s && !g) return null;
2088
+ // Deprecated: chat_model, notepads, old_format, --json (use -o json), --allowed-tools (use policy.toml)
2089
+ return !/chat_model|notepads|old_format/i.test(combined) && !/--json\b/i.test(combined) && !/--allowed-tools\b/i.test(combined);
2090
+ },
2091
+ impact: 'high', rating: 4, category: 'release-freshness',
2092
+ fix: 'Update deprecated references: --json → -o json (v0.36.0), --allowed-tools → policy.toml, chat_model/notepads → removed.',
2071
2093
  template: 'gemini-settings', file: () => '.gemini/settings.json', line: () => 1,
2072
2094
  },
2073
2095
  geminiPropagationCompleteness: {
@@ -2077,8 +2099,140 @@ const GEMINI_TECHNIQUES = {
2077
2099
  fix: 'Ensure all surfaces mentioned in GEMINI.md have corresponding definition files.',
2078
2100
  template: 'gemini-md', file: () => 'GEMINI.md', line: () => 1,
2079
2101
  },
2102
+
2103
+ // =============================================
2104
+ // Q. Experiment-Verified Fixes (5 checks) — GM-Q01..GM-Q05
2105
+ // Added from v0.36.0 experiment findings (2026-04-05)
2106
+ // =============================================
2107
+
2108
+ geminiApprovalFieldValidation: {
2109
+ id: 'GM-Q01',
2110
+ name: 'Approval field in settings.json has valid value (not --yolo)',
2111
+ check: (ctx) => {
2112
+ const data = settingsData(ctx);
2113
+ if (!data || !data.approval) return null;
2114
+ const approval = String(data.approval).toLowerCase();
2115
+ // v0.36.0: "--yolo" is silently accepted in approval field without validation
2116
+ // Valid values: suggest, auto_fix, auto_edit, plan
2117
+ const validValues = ['suggest', 'auto_fix', 'auto_edit', 'plan'];
2118
+ if (/yolo/i.test(approval)) return false;
2119
+ return validValues.includes(approval);
2120
+ },
2121
+ impact: 'critical',
2122
+ rating: 5,
2123
+ category: 'trust',
2124
+ fix: 'SECURITY: v0.36.0 silently accepts "--yolo" in the approval field. Use valid values: suggest, auto_fix, auto_edit, or plan (read-only mode).',
2125
+ template: 'gemini-settings',
2126
+ file: () => '.gemini/settings.json',
2127
+ line: (ctx) => ctx.lineNumber('.gemini/settings.json', /"approval"/),
2128
+ },
2129
+
2130
+ geminiPlanModeDocumented: {
2131
+ id: 'GM-Q02',
2132
+ name: 'Plan mode (read-only 4th approval mode) documented if used',
2133
+ check: (ctx) => {
2134
+ const data = settingsData(ctx);
2135
+ if (!data) return null;
2136
+ const approval = data.approval || data.approvalMode || data.approval_mode;
2137
+ if (!approval || String(approval).toLowerCase() !== 'plan') return null;
2138
+ // If plan mode is active, check it's documented
2139
+ const gmd = geminiMd(ctx) || '';
2140
+ return /\bplan\s*mode\b|\bread.only\b|\bplan\b.*approval/i.test(gmd);
2141
+ },
2142
+ impact: 'medium',
2143
+ rating: 3,
2144
+ category: 'config',
2145
+ fix: 'Document that plan mode is a read-only approval mode (undocumented 4th mode in v0.36.0) that prevents all file modifications.',
2146
+ template: 'gemini-md',
2147
+ file: () => 'GEMINI.md',
2148
+ line: () => 1,
2149
+ },
2150
+
2151
+ geminiNoAllowedToolsDeprecated: {
2152
+ id: 'GM-Q03',
2153
+ name: 'No deprecated --allowed-tools flag (use policy.toml)',
2154
+ check: (ctx) => {
2155
+ const gmd = geminiMd(ctx) || '';
2156
+ const raw = settingsRaw(ctx);
2157
+ // Check workflow files
2158
+ for (const wf of workflowArtifacts(ctx)) {
2159
+ if (/--allowed-tools\b/i.test(wf.content)) return false;
2160
+ }
2161
+ // Check docs and settings
2162
+ if (/--allowed-tools\b/i.test(gmd)) return false;
2163
+ if (/--allowed-tools\b|allowedTools/i.test(raw)) return false;
2164
+ // Check package.json scripts
2165
+ const pkg = ctx.jsonFile ? ctx.jsonFile('package.json') : null;
2166
+ if (pkg && pkg.scripts) {
2167
+ const scriptValues = Object.values(pkg.scripts).join('\n');
2168
+ if (/--allowed-tools\b/i.test(scriptValues)) return false;
2169
+ }
2170
+ return true;
2171
+ },
2172
+ impact: 'high',
2173
+ rating: 4,
2174
+ category: 'release-freshness',
2175
+ fix: '--allowed-tools is DEPRECATED in v0.36.0. Migrate to the Policy Engine with policy.toml files under .gemini/policy/.',
2176
+ template: null,
2177
+ file: (ctx) => {
2178
+ for (const wf of workflowArtifacts(ctx)) {
2179
+ if (/--allowed-tools\b/i.test(wf.content)) return wf.filePath;
2180
+ }
2181
+ const gmd = geminiMd(ctx) || '';
2182
+ if (/--allowed-tools\b/i.test(gmd)) return 'GEMINI.md';
2183
+ return '.gemini/settings.json';
2184
+ },
2185
+ line: (ctx) => {
2186
+ const gmd = geminiMd(ctx) || '';
2187
+ return firstLineMatching(gmd, /--allowed-tools/i) || firstLineMatching(settingsRaw(ctx), /allowed.?tools/i);
2188
+ },
2189
+ },
2190
+
2191
+ geminiEagerLoadingAwareness: {
2192
+ id: 'GM-Q04',
2193
+ name: 'GEMINI.md hierarchy loading behavior is correctly documented',
2194
+ check: (ctx) => {
2195
+ const gmd = geminiMd(ctx) || '';
2196
+ if (!gmd) return null;
2197
+ // Flag if docs mention JIT/lazy loading — this is falsified in v0.36.0
2198
+ if (/\bjit\b|\blazy.load|\bload.*on.demand|\bdynamic.*load/i.test(gmd)) return false;
2199
+ return true;
2200
+ },
2201
+ impact: 'medium',
2202
+ rating: 3,
2203
+ category: 'instructions',
2204
+ fix: 'Remove JIT/lazy-loading claims from GEMINI.md. v0.36.0 loads ALL subdirectory GEMINI.md files eagerly at startup — be mindful of token budget in monorepos.',
2205
+ template: 'gemini-md',
2206
+ file: () => 'GEMINI.md',
2207
+ line: (ctx) => {
2208
+ const gmd = geminiMd(ctx) || '';
2209
+ return firstLineMatching(gmd, /jit|lazy.load|on.demand/i);
2210
+ },
2211
+ },
2212
+
2213
+ geminiModelStringNotObject: {
2214
+ id: 'GM-Q05',
2215
+ name: 'Model field is not a bare string (v0.36.0 requires object)',
2216
+ check: (ctx) => {
2217
+ const raw = settingsRaw(ctx);
2218
+ if (!raw) return null;
2219
+ // Quick check: if "model" key exists and its value is a string, fail
2220
+ const match = raw.match(/"model"\s*:\s*"([^"]+)"/);
2221
+ if (match) return false; // String format detected — will cause exit code 41
2222
+ return true;
2223
+ },
2224
+ impact: 'critical',
2225
+ rating: 5,
2226
+ category: 'config',
2227
+ fix: 'BREAKING: v0.36.0 requires model as object: {"model": {"name": "gemini-2.5-flash"}}. String format causes exit code 41.',
2228
+ template: 'gemini-settings',
2229
+ file: () => '.gemini/settings.json',
2230
+ line: (ctx) => ctx.lineNumber('.gemini/settings.json', /"model"/),
2231
+ },
2080
2232
  };
2081
2233
 
2234
+ attachSourceUrls('gemini', GEMINI_TECHNIQUES);
2235
+
2082
2236
  module.exports = {
2083
2237
  GEMINI_TECHNIQUES,
2084
2238
  };