@nerviq/cli 1.6.4 → 1.7.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/bin/cli.js +127 -11
- package/package.json +1 -1
- package/src/benchmark.js +3 -1
- package/src/governance.js +19 -6
- package/src/setup.js +1 -1
package/bin/cli.js
CHANGED
|
@@ -26,7 +26,7 @@ const COMMAND_ALIASES = {
|
|
|
26
26
|
gov: 'governance',
|
|
27
27
|
outcome: 'feedback',
|
|
28
28
|
};
|
|
29
|
-
const KNOWN_COMMANDS = ['audit', 'org', 'setup', 'augment', 'suggest-only', 'plan', 'apply', 'fix', 'governance', 'benchmark', 'deep-review', 'interactive', 'watch', 'badge', 'insights', 'history', 'compare', 'trend', 'scan', 'feedback', 'doctor', 'convert', 'migrate', 'catalog', 'certify', 'serve', 'check-health', 'harmony-audit', 'harmony-sync', 'harmony-drift', 'harmony-advise', 'harmony-watch', 'harmony-governance', 'harmony-add', 'synergy-report', 'anti-patterns', 'rules-export', 'freshness', 'help', 'version'];
|
|
29
|
+
const KNOWN_COMMANDS = ['audit', 'org', 'setup', 'augment', 'suggest-only', 'plan', 'apply', 'fix', 'rollback', 'governance', 'benchmark', 'deep-review', 'interactive', 'watch', 'badge', 'insights', 'history', 'compare', 'trend', 'scan', 'feedback', 'doctor', 'convert', 'migrate', 'catalog', 'certify', 'serve', 'check-health', 'harmony-audit', 'harmony-sync', 'harmony-drift', 'harmony-advise', 'harmony-watch', 'harmony-governance', 'harmony-add', 'synergy-report', 'anti-patterns', 'rules-export', 'freshness', 'help', 'version'];
|
|
30
30
|
|
|
31
31
|
function levenshtein(a, b) {
|
|
32
32
|
const matrix = Array.from({ length: a.length + 1 }, () => Array(b.length + 1).fill(0));
|
|
@@ -313,6 +313,9 @@ const HELP = `
|
|
|
313
313
|
nerviq fix <key> Auto-fix a specific check (with score impact)
|
|
314
314
|
nerviq fix --all-critical Fix all critical issues at once
|
|
315
315
|
nerviq fix --dry-run Preview fixes without writing
|
|
316
|
+
nerviq rollback Undo the most recent apply (delete created files)
|
|
317
|
+
nerviq rollback --list Show available rollback points
|
|
318
|
+
nerviq rollback --dry-run Preview what would be deleted
|
|
316
319
|
|
|
317
320
|
IMPROVE
|
|
318
321
|
nerviq augment Improvement plan (no writes)
|
|
@@ -609,7 +612,30 @@ async function main() {
|
|
|
609
612
|
}
|
|
610
613
|
process.exit(0);
|
|
611
614
|
} else if (normalizedCommand === 'history') {
|
|
612
|
-
const { formatHistory } = require('../src/activity');
|
|
615
|
+
const { formatHistory, readSnapshotIndex } = require('../src/activity');
|
|
616
|
+
// Handle --prune N
|
|
617
|
+
const pruneIdx = flags.indexOf('--prune');
|
|
618
|
+
if (pruneIdx >= 0) {
|
|
619
|
+
const keepCount = parseInt(flags[pruneIdx + 1] || parsed.extraArgs[0], 10) || 10;
|
|
620
|
+
const fsMod = require('fs');
|
|
621
|
+
const pathMod = require('path');
|
|
622
|
+
const entries = readSnapshotIndex(options.dir);
|
|
623
|
+
if (entries.length <= keepCount) {
|
|
624
|
+
console.log(`\n Nothing to prune (${entries.length} snapshots, keeping ${keepCount}).\n`);
|
|
625
|
+
} else {
|
|
626
|
+
const toRemove = entries.slice(0, entries.length - keepCount);
|
|
627
|
+
let removed = 0;
|
|
628
|
+
for (const entry of toRemove) {
|
|
629
|
+
const fp = pathMod.join(options.dir, entry.relativePath);
|
|
630
|
+
try { fsMod.unlinkSync(fp); removed++; } catch {}
|
|
631
|
+
}
|
|
632
|
+
const kept = entries.slice(entries.length - keepCount);
|
|
633
|
+
const indexPath = pathMod.join(options.dir, '.nerviq', 'snapshots', 'index.json');
|
|
634
|
+
try { fsMod.writeFileSync(indexPath, JSON.stringify(kept, null, 2), 'utf8'); } catch {}
|
|
635
|
+
console.log(`\n Pruned ${removed} snapshots, kept ${kept.length}.\n`);
|
|
636
|
+
}
|
|
637
|
+
process.exit(0);
|
|
638
|
+
}
|
|
613
639
|
console.log('');
|
|
614
640
|
console.log(formatHistory(options.dir));
|
|
615
641
|
console.log('');
|
|
@@ -761,6 +787,88 @@ async function main() {
|
|
|
761
787
|
}
|
|
762
788
|
console.log('');
|
|
763
789
|
}
|
|
790
|
+
} else if (normalizedCommand === 'rollback') {
|
|
791
|
+
const fsMod = require('fs');
|
|
792
|
+
const pathMod = require('path');
|
|
793
|
+
const rollbackDir = pathMod.join(options.dir, '.nerviq', 'rollbacks');
|
|
794
|
+
|
|
795
|
+
if (!fsMod.existsSync(rollbackDir)) {
|
|
796
|
+
console.log('\n No rollback artifacts found. Run `nerviq apply` first to create rollback data.\n');
|
|
797
|
+
process.exit(0);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
const rollbackFiles = fsMod.readdirSync(rollbackDir)
|
|
801
|
+
.filter(f => f.endsWith('.json'))
|
|
802
|
+
.sort()
|
|
803
|
+
.reverse();
|
|
804
|
+
|
|
805
|
+
if (rollbackFiles.length === 0) {
|
|
806
|
+
console.log('\n No rollback artifacts found.\n');
|
|
807
|
+
process.exit(0);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// --list mode
|
|
811
|
+
if (flags.includes('--list')) {
|
|
812
|
+
console.log(`\n Rollback points (${rollbackFiles.length}):\n`);
|
|
813
|
+
for (const f of rollbackFiles) {
|
|
814
|
+
try {
|
|
815
|
+
const data = JSON.parse(fsMod.readFileSync(pathMod.join(rollbackDir, f), 'utf8'));
|
|
816
|
+
const created = (data.createdFiles || []).length;
|
|
817
|
+
const patched = (data.patchedFiles || []).length;
|
|
818
|
+
console.log(` ${f.replace('.json', '')} (${created} created, ${patched} patched)`);
|
|
819
|
+
} catch {
|
|
820
|
+
console.log(` ${f} (unreadable)`);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
console.log(`\n Run \`nerviq rollback\` to undo the most recent.\n`);
|
|
824
|
+
process.exit(0);
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
// Execute rollback of most recent
|
|
828
|
+
const latestFile = rollbackFiles[0];
|
|
829
|
+
const latestPath = pathMod.join(rollbackDir, latestFile);
|
|
830
|
+
let rollbackData;
|
|
831
|
+
try {
|
|
832
|
+
rollbackData = JSON.parse(fsMod.readFileSync(latestPath, 'utf8'));
|
|
833
|
+
} catch (e) {
|
|
834
|
+
console.error(`\n Error: Cannot parse rollback file: ${e.message}\n`);
|
|
835
|
+
process.exit(1);
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
const createdFiles = rollbackData.createdFiles || [];
|
|
839
|
+
if (createdFiles.length === 0) {
|
|
840
|
+
console.log('\n Rollback artifact has no files to remove.\n');
|
|
841
|
+
process.exit(0);
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
if (options.dryRun) {
|
|
845
|
+
console.log(`\n [dry-run] Would delete ${createdFiles.length} files:\n`);
|
|
846
|
+
for (const f of createdFiles) {
|
|
847
|
+
console.log(` - ${f}`);
|
|
848
|
+
}
|
|
849
|
+
console.log('');
|
|
850
|
+
process.exit(0);
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
let deleted = 0;
|
|
854
|
+
let missing = 0;
|
|
855
|
+
console.log('');
|
|
856
|
+
for (const relPath of createdFiles) {
|
|
857
|
+
const fullPath = pathMod.join(options.dir, relPath);
|
|
858
|
+
if (fsMod.existsSync(fullPath)) {
|
|
859
|
+
fsMod.unlinkSync(fullPath);
|
|
860
|
+
console.log(` 🗑️ Deleted: ${relPath}`);
|
|
861
|
+
deleted++;
|
|
862
|
+
} else {
|
|
863
|
+
missing++;
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
// Remove rollback artifact after use
|
|
868
|
+
fsMod.unlinkSync(latestPath);
|
|
869
|
+
|
|
870
|
+
console.log(`\n Rollback complete: ${deleted} files deleted${missing > 0 ? `, ${missing} already missing` : ''}.\n`);
|
|
871
|
+
|
|
764
872
|
} else if (normalizedCommand === 'apply') {
|
|
765
873
|
if (flags.includes('--rollback')) {
|
|
766
874
|
console.error('\n Error: --rollback is not yet supported as a flag.');
|
|
@@ -1037,16 +1145,24 @@ async function main() {
|
|
|
1037
1145
|
} else if (normalizedCommand === 'rules-export') {
|
|
1038
1146
|
const { generateRecommendationRules } = require('../src/recommendation-rules');
|
|
1039
1147
|
const rules = generateRecommendationRules();
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
} else {
|
|
1046
|
-
console.log(output);
|
|
1047
|
-
}
|
|
1148
|
+
if (options.json) {
|
|
1149
|
+
console.log(JSON.stringify(rules, null, 2));
|
|
1150
|
+
} else if (options.out) {
|
|
1151
|
+
require('fs').writeFileSync(options.out, JSON.stringify(rules, null, 2), 'utf8');
|
|
1152
|
+
console.log(`\n Rules exported to ${options.out} (${rules.totalRules} rules)\n`);
|
|
1048
1153
|
} else {
|
|
1049
|
-
|
|
1154
|
+
// Human-readable summary
|
|
1155
|
+
console.log(`\n Nerviq Recommendation Rules (${rules.totalRules} rules)\n`);
|
|
1156
|
+
const byCategory = {};
|
|
1157
|
+
for (const rule of (rules.rules || [])) {
|
|
1158
|
+
const cat = rule.category || 'other';
|
|
1159
|
+
if (!byCategory[cat]) byCategory[cat] = 0;
|
|
1160
|
+
byCategory[cat]++;
|
|
1161
|
+
}
|
|
1162
|
+
for (const [cat, count] of Object.entries(byCategory).sort((a, b) => b[1] - a[1])) {
|
|
1163
|
+
console.log(` ${cat.padEnd(20)} ${count} rules`);
|
|
1164
|
+
}
|
|
1165
|
+
console.log(`\n Use --json for full output or --out <file> to save.\n`);
|
|
1050
1166
|
}
|
|
1051
1167
|
process.exit(0);
|
|
1052
1168
|
} else if (normalizedCommand === 'check-health') {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nerviq/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "The intelligent nervous system for AI coding agents — 2,431 checks across 8 platforms, 10 languages, and 62 domain packs. Audit, align, and amplify.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
package/src/benchmark.js
CHANGED
|
@@ -145,7 +145,9 @@ function buildExecutiveSummary(before, after, workflowEvidence) {
|
|
|
145
145
|
const scoreDelta = after.score - before.score;
|
|
146
146
|
const organicDelta = after.organicScore - before.organicScore;
|
|
147
147
|
const workflowCoverage = workflowEvidence.summary.coverageScore;
|
|
148
|
-
let headline =
|
|
148
|
+
let headline = before.score >= 60
|
|
149
|
+
? 'Setup is already applied — benchmark shows no additional improvement. Run benchmark on a project before running setup to see the full delta.'
|
|
150
|
+
: 'Benchmark did not improve the score in this run.';
|
|
149
151
|
|
|
150
152
|
if (scoreDelta < 0) {
|
|
151
153
|
headline = `Warning: score decreased by ${Math.abs(scoreDelta)} points. Setup may have introduced a regression.`;
|
package/src/governance.js
CHANGED
|
@@ -433,22 +433,35 @@ function printGovernanceSummary(summary, options = {}) {
|
|
|
433
433
|
}
|
|
434
434
|
console.log('');
|
|
435
435
|
|
|
436
|
-
|
|
437
|
-
|
|
436
|
+
const domainPacks = summary.domainPacks || [];
|
|
437
|
+
const mcpPacks = summary.mcpPacks || [];
|
|
438
|
+
const compact = !options.verbose;
|
|
439
|
+
const COMPACT_LIMIT = 5;
|
|
440
|
+
|
|
441
|
+
console.log(` Domain Packs (${domainPacks.length})`);
|
|
442
|
+
if (domainPacks.length === 0) {
|
|
438
443
|
console.log(' - none shipped yet for this platform');
|
|
439
444
|
}
|
|
440
|
-
|
|
445
|
+
const domainShow = compact ? domainPacks.slice(0, COMPACT_LIMIT) : domainPacks;
|
|
446
|
+
for (const pack of domainShow) {
|
|
441
447
|
console.log(` - ${pack.label}: ${pack.useWhen}`);
|
|
442
448
|
}
|
|
449
|
+
if (compact && domainPacks.length > COMPACT_LIMIT) {
|
|
450
|
+
console.log(` ... and ${domainPacks.length - COMPACT_LIMIT} more (use --verbose to see all)`);
|
|
451
|
+
}
|
|
443
452
|
console.log('');
|
|
444
453
|
|
|
445
|
-
console.log(
|
|
446
|
-
if (
|
|
454
|
+
console.log(` MCP Packs (${mcpPacks.length})`);
|
|
455
|
+
if (mcpPacks.length === 0) {
|
|
447
456
|
console.log(' - none shipped yet for this platform');
|
|
448
457
|
}
|
|
449
|
-
|
|
458
|
+
const mcpShow = compact ? mcpPacks.slice(0, COMPACT_LIMIT) : mcpPacks;
|
|
459
|
+
for (const pack of mcpShow) {
|
|
450
460
|
console.log(` - ${pack.label}: ${Object.keys(pack.servers).join(', ')}`);
|
|
451
461
|
}
|
|
462
|
+
if (compact && mcpPacks.length > COMPACT_LIMIT) {
|
|
463
|
+
console.log(` ... and ${mcpPacks.length - COMPACT_LIMIT} more (use --verbose to see all)`);
|
|
464
|
+
}
|
|
452
465
|
console.log('');
|
|
453
466
|
|
|
454
467
|
if (Array.isArray(summary.platformCaveats) && summary.platformCaveats.length > 0) {
|
package/src/setup.js
CHANGED
|
@@ -1022,7 +1022,7 @@ Prepare a release candidate for: $ARGUMENTS
|
|
|
1022
1022
|
const hasTS = stacks.some(s => s.key === 'typescript');
|
|
1023
1023
|
const hasPython = stacks.some(s => s.key === 'python');
|
|
1024
1024
|
const hasFrontend = stacks.some(s => ['react', 'vue', 'angular', 'svelte', 'nextjs'].includes(s.key));
|
|
1025
|
-
const hasBackend = stacks.some(s => ['go', 'python', 'django', 'fastapi', 'rust', 'java'].includes(s.key));
|
|
1025
|
+
const hasBackend = stacks.some(s => ['go', 'python', 'django', 'fastapi', 'rust', 'java', 'node', 'nestjs'].includes(s.key));
|
|
1026
1026
|
|
|
1027
1027
|
if (hasFrontend || (hasTS && !hasBackend)) {
|
|
1028
1028
|
rules['frontend.md'] = `When editing JavaScript/TypeScript files (*.ts, *.tsx, *.js, *.jsx, *.vue):
|