@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.
- package/README.md +115 -30
- package/package.json +1 -1
- package/src/aider/techniques.js +82 -11
- package/src/copilot/techniques.js +122 -11
- package/src/cursor/techniques.js +90 -10
- package/src/gemini/techniques.js +174 -23
- package/src/opencode/techniques.js +70 -99
- package/src/windsurf/techniques.js +211 -138
package/src/cursor/techniques.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Cursor techniques module — CHECK CATALOG
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* v0.1 (40): A. Rules(9), B. Config(
|
|
6
|
-
* v0.5 (55): G. Background Agents(5), H. Automations(
|
|
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
|
|
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: '
|
|
219
|
-
rating:
|
|
219
|
+
impact: 'critical',
|
|
220
|
+
rating: 5,
|
|
220
221
|
category: 'rules',
|
|
221
|
-
fix: 'Fix YAML frontmatter in .mdc files.
|
|
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 (
|
|
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: '
|
|
513
|
+
impact: 'critical',
|
|
493
514
|
rating: 5,
|
|
494
515
|
category: 'trust',
|
|
495
|
-
fix: '
|
|
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
|
// =============================================
|
package/src/gemini/techniques.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Gemini CLI techniques module — CHECK CATALOG
|
|
3
3
|
*
|
|
4
|
-
*
|
|
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
|
|
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
|
|
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
|
-
|
|
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: '
|
|
386
|
-
rating:
|
|
391
|
+
impact: 'critical',
|
|
392
|
+
rating: 5,
|
|
387
393
|
category: 'config',
|
|
388
|
-
fix: '
|
|
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
|
|
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.
|
|
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
|
|
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
|
-
|
|
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: '
|
|
1444
|
-
rating:
|
|
1455
|
+
impact: 'critical',
|
|
1456
|
+
rating: 5,
|
|
1445
1457
|
category: 'automation',
|
|
1446
|
-
fix: '
|
|
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) &&
|
|
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 &&
|
|
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
|
|
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
|
-
|
|
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
|
|
2068
|
-
check: (ctx) => {
|
|
2069
|
-
|
|
2070
|
-
|
|
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 = {
|