@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.
- package/bin/cli.js +64 -3
- package/package.json +3 -2
- package/src/aider/techniques.js +85 -11
- package/src/audit.js +3 -2
- package/src/codex/techniques.js +3 -0
- package/src/convert.js +336 -0
- package/src/copilot/techniques.js +125 -11
- package/src/cursor/techniques.js +93 -10
- package/src/doctor.js +253 -0
- package/src/feedback.js +173 -0
- package/src/freshness.js +177 -0
- package/src/gemini/techniques.js +177 -23
- package/src/mcp-server.js +373 -0
- package/src/migrate.js +354 -0
- package/src/opencode/techniques.js +73 -99
- package/src/source-urls.js +219 -0
- package/src/techniques.js +3 -0
- package/src/windsurf/techniques.js +214 -138
package/src/freshness.js
ADDED
|
@@ -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
|
+
};
|
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
|
*/
|
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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: '
|
|
386
|
-
rating:
|
|
392
|
+
impact: 'critical',
|
|
393
|
+
rating: 5,
|
|
387
394
|
category: 'config',
|
|
388
|
-
fix: '
|
|
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
|
|
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.
|
|
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
|
|
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
|
-
|
|
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: '
|
|
1444
|
-
rating:
|
|
1456
|
+
impact: 'critical',
|
|
1457
|
+
rating: 5,
|
|
1445
1458
|
category: 'automation',
|
|
1446
|
-
fix: '
|
|
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) &&
|
|
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 &&
|
|
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
|
|
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
|
-
|
|
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
|
|
2068
|
-
check: (ctx) => {
|
|
2069
|
-
|
|
2070
|
-
|
|
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
|
};
|