@nerviq/cli 1.11.0 → 1.13.0
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 +216 -124
- package/bin/cli.js +620 -183
- package/package.json +3 -2
- package/src/activity.js +49 -9
- package/src/adoption-advisor.js +299 -0
- package/src/aider/freshness.js +65 -20
- package/src/aider/techniques.js +16 -11
- package/src/analyze.js +128 -0
- package/src/anti-patterns.js +13 -0
- package/src/audit/instruction-files.js +180 -0
- package/src/audit/recommendations.js +531 -0
- package/src/audit.js +53 -681
- package/src/behavioral-drift.js +801 -0
- package/src/codex/freshness.js +84 -25
- package/src/continuous-ops.js +681 -0
- package/src/copilot/freshness.js +57 -20
- package/src/cost-tracking.js +61 -0
- package/src/cursor/freshness.js +65 -20
- package/src/cursor/techniques.js +17 -12
- package/src/deep-review.js +83 -0
- package/src/diff-only.js +280 -0
- package/src/doctor.js +118 -55
- package/src/freshness.js +74 -21
- package/src/gemini/freshness.js +66 -21
- package/src/governance.js +59 -43
- package/src/hook-validation.js +342 -0
- package/src/index.js +5 -0
- package/src/integrations.js +42 -5
- package/src/mcp-server.js +95 -59
- package/src/mcp-validation.js +337 -0
- package/src/opencode/freshness.js +66 -21
- package/src/opencode/techniques.js +12 -7
- package/src/operating-profile.js +574 -0
- package/src/org.js +97 -13
- package/src/plans.js +192 -8
- package/src/platform-change-manifest.js +86 -0
- package/src/policy-layers.js +210 -0
- package/src/profiles.js +4 -1
- package/src/prompt-injection.js +74 -0
- package/src/repo-archetype.js +386 -0
- package/src/setup/analysis.js +619 -0
- package/src/setup/runtime.js +172 -0
- package/src/setup.js +62 -748
- package/src/source-urls.js +132 -132
- package/src/supplemental-checks.js +13 -12
- package/src/techniques/api.js +407 -0
- package/src/techniques/automation.js +316 -0
- package/src/techniques/compliance.js +257 -0
- package/src/techniques/hygiene.js +294 -0
- package/src/techniques/instructions.js +243 -0
- package/src/techniques/observability.js +226 -0
- package/src/techniques/optimization.js +142 -0
- package/src/techniques/quality.js +317 -0
- package/src/techniques/security.js +237 -0
- package/src/techniques/shared.js +443 -0
- package/src/techniques/stacks.js +2294 -0
- package/src/techniques/tools.js +106 -0
- package/src/techniques/workflow.js +413 -0
- package/src/techniques.js +78 -5607
- package/src/watch.js +18 -0
- package/src/windsurf/freshness.js +36 -21
- package/src/windsurf/techniques.js +17 -12
package/src/doctor.js
CHANGED
|
@@ -5,11 +5,13 @@
|
|
|
5
5
|
* Checks: Node version, dependencies, platform detection, freshness gates.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
'use strict';
|
|
9
|
-
|
|
10
|
-
const fs = require('fs');
|
|
11
|
-
const path = require('path');
|
|
12
|
-
const { version } = require('../package.json');
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const { version } = require('../package.json');
|
|
13
|
+
const { validateDeclaredMcpServers } = require('./mcp-validation');
|
|
14
|
+
const { validateDeclaredHooks } = require('./hook-validation');
|
|
13
15
|
|
|
14
16
|
const COLORS = {
|
|
15
17
|
reset: '\x1b[0m',
|
|
@@ -25,11 +27,11 @@ function c(text, color) {
|
|
|
25
27
|
return `${COLORS[color] || ''}${text}${COLORS.reset}`;
|
|
26
28
|
}
|
|
27
29
|
|
|
28
|
-
const PLATFORM_SIGNALS = {
|
|
29
|
-
claude: ['CLAUDE.md', '.claude/CLAUDE.md', '.claude/settings.json'],
|
|
30
|
-
codex: ['AGENTS.md', '.codex/', '.codex/config.toml'],
|
|
31
|
-
cursor: ['.cursor/rules/', '.cursor/mcp.json', '.cursorrules'],
|
|
32
|
-
copilot: ['.github/copilot-instructions.md', '.github/'],
|
|
30
|
+
const PLATFORM_SIGNALS = {
|
|
31
|
+
claude: ['CLAUDE.md', '.claude/CLAUDE.md', '.claude/settings.json', '.mcp.json'],
|
|
32
|
+
codex: ['AGENTS.md', '.codex/', '.codex/config.toml'],
|
|
33
|
+
cursor: ['.cursor/rules/', '.cursor/mcp.json', '.cursorrules'],
|
|
34
|
+
copilot: ['.github/copilot-instructions.md', '.github/', '.vscode/mcp.json'],
|
|
33
35
|
gemini: ['GEMINI.md', '.gemini/', '.gemini/settings.json'],
|
|
34
36
|
windsurf: ['.windsurf/', '.windsurfrules', '.windsurf/rules/'],
|
|
35
37
|
aider: ['.aider.conf.yml', '.aider.model.settings.yml'],
|
|
@@ -162,46 +164,59 @@ function checkGitRepo(dir) {
|
|
|
162
164
|
|
|
163
165
|
// ─── Main doctor function ────────────────────────────────────────────────────
|
|
164
166
|
|
|
165
|
-
async function runDoctor({ dir = process.cwd(), json = false, verbose = false } = {}) {
|
|
166
|
-
const startMs = Date.now();
|
|
167
|
-
|
|
168
|
-
const checks = [
|
|
167
|
+
async function runDoctor({ dir = process.cwd(), json = false, verbose = false } = {}) {
|
|
168
|
+
const startMs = Date.now();
|
|
169
|
+
|
|
170
|
+
const checks = [
|
|
169
171
|
checkNodeVersion(),
|
|
170
172
|
checkDeps(),
|
|
171
173
|
checkJestInstalled(),
|
|
172
174
|
checkCliPermissions(),
|
|
173
175
|
checkGitRepo(dir),
|
|
174
176
|
checkPlatformDetection(dir),
|
|
175
|
-
];
|
|
176
|
-
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
const
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
const overallOk = totalFail === 0;
|
|
187
|
-
const elapsed = Date.now() - startMs;
|
|
177
|
+
];
|
|
178
|
+
|
|
179
|
+
const detectedPlatforms = (checks.find(c => c.detected) || {}).detected || [];
|
|
180
|
+
const freshnessChecks = checkFreshnessGates();
|
|
181
|
+
const mcpSummary = await validateDeclaredMcpServers({ dir, detectedPlatforms });
|
|
182
|
+
const hookSummary = validateDeclaredHooks({ dir, detectedPlatforms });
|
|
183
|
+
|
|
184
|
+
const totalPass = checks.filter(c => c.status === 'pass').length;
|
|
185
|
+
const totalWarn = checks.filter(c => c.status === 'warn').length;
|
|
186
|
+
const totalFail = checks.filter(c => c.status === 'fail').length;
|
|
188
187
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
188
|
+
const freshPass = freshnessChecks.filter(f => f.status === 'pass').length;
|
|
189
|
+
const freshWarn = freshnessChecks.filter(f => f.status !== 'pass').length;
|
|
190
|
+
|
|
191
|
+
const overallOk = totalFail === 0 && mcpSummary.fail === 0 && hookSummary.fail === 0;
|
|
192
|
+
const elapsed = Date.now() - startMs;
|
|
193
|
+
|
|
194
|
+
if (json) {
|
|
195
|
+
return JSON.stringify({
|
|
196
|
+
nerviq: version,
|
|
192
197
|
node: process.version,
|
|
193
198
|
dir,
|
|
194
199
|
overallOk,
|
|
195
200
|
checks,
|
|
196
|
-
freshnessChecks,
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
201
|
+
freshnessChecks,
|
|
202
|
+
mcpChecks: mcpSummary.checks,
|
|
203
|
+
hookChecks: hookSummary.checks,
|
|
204
|
+
totalPass,
|
|
205
|
+
totalWarn,
|
|
206
|
+
totalFail,
|
|
207
|
+
freshPass,
|
|
208
|
+
freshWarn,
|
|
209
|
+
mcpDeclared: mcpSummary.declared,
|
|
210
|
+
mcpPass: mcpSummary.pass,
|
|
211
|
+
mcpWarn: mcpSummary.warn,
|
|
212
|
+
mcpFail: mcpSummary.fail,
|
|
213
|
+
hookDeclared: hookSummary.declared,
|
|
214
|
+
hookPass: hookSummary.pass,
|
|
215
|
+
hookWarn: hookSummary.warn,
|
|
216
|
+
hookFail: hookSummary.fail,
|
|
217
|
+
elapsed,
|
|
218
|
+
}, null, 2);
|
|
219
|
+
}
|
|
205
220
|
|
|
206
221
|
const lines = [''];
|
|
207
222
|
lines.push(c(` nerviq doctor v${version}`, 'bold'));
|
|
@@ -219,10 +234,9 @@ async function runDoctor({ dir = process.cwd(), json = false, verbose = false }
|
|
|
219
234
|
}
|
|
220
235
|
|
|
221
236
|
// Platform detection detail
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
lines.push('');
|
|
225
|
-
lines.push(c(' Detected Platforms', 'bold'));
|
|
237
|
+
if (detectedPlatforms.length > 0) {
|
|
238
|
+
lines.push('');
|
|
239
|
+
lines.push(c(' Detected Platforms', 'bold'));
|
|
226
240
|
for (const p of detectedPlatforms) {
|
|
227
241
|
lines.push(` ${c('✓', 'green')} ${p}`);
|
|
228
242
|
}
|
|
@@ -231,18 +245,67 @@ async function runDoctor({ dir = process.cwd(), json = false, verbose = false }
|
|
|
231
245
|
// Freshness
|
|
232
246
|
lines.push('');
|
|
233
247
|
lines.push(c(' Freshness Gates', 'bold'));
|
|
234
|
-
for (const f of freshnessChecks) {
|
|
235
|
-
const icon = f.status === 'pass' ? c('✓', 'green') : c('⚠', 'yellow');
|
|
236
|
-
const label = f.platform.padEnd(12);
|
|
237
|
-
lines.push(` ${icon} ${label} ${c(f.detail || f.status, f.status === 'pass' ? 'dim' : 'yellow')}`);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
lines.push('');
|
|
241
|
-
lines.push(c('
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
248
|
+
for (const f of freshnessChecks) {
|
|
249
|
+
const icon = f.status === 'pass' ? c('✓', 'green') : c('⚠', 'yellow');
|
|
250
|
+
const label = f.platform.padEnd(12);
|
|
251
|
+
lines.push(` ${icon} ${label} ${c(f.detail || f.status, f.status === 'pass' ? 'dim' : 'yellow')}`);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
lines.push('');
|
|
255
|
+
lines.push(c(' MCP Servers', 'bold'));
|
|
256
|
+
if (mcpSummary.checks.length === 0) {
|
|
257
|
+
lines.push(c(' No declared MCP servers found in the detected project surfaces.', 'dim'));
|
|
258
|
+
} else {
|
|
259
|
+
for (const item of mcpSummary.checks) {
|
|
260
|
+
const icon = item.status === 'pass'
|
|
261
|
+
? c('✓', 'green')
|
|
262
|
+
: item.status === 'warn'
|
|
263
|
+
? c('⚠', 'yellow')
|
|
264
|
+
: c('✗', 'red');
|
|
265
|
+
const label = `${item.platform}/${item.scope}`.padEnd(16);
|
|
266
|
+
lines.push(` ${icon} ${label} ${item.serverName} ${c(item.detail, item.status === 'pass' ? 'dim' : item.status === 'warn' ? 'yellow' : 'red')}`);
|
|
267
|
+
if (verbose && item.source) {
|
|
268
|
+
lines.push(c(` Source: ${item.source}`, 'dim'));
|
|
269
|
+
}
|
|
270
|
+
if (item.fix && (verbose || item.status === 'fail')) {
|
|
271
|
+
lines.push(c(` Fix: ${item.fix}`, item.status === 'fail' ? 'yellow' : 'dim'));
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
lines.push('');
|
|
277
|
+
lines.push(c(' Hook Runtime', 'bold'));
|
|
278
|
+
if (hookSummary.checks.length === 0) {
|
|
279
|
+
lines.push(c(' No declared hooks found in the detected project surfaces.', 'dim'));
|
|
280
|
+
} else {
|
|
281
|
+
for (const item of hookSummary.checks) {
|
|
282
|
+
const icon = item.status === 'pass'
|
|
283
|
+
? c('✓', 'green')
|
|
284
|
+
: item.status === 'warn'
|
|
285
|
+
? c('⚠', 'yellow')
|
|
286
|
+
: c('✗', 'red');
|
|
287
|
+
const label = `${item.platform}/${item.validationMode}`.padEnd(16);
|
|
288
|
+
lines.push(` ${icon} ${label} ${item.label} ${c(item.detail, item.status === 'pass' ? 'dim' : item.status === 'warn' ? 'yellow' : 'red')}`);
|
|
289
|
+
if (verbose && item.script) {
|
|
290
|
+
lines.push(c(` Script: ${item.script}`, 'dim'));
|
|
291
|
+
}
|
|
292
|
+
if (verbose && item.executable) {
|
|
293
|
+
lines.push(c(` Runtime: ${item.executable}`, 'dim'));
|
|
294
|
+
}
|
|
295
|
+
if (item.fix && (verbose || item.status === 'fail')) {
|
|
296
|
+
lines.push(c(` Fix: ${item.fix}`, item.status === 'fail' ? 'yellow' : 'dim'));
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
lines.push('');
|
|
302
|
+
lines.push(c(' Summary', 'bold'));
|
|
303
|
+
lines.push(` Checks: ${c(String(totalPass), 'green')} pass ${totalWarn > 0 ? c(String(totalWarn), 'yellow') + ' warn ' : ''}${totalFail > 0 ? c(String(totalFail), 'red') + ' fail' : ''}`);
|
|
304
|
+
lines.push(` Freshness: ${c(String(freshPass), 'green')} fresh ${freshWarn > 0 ? c(String(freshWarn), 'yellow') + ' stale/unverified' : ''}`);
|
|
305
|
+
lines.push(` MCP: ${c(String(mcpSummary.pass), 'green')} pass ${mcpSummary.warn > 0 ? c(String(mcpSummary.warn), 'yellow') + ' warn ' : ''}${mcpSummary.fail > 0 ? c(String(mcpSummary.fail), 'red') + ' fail' : ''}${c(`(${mcpSummary.declared} declared)`, 'dim')}`);
|
|
306
|
+
lines.push(` Hooks: ${c(String(hookSummary.pass), 'green')} pass ${hookSummary.warn > 0 ? c(String(hookSummary.warn), 'yellow') + ' warn ' : ''}${hookSummary.fail > 0 ? c(String(hookSummary.fail), 'red') + ' fail' : ''}${c(`(${hookSummary.declared} declared)`, 'dim')}`);
|
|
307
|
+
lines.push(` Status: ${overallOk ? c('✓ Healthy', 'green') : c('✗ Issues found', 'red')}`);
|
|
308
|
+
lines.push(` Duration: ${elapsed}ms`);
|
|
246
309
|
lines.push('');
|
|
247
310
|
|
|
248
311
|
if (!overallOk) {
|
package/src/freshness.js
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* Release gates, recurring probes, propagation checklists,
|
|
5
5
|
* and staleness blocking for Claude Code surfaces.
|
|
6
6
|
*
|
|
7
|
-
* P0 sources from code.claude.com/docs
|
|
7
|
+
* P0 sources from code.claude.com/docs and official Anthropic launch posts,
|
|
8
|
+
* with propagation for CLAUDE.md, output style, and agent harness changes.
|
|
8
9
|
*/
|
|
9
10
|
|
|
10
11
|
const { version } = require('../package.json');
|
|
@@ -55,18 +56,46 @@ const P0_SOURCES = [
|
|
|
55
56
|
stalenessThresholdDays: 14,
|
|
56
57
|
verifiedAt: '2026-04-07',
|
|
57
58
|
},
|
|
58
|
-
{
|
|
59
|
-
key: 'claude-settings-docs',
|
|
60
|
-
label: 'Claude Code Settings Documentation',
|
|
61
|
-
url: 'https://code.claude.com/docs/en/settings',
|
|
62
|
-
stalenessThresholdDays: 30,
|
|
63
|
-
verifiedAt: '2026-04-07',
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
key: '
|
|
67
|
-
label: 'Claude Code
|
|
68
|
-
url: 'https://code.claude.com/docs/en/
|
|
69
|
-
stalenessThresholdDays: 14,
|
|
59
|
+
{
|
|
60
|
+
key: 'claude-settings-docs',
|
|
61
|
+
label: 'Claude Code Settings Documentation',
|
|
62
|
+
url: 'https://code.claude.com/docs/en/settings',
|
|
63
|
+
stalenessThresholdDays: 30,
|
|
64
|
+
verifiedAt: '2026-04-07',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
key: 'claude-output-styles-docs',
|
|
68
|
+
label: 'Claude Code Output Styles / Insights',
|
|
69
|
+
url: 'https://code.claude.com/docs/en/output-styles',
|
|
70
|
+
stalenessThresholdDays: 14,
|
|
71
|
+
verifiedAt: '2026-04-10',
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
key: 'claude-best-practices-docs',
|
|
75
|
+
label: 'Claude Code Best Practices / Auto Mode',
|
|
76
|
+
url: 'https://code.claude.com/docs/en/best-practices',
|
|
77
|
+
stalenessThresholdDays: 14,
|
|
78
|
+
verifiedAt: '2026-04-10',
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
key: 'claude-agent-sdk-docs',
|
|
82
|
+
label: 'Claude Agent SDK Overview',
|
|
83
|
+
url: 'https://code.claude.com/docs/en/agent-sdk/overview',
|
|
84
|
+
stalenessThresholdDays: 14,
|
|
85
|
+
verifiedAt: '2026-04-10',
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
key: 'claude-xcode-agent-sdk',
|
|
89
|
+
label: 'Anthropic Xcode Agent SDK Launch',
|
|
90
|
+
url: 'https://www.anthropic.com/news/apple-xcode-claude-agent-sdk',
|
|
91
|
+
stalenessThresholdDays: 30,
|
|
92
|
+
verifiedAt: '2026-04-10',
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
key: 'anthropic-changelog',
|
|
96
|
+
label: 'Claude Code Changelog',
|
|
97
|
+
url: 'https://code.claude.com/docs/en/changelog',
|
|
98
|
+
stalenessThresholdDays: 14,
|
|
70
99
|
verifiedAt: '2026-04-07',
|
|
71
100
|
},
|
|
72
101
|
];
|
|
@@ -98,14 +127,38 @@ const PROPAGATION_CHECKLIST = [
|
|
|
98
127
|
'src/context.js — update mcpConfig parsing',
|
|
99
128
|
],
|
|
100
129
|
},
|
|
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
|
-
|
|
130
|
+
{
|
|
131
|
+
trigger: 'Permissions model change (allow/deny lists, operator/user split)',
|
|
132
|
+
targets: [
|
|
133
|
+
'src/governance.js — update permissionProfiles',
|
|
134
|
+
'src/techniques.js — update permission checks',
|
|
135
|
+
],
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
trigger: 'Output style / Insights change (system prompt layering, outputStyle storage, learning mode behavior)',
|
|
139
|
+
targets: [
|
|
140
|
+
'src/techniques.js — update Claude settings and instruction-surface checks that depend on system-prompt-adjacent behavior',
|
|
141
|
+
'src/setup.js — update Claude settings starter templates if outputStyle guidance changes',
|
|
142
|
+
'src/source-urls.js — refresh Claude feature source mappings when output style docs move or split',
|
|
143
|
+
],
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
trigger: 'Best-practices or auto mode change (permission classifier, unattended mode, safety fallback behavior)',
|
|
147
|
+
targets: [
|
|
148
|
+
'src/governance.js — update permission mode caveats and policy guidance',
|
|
149
|
+
'src/techniques.js — update Claude trust/verification checks tied to auto mode or unattended workflows',
|
|
150
|
+
'src/source-urls.js — refresh Claude best-practice source mappings if guidance moves',
|
|
151
|
+
],
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
trigger: 'Agent SDK / harness or native integration change (SDK surfaces, subagents, background tasks, Xcode bridge)',
|
|
155
|
+
targets: [
|
|
156
|
+
'src/techniques.js — update Claude modern-capability checks and cross-surface expectations',
|
|
157
|
+
'src/mcp-packs.js — revisit pack assumptions when native integrations change MCP usage',
|
|
158
|
+
'src/source-urls.js — refresh Claude source mappings for SDK and integration surfaces',
|
|
159
|
+
],
|
|
160
|
+
},
|
|
161
|
+
];
|
|
109
162
|
|
|
110
163
|
/**
|
|
111
164
|
* Release gate: check if all P0 sources are within staleness threshold.
|
package/src/gemini/freshness.js
CHANGED
|
@@ -27,18 +27,39 @@ const P0_SOURCES = [
|
|
|
27
27
|
stalenessThresholdDays: 30,
|
|
28
28
|
verifiedAt: '2026-04-07',
|
|
29
29
|
},
|
|
30
|
-
{
|
|
31
|
-
key: 'gemini-md-guide',
|
|
32
|
-
label: 'GEMINI.md Guide',
|
|
33
|
-
url: 'https://google-gemini.github.io/gemini-cli/docs/cli/gemini-md.html',
|
|
34
|
-
stalenessThresholdDays: 30,
|
|
35
|
-
verifiedAt: '2026-04-07',
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
key: 'gemini-
|
|
39
|
-
label: 'Gemini
|
|
40
|
-
url: 'https://google-gemini.github.io/gemini-cli/docs/
|
|
41
|
-
stalenessThresholdDays:
|
|
30
|
+
{
|
|
31
|
+
key: 'gemini-md-guide',
|
|
32
|
+
label: 'GEMINI.md Guide',
|
|
33
|
+
url: 'https://google-gemini.github.io/gemini-cli/docs/cli/gemini-md.html',
|
|
34
|
+
stalenessThresholdDays: 30,
|
|
35
|
+
verifiedAt: '2026-04-07',
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
key: 'gemini-trusted-folders-docs',
|
|
39
|
+
label: 'Gemini Trusted Folders',
|
|
40
|
+
url: 'https://google-gemini.github.io/gemini-cli/docs/cli/trusted-folders.html',
|
|
41
|
+
stalenessThresholdDays: 14,
|
|
42
|
+
verifiedAt: '2026-04-10',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
key: 'gemini-ide-integration-docs',
|
|
46
|
+
label: 'Gemini IDE Integration',
|
|
47
|
+
url: 'https://google-gemini.github.io/gemini-cli/docs/ide-integration.html',
|
|
48
|
+
stalenessThresholdDays: 14,
|
|
49
|
+
verifiedAt: '2026-04-10',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
key: 'gemini-architecture-docs',
|
|
53
|
+
label: 'Gemini Architecture Overview',
|
|
54
|
+
url: 'https://google-gemini.github.io/gemini-cli/docs/architecture.html',
|
|
55
|
+
stalenessThresholdDays: 30,
|
|
56
|
+
verifiedAt: '2026-04-10',
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
key: 'gemini-hooks-docs',
|
|
60
|
+
label: 'Gemini Hooks Documentation',
|
|
61
|
+
url: 'https://google-gemini.github.io/gemini-cli/docs/hooks/',
|
|
62
|
+
stalenessThresholdDays: 30,
|
|
42
63
|
verifiedAt: '2026-04-07',
|
|
43
64
|
},
|
|
44
65
|
{
|
|
@@ -114,15 +135,39 @@ const PROPAGATION_CHECKLIST = [
|
|
|
114
135
|
'src/gemini/setup.js — update sandbox starter template',
|
|
115
136
|
],
|
|
116
137
|
},
|
|
117
|
-
{
|
|
118
|
-
trigger: 'Policy engine syntax or rule format change',
|
|
119
|
-
targets: [
|
|
120
|
-
'src/gemini/techniques.js — update policy validation checks',
|
|
121
|
-
'src/gemini/governance.js — update policy templates and caveats',
|
|
122
|
-
'src/gemini/config-parser.js — update policy parsing rules',
|
|
123
|
-
],
|
|
124
|
-
},
|
|
125
|
-
|
|
138
|
+
{
|
|
139
|
+
trigger: 'Policy engine syntax or rule format change',
|
|
140
|
+
targets: [
|
|
141
|
+
'src/gemini/techniques.js — update policy validation checks',
|
|
142
|
+
'src/gemini/governance.js — update policy templates and caveats',
|
|
143
|
+
'src/gemini/config-parser.js — update policy parsing rules',
|
|
144
|
+
],
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
trigger: 'Trusted-folder or safe-mode behavior change',
|
|
148
|
+
targets: [
|
|
149
|
+
'src/gemini/techniques.js — update trust/safe-mode checks',
|
|
150
|
+
'src/gemini/governance.js — update trusted-folder caveats and permission guidance',
|
|
151
|
+
'src/source-urls.js — refresh Gemini trust source mappings',
|
|
152
|
+
],
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
trigger: 'IDE integration change (workspace context, diffing, companion extension behavior)',
|
|
156
|
+
targets: [
|
|
157
|
+
'src/gemini/techniques.js — update IDE-assist and context-surface checks',
|
|
158
|
+
'src/gemini/setup.js — update IDE integration starter guidance',
|
|
159
|
+
'src/source-urls.js — refresh Gemini IDE source mappings',
|
|
160
|
+
],
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
trigger: 'Architecture or orchestration change (packages/core, tool flow, approval path)',
|
|
164
|
+
targets: [
|
|
165
|
+
'src/gemini/context.js — revisit assumptions tied to tool and session orchestration',
|
|
166
|
+
'src/gemini/techniques.js — update modern-feature and tool-flow checks',
|
|
167
|
+
'src/source-urls.js — refresh Gemini architecture source mappings',
|
|
168
|
+
],
|
|
169
|
+
},
|
|
170
|
+
];
|
|
126
171
|
|
|
127
172
|
/**
|
|
128
173
|
* Release gate: check if all P0 sources are within staleness threshold.
|
package/src/governance.js
CHANGED
|
@@ -104,19 +104,19 @@ const HOOK_REGISTRY = [
|
|
|
104
104
|
dryRunExample: 'Edit a catalog file and verify duplicate check runs without blocking.',
|
|
105
105
|
rollbackPath: 'Remove the PostToolUse hook entry from settings.',
|
|
106
106
|
},
|
|
107
|
-
{
|
|
108
|
-
key: 'injection-defense',
|
|
109
|
-
file: '.claude/hooks/injection-defense.
|
|
110
|
-
triggerPoint: 'PostToolUse',
|
|
111
|
-
matcher: 'WebFetch|WebSearch',
|
|
112
|
-
purpose: 'Scans
|
|
113
|
-
filesTouched: ['
|
|
114
|
-
sideEffects: ['
|
|
115
|
-
risk: 'low',
|
|
116
|
-
riskLevel: 'low',
|
|
117
|
-
dryRunExample: 'Run a WebFetch and verify
|
|
118
|
-
rollbackPath: 'Remove the PostToolUse hook entry from settings.',
|
|
119
|
-
},
|
|
107
|
+
{
|
|
108
|
+
key: 'injection-defense',
|
|
109
|
+
file: '.claude/hooks/injection-defense.js',
|
|
110
|
+
triggerPoint: 'PostToolUse',
|
|
111
|
+
matcher: 'WebFetch|WebSearch|Read|Grep|Glob|mcp__.*',
|
|
112
|
+
purpose: 'Scans external content flows for common prompt injection patterns and logs suspicious findings.',
|
|
113
|
+
filesTouched: ['.claude/logs/prompt-injection-alerts.log'],
|
|
114
|
+
sideEffects: ['Appends an alert line when suspicious external content is detected.'],
|
|
115
|
+
risk: 'low',
|
|
116
|
+
riskLevel: 'low',
|
|
117
|
+
dryRunExample: 'Run a WebFetch or MCP-backed tool call and verify suspicious content is logged for review.',
|
|
118
|
+
rollbackPath: 'Remove the PostToolUse hook entry from settings.',
|
|
119
|
+
},
|
|
120
120
|
{
|
|
121
121
|
key: 'trust-drift-check',
|
|
122
122
|
file: '.claude/hooks/trust-drift-check.sh',
|
|
@@ -299,23 +299,24 @@ function buildHookConfig(hookFiles, profileKey) {
|
|
|
299
299
|
return {};
|
|
300
300
|
}
|
|
301
301
|
|
|
302
|
-
// Detect hook runtime: .js files use node, .sh files use bash
|
|
303
|
-
const hookCommand = (file) => {
|
|
304
|
-
if (file.endsWith('.js')) return `node .claude/hooks/${file}`;
|
|
305
|
-
return `bash .claude/hooks/${file}`;
|
|
306
|
-
};
|
|
307
|
-
const isSecrets = (f) => f === 'protect-secrets.sh' || f === 'protect-secrets.js';
|
|
308
|
-
const isSession = (f) => f === 'session-start.sh' || f === 'session-start.js';
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
.
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
302
|
+
// Detect hook runtime: .js files use node, .sh files use bash
|
|
303
|
+
const hookCommand = (file) => {
|
|
304
|
+
if (file.endsWith('.js')) return `node .claude/hooks/${file}`;
|
|
305
|
+
return `bash .claude/hooks/${file}`;
|
|
306
|
+
};
|
|
307
|
+
const isSecrets = (f) => f === 'protect-secrets.sh' || f === 'protect-secrets.js';
|
|
308
|
+
const isSession = (f) => f === 'session-start.sh' || f === 'session-start.js';
|
|
309
|
+
const isInjection = (f) => f === 'injection-defense.sh' || f === 'injection-defense.js';
|
|
310
|
+
|
|
311
|
+
const hookConfig = {
|
|
312
|
+
PostToolUse: [{
|
|
313
|
+
matcher: 'Write|Edit',
|
|
314
|
+
hooks: uniqueFiles
|
|
315
|
+
.filter(file => !isSecrets(file) && !isSession(file) && !isInjection(file))
|
|
316
|
+
.map(file => ({
|
|
317
|
+
type: 'command',
|
|
318
|
+
command: hookCommand(file),
|
|
319
|
+
timeout: 10,
|
|
319
320
|
})),
|
|
320
321
|
}],
|
|
321
322
|
};
|
|
@@ -333,16 +334,29 @@ function buildHookConfig(hookFiles, profileKey) {
|
|
|
333
334
|
}
|
|
334
335
|
|
|
335
336
|
const sessionFile = uniqueFiles.find(isSession);
|
|
336
|
-
if (sessionFile) {
|
|
337
|
-
hookConfig.SessionStart = [{
|
|
338
|
-
matcher: '*',
|
|
339
|
-
hooks: [{
|
|
340
|
-
type: 'command',
|
|
337
|
+
if (sessionFile) {
|
|
338
|
+
hookConfig.SessionStart = [{
|
|
339
|
+
matcher: '*',
|
|
340
|
+
hooks: [{
|
|
341
|
+
type: 'command',
|
|
341
342
|
command: hookCommand(sessionFile),
|
|
342
343
|
timeout: 5,
|
|
343
|
-
}],
|
|
344
|
-
}];
|
|
345
|
-
}
|
|
344
|
+
}],
|
|
345
|
+
}];
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const injectionFile = uniqueFiles.find(isInjection);
|
|
349
|
+
if (injectionFile) {
|
|
350
|
+
hookConfig.PostToolUse = hookConfig.PostToolUse || [];
|
|
351
|
+
hookConfig.PostToolUse.push({
|
|
352
|
+
matcher: 'WebFetch|WebSearch|Read|Grep|Glob|mcp__.*',
|
|
353
|
+
hooks: [{
|
|
354
|
+
type: 'command',
|
|
355
|
+
command: hookCommand(injectionFile),
|
|
356
|
+
timeout: 5,
|
|
357
|
+
}],
|
|
358
|
+
});
|
|
359
|
+
}
|
|
346
360
|
|
|
347
361
|
if ((hookConfig.PostToolUse[0].hooks || []).length === 0) {
|
|
348
362
|
delete hookConfig.PostToolUse;
|
|
@@ -576,11 +590,13 @@ function renderGovernanceMarkdown(summary) {
|
|
|
576
590
|
return lines.join('\n');
|
|
577
591
|
}
|
|
578
592
|
|
|
579
|
-
module.exports = {
|
|
580
|
-
PERMISSION_PROFILES,
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
593
|
+
module.exports = {
|
|
594
|
+
PERMISSION_PROFILES,
|
|
595
|
+
HOOK_REGISTRY,
|
|
596
|
+
POLICY_PACKS,
|
|
597
|
+
getPermissionProfile,
|
|
598
|
+
isWritableProfile,
|
|
599
|
+
ensureWritableProfile,
|
|
584
600
|
buildSettingsForProfile,
|
|
585
601
|
getGovernanceSummary,
|
|
586
602
|
printGovernanceSummary,
|