@nerviq/cli 1.8.9 → 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 +32 -24
- package/bin/cli.js +173 -104
- package/package.json +59 -59
- package/src/activity.js +68 -12
- package/src/aider/freshness.js +168 -168
- package/src/anti-patterns.js +13 -11
- package/src/audit.js +16 -14
- package/src/auto-suggest.js +62 -9
- package/src/benchmark.js +52 -41
- package/src/codex/freshness.js +167 -167
- package/src/copilot/freshness.js +197 -197
- package/src/cursor/freshness.js +214 -214
- package/src/dashboard.js +36 -14
- package/src/freshness.js +19 -19
- package/src/gemini/freshness.js +204 -204
- package/src/i18n.js +63 -0
- package/src/instruction-surfaces.js +185 -0
- package/src/locales/en.json +34 -0
- package/src/locales/es.json +34 -0
- package/src/mcp-server.js +1 -1
- package/src/opencode/freshness.js +158 -158
- package/src/stack-checks.js +1 -1
- package/src/synergy/report.js +1 -0
- package/src/techniques.js +174 -58
- package/src/windsurf/freshness.js +215 -215
- package/src/workspace.js +51 -6
package/package.json
CHANGED
|
@@ -1,59 +1,59 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@nerviq/cli",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "The intelligent nervous system for AI coding agents — 2,
|
|
5
|
-
"main": "src/index.js",
|
|
6
|
-
"bin": {
|
|
7
|
-
"nerviq": "bin/cli.js",
|
|
8
|
-
"@nerviq/cli": "bin/cli.js",
|
|
9
|
-
"nerviq-mcp": "src/mcp-server.js"
|
|
10
|
-
},
|
|
11
|
-
"files": [
|
|
12
|
-
"bin",
|
|
13
|
-
"src",
|
|
14
|
-
"README.md"
|
|
15
|
-
],
|
|
16
|
-
"scripts": {
|
|
17
|
-
"start": "node bin/cli.js",
|
|
18
|
-
"build": "npm pack --dry-run",
|
|
19
|
-
"test": "node test/run.js",
|
|
20
|
-
"test:jest": "jest",
|
|
21
|
-
"test:coverage": "jest --coverage",
|
|
22
|
-
"test:all": "npm test && npx jest && node test/check-matrix.js && node test/codex-check-matrix.js && node test/gemini-check-matrix.js && node test/copilot-check-matrix.js && node test/cursor-check-matrix.js && node test/windsurf-check-matrix.js && node test/aider-check-matrix.js && node test/opencode-check-matrix.js && node test/golden-matrix.js && node test/codex-golden-matrix.js && node test/gemini-golden-matrix.js && node test/copilot-golden-matrix.js && node test/cursor-golden-matrix.js && node test/windsurf-golden-matrix.js && node test/aider-golden-matrix.js && node test/opencode-golden-matrix.js",
|
|
23
|
-
"benchmark:perf": "node tools/benchmark.js",
|
|
24
|
-
"catalog": "node -e \"const {generateCatalog}=require('./src/catalog');console.log(JSON.stringify(generateCatalog(),null,2))\""
|
|
25
|
-
},
|
|
26
|
-
"keywords": [
|
|
27
|
-
"nerviq",
|
|
28
|
-
"ai-agents",
|
|
29
|
-
"agent-governance",
|
|
30
|
-
"agent-config",
|
|
31
|
-
"harmony",
|
|
32
|
-
"synergy",
|
|
33
|
-
"audit",
|
|
34
|
-
"claude",
|
|
35
|
-
"codex",
|
|
36
|
-
"gemini",
|
|
37
|
-
"copilot",
|
|
38
|
-
"cursor",
|
|
39
|
-
"windsurf",
|
|
40
|
-
"aider",
|
|
41
|
-
"developer-tools",
|
|
42
|
-
"cli",
|
|
43
|
-
"mcp",
|
|
44
|
-
"multi-agent"
|
|
45
|
-
],
|
|
46
|
-
"author": "Nerviq <hello@nerviq.net>",
|
|
47
|
-
"license": "AGPL-3.0",
|
|
48
|
-
"repository": {
|
|
49
|
-
"type": "git",
|
|
50
|
-
"url": "git+https://github.com/nerviq/nerviq.git"
|
|
51
|
-
},
|
|
52
|
-
"homepage": "https://nerviq.net",
|
|
53
|
-
"engines": {
|
|
54
|
-
"node": ">=18.0.0"
|
|
55
|
-
},
|
|
56
|
-
"devDependencies": {
|
|
57
|
-
"jest": "^30.3.0"
|
|
58
|
-
}
|
|
59
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@nerviq/cli",
|
|
3
|
+
"version": "1.10.0",
|
|
4
|
+
"description": "The intelligent nervous system for AI coding agents — 2,438 checks (8 platforms × ~300 governance rules), 10 languages, 62 domain packs. Audit, align, and amplify.",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"nerviq": "bin/cli.js",
|
|
8
|
+
"@nerviq/cli": "bin/cli.js",
|
|
9
|
+
"nerviq-mcp": "src/mcp-server.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"bin",
|
|
13
|
+
"src",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"start": "node bin/cli.js",
|
|
18
|
+
"build": "npm pack --dry-run",
|
|
19
|
+
"test": "node test/run.js",
|
|
20
|
+
"test:jest": "jest",
|
|
21
|
+
"test:coverage": "jest --coverage",
|
|
22
|
+
"test:all": "npm test && npx jest && node test/check-matrix.js && node test/codex-check-matrix.js && node test/gemini-check-matrix.js && node test/copilot-check-matrix.js && node test/cursor-check-matrix.js && node test/windsurf-check-matrix.js && node test/aider-check-matrix.js && node test/opencode-check-matrix.js && node test/golden-matrix.js && node test/codex-golden-matrix.js && node test/gemini-golden-matrix.js && node test/copilot-golden-matrix.js && node test/cursor-golden-matrix.js && node test/windsurf-golden-matrix.js && node test/aider-golden-matrix.js && node test/opencode-golden-matrix.js",
|
|
23
|
+
"benchmark:perf": "node tools/benchmark.js",
|
|
24
|
+
"catalog": "node -e \"const {generateCatalog}=require('./src/catalog');console.log(JSON.stringify(generateCatalog(),null,2))\""
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"nerviq",
|
|
28
|
+
"ai-agents",
|
|
29
|
+
"agent-governance",
|
|
30
|
+
"agent-config",
|
|
31
|
+
"harmony",
|
|
32
|
+
"synergy",
|
|
33
|
+
"audit",
|
|
34
|
+
"claude",
|
|
35
|
+
"codex",
|
|
36
|
+
"gemini",
|
|
37
|
+
"copilot",
|
|
38
|
+
"cursor",
|
|
39
|
+
"windsurf",
|
|
40
|
+
"aider",
|
|
41
|
+
"developer-tools",
|
|
42
|
+
"cli",
|
|
43
|
+
"mcp",
|
|
44
|
+
"multi-agent"
|
|
45
|
+
],
|
|
46
|
+
"author": "Nerviq <hello@nerviq.net>",
|
|
47
|
+
"license": "AGPL-3.0",
|
|
48
|
+
"repository": {
|
|
49
|
+
"type": "git",
|
|
50
|
+
"url": "git+https://github.com/nerviq/nerviq.git"
|
|
51
|
+
},
|
|
52
|
+
"homepage": "https://nerviq.net",
|
|
53
|
+
"engines": {
|
|
54
|
+
"node": ">=18.0.0"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"jest": "^30.3.0"
|
|
58
|
+
}
|
|
59
|
+
}
|
package/src/activity.js
CHANGED
|
@@ -268,8 +268,9 @@ function compareLatest(dir) {
|
|
|
268
268
|
}
|
|
269
269
|
|
|
270
270
|
return {
|
|
271
|
-
|
|
272
|
-
|
|
271
|
+
scoreType: 'audit-snapshot-score',
|
|
272
|
+
current: { date: current.createdAt, score: current.summary?.score, passed: current.summary?.passed, scoreType: 'audit-snapshot-score' },
|
|
273
|
+
previous: { date: previous.createdAt, score: previous.summary?.score, passed: previous.summary?.passed, scoreType: 'audit-snapshot-score' },
|
|
273
274
|
delta,
|
|
274
275
|
regressions,
|
|
275
276
|
improvements,
|
|
@@ -277,11 +278,60 @@ function compareLatest(dir) {
|
|
|
277
278
|
};
|
|
278
279
|
}
|
|
279
280
|
|
|
281
|
+
function formatSnapshotBootstrap(dir, goal = 'history') {
|
|
282
|
+
const snapshotCount = getHistory(dir, 50).length;
|
|
283
|
+
const lines = [];
|
|
284
|
+
const snapshotLabel = snapshotCount === 1
|
|
285
|
+
? '1 saved audit snapshot'
|
|
286
|
+
: `${snapshotCount} saved audit snapshots`;
|
|
287
|
+
|
|
288
|
+
if (goal === 'compare') {
|
|
289
|
+
lines.push(snapshotCount === 0
|
|
290
|
+
? 'Compare needs 2 audit snapshots.'
|
|
291
|
+
: 'Compare needs one more audit snapshot.');
|
|
292
|
+
} else if (goal === 'trend') {
|
|
293
|
+
lines.push(snapshotCount === 0
|
|
294
|
+
? 'Trend needs 2 audit snapshots to start.'
|
|
295
|
+
: 'Trend needs one more audit snapshot to become meaningful.');
|
|
296
|
+
} else {
|
|
297
|
+
lines.push(snapshotCount === 0
|
|
298
|
+
? 'No audit snapshots found yet.'
|
|
299
|
+
: 'History is initialized, but compare/trend still need one more snapshot.');
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
lines.push(` Current state: ${snapshotLabel}.`);
|
|
303
|
+
|
|
304
|
+
if (snapshotCount === 0) {
|
|
305
|
+
lines.push(' Bootstrap it with:');
|
|
306
|
+
lines.push(' 1. Run `nerviq audit --snapshot` to save the baseline.');
|
|
307
|
+
lines.push(' 2. Make a meaningful repo change (`nerviq setup --auto` or `nerviq fix --all-critical --auto`).');
|
|
308
|
+
lines.push(' 3. Run `nerviq audit --snapshot` again to capture the next state.');
|
|
309
|
+
} else {
|
|
310
|
+
lines.push(' Next:');
|
|
311
|
+
lines.push(' 1. Make a meaningful repo change (`nerviq setup --auto` or `nerviq fix --all-critical --auto`).');
|
|
312
|
+
lines.push(' 2. Run `nerviq audit --snapshot` again.');
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (goal === 'compare') {
|
|
316
|
+
lines.push(' Then rerun `nerviq compare`.');
|
|
317
|
+
} else if (goal === 'trend') {
|
|
318
|
+
lines.push(' Then rerun `nerviq trend`.');
|
|
319
|
+
} else {
|
|
320
|
+
lines.push(' Then rerun `nerviq history`, `nerviq compare`, or `nerviq trend`.');
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return lines.join('\n');
|
|
324
|
+
}
|
|
325
|
+
|
|
280
326
|
function formatHistory(dir) {
|
|
281
327
|
const history = getHistory(dir, 10);
|
|
282
|
-
if (history.length === 0) return
|
|
328
|
+
if (history.length === 0) return formatSnapshotBootstrap(dir, 'history');
|
|
283
329
|
|
|
284
|
-
const lines = [
|
|
330
|
+
const lines = [
|
|
331
|
+
'Audit snapshot history (most recent first):',
|
|
332
|
+
' Score type: saved audit snapshot scores only (not live audits or benchmark projections).',
|
|
333
|
+
'',
|
|
334
|
+
];
|
|
285
335
|
for (const entry of history) {
|
|
286
336
|
const dateStr = entry.createdAt || 'unknown';
|
|
287
337
|
const date = dateStr.split('T')[0] || 'unknown';
|
|
@@ -290,14 +340,14 @@ function formatHistory(dir) {
|
|
|
290
340
|
const score = entry.summary?.score ?? '?';
|
|
291
341
|
const passed = entry.summary?.passed ?? '?';
|
|
292
342
|
const total = entry.summary?.checkCount ?? '?';
|
|
293
|
-
lines.push(` ${dateDisplay} ${score}/100 (${passed}/${total} passing)`);
|
|
343
|
+
lines.push(` ${dateDisplay} snapshot ${score}/100 (${passed}/${total} checks passing)`);
|
|
294
344
|
}
|
|
295
345
|
|
|
296
346
|
const comparison = compareLatest(dir);
|
|
297
347
|
if (comparison) {
|
|
298
348
|
lines.push('');
|
|
299
349
|
const sign = comparison.delta.score >= 0 ? '+' : '';
|
|
300
|
-
lines.push(`
|
|
350
|
+
lines.push(` Latest snapshot trend: ${comparison.trend} (${sign}${comparison.delta.score} since previous snapshot)`);
|
|
301
351
|
if (comparison.improvements.length > 0) {
|
|
302
352
|
lines.push(` Fixed: ${comparison.improvements.join(', ')}`);
|
|
303
353
|
}
|
|
@@ -306,6 +356,11 @@ function formatHistory(dir) {
|
|
|
306
356
|
}
|
|
307
357
|
}
|
|
308
358
|
|
|
359
|
+
if (history.length === 1) {
|
|
360
|
+
lines.push('');
|
|
361
|
+
lines.push(formatSnapshotBootstrap(dir, 'history'));
|
|
362
|
+
}
|
|
363
|
+
|
|
309
364
|
return lines.join('\n');
|
|
310
365
|
}
|
|
311
366
|
|
|
@@ -315,13 +370,13 @@ function exportTrendReport(dir) {
|
|
|
315
370
|
|
|
316
371
|
const comparison = compareLatest(dir);
|
|
317
372
|
const lines = [
|
|
318
|
-
'#
|
|
373
|
+
'# Nerviq Audit Snapshot Trend Report',
|
|
319
374
|
'',
|
|
320
375
|
`**Project:** ${path.basename(dir)}`,
|
|
321
376
|
`**Generated:** ${new Date().toISOString().split('T')[0]}`,
|
|
322
|
-
`**
|
|
377
|
+
`**Audit snapshots:** ${history.length}`,
|
|
323
378
|
'',
|
|
324
|
-
'##
|
|
379
|
+
'## Audit Snapshot History',
|
|
325
380
|
'',
|
|
326
381
|
'| Date | Score | Passed | Checks |',
|
|
327
382
|
'|------|-------|--------|--------|',
|
|
@@ -336,9 +391,9 @@ function exportTrendReport(dir) {
|
|
|
336
391
|
lines.push('');
|
|
337
392
|
lines.push('## Latest Comparison');
|
|
338
393
|
lines.push('');
|
|
339
|
-
lines.push(`- **Previous:** ${comparison.previous.score}/100 (${comparison.previous.date?.split('T')[0]})`);
|
|
340
|
-
lines.push(`- **Current:** ${comparison.current.score}/100 (${comparison.current.date?.split('T')[0]})`);
|
|
341
|
-
lines.push(`- **
|
|
394
|
+
lines.push(`- **Previous snapshot score:** ${comparison.previous.score}/100 (${comparison.previous.date?.split('T')[0]})`);
|
|
395
|
+
lines.push(`- **Current snapshot score:** ${comparison.current.score}/100 (${comparison.current.date?.split('T')[0]})`);
|
|
396
|
+
lines.push(`- **Snapshot delta:** ${comparison.delta.score >= 0 ? '+' : ''}${comparison.delta.score} points`);
|
|
342
397
|
lines.push(`- **Trend:** ${comparison.trend}`);
|
|
343
398
|
if (comparison.improvements.length > 0) lines.push(`- **Fixed:** ${comparison.improvements.join(', ')}`);
|
|
344
399
|
if (comparison.regressions.length > 0) lines.push(`- **New gaps:** ${comparison.regressions.join(', ')}`);
|
|
@@ -803,6 +858,7 @@ module.exports = {
|
|
|
803
858
|
readSnapshotIndex,
|
|
804
859
|
getHistory,
|
|
805
860
|
compareLatest,
|
|
861
|
+
formatSnapshotBootstrap,
|
|
806
862
|
formatHistory,
|
|
807
863
|
exportTrendReport,
|
|
808
864
|
readOutcomeIndex,
|
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
|
},
|