@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/package.json CHANGED
@@ -1,59 +1,59 @@
1
- {
2
- "name": "@nerviq/cli",
3
- "version": "1.8.9",
4
- "description": "The intelligent nervous system for AI coding agents — 2,431 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
- }
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
- current: { date: current.createdAt, score: current.summary?.score, passed: current.summary?.passed },
272
- previous: { date: previous.createdAt, score: previous.summary?.score, passed: previous.summary?.passed },
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 'No snapshots found. Run `npx nerviq --snapshot` to save one.';
328
+ if (history.length === 0) return formatSnapshotBootstrap(dir, 'history');
283
329
 
284
- const lines = ['Score history (most recent first):', ''];
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(` Trend: ${comparison.trend} (${sign}${comparison.delta.score} since previous)`);
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
- '# Claude Code Setup Trend Report',
373
+ '# Nerviq Audit Snapshot Trend Report',
319
374
  '',
320
375
  `**Project:** ${path.basename(dir)}`,
321
376
  `**Generated:** ${new Date().toISOString().split('T')[0]}`,
322
- `**Snapshots:** ${history.length}`,
377
+ `**Audit snapshots:** ${history.length}`,
323
378
  '',
324
- '## Score History',
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(`- **Delta:** ${comparison.delta.score >= 0 ? '+' : ''}${comparison.delta.score} points`);
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,
@@ -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: null,
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: null,
27
- },
28
- {
29
- key: 'aider-github-releases',
30
- label: 'Aider GitHub Releases',
31
- url: 'https://github.com/paul-gauthier/aider/releases',
32
- stalenessThresholdDays: 14,
33
- verifiedAt: null,
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: null,
41
- },
42
- {
43
- key: 'aider-pypi',
44
- label: 'Aider PyPI Package',
45
- url: 'https://pypi.org/project/aider-chat/',
46
- stalenessThresholdDays: 14,
47
- verifiedAt: null,
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
+ };
@@ -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 path = require('path');
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 in CLAUDE.md, the agent cannot self-verify its changes.',
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 ## Verification Commands section with test, lint, and build commands to your instruction file.',
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 = ctx.fileContent('CLAUDE.md') || ctx.fileContent('.claude/CLAUDE.md') || '';
104
+ const content = getRepoInstructionBundle(ctx);
101
105
  if (!content) return false;
102
- const hasVerification = /\b(test|lint|build|check|verify)\b/i.test(content) &&
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, the agent cannot verify its changes work before presenting them for review.',
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 your instruction file, e.g., "Test: npm test" or "Test: pytest".',
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 = ctx.fileContent('CLAUDE.md') || ctx.fileContent('.claude/CLAUDE.md') || '';
212
+ const content = getRepoInstructionBundle(ctx);
211
213
  const pkg = ctx.jsonFile('package.json');
212
- const hasTestInMd = /(?:test|testing)\s*(?:command)?[:\s]+[`"']*(?:npm|yarn|pnpm|pytest|cargo|go|make)\s/i.test(content);
214
+ const hasTestInMd = hasDocumentedTestCommand(content);
213
215
  const hasTestScript = pkg && pkg.scripts && pkg.scripts.test;
214
216
  return !hasTestInMd && !hasTestScript;
215
217
  },