@nerviq/cli 1.9.0 → 1.10.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 +28 -33
- package/bin/cli.js +161 -106
- package/package.json +1 -1
- package/src/activity.js +68 -12
- package/src/aider/freshness.js +168 -168
- package/src/anti-patterns.js +13 -11
- package/src/audit.js +6 -5
- package/src/auto-suggest.js +62 -9
- package/src/benchmark.js +52 -41
- package/src/dashboard.js +36 -14
- package/src/instruction-surfaces.js +185 -0
- package/src/locales/en.json +1 -1
- package/src/locales/es.json +1 -1
- package/src/stack-checks.js +1 -1
- package/src/synergy/report.js +1 -0
- package/src/techniques.js +61 -58
- package/src/workspace.js +51 -6
package/src/aider/freshness.js
CHANGED
|
@@ -1,168 +1,168 @@
|
|
|
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
|
-
/**
|
|
96
|
-
* Check release gate — are all P0 sources fresh?
|
|
97
|
-
*/
|
|
98
|
-
function checkReleaseGate(overrides = {}) {
|
|
99
|
-
const now = new Date();
|
|
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 allFresh = results.every(r => r.status === 'fresh');
|
|
118
|
-
|
|
119
|
-
return {
|
|
120
|
-
ready: allFresh,
|
|
121
|
-
results,
|
|
122
|
-
nerviqVersion: version,
|
|
123
|
-
checkedAt: now.toISOString(),
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Format release gate for display.
|
|
129
|
-
*/
|
|
130
|
-
function formatReleaseGate(overrides = {}) {
|
|
131
|
-
const gateResult = checkReleaseGate(overrides);
|
|
132
|
-
const lines = [
|
|
133
|
-
`Aider Release Freshness Gate (nerviq v${version})`,
|
|
134
|
-
`Status: ${gateResult.ready ? 'READY' : 'NOT READY'}`,
|
|
135
|
-
'',
|
|
136
|
-
];
|
|
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
|
-
};
|
|
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
|
+
* Check release gate — are all P0 sources fresh?
|
|
97
|
+
*/
|
|
98
|
+
function checkReleaseGate(overrides = {}) {
|
|
99
|
+
const now = new Date();
|
|
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 allFresh = results.every(r => r.status === 'fresh');
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
ready: allFresh,
|
|
121
|
+
results,
|
|
122
|
+
nerviqVersion: version,
|
|
123
|
+
checkedAt: now.toISOString(),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Format release gate for display.
|
|
129
|
+
*/
|
|
130
|
+
function formatReleaseGate(overrides = {}) {
|
|
131
|
+
const gateResult = checkReleaseGate(overrides);
|
|
132
|
+
const lines = [
|
|
133
|
+
`Aider Release Freshness Gate (nerviq v${version})`,
|
|
134
|
+
`Status: ${gateResult.ready ? 'READY' : 'NOT READY'}`,
|
|
135
|
+
'',
|
|
136
|
+
];
|
|
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
|
+
};
|
package/src/anti-patterns.js
CHANGED
|
@@ -3,7 +3,11 @@
|
|
|
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');
|
|
7
11
|
|
|
8
12
|
const ANTI_PATTERNS = [
|
|
9
13
|
{
|
|
@@ -93,15 +97,13 @@ const ANTI_PATTERNS = [
|
|
|
93
97
|
id: 'AP007',
|
|
94
98
|
name: 'No verification commands',
|
|
95
99
|
severity: 'medium',
|
|
96
|
-
description: 'Without test, lint, or build commands
|
|
100
|
+
description: 'Without test, lint, or build commands across the repo instruction surfaces, agents cannot self-verify changes consistently.',
|
|
97
101
|
platforms: ['claude', 'codex', 'cursor', 'windsurf', 'copilot', 'gemini', 'aider', 'opencode'],
|
|
98
|
-
fix: 'Add
|
|
102
|
+
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
103
|
detect: (ctx) => {
|
|
100
|
-
const content =
|
|
104
|
+
const content = getRepoInstructionBundle(ctx);
|
|
101
105
|
if (!content) return false;
|
|
102
|
-
|
|
103
|
-
/\b(npm |yarn |pnpm |pytest|cargo |go |make )/i.test(content);
|
|
104
|
-
return !hasVerification;
|
|
106
|
+
return !hasDocumentedVerificationGuidance(content);
|
|
105
107
|
},
|
|
106
108
|
},
|
|
107
109
|
{
|
|
@@ -203,13 +205,13 @@ const ANTI_PATTERNS = [
|
|
|
203
205
|
id: 'AP014',
|
|
204
206
|
name: 'No test command defined',
|
|
205
207
|
severity: 'medium',
|
|
206
|
-
description: 'Without a test command,
|
|
208
|
+
description: 'Without a canonical test command in repo instructions or scripts, agents cannot verify changes reliably before handoff.',
|
|
207
209
|
platforms: ['claude', 'codex', 'cursor', 'windsurf', 'copilot', 'gemini', 'aider', 'opencode'],
|
|
208
|
-
fix: 'Add a test command in
|
|
210
|
+
fix: 'Add a canonical test command in repo instructions or package scripts, e.g. "Test: npm test" or "Test: pytest".',
|
|
209
211
|
detect: (ctx) => {
|
|
210
|
-
const content =
|
|
212
|
+
const content = getRepoInstructionBundle(ctx);
|
|
211
213
|
const pkg = ctx.jsonFile('package.json');
|
|
212
|
-
const hasTestInMd =
|
|
214
|
+
const hasTestInMd = hasDocumentedTestCommand(content);
|
|
213
215
|
const hasTestScript = pkg && pkg.scripts && pkg.scripts.test;
|
|
214
216
|
return !hasTestInMd && !hasTestScript;
|
|
215
217
|
},
|
package/src/audit.js
CHANGED
|
@@ -882,7 +882,7 @@ function printLiteAudit(result, dir) {
|
|
|
882
882
|
console.log(colorize(` Found: ${result.detectedConfigFiles.join(', ')}`, 'dim'));
|
|
883
883
|
}
|
|
884
884
|
console.log('');
|
|
885
|
-
console.log(` ${t('audit.score', { score: colorize(`${result.score}/100`, 'bold'), passed: result.passed, total: result.passed + result.failed })}`);
|
|
885
|
+
console.log(` ${t('audit.score', { score: colorize(`${result.score}/100`, 'bold'), passed: result.passed, total: result.passed + result.failed })}`);
|
|
886
886
|
|
|
887
887
|
// Score explanation line (lite mode only)
|
|
888
888
|
const _critCount = (result.results || []).filter(r => r.passed === false && r.impact === 'critical').length;
|
|
@@ -905,10 +905,11 @@ function printLiteAudit(result, dir) {
|
|
|
905
905
|
scoreExplanation = t('audit.basic', { category: weakestCategory });
|
|
906
906
|
} else {
|
|
907
907
|
scoreExplanation = t('audit.early');
|
|
908
|
-
}
|
|
909
|
-
console.log(colorize(` ${scoreExplanation}`, 'dim'));
|
|
910
|
-
|
|
911
|
-
|
|
908
|
+
}
|
|
909
|
+
console.log(colorize(` ${scoreExplanation}`, 'dim'));
|
|
910
|
+
console.log(colorize(' Score type: live repo audit (current files only, not snapshot history or benchmark projection).', 'dim'));
|
|
911
|
+
|
|
912
|
+
if (result.platformScopeNote) {
|
|
912
913
|
console.log(colorize(` Scope: ${result.platformScopeNote.message}`, 'dim'));
|
|
913
914
|
}
|
|
914
915
|
if (result.workspaceHint && result.workspaceHint.workspaces.length > 0) {
|
package/src/auto-suggest.js
CHANGED
|
@@ -52,23 +52,72 @@ function analyzeSuggestions(dir) {
|
|
|
52
52
|
.sort((a, b) => b[1] - a[1])
|
|
53
53
|
.map(([key, count]) => ({ key, failCount: count, auditCount: auditSnapshots.length }));
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
const hasSuggestions = suggestedRules.length > 0 || suggestedSuppressions.length > 0 || suggestedPriorities.length > 0;
|
|
56
|
+
let bootstrap = { ready: true, state: 'ready', message: null, steps: [] };
|
|
57
|
+
|
|
58
|
+
if (totalEvents === 0 && auditSnapshots.length === 0) {
|
|
59
|
+
bootstrap = {
|
|
60
|
+
ready: false,
|
|
61
|
+
state: 'empty',
|
|
62
|
+
message: 'No local usage or snapshot history exists yet.',
|
|
63
|
+
steps: [
|
|
64
|
+
'Run `nerviq audit --snapshot` to save the baseline.',
|
|
65
|
+
'Use `nerviq fix`, `nerviq fix --all-critical`, or `nerviq feedback` to record recommendation outcomes.',
|
|
66
|
+
'Run `nerviq audit --snapshot` again after a meaningful repo change.',
|
|
67
|
+
'Re-run `nerviq suggest-rules`.',
|
|
68
|
+
],
|
|
69
|
+
};
|
|
70
|
+
} else if (!hasSuggestions && totalEvents === 0 && auditSnapshots.length > 0) {
|
|
71
|
+
bootstrap = {
|
|
72
|
+
ready: false,
|
|
73
|
+
state: 'snapshots-only',
|
|
74
|
+
message: `${auditSnapshots.length} audit snapshot(s) exist, but no recommendation outcomes have been recorded yet.`,
|
|
75
|
+
steps: [
|
|
76
|
+
'Run `nerviq fix` or `nerviq feedback` so Nerviq can learn which recommendations you accept or reject.',
|
|
77
|
+
'Re-run `nerviq suggest-rules` after another fix cycle.',
|
|
78
|
+
],
|
|
79
|
+
};
|
|
80
|
+
} else if (!hasSuggestions && totalEvents > 0 && auditSnapshots.length === 0) {
|
|
81
|
+
bootstrap = {
|
|
82
|
+
ready: false,
|
|
83
|
+
state: 'patterns-only',
|
|
84
|
+
message: `${totalEvents} usage event(s) exist, but no audit snapshots have been saved yet.`,
|
|
85
|
+
steps: [
|
|
86
|
+
'Run `nerviq audit --snapshot` to save the baseline.',
|
|
87
|
+
'Run it again after changes so repeated failures can be prioritized.',
|
|
88
|
+
'Re-run `nerviq suggest-rules`.',
|
|
89
|
+
],
|
|
90
|
+
};
|
|
91
|
+
} else if (!hasSuggestions) {
|
|
92
|
+
bootstrap = {
|
|
93
|
+
ready: false,
|
|
94
|
+
state: 'warming-up',
|
|
95
|
+
message: `Nerviq has some local history (${totalEvents} pattern events, ${auditSnapshots.length} audit snapshots), but not enough repeated signals yet.`,
|
|
96
|
+
steps: [
|
|
97
|
+
'Keep saving snapshots with `nerviq audit --snapshot`.',
|
|
98
|
+
'Keep recording outcomes with `nerviq fix` or `nerviq feedback`.',
|
|
99
|
+
'Re-run `nerviq suggest-rules` after another change cycle.',
|
|
100
|
+
],
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return { totalEvents, auditCount: auditSnapshots.length, suggestedRules, suggestedSuppressions, suggestedPriorities, bootstrap };
|
|
56
105
|
}
|
|
57
106
|
|
|
58
107
|
/**
|
|
59
108
|
* Format suggestions for CLI output.
|
|
60
109
|
*/
|
|
61
110
|
function formatSuggestions(suggestions) {
|
|
62
|
-
const { totalEvents, auditCount, suggestedRules, suggestedSuppressions, suggestedPriorities } = suggestions;
|
|
63
|
-
|
|
64
|
-
if (totalEvents === 0 && auditCount === 0) {
|
|
65
|
-
return ' No usage data yet. Run nerviq fix or nerviq audit to build pattern history.';
|
|
66
|
-
}
|
|
111
|
+
const { totalEvents, auditCount, suggestedRules, suggestedSuppressions, suggestedPriorities, bootstrap } = suggestions;
|
|
67
112
|
|
|
68
113
|
const sources = [];
|
|
69
114
|
if (totalEvents > 0) sources.push(`${totalEvents} pattern events`);
|
|
70
115
|
if (auditCount > 0) sources.push(`${auditCount} audit snapshots`);
|
|
71
|
-
const lines = [
|
|
116
|
+
const lines = [
|
|
117
|
+
sources.length > 0
|
|
118
|
+
? ` Auto-Suggested Rules (based on ${sources.join(', ')}):`
|
|
119
|
+
: ' Auto-Suggested Rules:',
|
|
120
|
+
];
|
|
72
121
|
|
|
73
122
|
if (suggestedRules.length > 0) {
|
|
74
123
|
lines.push('', ' Suggested as required (always accepted):');
|
|
@@ -91,8 +140,12 @@ function formatSuggestions(suggestions) {
|
|
|
91
140
|
}
|
|
92
141
|
}
|
|
93
142
|
|
|
94
|
-
if (suggestedRules.length === 0 && suggestedSuppressions.length === 0 && suggestedPriorities.length === 0) {
|
|
95
|
-
lines.push('',
|
|
143
|
+
if (suggestedRules.length === 0 && suggestedSuppressions.length === 0 && suggestedPriorities.length === 0 && bootstrap && !bootstrap.ready) {
|
|
144
|
+
lines.push('', ` ${bootstrap.message}`);
|
|
145
|
+
lines.push(' Bootstrap it with:');
|
|
146
|
+
for (let i = 0; i < bootstrap.steps.length; i++) {
|
|
147
|
+
lines.push(` ${i + 1}. ${bootstrap.steps[i]}`);
|
|
148
|
+
}
|
|
96
149
|
}
|
|
97
150
|
|
|
98
151
|
return lines.join('\n');
|
package/src/benchmark.js
CHANGED
|
@@ -201,31 +201,36 @@ function buildCaseStudy(before, after, applyResult) {
|
|
|
201
201
|
};
|
|
202
202
|
}
|
|
203
203
|
|
|
204
|
-
function renderBenchmarkMarkdown(report) {
|
|
205
|
-
return [
|
|
206
|
-
'# NERVIQ CLI Benchmark Report',
|
|
207
|
-
'',
|
|
208
|
-
`- Generated by: ${report.generatedBy}`,
|
|
209
|
-
`- Created at: ${report.createdAt}`,
|
|
210
|
-
`- Source repo: ${report.directory}`,
|
|
211
|
-
'',
|
|
212
|
-
'##
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
'',
|
|
220
|
-
'##
|
|
221
|
-
`-
|
|
222
|
-
`- Organic score: ${report.
|
|
223
|
-
`- Passing checks: ${report.
|
|
224
|
-
'',
|
|
225
|
-
'##
|
|
226
|
-
`-
|
|
227
|
-
`-
|
|
228
|
-
`-
|
|
204
|
+
function renderBenchmarkMarkdown(report) {
|
|
205
|
+
return [
|
|
206
|
+
'# NERVIQ CLI Benchmark Report',
|
|
207
|
+
'',
|
|
208
|
+
`- Generated by: ${report.generatedBy}`,
|
|
209
|
+
`- Created at: ${report.createdAt}`,
|
|
210
|
+
`- Source repo: ${report.directory}`,
|
|
211
|
+
'',
|
|
212
|
+
'## Score Semantics',
|
|
213
|
+
`- Baseline live audit score: ${report.scoreSemantics.baseline}`,
|
|
214
|
+
`- Projected benchmark score: ${report.scoreSemantics.projected}`,
|
|
215
|
+
`- Organic score: ${report.scoreSemantics.organic}`,
|
|
216
|
+
'',
|
|
217
|
+
'## Methodology',
|
|
218
|
+
...report.methodology.map(item => `- ${item}`),
|
|
219
|
+
'',
|
|
220
|
+
'## Baseline (Live Repo)',
|
|
221
|
+
`- Live audit score: ${report.before.score}/100`,
|
|
222
|
+
`- Organic live score: ${report.before.organicScore}/100`,
|
|
223
|
+
`- Passing checks: ${report.before.passed}/${report.before.checkCount}`,
|
|
224
|
+
'',
|
|
225
|
+
'## Projected (Isolated Benchmark Copy)',
|
|
226
|
+
`- Projected benchmark score: ${report.after.score}/100`,
|
|
227
|
+
`- Projected organic score: ${report.after.organicScore}/100`,
|
|
228
|
+
`- Passing checks: ${report.after.passed}/${report.after.checkCount}`,
|
|
229
|
+
'',
|
|
230
|
+
'## Delta',
|
|
231
|
+
`- Projected score delta: ${report.delta.score}`,
|
|
232
|
+
`- Projected organic score delta: ${report.delta.organicScore}`,
|
|
233
|
+
`- Passed checks delta: ${report.delta.passed}`,
|
|
229
234
|
'',
|
|
230
235
|
'## Executive Summary',
|
|
231
236
|
`- ${report.executiveSummary.headline}`,
|
|
@@ -285,9 +290,14 @@ async function runBenchmark(options) {
|
|
|
285
290
|
schemaVersion: 1,
|
|
286
291
|
generatedBy: `nerviq@${version}`,
|
|
287
292
|
createdAt: new Date().toISOString(),
|
|
288
|
-
directory: sourceDir,
|
|
289
|
-
platform,
|
|
290
|
-
|
|
293
|
+
directory: sourceDir,
|
|
294
|
+
platform,
|
|
295
|
+
scoreSemantics: {
|
|
296
|
+
baseline: 'current repo state before benchmark runs',
|
|
297
|
+
projected: 'starter-safe post-setup score measured on an isolated temp copy',
|
|
298
|
+
organic: 'repo-owned config quality excluding starter-generated Nerviq assets',
|
|
299
|
+
},
|
|
300
|
+
methodology: [
|
|
291
301
|
'Run a baseline audit on the source repo.',
|
|
292
302
|
'Copy the repo into a temporary isolated workspace.',
|
|
293
303
|
`Apply starter-safe ${platform === 'codex' ? 'Codex' : 'Claude'} artifacts only on the isolated copy.`,
|
|
@@ -316,19 +326,20 @@ function printBenchmark(report, options = {}) {
|
|
|
316
326
|
return;
|
|
317
327
|
}
|
|
318
328
|
|
|
319
|
-
console.log('');
|
|
320
|
-
console.log(' nerviq benchmark');
|
|
321
|
-
console.log(' ═══════════════════════════════════════');
|
|
322
|
-
console.log(' Runs in an isolated temp copy. Your current repo is not modified.');
|
|
323
|
-
console.log('');
|
|
324
|
-
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
console.log(`
|
|
328
|
-
console.log(
|
|
329
|
-
console.log(
|
|
330
|
-
console.log(`
|
|
331
|
-
console.log(
|
|
329
|
+
console.log('');
|
|
330
|
+
console.log(' nerviq benchmark');
|
|
331
|
+
console.log(' ═══════════════════════════════════════');
|
|
332
|
+
console.log(' Runs in an isolated temp copy. Your current repo is not modified.');
|
|
333
|
+
console.log(' Score type: baseline = live repo audit, projected = isolated post-setup benchmark.');
|
|
334
|
+
console.log('');
|
|
335
|
+
const orgDeltaSign = report.delta.organicScore >= 0 ? '+' : '';
|
|
336
|
+
const totalDeltaSign = report.delta.score >= 0 ? '+' : '';
|
|
337
|
+
console.log(` Projected organic delta: \x1b[1m${orgDeltaSign}${report.delta.organicScore} points\x1b[0m (repo-owned config quality)`);
|
|
338
|
+
console.log(` Projected total delta with nerviq setup: ${totalDeltaSign}${report.delta.score} points`);
|
|
339
|
+
console.log('');
|
|
340
|
+
console.log(` Baseline live audit: organic ${report.before.organicScore}/100, total ${report.before.score}/100`);
|
|
341
|
+
console.log(` Projected after setup: organic ${report.after.organicScore}/100, total ${report.after.score}/100`);
|
|
342
|
+
console.log('');
|
|
332
343
|
console.log(` ${report.executiveSummary.headline}`);
|
|
333
344
|
console.log(` Recommendation: ${report.executiveSummary.decisionGuidance}`);
|
|
334
345
|
console.log(` Workflow evidence: ${report.workflowEvidence.summary.passed}/${report.workflowEvidence.summary.total} tasks (${report.workflowEvidence.summary.coverageScore}%)`);
|