@nerviq/cli 1.9.0 → 1.11.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 +106 -60
- package/bin/cli.js +382 -208
- package/package.json +1 -1
- package/src/activity.js +245 -63
- package/src/aider/freshness.js +149 -146
- package/src/analyze.js +3 -1
- package/src/anti-patterns.js +17 -13
- package/src/audit.js +106 -79
- package/src/auto-suggest.js +62 -9
- package/src/benchmark.js +67 -51
- package/src/dashboard.js +36 -14
- package/src/governance.js +13 -7
- package/src/index.js +2 -1
- package/src/instruction-surfaces.js +185 -0
- package/src/integrations.js +102 -55
- package/src/locales/en.json +1 -1
- package/src/locales/es.json +1 -1
- package/src/permission-rules.js +218 -0
- package/src/secret-patterns.js +9 -0
- package/src/server.js +398 -3
- package/src/setup.js +2 -2
- package/src/stack-checks.js +1 -1
- package/src/synergy/report.js +1 -0
- package/src/techniques.js +102 -103
- package/src/terminology.js +73 -0
- package/src/token-estimate.js +35 -0
- package/src/workspace.js +155 -13
package/src/aider/freshness.js
CHANGED
|
@@ -1,123 +1,127 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Aider Freshness Operationalization
|
|
3
|
-
*
|
|
4
|
-
* Release gates, recurring probes, propagation checklists,
|
|
5
|
-
* and staleness blocking for Aider surfaces.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const { version } = require('../../package.json');
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* P0 sources that must be fresh before any Aider release claim.
|
|
12
|
-
*/
|
|
13
|
-
const P0_SOURCES = [
|
|
14
|
-
{
|
|
15
|
-
key: 'aider-docs',
|
|
16
|
-
label: 'Aider Official Docs',
|
|
17
|
-
url: 'https://aider.chat',
|
|
18
|
-
stalenessThresholdDays: 30,
|
|
19
|
-
verifiedAt:
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
key: 'aider-config-reference',
|
|
23
|
-
label: 'Aider Config Reference',
|
|
24
|
-
url: 'https://aider.chat/docs/config/aider_conf.html',
|
|
25
|
-
stalenessThresholdDays: 30,
|
|
26
|
-
verifiedAt:
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
key: 'aider-github-releases',
|
|
30
|
-
label: 'Aider GitHub Releases',
|
|
31
|
-
url: 'https://github.com/
|
|
32
|
-
stalenessThresholdDays: 14,
|
|
33
|
-
verifiedAt:
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
key: 'aider-model-docs',
|
|
37
|
-
label: 'Aider Model Documentation',
|
|
38
|
-
url: 'https://aider.chat/docs/llms.html',
|
|
39
|
-
stalenessThresholdDays: 30,
|
|
40
|
-
verifiedAt:
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
key: 'aider-pypi',
|
|
44
|
-
label: 'Aider PyPI Package',
|
|
45
|
-
url: 'https://pypi.org/project/aider-chat/',
|
|
46
|
-
stalenessThresholdDays: 14,
|
|
47
|
-
verifiedAt:
|
|
48
|
-
},
|
|
49
|
-
];
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Propagation checklist: when an Aider source changes, these must update.
|
|
53
|
-
*/
|
|
54
|
-
const PROPAGATION_CHECKLIST = [
|
|
55
|
-
{
|
|
56
|
-
trigger: 'Aider release with new config keys',
|
|
57
|
-
targets: [
|
|
58
|
-
'src/aider/techniques.js — update checks for new keys',
|
|
59
|
-
'src/aider/config-parser.js — update if YAML handling changes',
|
|
60
|
-
'src/aider/setup.js — update generated .aider.conf.yml template',
|
|
61
|
-
],
|
|
62
|
-
},
|
|
63
|
-
{
|
|
64
|
-
trigger: 'New Aider model support or role changes',
|
|
65
|
-
targets: [
|
|
66
|
-
'src/aider/techniques.js — update model config checks',
|
|
67
|
-
'src/aider/context.js — update modelRoles()',
|
|
68
|
-
'src/aider/governance.js — update policy packs if needed',
|
|
69
|
-
],
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
trigger: 'New Aider edit format or architect changes',
|
|
73
|
-
targets: [
|
|
74
|
-
'src/aider/techniques.js — update edit format checks',
|
|
75
|
-
'src/aider/setup.js — update template comments',
|
|
76
|
-
],
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
trigger: 'Aider CLI flag changes (renamed/removed)',
|
|
80
|
-
targets: [
|
|
81
|
-
'src/aider/techniques.js — update flag pattern matching',
|
|
82
|
-
'src/aider/setup.js — update generated config',
|
|
83
|
-
'src/aider/interactive.js — update wizard options',
|
|
84
|
-
],
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
trigger: 'Aider domain pack definitions change',
|
|
88
|
-
targets: [
|
|
89
|
-
'src/aider/domain-packs.js — update pack registry',
|
|
90
|
-
'src/aider/governance.js — governance export picks up changes',
|
|
91
|
-
],
|
|
92
|
-
},
|
|
93
|
-
];
|
|
94
|
-
|
|
95
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* Aider Freshness Operationalization
|
|
3
|
+
*
|
|
4
|
+
* Release gates, recurring probes, propagation checklists,
|
|
5
|
+
* and staleness blocking for Aider surfaces.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { version } = require('../../package.json');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* P0 sources that must be fresh before any Aider release claim.
|
|
12
|
+
*/
|
|
13
|
+
const P0_SOURCES = [
|
|
14
|
+
{
|
|
15
|
+
key: 'aider-docs',
|
|
16
|
+
label: 'Aider Official Docs',
|
|
17
|
+
url: 'https://aider.chat',
|
|
18
|
+
stalenessThresholdDays: 30,
|
|
19
|
+
verifiedAt: '2026-04-08',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
key: 'aider-config-reference',
|
|
23
|
+
label: 'Aider Config Reference',
|
|
24
|
+
url: 'https://aider.chat/docs/config/aider_conf.html',
|
|
25
|
+
stalenessThresholdDays: 30,
|
|
26
|
+
verifiedAt: '2026-04-08',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
key: 'aider-github-releases',
|
|
30
|
+
label: 'Aider GitHub Releases',
|
|
31
|
+
url: 'https://github.com/Aider-AI/aider/releases',
|
|
32
|
+
stalenessThresholdDays: 14,
|
|
33
|
+
verifiedAt: '2026-04-08',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
key: 'aider-model-docs',
|
|
37
|
+
label: 'Aider Model Documentation',
|
|
38
|
+
url: 'https://aider.chat/docs/llms.html',
|
|
39
|
+
stalenessThresholdDays: 30,
|
|
40
|
+
verifiedAt: '2026-04-08',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
key: 'aider-pypi',
|
|
44
|
+
label: 'Aider PyPI Package',
|
|
45
|
+
url: 'https://pypi.org/project/aider-chat/',
|
|
46
|
+
stalenessThresholdDays: 14,
|
|
47
|
+
verifiedAt: '2026-04-08',
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Propagation checklist: when an Aider source changes, these must update.
|
|
53
|
+
*/
|
|
54
|
+
const PROPAGATION_CHECKLIST = [
|
|
55
|
+
{
|
|
56
|
+
trigger: 'Aider release with new config keys',
|
|
57
|
+
targets: [
|
|
58
|
+
'src/aider/techniques.js — update checks for new keys',
|
|
59
|
+
'src/aider/config-parser.js — update if YAML handling changes',
|
|
60
|
+
'src/aider/setup.js — update generated .aider.conf.yml template',
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
trigger: 'New Aider model support or role changes',
|
|
65
|
+
targets: [
|
|
66
|
+
'src/aider/techniques.js — update model config checks',
|
|
67
|
+
'src/aider/context.js — update modelRoles()',
|
|
68
|
+
'src/aider/governance.js — update policy packs if needed',
|
|
69
|
+
],
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
trigger: 'New Aider edit format or architect changes',
|
|
73
|
+
targets: [
|
|
74
|
+
'src/aider/techniques.js — update edit format checks',
|
|
75
|
+
'src/aider/setup.js — update template comments',
|
|
76
|
+
],
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
trigger: 'Aider CLI flag changes (renamed/removed)',
|
|
80
|
+
targets: [
|
|
81
|
+
'src/aider/techniques.js — update flag pattern matching',
|
|
82
|
+
'src/aider/setup.js — update generated config',
|
|
83
|
+
'src/aider/interactive.js — update wizard options',
|
|
84
|
+
],
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
trigger: 'Aider domain pack definitions change',
|
|
88
|
+
targets: [
|
|
89
|
+
'src/aider/domain-packs.js — update pack registry',
|
|
90
|
+
'src/aider/governance.js — governance export picks up changes',
|
|
91
|
+
],
|
|
92
|
+
},
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
96
|
* Check release gate — are all P0 sources fresh?
|
|
97
97
|
*/
|
|
98
98
|
function checkReleaseGate(overrides = {}) {
|
|
99
99
|
const now = new Date();
|
|
100
100
|
const results = P0_SOURCES.map(source => {
|
|
101
|
-
const verifiedAt = overrides[source.key] || source.verifiedAt;
|
|
102
|
-
if (!verifiedAt) {
|
|
103
|
-
return { ...source, status: 'unverified', daysStale: null };
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const verifiedDate = new Date(verifiedAt);
|
|
107
|
-
const daysSince = Math.floor((now - verifiedDate) / (1000 * 60 * 60 * 24));
|
|
108
|
-
|
|
109
|
-
return {
|
|
110
|
-
...source,
|
|
111
|
-
verifiedAt,
|
|
112
|
-
status: daysSince <= source.stalenessThresholdDays ? 'fresh' : 'stale',
|
|
113
|
-
daysStale: daysSince,
|
|
114
|
-
};
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
const
|
|
101
|
+
const verifiedAt = overrides[source.key] || source.verifiedAt;
|
|
102
|
+
if (!verifiedAt) {
|
|
103
|
+
return { ...source, status: 'unverified', daysStale: null };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const verifiedDate = new Date(verifiedAt);
|
|
107
|
+
const daysSince = Math.floor((now - verifiedDate) / (1000 * 60 * 60 * 24));
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
...source,
|
|
111
|
+
verifiedAt,
|
|
112
|
+
status: daysSince <= source.stalenessThresholdDays ? 'fresh' : 'stale',
|
|
113
|
+
daysStale: daysSince,
|
|
114
|
+
};
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const stale = results.filter((result) => result.status === 'stale' || result.status === 'unverified');
|
|
118
|
+
const fresh = results.filter((result) => result.status === 'fresh');
|
|
119
|
+
const allFresh = stale.length === 0;
|
|
118
120
|
|
|
119
121
|
return {
|
|
120
122
|
ready: allFresh,
|
|
123
|
+
stale,
|
|
124
|
+
fresh,
|
|
121
125
|
results,
|
|
122
126
|
nerviqVersion: version,
|
|
123
127
|
checkedAt: now.toISOString(),
|
|
@@ -127,42 +131,41 @@ function checkReleaseGate(overrides = {}) {
|
|
|
127
131
|
/**
|
|
128
132
|
* Format release gate for display.
|
|
129
133
|
*/
|
|
130
|
-
function formatReleaseGate(
|
|
131
|
-
const gateResult = checkReleaseGate(overrides);
|
|
134
|
+
function formatReleaseGate(gateResult) {
|
|
132
135
|
const lines = [
|
|
133
136
|
`Aider Release Freshness Gate (nerviq v${version})`,
|
|
134
137
|
`Status: ${gateResult.ready ? 'READY' : 'NOT READY'}`,
|
|
135
138
|
'',
|
|
136
139
|
];
|
|
137
|
-
|
|
138
|
-
for (const result of gateResult.results) {
|
|
139
|
-
const icon = result.status === 'fresh' ? '✓' : result.status === 'stale' ? '✗' : '?';
|
|
140
|
-
const age = result.daysStale !== null ? ` (${result.daysStale}d ago)` : ' (unverified)';
|
|
141
|
-
lines.push(` ${icon} ${result.label}${age} — threshold: ${result.stalenessThresholdDays}d`);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (!gateResult.ready) {
|
|
145
|
-
lines.push('');
|
|
146
|
-
lines.push('Action required: verify stale/unverified sources before claiming release freshness.');
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
return lines.join('\n');
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Get propagation targets for a given trigger.
|
|
154
|
-
*/
|
|
155
|
-
function getPropagationTargets(triggerKeyword) {
|
|
156
|
-
const keyword = triggerKeyword.toLowerCase();
|
|
157
|
-
return PROPAGATION_CHECKLIST.filter(item =>
|
|
158
|
-
item.trigger.toLowerCase().includes(keyword)
|
|
159
|
-
);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
module.exports = {
|
|
163
|
-
P0_SOURCES,
|
|
164
|
-
PROPAGATION_CHECKLIST,
|
|
165
|
-
checkReleaseGate,
|
|
166
|
-
formatReleaseGate,
|
|
167
|
-
getPropagationTargets,
|
|
168
|
-
};
|
|
140
|
+
|
|
141
|
+
for (const result of gateResult.results) {
|
|
142
|
+
const icon = result.status === 'fresh' ? '✓' : result.status === 'stale' ? '✗' : '?';
|
|
143
|
+
const age = result.daysStale !== null ? ` (${result.daysStale}d ago)` : ' (unverified)';
|
|
144
|
+
lines.push(` ${icon} ${result.label}${age} — threshold: ${result.stalenessThresholdDays}d`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (!gateResult.ready) {
|
|
148
|
+
lines.push('');
|
|
149
|
+
lines.push('Action required: verify stale/unverified sources before claiming release freshness.');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return lines.join('\n');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Get propagation targets for a given trigger.
|
|
157
|
+
*/
|
|
158
|
+
function getPropagationTargets(triggerKeyword) {
|
|
159
|
+
const keyword = triggerKeyword.toLowerCase();
|
|
160
|
+
return PROPAGATION_CHECKLIST.filter(item =>
|
|
161
|
+
item.trigger.toLowerCase().includes(keyword)
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
module.exports = {
|
|
166
|
+
P0_SOURCES,
|
|
167
|
+
PROPAGATION_CHECKLIST,
|
|
168
|
+
checkReleaseGate,
|
|
169
|
+
formatReleaseGate,
|
|
170
|
+
getPropagationTargets,
|
|
171
|
+
};
|
package/src/analyze.js
CHANGED
|
@@ -11,6 +11,7 @@ const { STACKS } = require('./techniques');
|
|
|
11
11
|
const { detectDomainPacks } = require('./domain-packs');
|
|
12
12
|
const { detectCodexDomainPacks } = require('./codex/domain-packs');
|
|
13
13
|
const { recommendMcpPacks } = require('./mcp-packs');
|
|
14
|
+
const { collectClaudeDenyRules } = require('./permission-rules');
|
|
14
15
|
|
|
15
16
|
const COLORS = {
|
|
16
17
|
reset: '\x1b[0m',
|
|
@@ -101,6 +102,7 @@ function collectClaudeAssets(ctx) {
|
|
|
101
102
|
const sharedSettings = ctx.jsonFile('.claude/settings.json');
|
|
102
103
|
const localSettings = ctx.jsonFile('.claude/settings.local.json');
|
|
103
104
|
const settings = sharedSettings || localSettings || null;
|
|
105
|
+
const denyRules = collectClaudeDenyRules(ctx);
|
|
104
106
|
|
|
105
107
|
const assetFiles = {
|
|
106
108
|
claudeMd: ctx.fileContent('CLAUDE.md') ? 'CLAUDE.md' : (ctx.fileContent('.claude/CLAUDE.md') ? '.claude/CLAUDE.md' : null),
|
|
@@ -129,7 +131,7 @@ function collectClaudeAssets(ctx) {
|
|
|
129
131
|
},
|
|
130
132
|
permissions: settings && settings.permissions ? {
|
|
131
133
|
defaultMode: settings.permissions.defaultMode || null,
|
|
132
|
-
hasDenyRules:
|
|
134
|
+
hasDenyRules: denyRules.length > 0,
|
|
133
135
|
} : null,
|
|
134
136
|
settingsSource: assetFiles.settings,
|
|
135
137
|
summaryLine: `Commands: ${assetFiles.commands.length} | Rules: ${assetFiles.rules.length} | Hooks: ${assetFiles.hooks.length} | Agents: ${assetFiles.agents.length} | Skills: ${assetFiles.skills.length} | MCP servers: ${settings && settings.mcpServers ? Object.keys(settings.mcpServers).length : 0}`,
|
package/src/anti-patterns.js
CHANGED
|
@@ -3,7 +3,13 @@
|
|
|
3
3
|
* Provides a static catalog and a runtime detector that checks a project context.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
const
|
|
6
|
+
const {
|
|
7
|
+
getRepoInstructionBundle,
|
|
8
|
+
hasDocumentedVerificationGuidance,
|
|
9
|
+
hasDocumentedTestCommand,
|
|
10
|
+
} = require('./instruction-surfaces');
|
|
11
|
+
const { collectClaudeDenyRules } = require('./permission-rules');
|
|
12
|
+
const { containsEmbeddedSecret } = require('./secret-patterns');
|
|
7
13
|
|
|
8
14
|
const ANTI_PATTERNS = [
|
|
9
15
|
{
|
|
@@ -28,7 +34,7 @@ const ANTI_PATTERNS = [
|
|
|
28
34
|
detect: (ctx) => {
|
|
29
35
|
const settings = ctx.jsonFile('.claude/settings.json') || ctx.jsonFile('.claude/settings.local.json');
|
|
30
36
|
if (!settings || !settings.permissions) return true;
|
|
31
|
-
return
|
|
37
|
+
return collectClaudeDenyRules(ctx).length === 0;
|
|
32
38
|
},
|
|
33
39
|
},
|
|
34
40
|
{
|
|
@@ -93,15 +99,13 @@ const ANTI_PATTERNS = [
|
|
|
93
99
|
id: 'AP007',
|
|
94
100
|
name: 'No verification commands',
|
|
95
101
|
severity: 'medium',
|
|
96
|
-
description: 'Without test, lint, or build commands
|
|
102
|
+
description: 'Without test, lint, or build commands across the repo instruction surfaces, agents cannot self-verify changes consistently.',
|
|
97
103
|
platforms: ['claude', 'codex', 'cursor', 'windsurf', 'copilot', 'gemini', 'aider', 'opencode'],
|
|
98
|
-
fix: 'Add
|
|
104
|
+
fix: 'Add a canonical verification section or command doc in your repo instruction surfaces (for example CLAUDE.md, AGENTS.md, README, or platform rules).',
|
|
99
105
|
detect: (ctx) => {
|
|
100
|
-
const content =
|
|
106
|
+
const content = getRepoInstructionBundle(ctx);
|
|
101
107
|
if (!content) return false;
|
|
102
|
-
|
|
103
|
-
/\b(npm |yarn |pnpm |pytest|cargo |go |make )/i.test(content);
|
|
104
|
-
return !hasVerification;
|
|
108
|
+
return !hasDocumentedVerificationGuidance(content);
|
|
105
109
|
},
|
|
106
110
|
},
|
|
107
111
|
{
|
|
@@ -203,13 +207,13 @@ const ANTI_PATTERNS = [
|
|
|
203
207
|
id: 'AP014',
|
|
204
208
|
name: 'No test command defined',
|
|
205
209
|
severity: 'medium',
|
|
206
|
-
description: 'Without a test command,
|
|
210
|
+
description: 'Without a canonical test command in repo instructions or scripts, agents cannot verify changes reliably before handoff.',
|
|
207
211
|
platforms: ['claude', 'codex', 'cursor', 'windsurf', 'copilot', 'gemini', 'aider', 'opencode'],
|
|
208
|
-
fix: 'Add a test command in
|
|
212
|
+
fix: 'Add a canonical test command in repo instructions or package scripts, e.g. "Test: npm test" or "Test: pytest".',
|
|
209
213
|
detect: (ctx) => {
|
|
210
|
-
const content =
|
|
214
|
+
const content = getRepoInstructionBundle(ctx);
|
|
211
215
|
const pkg = ctx.jsonFile('package.json');
|
|
212
|
-
const hasTestInMd =
|
|
216
|
+
const hasTestInMd = hasDocumentedTestCommand(content);
|
|
213
217
|
const hasTestScript = pkg && pkg.scripts && pkg.scripts.test;
|
|
214
218
|
return !hasTestInMd && !hasTestScript;
|
|
215
219
|
},
|
|
@@ -320,7 +324,7 @@ const ANTI_PATTERNS = [
|
|
|
320
324
|
];
|
|
321
325
|
for (const file of hookFiles) {
|
|
322
326
|
const content = ctx.fileContent(`.claude/hooks/${file}`) || '';
|
|
323
|
-
if (secretPatterns.some(p => p.test(content))) {
|
|
327
|
+
if (secretPatterns.some(p => p.test(content)) || containsEmbeddedSecret(content)) {
|
|
324
328
|
return true;
|
|
325
329
|
}
|
|
326
330
|
}
|