@nerviq/cli 0.9.1 → 0.9.3

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.
@@ -1,11 +1,12 @@
1
1
  /**
2
2
  * Cursor techniques module — CHECK CATALOG
3
3
  *
4
- * 82 checks across 16 categories:
5
- * v0.1 (40): A. Rules(9), B. Config(7), C. Trust & Safety(9), D. Agent Mode(5), E. MCP(5), F. Instructions Quality(5)
6
- * v0.5 (55): G. Background Agents(5), H. Automations(5), I. Enterprise(5)
4
+ * 88 checks across 16 categories:
5
+ * v0.1 (40): A. Rules(9), B. Config(8), C. Trust & Safety(11), D. Agent Mode(5), E. MCP(5), F. Instructions Quality(5)
6
+ * v0.5 (55): G. Background Agents(5), H. Automations(6), I. Enterprise(5)
7
7
  * v1.0 (70): J. BugBot & Code Review(4), K. Cross-Surface(4), L. Quality Deep(7)
8
8
  * CP-08 (82): M. Advisory(4), N. Pack(4), O. Repeat(3), P. Freshness(3)
9
+ * Exp-fixes (88): +4 new checks from experiment findings
9
10
  *
10
11
  * Each check: { id, name, check(ctx), impact, rating, category, fix, template, file(), line() }
11
12
  */
@@ -180,7 +181,7 @@ const CURSOR_TECHNIQUES = {
180
181
  impact: 'critical',
181
182
  rating: 5,
182
183
  category: 'rules',
183
- fix: 'Migrate .cursorrules to .cursor/rules/*.mdc with proper frontmatter. AGENT MODE IGNORES .cursorrules completely!',
184
+ fix: 'Migrate .cursorrules to .cursor/rules/*.mdc with alwaysApply: true. AGENT MODE COMPLETELY IGNORES .cursorrules (confirmed by direct observation). 82% of projects have broken rules because of this — cursor-doctor audit.',
184
185
  template: 'cursor-legacy-migration',
185
186
  file: () => '.cursorrules',
186
187
  line: () => 1,
@@ -215,10 +216,10 @@ const CURSOR_TECHNIQUES = {
215
216
  return validation.valid;
216
217
  });
217
218
  },
218
- impact: 'high',
219
- rating: 4,
219
+ impact: 'critical',
220
+ rating: 5,
220
221
  category: 'rules',
221
- fix: 'Fix YAML frontmatter in .mdc files. Use only: description, globs, alwaysApply fields.',
222
+ fix: 'Fix YAML frontmatter in .mdc files. Invalid YAML silently skips the entire rule file — no error, no warning. Only 3 fields recognized: description, globs, alwaysApply. 82% of audited projects have broken rules from this issue.',
222
223
  template: null,
223
224
  file: () => '.cursor/rules/',
224
225
  line: () => 1,
@@ -476,8 +477,28 @@ const CURSOR_TECHNIQUES = {
476
477
  line: () => null,
477
478
  },
478
479
 
480
+ cursorMcpServersRootKey: {
481
+ id: 'CU-B08',
482
+ name: 'MCP config has required mcpServers root key',
483
+ check: (ctx) => {
484
+ const raw = mcpJsonRaw(ctx);
485
+ if (!raw) return null;
486
+ const data = mcpJsonData(ctx);
487
+ if (!data) return null;
488
+ // Must have mcpServers key at root — any other key causes silent failure
489
+ return Object.prototype.hasOwnProperty.call(data, 'mcpServers');
490
+ },
491
+ impact: 'critical',
492
+ rating: 5,
493
+ category: 'config',
494
+ fix: 'Ensure .cursor/mcp.json has the "mcpServers" root key. Using "servers" or any other key causes silent failure — zero tools load with no error shown (confirmed by experiment).',
495
+ template: null,
496
+ file: () => '.cursor/mcp.json',
497
+ line: () => 1,
498
+ },
499
+
479
500
  // =============================================
480
- // C. Trust & Safety (9 checks) — CU-C01..CU-C09
501
+ // C. Trust & Safety (11 checks) — CU-C01..CU-C11
481
502
  // =============================================
