@nerviq/cli 1.10.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 +80 -29
- package/bin/cli.js +229 -110
- package/package.json +1 -1
- package/src/activity.js +185 -59
- package/src/aider/freshness.js +28 -25
- package/src/analyze.js +3 -1
- package/src/anti-patterns.js +4 -2
- package/src/audit.js +100 -74
- package/src/benchmark.js +15 -10
- package/src/governance.js +13 -7
- package/src/index.js +2 -1
- package/src/integrations.js +102 -55
- 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/techniques.js +41 -45
- package/src/terminology.js +73 -0
- package/src/token-estimate.js +35 -0
- package/src/workspace.js +105 -8
package/bin/cli.js
CHANGED
|
@@ -47,7 +47,7 @@ function levenshtein(a, b) {
|
|
|
47
47
|
return matrix[a.length][b.length];
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
function suggestCommand(input) {
|
|
50
|
+
function suggestCommand(input) {
|
|
51
51
|
const candidates = [...KNOWN_COMMANDS, ...Object.keys(COMMAND_ALIASES)];
|
|
52
52
|
let best = null;
|
|
53
53
|
let bestDistance = Infinity;
|
|
@@ -58,9 +58,30 @@ function suggestCommand(input) {
|
|
|
58
58
|
bestDistance = distance;
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
|
-
return bestDistance <= 3 ? best : null;
|
|
62
|
-
}
|
|
63
|
-
|
|
61
|
+
return bestDistance <= 3 ? best : null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function parseNonNegativeIntegerFlag(value, flagName) {
|
|
65
|
+
const parsed = Number(value);
|
|
66
|
+
if (!Number.isInteger(parsed) || parsed < 0) {
|
|
67
|
+
throw new Error(`${flagName} requires a non-negative integer`);
|
|
68
|
+
}
|
|
69
|
+
return parsed;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function parseWebhookHeader(rawValue) {
|
|
73
|
+
const separator = rawValue.indexOf(':');
|
|
74
|
+
if (separator <= 0) {
|
|
75
|
+
throw new Error('--webhook-header requires NAME: VALUE');
|
|
76
|
+
}
|
|
77
|
+
const name = rawValue.slice(0, separator).trim();
|
|
78
|
+
const value = rawValue.slice(separator + 1).trim();
|
|
79
|
+
if (!name || !value) {
|
|
80
|
+
throw new Error('--webhook-header requires NAME: VALUE');
|
|
81
|
+
}
|
|
82
|
+
return { name, value };
|
|
83
|
+
}
|
|
84
|
+
|
|
64
85
|
function parseArgs(rawArgs) {
|
|
65
86
|
const flags = [];
|
|
66
87
|
let command = 'audit';
|
|
@@ -79,9 +100,12 @@ function parseArgs(rawArgs) {
|
|
|
79
100
|
let feedbackScoreDelta = null;
|
|
80
101
|
let platform = 'claude';
|
|
81
102
|
let format = null;
|
|
82
|
-
let port = null;
|
|
83
|
-
let workspace = null;
|
|
84
|
-
let webhookUrl = null;
|
|
103
|
+
let port = null;
|
|
104
|
+
let workspace = null;
|
|
105
|
+
let webhookUrl = null;
|
|
106
|
+
let webhookHeaders = [];
|
|
107
|
+
let webhookRetries = null;
|
|
108
|
+
let snapshotTags = [];
|
|
85
109
|
let commandSet = false;
|
|
86
110
|
let extraArgs = [];
|
|
87
111
|
let convertFrom = null;
|
|
@@ -98,11 +122,11 @@ function parseArgs(rawArgs) {
|
|
|
98
122
|
for (let i = 0; i < rawArgs.length; i++) {
|
|
99
123
|
const arg = rawArgs[i];
|
|
100
124
|
|
|
101
|
-
if (arg === '--threshold' || arg === '--out' || arg === '--plan' || arg === '--only' || arg === '--profile' || arg === '--mcp-pack' || arg === '--require' || arg === '--key' || arg === '--status' || arg === '--effect' || arg === '--notes' || arg === '--source' || arg === '--score-delta' || arg === '--platform' || arg === '--format' || arg === '--from' || arg === '--to' || arg === '--port' || arg === '--workspace' || arg === '--check-version' || arg === '--webhook' || arg === '--external' || arg === '--team-profile' || arg === '--lang') {
|
|
102
|
-
const value = rawArgs[i + 1];
|
|
103
|
-
if (!value || value.startsWith('--')) {
|
|
104
|
-
throw new Error(`${arg} requires a value`);
|
|
105
|
-
}
|
|
125
|
+
if (arg === '--threshold' || arg === '--out' || arg === '--plan' || arg === '--only' || arg === '--profile' || arg === '--mcp-pack' || arg === '--require' || arg === '--key' || arg === '--status' || arg === '--effect' || arg === '--notes' || arg === '--source' || arg === '--score-delta' || arg === '--platform' || arg === '--format' || arg === '--from' || arg === '--to' || arg === '--port' || arg === '--workspace' || arg === '--check-version' || arg === '--webhook' || arg === '--webhook-header' || arg === '--webhook-retries' || arg === '--external' || arg === '--team-profile' || arg === '--lang' || arg === '--tag') {
|
|
126
|
+
const value = rawArgs[i + 1];
|
|
127
|
+
if (!value || value.startsWith('--')) {
|
|
128
|
+
throw new Error(`${arg} requires a value`);
|
|
129
|
+
}
|
|
106
130
|
if (arg === '--threshold') threshold = value;
|
|
107
131
|
if (arg === '--out') out = value;
|
|
108
132
|
if (arg === '--plan') planFile = value;
|
|
@@ -121,15 +145,18 @@ function parseArgs(rawArgs) {
|
|
|
121
145
|
if (arg === '--from') { convertFrom = value.trim(); migrateFrom = value.trim(); }
|
|
122
146
|
if (arg === '--to') { convertTo = value.trim(); migrateTo = value.trim(); }
|
|
123
147
|
if (arg === '--port') port = value.trim();
|
|
124
|
-
if (arg === '--workspace') workspace = value.trim();
|
|
125
|
-
if (arg === '--check-version') checkVersion = value.trim();
|
|
126
|
-
if (arg === '--webhook') webhookUrl = value.trim();
|
|
127
|
-
if (arg === '--
|
|
128
|
-
if (arg === '--
|
|
129
|
-
if (arg === '--
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
148
|
+
if (arg === '--workspace') workspace = value.trim();
|
|
149
|
+
if (arg === '--check-version') checkVersion = value.trim();
|
|
150
|
+
if (arg === '--webhook') webhookUrl = value.trim();
|
|
151
|
+
if (arg === '--webhook-header') webhookHeaders.push(parseWebhookHeader(value));
|
|
152
|
+
if (arg === '--webhook-retries') webhookRetries = parseNonNegativeIntegerFlag(value.trim(), '--webhook-retries');
|
|
153
|
+
if (arg === '--external') external = value.trim();
|
|
154
|
+
if (arg === '--team-profile') teamProfile = value.trim();
|
|
155
|
+
if (arg === '--lang') lang = value.trim().toLowerCase();
|
|
156
|
+
if (arg === '--tag') snapshotTags.push(value.trim());
|
|
157
|
+
i++;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
133
160
|
|
|
134
161
|
if (arg.startsWith('--lang=')) {
|
|
135
162
|
lang = arg.split('=').slice(1).join('=').trim().toLowerCase();
|
|
@@ -141,10 +168,15 @@ function parseArgs(rawArgs) {
|
|
|
141
168
|
continue;
|
|
142
169
|
}
|
|
143
170
|
|
|
144
|
-
if (arg.startsWith('--external=')) {
|
|
145
|
-
external = arg.split('=').slice(1).join('=').trim();
|
|
146
|
-
continue;
|
|
147
|
-
}
|
|
171
|
+
if (arg.startsWith('--external=')) {
|
|
172
|
+
external = arg.split('=').slice(1).join('=').trim();
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (arg.startsWith('--tag=')) {
|
|
177
|
+
snapshotTags.push(arg.split('=').slice(1).join('=').trim());
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
148
180
|
|
|
149
181
|
if (arg === '--repos') {
|
|
150
182
|
// Collect all following non-flag args as repo paths (supports comma-separated too)
|
|
@@ -247,14 +279,29 @@ function parseArgs(rawArgs) {
|
|
|
247
279
|
continue;
|
|
248
280
|
}
|
|
249
281
|
|
|
250
|
-
if (arg.startsWith('--check-version=')) {
|
|
251
|
-
checkVersion = arg.split('=').slice(1).join('=').trim();
|
|
252
|
-
continue;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
if (arg.startsWith('--')) {
|
|
256
|
-
|
|
257
|
-
continue;
|
|
282
|
+
if (arg.startsWith('--check-version=')) {
|
|
283
|
+
checkVersion = arg.split('=').slice(1).join('=').trim();
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (arg.startsWith('--webhook=')) {
|
|
288
|
+
webhookUrl = arg.split('=').slice(1).join('=').trim();
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (arg.startsWith('--webhook-header=')) {
|
|
293
|
+
webhookHeaders.push(parseWebhookHeader(arg.split('=').slice(1).join('=')));
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (arg.startsWith('--webhook-retries=')) {
|
|
298
|
+
webhookRetries = parseNonNegativeIntegerFlag(arg.split('=').slice(1).join('=').trim(), '--webhook-retries');
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (arg.startsWith('--')) {
|
|
303
|
+
flags.push(arg);
|
|
304
|
+
continue;
|
|
258
305
|
}
|
|
259
306
|
|
|
260
307
|
if (!commandSet) {
|
|
@@ -268,7 +315,7 @@ function parseArgs(rawArgs) {
|
|
|
268
315
|
|
|
269
316
|
const normalizedCommand = COMMAND_ALIASES[command] || command;
|
|
270
317
|
|
|
271
|
-
return { flags, command, commandExplicit, normalizedCommand, threshold, out, planFile, only, profile, mcpPacks, requireChecks, feedbackKey, feedbackStatus, feedbackEffect, feedbackNotes, feedbackSource, feedbackScoreDelta, platform, format, port, workspace, extraArgs, convertFrom, convertTo, migrateFrom, migrateTo, checkVersion, webhookUrl, external, repos, teamProfile, lang };
|
|
318
|
+
return { flags, command, commandExplicit, normalizedCommand, threshold, out, planFile, only, profile, mcpPacks, requireChecks, feedbackKey, feedbackStatus, feedbackEffect, feedbackNotes, feedbackSource, feedbackScoreDelta, platform, format, port, workspace, extraArgs, convertFrom, convertTo, migrateFrom, migrateTo, checkVersion, webhookUrl, webhookHeaders, webhookRetries, external, repos, teamProfile, lang, snapshotTags };
|
|
272
319
|
}
|
|
273
320
|
|
|
274
321
|
function printWorkspaceSummary(summary, options) {
|
|
@@ -291,20 +338,41 @@ function printWorkspaceSummary(summary, options) {
|
|
|
291
338
|
}
|
|
292
339
|
console.log(` Root governance audit: \x1b[1m${rootScore}\x1b[0m`);
|
|
293
340
|
console.log(` Workspace audit average: \x1b[1m${workspaceAverage}/100\x1b[0m`);
|
|
341
|
+
if (summary.profileBreakdown?.length > 0) {
|
|
342
|
+
const profileLine = summary.profileBreakdown
|
|
343
|
+
.map((item) => `${item.profileLabel} (${item.workspaceCount})`)
|
|
344
|
+
.join(', ');
|
|
345
|
+
console.log(` Workspace profiles: ${profileLine}`);
|
|
346
|
+
}
|
|
294
347
|
console.log(' Score semantics: root governance shows shared repo policy health; workspace average shows package-level coverage across the selected workspaces.');
|
|
295
348
|
console.log(' Aggregate vs package: per-workspace scores can legitimately trail the root repo score in a monorepo.');
|
|
349
|
+
console.log(' Stack-specific checks: Go, Python, Node, and other workspace types can have different applicable totals.');
|
|
296
350
|
console.log('');
|
|
297
|
-
console.log('\x1b[1m Workspace Audit Pass Total Top action\x1b[0m');
|
|
298
|
-
console.log(' ' + '─'.repeat(
|
|
351
|
+
console.log('\x1b[1m Workspace Profile Audit Pass Total Top action\x1b[0m');
|
|
352
|
+
console.log(' ' + '─'.repeat(96));
|
|
299
353
|
for (const item of summary.workspaces) {
|
|
300
354
|
const score = item.score === null ? 'ERR' : String(item.score);
|
|
301
|
-
const topAction = item.error || item.topAction || '-';
|
|
302
|
-
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
|
|
355
|
+
const topAction = item.error || item.topAction || '-';
|
|
356
|
+
const profile = (item.workspaceProfile?.label || 'General workspace').slice(0, 20);
|
|
357
|
+
console.log(` ${item.workspace.padEnd(26)} ${profile.padEnd(20)} ${score.padStart(5)} ${String(item.passed).padStart(5)} ${String(item.total).padStart(6)} ${topAction}`);
|
|
358
|
+
if (item.stackLabels?.length > 0) {
|
|
359
|
+
console.log(`\x1b[2m Stacks: ${item.stackLabels.join(', ')}\x1b[0m`);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
console.log('');
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function printCompareCheckSection(title, items, prefix) {
|
|
366
|
+
if (!Array.isArray(items) || items.length === 0) return;
|
|
367
|
+
console.log(` ${title} (${items.length}):`);
|
|
368
|
+
for (const item of items) {
|
|
369
|
+
const impact = item.impact ? ` [${item.impact}]` : '';
|
|
370
|
+
const category = item.category ? ` — ${item.category}` : '';
|
|
371
|
+
console.log(` ${prefix} ${item.key}${impact}: ${item.name}${category}`);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function printScanDetail(summary, options) {
|
|
308
376
|
if (options.json) {
|
|
309
377
|
console.log(JSON.stringify(summary, null, 2));
|
|
310
378
|
return;
|
|
@@ -388,7 +456,7 @@ const HELP = `
|
|
|
388
456
|
nerviq audit --full Full audit with all checks, weakest areas, badge
|
|
389
457
|
nerviq audit --platform X Audit specific platform (claude|codex|cursor|copilot|gemini|windsurf|aider|opencode)
|
|
390
458
|
nerviq audit --json Machine-readable JSON output (for CI)
|
|
391
|
-
nerviq audit --workspace packages/* Audit monorepo workspaces with
|
|
459
|
+
nerviq audit --workspace packages/* Audit monorepo workspaces with stack-specific package profiles
|
|
392
460
|
nerviq scan dir1 dir2 Compare multiple repos side-by-side
|
|
393
461
|
nerviq org scan dir1 dir2 Aggregate multiple repos into one score table
|
|
394
462
|
nerviq catalog Full check catalog (all 8 platforms)
|
|
@@ -423,7 +491,7 @@ const HELP = `
|
|
|
423
491
|
nerviq apply --dry-run Preview changes without writing
|
|
424
492
|
|
|
425
493
|
GOVERN
|
|
426
|
-
nerviq governance Permission profiles + hooks + policy packs
|
|
494
|
+
nerviq governance Permission profiles + hooks + policy packs (the rollout safety layer)
|
|
427
495
|
nerviq governance --json Machine-readable governance summary
|
|
428
496
|
nerviq benchmark Baseline vs projected score in isolated temp copy
|
|
429
497
|
nerviq benchmark --external /path Benchmark an external repo
|
|
@@ -447,10 +515,11 @@ const HELP = `
|
|
|
447
515
|
nerviq dashboard --open Open dashboard in browser after generating
|
|
448
516
|
nerviq watch Live config monitoring (re-audits on file change)
|
|
449
517
|
nerviq history Audit snapshot history from saved snapshots
|
|
450
|
-
nerviq compare
|
|
518
|
+
nerviq compare Detailed per-check diff between latest two audit snapshots
|
|
451
519
|
nerviq trend Audit snapshot trend over time
|
|
452
|
-
nerviq trend --out report.md Export trend report as markdown
|
|
453
|
-
nerviq
|
|
520
|
+
nerviq trend --out report.md Export trend report as markdown
|
|
521
|
+
nerviq audit --snapshot --tag "pre-refactor" Save a named audit snapshot
|
|
522
|
+
nerviq feedback Record recommendation outcomes
|
|
454
523
|
|
|
455
524
|
TEAM PROFILES
|
|
456
525
|
nerviq profile save <name> Save current preferences as a named profile
|
|
@@ -460,7 +529,7 @@ const HELP = `
|
|
|
460
529
|
|
|
461
530
|
ADVANCED
|
|
462
531
|
nerviq deep-review AI-powered config review (opt-in, uses API key)
|
|
463
|
-
nerviq serve --port 3000 Start local Nerviq REST API server
|
|
532
|
+
nerviq serve --port 3000 Start local Nerviq REST API server + OpenAPI contract
|
|
464
533
|
nerviq badge Generate shields.io badge markdown
|
|
465
534
|
nerviq rules-export Export recommendation rules as JSON
|
|
466
535
|
nerviq rules-export --out F Save rules to file
|
|
@@ -475,15 +544,18 @@ const HELP = `
|
|
|
475
544
|
--only A,B Limit plan/apply to selected proposal IDs
|
|
476
545
|
--profile NAME Permission profile: read-only | suggest-only | safe-write | power-user
|
|
477
546
|
--team-profile N Load a saved team profile for audit (overrides threshold/platform)
|
|
478
|
-
--mcp-pack A,B Merge MCP packs into setup (e.g. context7-docs,next-devtools)
|
|
479
|
-
--check-version V Pin catalog to a specific version (warn on mismatch)
|
|
480
|
-
--format NAME Output format: json | sarif | otel
|
|
481
|
-
--webhook URL Send audit results to a webhook (Slack/Discord/generic JSON)
|
|
482
|
-
--
|
|
483
|
-
--
|
|
484
|
-
--
|
|
485
|
-
--
|
|
486
|
-
--
|
|
547
|
+
--mcp-pack A,B Merge MCP packs into setup (live tool connectors; e.g. context7-docs,next-devtools)
|
|
548
|
+
--check-version V Pin catalog to a specific version (warn on mismatch)
|
|
549
|
+
--format NAME Output format: json | sarif | otel
|
|
550
|
+
--webhook URL Send audit results to a webhook (Slack/Discord/generic JSON)
|
|
551
|
+
--webhook-header H Add a custom webhook header (repeat; format: Name: Value)
|
|
552
|
+
--webhook-retries N Retry transient webhook failures N times (default: 2)
|
|
553
|
+
--external PATH Benchmark an external repo instead of cwd
|
|
554
|
+
--port N Port for \`serve\` (default: 3000)
|
|
555
|
+
--workspace GLOBS Audit workspaces separately with root/package score semantics and stack-specific profiles
|
|
556
|
+
--snapshot Save snapshot artifact under .claude/nerviq/snapshots/
|
|
557
|
+
--tag LABEL Tag the saved snapshot (use with --snapshot; repeat or comma-separate for more)
|
|
558
|
+
--full Show full audit output (all checks, weakest areas, badge)
|
|
487
559
|
--lite Short top-3 scan (default behavior since v1.5.2)
|
|
488
560
|
--dry-run Preview changes without writing files
|
|
489
561
|
--config-only Only write config files (.claude/, rules, hooks) — never source code
|
|
@@ -579,7 +651,7 @@ async function main() {
|
|
|
579
651
|
process.exit(0);
|
|
580
652
|
}
|
|
581
653
|
|
|
582
|
-
const options = {
|
|
654
|
+
const options = {
|
|
583
655
|
verbose: flags.includes('--verbose'),
|
|
584
656
|
json: flags.includes('--json'),
|
|
585
657
|
auto: flags.includes('--auto'),
|
|
@@ -601,13 +673,21 @@ async function main() {
|
|
|
601
673
|
require: parsed.requireChecks,
|
|
602
674
|
platform: parsed.platform || 'claude',
|
|
603
675
|
format: parsed.format || null,
|
|
604
|
-
port: parsed.port !== null ? Number(parsed.port) : null,
|
|
605
|
-
workspace: parsed.workspace || null,
|
|
606
|
-
webhookUrl: parsed.webhookUrl || null,
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
676
|
+
port: parsed.port !== null ? Number(parsed.port) : null,
|
|
677
|
+
workspace: parsed.workspace || null,
|
|
678
|
+
webhookUrl: parsed.webhookUrl || null,
|
|
679
|
+
webhookHeaders: Object.fromEntries((parsed.webhookHeaders || []).map((entry) => [entry.name, entry.value])),
|
|
680
|
+
webhookRetries: parsed.webhookRetries ?? 2,
|
|
681
|
+
lang: parsed.lang || null,
|
|
682
|
+
external: parsed.external || null,
|
|
683
|
+
snapshotTags: parsed.snapshotTags || [],
|
|
684
|
+
dir: process.cwd()
|
|
685
|
+
};
|
|
686
|
+
|
|
687
|
+
if (options.snapshotTags.length > 0 && !options.snapshot) {
|
|
688
|
+
console.error('\n Error: --tag requires --snapshot.\n');
|
|
689
|
+
process.exit(1);
|
|
690
|
+
}
|
|
611
691
|
|
|
612
692
|
if (parsed.checkVersion) {
|
|
613
693
|
if (parsed.checkVersion !== version) {
|
|
@@ -820,7 +900,7 @@ async function main() {
|
|
|
820
900
|
console.log('');
|
|
821
901
|
process.exit(0);
|
|
822
902
|
} else if (normalizedCommand === 'compare') {
|
|
823
|
-
const { compareLatest, formatSnapshotBootstrap } = require('../src/activity');
|
|
903
|
+
const { compareLatest, formatSnapshotBootstrap, formatSnapshotTags } = require('../src/activity');
|
|
824
904
|
const result = compareLatest(options.dir);
|
|
825
905
|
if (!result) {
|
|
826
906
|
console.log('');
|
|
@@ -830,16 +910,41 @@ async function main() {
|
|
|
830
910
|
}
|
|
831
911
|
if (options.json) {
|
|
832
912
|
console.log(JSON.stringify(result, null, 2));
|
|
833
|
-
} else {
|
|
834
|
-
const sign = result.delta.score >= 0 ? '+' : '';
|
|
835
|
-
console.log('');
|
|
836
|
-
console.log(` Previous snapshot: ${result.previous.score}/100 (${result.previous.date?.split('T')[0]})`);
|
|
837
|
-
console.log(` Current snapshot: ${result.current.score}/100 (${result.current.date?.split('T')[0]})`);
|
|
913
|
+
} else {
|
|
914
|
+
const sign = result.delta.score >= 0 ? '+' : '';
|
|
915
|
+
console.log('');
|
|
916
|
+
console.log(` Previous snapshot: ${result.previous.score}/100 (${result.previous.date?.split('T')[0]})${formatSnapshotTags(result.previous.tags)}`);
|
|
917
|
+
console.log(` Current snapshot: ${result.current.score}/100 (${result.current.date?.split('T')[0]})${formatSnapshotTags(result.current.tags)}`);
|
|
838
918
|
console.log(` Snapshot delta: ${sign}${result.delta.score} points`);
|
|
839
919
|
console.log(` Trend: ${result.trend}`);
|
|
840
|
-
if (result.
|
|
841
|
-
|
|
842
|
-
|
|
920
|
+
if (result.detailedDiffAvailable) {
|
|
921
|
+
console.log('');
|
|
922
|
+
console.log(' Detailed check diff:');
|
|
923
|
+
printCompareCheckSection('Regressions', result.regressionDetails, '🔴');
|
|
924
|
+
printCompareCheckSection('Improvements', result.improvementDetails, '✅');
|
|
925
|
+
printCompareCheckSection('Newly applicable', result.newlyApplicableDetails, '🆕');
|
|
926
|
+
printCompareCheckSection('No longer applicable', result.noLongerApplicableDetails, '↩');
|
|
927
|
+
if (Array.isArray(result.newChecks) && result.newChecks.length > 0) {
|
|
928
|
+
printCompareCheckSection('New checks', result.newChecks, '➕');
|
|
929
|
+
}
|
|
930
|
+
if (Array.isArray(result.removedChecks) && result.removedChecks.length > 0) {
|
|
931
|
+
printCompareCheckSection('Removed checks', result.removedChecks, '➖');
|
|
932
|
+
}
|
|
933
|
+
if (
|
|
934
|
+
result.regressionDetails.length === 0 &&
|
|
935
|
+
result.improvementDetails.length === 0 &&
|
|
936
|
+
result.newlyApplicableDetails.length === 0 &&
|
|
937
|
+
result.noLongerApplicableDetails.length === 0 &&
|
|
938
|
+
result.newChecks.length === 0 &&
|
|
939
|
+
result.removedChecks.length === 0
|
|
940
|
+
) {
|
|
941
|
+
console.log(' No per-check state changes detected.');
|
|
942
|
+
}
|
|
943
|
+
} else {
|
|
944
|
+
if (result.improvements.length > 0) console.log(` Fixed: ${result.improvements.join(', ')}`);
|
|
945
|
+
if (result.regressions.length > 0) console.log(` New gaps: ${result.regressions.join(', ')}`);
|
|
946
|
+
}
|
|
947
|
+
console.log('');
|
|
843
948
|
}
|
|
844
949
|
process.exit(0);
|
|
845
950
|
} else if (normalizedCommand === 'trend') {
|
|
@@ -959,9 +1064,10 @@ async function main() {
|
|
|
959
1064
|
process.exit(0);
|
|
960
1065
|
} else if (normalizedCommand === 'augment' || normalizedCommand === 'suggest-only') {
|
|
961
1066
|
const report = await analyzeProject({ ...options, mode: normalizedCommand });
|
|
962
|
-
const snapshot = options.snapshot ? writeSnapshotArtifact(options.dir, normalizedCommand, report, {
|
|
963
|
-
|
|
964
|
-
|
|
1067
|
+
const snapshot = options.snapshot ? writeSnapshotArtifact(options.dir, normalizedCommand, report, {
|
|
1068
|
+
tags: options.snapshotTags,
|
|
1069
|
+
sourceCommand: normalizedCommand,
|
|
1070
|
+
}) : null;
|
|
965
1071
|
if (options.out && !options.json) {
|
|
966
1072
|
const fs = require('fs');
|
|
967
1073
|
const md = exportMarkdown(report);
|
|
@@ -1092,9 +1198,10 @@ async function main() {
|
|
|
1092
1198
|
fs.writeFileSync(options.out, content, 'utf8');
|
|
1093
1199
|
}
|
|
1094
1200
|
printGovernanceSummary(summary, options);
|
|
1095
|
-
const snapshot = options.snapshot ? writeSnapshotArtifact(options.dir, 'governance', summary, {
|
|
1096
|
-
|
|
1097
|
-
|
|
1201
|
+
const snapshot = options.snapshot ? writeSnapshotArtifact(options.dir, 'governance', summary, {
|
|
1202
|
+
tags: options.snapshotTags,
|
|
1203
|
+
sourceCommand: normalizedCommand,
|
|
1204
|
+
}) : null;
|
|
1098
1205
|
if (options.out && !options.json) {
|
|
1099
1206
|
console.log(` Governance report written to ${options.out}`);
|
|
1100
1207
|
console.log('');
|
|
@@ -1106,9 +1213,10 @@ async function main() {
|
|
|
1106
1213
|
}
|
|
1107
1214
|
} else if (normalizedCommand === 'benchmark') {
|
|
1108
1215
|
const report = await runBenchmark(options);
|
|
1109
|
-
const snapshot = options.snapshot ? writeSnapshotArtifact(options.dir, 'benchmark', report, {
|
|
1110
|
-
|
|
1111
|
-
|
|
1216
|
+
const snapshot = options.snapshot ? writeSnapshotArtifact(options.dir, 'benchmark', report, {
|
|
1217
|
+
tags: options.snapshotTags,
|
|
1218
|
+
sourceCommand: normalizedCommand,
|
|
1219
|
+
}) : null;
|
|
1112
1220
|
if (options.out) {
|
|
1113
1221
|
writeBenchmarkReport(report, options.out);
|
|
1114
1222
|
}
|
|
@@ -1202,9 +1310,10 @@ async function main() {
|
|
|
1202
1310
|
const address = server.address();
|
|
1203
1311
|
const resolvedPort = address && typeof address === 'object' ? address.port : options.port;
|
|
1204
1312
|
console.log('');
|
|
1205
|
-
console.log(` nerviq API listening on http://127.0.0.1:${resolvedPort}`);
|
|
1206
|
-
console.log(' Endpoints: /api/health, /api/catalog, /api/audit, /api/harmony');
|
|
1207
|
-
console.log(
|
|
1313
|
+
console.log(` nerviq API listening on http://127.0.0.1:${resolvedPort}`);
|
|
1314
|
+
console.log(' Endpoints: /api/openapi.json, /api/health, /api/catalog, /api/audit, /api/harmony');
|
|
1315
|
+
console.log(` Contract: http://127.0.0.1:${resolvedPort}/api/openapi.json`);
|
|
1316
|
+
console.log('');
|
|
1208
1317
|
|
|
1209
1318
|
const closeServer = () => {
|
|
1210
1319
|
server.close(() => process.exit(0));
|
|
@@ -1982,14 +2091,15 @@ async function main() {
|
|
|
1982
2091
|
process.exit(0);
|
|
1983
2092
|
} else if (normalizedCommand === 'setup') {
|
|
1984
2093
|
await setup(options);
|
|
1985
|
-
if (options.snapshot) {
|
|
1986
|
-
const postSetupResult = await audit({ dir: options.dir, silent: true, platform: options.platform });
|
|
1987
|
-
const snapshot = writeSnapshotArtifact(options.dir, 'audit', postSetupResult, {
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
2094
|
+
if (options.snapshot) {
|
|
2095
|
+
const postSetupResult = await audit({ dir: options.dir, silent: true, platform: options.platform });
|
|
2096
|
+
const snapshot = writeSnapshotArtifact(options.dir, 'audit', postSetupResult, {
|
|
2097
|
+
tags: options.snapshotTags,
|
|
2098
|
+
sourceCommand: 'setup',
|
|
2099
|
+
});
|
|
2100
|
+
if (!options.json) {
|
|
2101
|
+
console.log(` Snapshot saved: ${snapshot.relativePath}`);
|
|
2102
|
+
}
|
|
1993
2103
|
}
|
|
1994
2104
|
} else {
|
|
1995
2105
|
if (options.workspace) {
|
|
@@ -2027,18 +2137,26 @@ async function main() {
|
|
|
2027
2137
|
// Generic webhook: send full JSON audit result
|
|
2028
2138
|
payload = { platform: result.platform, score: result.score, passed: result.passed, failed: result.failed, results: result.results };
|
|
2029
2139
|
}
|
|
2030
|
-
const webhookResp = await sendWebhook(options.webhookUrl, payload
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2140
|
+
const webhookResp = await sendWebhook(options.webhookUrl, payload, {
|
|
2141
|
+
headers: options.webhookHeaders,
|
|
2142
|
+
retries: options.webhookRetries,
|
|
2143
|
+
});
|
|
2144
|
+
if (!options.json) {
|
|
2145
|
+
if (webhookResp.ok) {
|
|
2146
|
+
const retryNote = webhookResp.attempts > 1 ? ` after ${webhookResp.attempts} attempts` : '';
|
|
2147
|
+
console.log(` Webhook sent${retryNote}: ${options.webhookUrl} (${webhookResp.status})`);
|
|
2148
|
+
} else {
|
|
2149
|
+
const retryNote = webhookResp.attempts > 1 ? ` after ${webhookResp.attempts} attempts` : '';
|
|
2150
|
+
console.error(` Webhook failed${retryNote}: ${webhookResp.status} — ${webhookResp.body.slice(0, 200)}`);
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
} catch (webhookErr) {
|
|
2154
|
+
if (!options.json) {
|
|
2155
|
+
const retryNote = webhookErr.attempts > 1 ? ` after ${webhookErr.attempts} attempts` : '';
|
|
2156
|
+
console.error(` Webhook error${retryNote}: ${webhookErr.message}`);
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2042
2160
|
if (options.feedback && !options.json && options.format === null) {
|
|
2043
2161
|
const feedbackTargets = options.lite
|
|
2044
2162
|
? (result.liteSummary?.topNextActions || [])
|
|
@@ -2058,9 +2176,10 @@ async function main() {
|
|
|
2058
2176
|
console.log('');
|
|
2059
2177
|
}
|
|
2060
2178
|
}
|
|
2061
|
-
const snapshot = options.snapshot ? writeSnapshotArtifact(options.dir, 'audit', result, {
|
|
2062
|
-
|
|
2063
|
-
|
|
2179
|
+
const snapshot = options.snapshot ? writeSnapshotArtifact(options.dir, 'audit', result, {
|
|
2180
|
+
tags: options.snapshotTags,
|
|
2181
|
+
sourceCommand: normalizedCommand,
|
|
2182
|
+
}) : null;
|
|
2064
2183
|
if (snapshot && !options.json) {
|
|
2065
2184
|
console.log(` Snapshot saved: ${snapshot.relativePath}`);
|
|
2066
2185
|
console.log(` Snapshot index: ${snapshot.indexPath}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nerviq/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.0",
|
|
4
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
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|