482
503
 
483
504
  cursorPrivacyMode: {
@@ -489,10 +510,10 @@ const CURSOR_TECHNIQUES = {
489
510
  const docs = docsBundle(ctx);
490
511
  return /privacy mode|zero.?retention|data retention|privacy.*enabled/i.test(docs);
491
512
  },
492
- impact: 'high',
513
+ impact: 'critical',
493
514
  rating: 5,
494
515
  category: 'trust',
495
- fix: 'Enable Privacy Mode in Cursor Settings for zero data retention, or document why it is disabled.',
516
+ fix: 'Privacy Mode is OFF by default — code is sent to all third-party providers (OpenAI, Anthropic, etc.) unless explicitly enabled. Enable in Cursor Settings Privacy Privacy Mode, or document the deliberate decision to keep it off.',
496
517
  template: null,
497
518
  file: () => '.cursor/rules/',
498
519
  line: () => null,
@@ -656,6 +677,44 @@ const CURSOR_TECHNIQUES = {
656
677
  line: () => null,
657
678
  },
658
679
 
680
+ cursorBackgroundAgentHomeDir: {
681
+ id: 'CU-C10',
682
+ name: 'Background agent home directory exposure documented',
683
+ check: (ctx) => {
684
+ const env = envJsonData(ctx);
685
+ if (!env) return null;
686
+ // If background agents are configured, check that the security risk is documented
687
+ const docs = docsBundle(ctx);
688
+ return /home.?dir|npmrc|aws.?credentials|ssh.*key|credential.*exposure|home.*access/i.test(docs);
689
+ },
690
+ impact: 'critical',
691
+ rating: 5,
692
+ category: 'trust',
693
+ fix: 'Background agents have FULL READ access to ~/.npmrc, ~/.aws/credentials, ~/.ssh/ (open security issue since Nov 2025). Document this risk and remove sensitive credentials from home directory before using background agents, or use environment variable references instead.',
694
+ template: null,
695
+ file: () => '.cursor/environment.json',
696
+ line: () => null,
697
+ },
698
+
699
+ cursorCursorignoreShellBypass: {
700
+ id: 'CU-C11',
701
+ name: '.cursorignore does not protect against shell command access',
702
+ check: (ctx) => {
703
+ const hasIgnore = Boolean(ctx.fileContent('.cursorignore'));
704
+ if (!hasIgnore) return null;
705
+ // If .cursorignore exists, check that docs acknowledge shell bypass gap
706
+ const docs = docsBundle(ctx);
707
+ return /cursorignore.*shell|shell.*bypass|terminal.*ignore|ignore.*terminal/i.test(docs);
708
+ },
709
+ impact: 'high',
710
+ rating: 4,
711
+ category: 'trust',
712
+ fix: '.cursorignore only protects files from @Codebase direct reads — agents can still access ignored files via terminal commands (cat, head, etc.). Do not rely on .cursorignore for security. Use proper OS-level file permissions for truly sensitive files.',
713
+ template: null,
714
+ file: () => '.cursorignore',
715
+ line: () => null,
716
+ },
717
+
659
718
  // =============================================
660
719
  // D. Agent Mode (5 checks) — CU-D01..CU-D05
661
720
  // =============================================
@@ -1140,6 +1199,27 @@ const CURSOR_TECHNIQUES = {
1140
1199
  line: () => null,
1141
1200
  },
1142
1201
 
1202
+ cursorAutomationFileSaveDebounce: {
1203
+ id: 'CU-H06',
1204
+ name: 'file_save automation triggers have debounce_ms set',
1205
+ check: (ctx) => {
1206
+ const configs = ctx.automationsConfig ? ctx.automationsConfig() : [];
1207
+ if (configs.length === 0) return null;
1208
+ const combined = configs.map(c => c.content).join('\n');
1209
+ // Only relevant if file_save trigger is used
1210
+ if (!/type:\s*file_save|file[_-]save/i.test(combined)) return null;
1211
+ // Must have debounce_ms set to avoid infinite loop
1212
+ return /debounce_ms|debounce-ms/i.test(combined);
1213
+ },
1214
+ impact: 'critical',
1215
+ rating: 5,
1216
+ category: 'automations',
1217
+ fix: 'Add debounce_ms: 30000 (minimum) to all file_save automation triggers. Without debounce, the automation saves a file → triggers itself → infinite loop that consumes your entire automation quota.',
1218
+ template: null,
1219
+ file: () => '.cursor/automations/',
1220
+ line: () => null,
1221
+ },
1222
+
1143
1223
  // =============================================
1144
1224
  // I. Enterprise (5 checks) — CU-I01..CU-I05
1145
1225
  // =============================================
@@ -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
  */
@@ -357,7 +358,7 @@ const GEMINI_TECHNIQUES = {
357
358
  impact: 'critical',
358
359
  rating: 5,
359
360
  category: 'config',
360
- fix: 'Fix malformed JSON in .gemini/settings.json so Gemini CLI does not silently ignore settings.',
361
+ fix: 'Fix malformed JSON in .gemini/settings.json. Invalid JSON causes exit code 52 — Gemini CLI will not start.',
361
362
  template: null,
362
363
  file: () => '.gemini/settings.json',
363
364
  line: (ctx) => {
@@ -376,16 +377,21 @@ const GEMINI_TECHNIQUES = {
376
377
 
377
378
  geminiModelExplicit: {
378
379
  id: 'GM-B03',
379
- name: 'Model is set explicitly (not relying on default/free tier)',
380
+ name: 'Model is set explicitly in object format (v0.36.0+)',
380
381
  check: (ctx) => {
381
382
  const data = settingsData(ctx);
382
383
  if (!data) return null;
383
- return Boolean(data.model);
384
+ if (!data.model) return false;
385
+ // v0.36.0: model field MUST be an object { name: "..." }, not a string
386
+ // String format causes exit code 41: "Expected object, received string"
387
+ if (typeof data.model === 'string') return false;
388
+ if (typeof data.model === 'object' && data.model.name) return true;
389
+ return false;
384
390
  },
385
- impact: 'medium',
386
- rating: 4,
391
+ impact: 'critical',
392
+ rating: 5,
387
393
  category: 'config',
388
- fix: 'Set "model" explicitly in settings.json so the team knows which Gemini model is used (Flash vs Pro).',
394
+ 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
395
  template: 'gemini-settings',
390
396
  file: () => '.gemini/settings.json',
391
397
  line: (ctx) => ctx.lineNumber('.gemini/settings.json', /"model"/),
@@ -483,13 +489,17 @@ const GEMINI_TECHNIQUES = {
483
489
 
484
490
  geminiNoYolo: {
485
491
  id: 'GM-C01',
486
- name: 'No --yolo in project settings or scripts',
492
+ name: 'No --yolo in project settings, scripts, or approval field',
487
493
  check: (ctx) => {
488
494
  const raw = settingsRaw(ctx);
489
495
  const gmd = geminiMd(ctx) || '';
490
496
  const combined = `${raw}\n${gmd}`;
491
497
  // Check settings and scripts for --yolo
492
498
  if (/--yolo\b|\byolo\b.*:\s*true/i.test(raw)) return false;
499
+ // CRITICAL: v0.36.0 silently accepts "--yolo" as an approval value in settings.json
500
+ // {"approval": "--yolo"} passes validation without warning
501
+ const data = settingsData(ctx);
502
+ if (data && data.approval && /yolo/i.test(String(data.approval))) return false;
493
503
  // Check package.json scripts
494
504
  const pkg = ctx.jsonFile ? ctx.jsonFile('package.json') : null;
495
505
  if (pkg && pkg.scripts) {
@@ -501,7 +511,7 @@ const GEMINI_TECHNIQUES = {
501
511
  impact: 'critical',
502
512
  rating: 5,
503
513
  category: 'trust',
504
- fix: 'Remove --yolo from project settings and scripts. It bypasses all safety controls and is never safe for shared repos.',
514
+ 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
515
  template: null,
506
516
  file: () => '.gemini/settings.json',
507
517
  line: (ctx) => {
@@ -1429,32 +1439,34 @@ const GEMINI_TECHNIQUES = {
1429
1439
 
1430
1440
  geminiCiJsonOutput: {
1431
1441
  id: 'GM-H04',
1432
- name: 'Headless output is --json for machine parsing',
1442
+ name: 'Headless output uses -o json (not deprecated --json)',
1433
1443
  check: (ctx) => {
1434
1444
  for (const wf of workflowArtifacts(ctx)) {
1435
1445
  if (!/\bgemini\b/i.test(wf.content)) continue;
1436
- // If gemini is used in CI with -p (prompt), check for --json
1446
+ // If gemini is used in CI with -p (prompt), check for -o json (correct) and flag --json (removed in v0.36.0)
1437
1447
  if (/gemini\s+.*-p\b/i.test(wf.content)) {
1438
- return /--json\b/i.test(wf.content);
1448
+ // CRITICAL: --json was removed in v0.36.0. Correct flag is -o json or --output-format json
1449
+ if (/--json\b/i.test(wf.content)) return false; // Using deprecated flag
1450
+ return /-o\s+json\b|--output-format\s+json\b/i.test(wf.content);
1439
1451
  }
1440
1452
  }
1441
1453
  return null; // Not relevant if no headless usage
1442
1454
  },
1443
- impact: 'low',
1444
- rating: 2,
1455
+ impact: 'critical',
1456
+ rating: 5,
1445
1457
  category: 'automation',
1446
- fix: 'Use --json flag when running gemini -p in CI for reliable machine-parseable output.',
1458
+ 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
1459
  template: null,
1448
1460
  file: (ctx) => {
1449
1461
  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;
1462
+ 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
1463
  }
1452
1464
  return null;
1453
1465
  },
1454
1466
  line: (ctx) => {
1455
1467
  for (const wf of workflowArtifacts(ctx)) {
1456
1468
  const line = firstLineMatching(wf.content, /gemini\s+.*-p\b/i);
1457
- if (line && !/--json\b/i.test(wf.content)) return line;
1469
+ if (line && (/--json\b/i.test(wf.content) || !/-o\s+json\b|--output-format\s+json\b/i.test(wf.content))) return line;
1458
1470
  }
1459
1471
  return null;
1460
1472
  },
@@ -1772,7 +1784,7 @@ const GEMINI_TECHNIQUES = {
1772
1784
  impact: 'low',
1773
1785
  rating: 2,
1774
1786
  category: 'quality-deep',
1775
- fix: 'For monorepos, add component-level GEMINI.md files in package subdirectories for JIT loading.',
1787
+ 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
1788
  template: null,
1777
1789
  file: () => 'GEMINI.md',
1778
1790
  line: () => 1,
@@ -1785,7 +1797,9 @@ const GEMINI_TECHNIQUES = {
1785
1797
  const gmd = geminiMd(ctx) || '';
1786
1798
  const data = settingsData(ctx);
1787
1799
  if (!data || !data.model) return null;
1788
- const model = String(data.model).toLowerCase();
1800
+ // v0.36.0: model is an object { name: "..." } or could be a legacy string
1801
+ const modelName = (typeof data.model === 'object' && data.model.name) ? data.model.name : String(data.model);
1802
+ const model = modelName.toLowerCase();
1789
1803
  // If using a specific model, check that implications are documented
1790
1804
  if (/flash|pro/i.test(model)) {
1791
1805
  return /\bflash\b|\bpro\b|\bmodel\b.*\b(fast|cheap|accurate|expensive|quality)\b/i.test(gmd);
@@ -2064,10 +2078,17 @@ const GEMINI_TECHNIQUES = {
2064
2078
  template: 'gemini-md', file: () => 'GEMINI.md', line: () => 1,
2065
2079
  },
2066
2080
  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.',
2081
+ id: 'GM-P02', name: 'Config and docs reference current Gemini features (no deprecated flags)',
2082
+ check: (ctx) => {
2083
+ const s = ctx.settingsJson();
2084
+ const g = ctx.geminiMdContent() || '';
2085
+ const combined = (s ? JSON.stringify(s) : '') + '\n' + g;
2086
+ if (!s && !g) return null;
2087
+ // Deprecated: chat_model, notepads, old_format, --json (use -o json), --allowed-tools (use policy.toml)
2088
+ return !/chat_model|notepads|old_format/i.test(combined) && !/--json\b/i.test(combined) && !/--allowed-tools\b/i.test(combined);
2089
+ },
2090
+ impact: 'high', rating: 4, category: 'release-freshness',
2091
+ fix: 'Update deprecated references: --json → -o json (v0.36.0), --allowed-tools → policy.toml, chat_model/notepads → removed.',
2071
2092
  template: 'gemini-settings', file: () => '.gemini/settings.json', line: () => 1,
2072
2093
  },
2073
2094
  geminiPropagationCompleteness: {
@@ -2077,6 +2098,136 @@ const GEMINI_TECHNIQUES = {
2077
2098
  fix: 'Ensure all surfaces mentioned in GEMINI.md have corresponding definition files.',
2078
2099
  template: 'gemini-md', file: () => 'GEMINI.md', line: () => 1,
2079
2100
  },
2101
+
2102
+ // =============================================
2103
+ // Q. Experiment-Verified Fixes (5 checks) — GM-Q01..GM-Q05
2104
+ // Added from v0.36.0 experiment findings (2026-04-05)
2105
+ // =============================================
2106
+
2107
+ geminiApprovalFieldValidation: {
2108
+ id: 'GM-Q01',
2109
+ name: 'Approval field in settings.json has valid value (not --yolo)',
2110
+ check: (ctx) => {
2111
+ const data = settingsData(ctx);
2112
+ if (!data || !data.approval) return null;
2113
+ const approval = String(data.approval).toLowerCase();
2114
+ // v0.36.0: "--yolo" is silently accepted in approval field without validation
2115
+ // Valid values: suggest, auto_fix, auto_edit, plan
2116
+ const validValues = ['suggest', 'auto_fix', 'auto_edit', 'plan'];
2117
+ if (/yolo/i.test(approval)) return false;
2118
+ return validValues.includes(approval);
2119
+ },
2120
+ impact: 'critical',
2121
+ rating: 5,
2122
+ category: 'trust',
2123
+ 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).',
2124
+ template: 'gemini-settings',
2125
+ file: () => '.gemini/settings.json',
2126
+ line: (ctx) => ctx.lineNumber('.gemini/settings.json', /"approval"/),
2127
+ },
2128
+
2129
+ geminiPlanModeDocumented: {
2130
+ id: 'GM-Q02',
2131
+ name: 'Plan mode (read-only 4th approval mode) documented if used',
2132
+ check: (ctx) => {
2133
+ const data = settingsData(ctx);
2134
+ if (!data) return null;
2135
+ const approval = data.approval || data.approvalMode || data.approval_mode;
2136
+ if (!approval || String(approval).toLowerCase() !== 'plan') return null;
2137
+ // If plan mode is active, check it's documented
2138
+ const gmd = geminiMd(ctx) || '';
2139
+ return /\bplan\s*mode\b|\bread.only\b|\bplan\b.*approval/i.test(gmd);
2140
+ },
2141
+ impact: 'medium',
2142
+ rating: 3,
2143
+ category: 'config',
2144
+ fix: 'Document that plan mode is a read-only approval mode (undocumented 4th mode in v0.36.0) that prevents all file modifications.',
2145
+ template: 'gemini-md',
2146
+ file: () => 'GEMINI.md',
2147
+ line: () => 1,
2148
+ },
2149
+
2150
+ geminiNoAllowedToolsDeprecated: {
2151
+ id: 'GM-Q03',
2152
+ name: 'No deprecated --allowed-tools flag (use policy.toml)',
2153
+ check: (ctx) => {
2154
+ const gmd = geminiMd(ctx) || '';
2155
+ const raw = settingsRaw(ctx);
2156
+ // Check workflow files
2157
+ for (const wf of workflowArtifacts(ctx)) {
2158
+ if (/--allowed-tools\b/i.test(wf.content)) return false;
2159
+ }
2160
+ // Check docs and settings
2161
+ if (/--allowed-tools\b/i.test(gmd)) return false;
2162
+ if (/--allowed-tools\b|allowedTools/i.test(raw)) return false;
2163
+ // Check package.json scripts
2164
+ const pkg = ctx.jsonFile ? ctx.jsonFile('package.json') : null;
2165
+ if (pkg && pkg.scripts) {
2166
+ const scriptValues = Object.values(pkg.scripts).join('\n');
2167
+ if (/--allowed-tools\b/i.test(scriptValues)) return false;
2168
+ }
2169
+ return true;
2170
+ },
2171
+ impact: 'high',
2172
+ rating: 4,
2173
+ category: 'release-freshness',
2174
+ fix: '--allowed-tools is DEPRECATED in v0.36.0. Migrate to the Policy Engine with policy.toml files under .gemini/policy/.',
2175
+ template: null,
2176
+ file: (ctx) => {
2177
+ for (const wf of workflowArtifacts(ctx)) {
2178
+ if (/--allowed-tools\b/i.test(wf.content)) return wf.filePath;
2179
+ }
2180
+ const gmd = geminiMd(ctx) || '';
2181
+ if (/--allowed-tools\b/i.test(gmd)) return 'GEMINI.md';
2182
+ return '.gemini/settings.json';
2183
+ },
2184
+ line: (ctx) => {
2185
+ const gmd = geminiMd(ctx) || '';
2186
+ return firstLineMatching(gmd, /--allowed-tools/i) || firstLineMatching(settingsRaw(ctx), /allowed.?tools/i);
2187
+ },
2188
+ },
2189
+
2190
+ geminiEagerLoadingAwareness: {
2191
+ id: 'GM-Q04',
2192
+ name: 'GEMINI.md hierarchy loading behavior is correctly documented',
2193
+ check: (ctx) => {
2194
+ const gmd = geminiMd(ctx) || '';
2195
+ if (!gmd) return null;
2196
+ // Flag if docs mention JIT/lazy loading — this is falsified in v0.36.0
2197
+ if (/\bjit\b|\blazy.load|\bload.*on.demand|\bdynamic.*load/i.test(gmd)) return false;
2198
+ return true;
2199
+ },
2200
+ impact: 'medium',
2201
+ rating: 3,
2202
+ category: 'instructions',
2203
+ 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.',
2204
+ template: 'gemini-md',
2205
+ file: () => 'GEMINI.md',
2206
+ line: (ctx) => {
2207
+ const gmd = geminiMd(ctx) || '';
2208
+ return firstLineMatching(gmd, /jit|lazy.load|on.demand/i);
2209
+ },
2210
+ },
2211
+
2212
+ geminiModelStringNotObject: {
2213
+ id: 'GM-Q05',
2214
+ name: 'Model field is not a bare string (v0.36.0 requires object)',
2215
+ check: (ctx) => {
2216
+ const raw = settingsRaw(ctx);
2217
+ if (!raw) return null;
2218
+ // Quick check: if "model" key exists and its value is a string, fail
2219
+ const match = raw.match(/"model"\s*:\s*"([^"]+)"/);
2220
+ if (match) return false; // String format detected — will cause exit code 41
2221
+ return true;
2222
+ },
2223
+ impact: 'critical',
2224
+ rating: 5,
2225
+ category: 'config',
2226
+ fix: 'BREAKING: v0.36.0 requires model as object: {"model": {"name": "gemini-2.5-flash"}}. String format causes exit code 41.',
2227
+ template: 'gemini-settings',
2228
+ file: () => '.gemini/settings.json',
2229
+ line: (ctx) => ctx.lineNumber('.gemini/settings.json', /"model"/),
2230
+ },
2080
2231
  };
2081
2232
 
2082
2233
  module.exports = {