@nerviq/cli 1.6.3 → 1.6.5
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 +41 -10
- package/package.json +1 -1
- package/src/activity.js +5 -2
- package/src/audit.js +26 -5
- package/src/governance.js +19 -6
- package/src/setup.js +5 -2
package/bin/cli.js
CHANGED
|
@@ -609,7 +609,30 @@ async function main() {
|
|
|
609
609
|
}
|
|
610
610
|
process.exit(0);
|
|
611
611
|
} else if (normalizedCommand === 'history') {
|
|
612
|
-
const { formatHistory } = require('../src/activity');
|
|
612
|
+
const { formatHistory, readSnapshotIndex } = require('../src/activity');
|
|
613
|
+
// Handle --prune N
|
|
614
|
+
const pruneIdx = flags.indexOf('--prune');
|
|
615
|
+
if (pruneIdx >= 0) {
|
|
616
|
+
const keepCount = parseInt(flags[pruneIdx + 1] || parsed.extraArgs[0], 10) || 10;
|
|
617
|
+
const fsMod = require('fs');
|
|
618
|
+
const pathMod = require('path');
|
|
619
|
+
const entries = readSnapshotIndex(options.dir);
|
|
620
|
+
if (entries.length <= keepCount) {
|
|
621
|
+
console.log(`\n Nothing to prune (${entries.length} snapshots, keeping ${keepCount}).\n`);
|
|
622
|
+
} else {
|
|
623
|
+
const toRemove = entries.slice(0, entries.length - keepCount);
|
|
624
|
+
let removed = 0;
|
|
625
|
+
for (const entry of toRemove) {
|
|
626
|
+
const fp = pathMod.join(options.dir, entry.relativePath);
|
|
627
|
+
try { fsMod.unlinkSync(fp); removed++; } catch {}
|
|
628
|
+
}
|
|
629
|
+
const kept = entries.slice(entries.length - keepCount);
|
|
630
|
+
const indexPath = pathMod.join(options.dir, '.nerviq', 'snapshots', 'index.json');
|
|
631
|
+
try { fsMod.writeFileSync(indexPath, JSON.stringify(kept, null, 2), 'utf8'); } catch {}
|
|
632
|
+
console.log(`\n Pruned ${removed} snapshots, kept ${kept.length}.\n`);
|
|
633
|
+
}
|
|
634
|
+
process.exit(0);
|
|
635
|
+
}
|
|
613
636
|
console.log('');
|
|
614
637
|
console.log(formatHistory(options.dir));
|
|
615
638
|
console.log('');
|
|
@@ -1037,16 +1060,24 @@ async function main() {
|
|
|
1037
1060
|
} else if (normalizedCommand === 'rules-export') {
|
|
1038
1061
|
const { generateRecommendationRules } = require('../src/recommendation-rules');
|
|
1039
1062
|
const rules = generateRecommendationRules();
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
} else {
|
|
1046
|
-
console.log(output);
|
|
1047
|
-
}
|
|
1063
|
+
if (options.json) {
|
|
1064
|
+
console.log(JSON.stringify(rules, null, 2));
|
|
1065
|
+
} else if (options.out) {
|
|
1066
|
+
require('fs').writeFileSync(options.out, JSON.stringify(rules, null, 2), 'utf8');
|
|
1067
|
+
console.log(`\n Rules exported to ${options.out} (${rules.totalRules} rules)\n`);
|
|
1048
1068
|
} else {
|
|
1049
|
-
|
|
1069
|
+
// Human-readable summary
|
|
1070
|
+
console.log(`\n Nerviq Recommendation Rules (${rules.totalRules} rules)\n`);
|
|
1071
|
+
const byCategory = {};
|
|
1072
|
+
for (const rule of (rules.rules || [])) {
|
|
1073
|
+
const cat = rule.category || 'other';
|
|
1074
|
+
if (!byCategory[cat]) byCategory[cat] = 0;
|
|
1075
|
+
byCategory[cat]++;
|
|
1076
|
+
}
|
|
1077
|
+
for (const [cat, count] of Object.entries(byCategory).sort((a, b) => b[1] - a[1])) {
|
|
1078
|
+
console.log(` ${cat.padEnd(20)} ${count} rules`);
|
|
1079
|
+
}
|
|
1080
|
+
console.log(`\n Use --json for full output or --out <file> to save.\n`);
|
|
1050
1081
|
}
|
|
1051
1082
|
process.exit(0);
|
|
1052
1083
|
} else if (normalizedCommand === 'check-health') {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nerviq/cli",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.5",
|
|
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/activity.js
CHANGED
|
@@ -283,11 +283,14 @@ function formatHistory(dir) {
|
|
|
283
283
|
|
|
284
284
|
const lines = ['Score history (most recent first):', ''];
|
|
285
285
|
for (const entry of history) {
|
|
286
|
-
const
|
|
286
|
+
const dateStr = entry.createdAt || 'unknown';
|
|
287
|
+
const date = dateStr.split('T')[0] || 'unknown';
|
|
288
|
+
const time = dateStr.includes('T') ? dateStr.split('T')[1]?.substring(0, 5) || '' : '';
|
|
289
|
+
const dateDisplay = time ? `${date} ${time}` : date;
|
|
287
290
|
const score = entry.summary?.score ?? '?';
|
|
288
291
|
const passed = entry.summary?.passed ?? '?';
|
|
289
292
|
const total = entry.summary?.checkCount ?? '?';
|
|
290
|
-
lines.push(` ${
|
|
293
|
+
lines.push(` ${dateDisplay} ${score}/100 (${passed}/${total} passing)`);
|
|
291
294
|
}
|
|
292
295
|
|
|
293
296
|
const comparison = compareLatest(dir);
|
package/src/audit.js
CHANGED
|
@@ -769,9 +769,10 @@ function inferSuggestedNextCommand(result) {
|
|
|
769
769
|
}
|
|
770
770
|
|
|
771
771
|
const actionKeys = new Set((result.topNextActions || []).map(item => item.key));
|
|
772
|
+
const platFlag = result.platform && result.platform !== 'claude' ? ` --platform ${result.platform}` : '';
|
|
772
773
|
|
|
773
774
|
if (result.failed === 0) {
|
|
774
|
-
return
|
|
775
|
+
return `npx nerviq${platFlag} augment`;
|
|
775
776
|
}
|
|
776
777
|
|
|
777
778
|
if (
|
|
@@ -781,14 +782,14 @@ function inferSuggestedNextCommand(result) {
|
|
|
781
782
|
actionKeys.has('settingsPermissions') ||
|
|
782
783
|
actionKeys.has('permissionDeny')
|
|
783
784
|
) {
|
|
784
|
-
return
|
|
785
|
+
return `npx nerviq${platFlag} setup`;
|
|
785
786
|
}
|
|
786
787
|
|
|
787
788
|
if (result.score < 80) {
|
|
788
|
-
return
|
|
789
|
+
return `npx nerviq${platFlag} suggest-only`;
|
|
789
790
|
}
|
|
790
791
|
|
|
791
|
-
return
|
|
792
|
+
return `npx nerviq${platFlag} augment`;
|
|
792
793
|
}
|
|
793
794
|
|
|
794
795
|
function getPlatformScopeNote(spec, ctx) {
|
|
@@ -876,7 +877,11 @@ function printLiteAudit(result, dir) {
|
|
|
876
877
|
console.log(colorize(' ═══════════════════════════════════════', 'dim'));
|
|
877
878
|
console.log(colorize(` Scanning: ${dir}`, 'dim'));
|
|
878
879
|
console.log('');
|
|
879
|
-
|
|
880
|
+
if (result.detectedConfigFiles && result.detectedConfigFiles.length > 0) {
|
|
881
|
+
console.log(colorize(` Found: ${result.detectedConfigFiles.join(', ')}`, 'dim'));
|
|
882
|
+
}
|
|
883
|
+
console.log('');
|
|
884
|
+
console.log(` Score: ${colorize(`${result.score}/100`, 'bold')} (${result.passed}/${result.passed + result.failed} checks passing)`);
|
|
880
885
|
if (result.platformScopeNote) {
|
|
881
886
|
console.log(colorize(` Scope: ${result.platformScopeNote.message}`, 'dim'));
|
|
882
887
|
}
|
|
@@ -1128,6 +1133,22 @@ async function audit(options) {
|
|
|
1128
1133
|
platformCaveats,
|
|
1129
1134
|
recommendedDomainPacks,
|
|
1130
1135
|
};
|
|
1136
|
+
// Detect which AI config files are present
|
|
1137
|
+
const configFiles = [];
|
|
1138
|
+
const configChecks = [
|
|
1139
|
+
['CLAUDE.md', 'CLAUDE.md'], ['.claude/settings.json', '.claude/settings.json'],
|
|
1140
|
+
['AGENTS.md', 'AGENTS.md'], ['.cursorrules', '.cursorrules'],
|
|
1141
|
+
['.cursor/rules', '.cursor/rules/'], ['GEMINI.md', 'GEMINI.md'],
|
|
1142
|
+
['.windsurfrules', '.windsurfrules'], ['.aider.conf.yml', '.aider.conf.yml'],
|
|
1143
|
+
['opencode.json', 'opencode.json'], ['.mcp.json', '.mcp.json'],
|
|
1144
|
+
];
|
|
1145
|
+
for (const [file, label] of configChecks) {
|
|
1146
|
+
try {
|
|
1147
|
+
if (require('fs').existsSync(require('path').join(options.dir, file))) configFiles.push(label);
|
|
1148
|
+
} catch {}
|
|
1149
|
+
}
|
|
1150
|
+
result.detectedConfigFiles = configFiles;
|
|
1151
|
+
|
|
1131
1152
|
result.suggestedNextCommand = inferSuggestedNextCommand(result);
|
|
1132
1153
|
result.liteSummary = {
|
|
1133
1154
|
topNextActions: topNextActions.slice(0, 3),
|
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):
|
|
@@ -1242,7 +1242,10 @@ async function setup(options) {
|
|
|
1242
1242
|
log(` \x1b[2m ${skipped} files already exist and were preserved.\x1b[0m`);
|
|
1243
1243
|
log(' \x1b[2m We never overwrite your existing config — your setup is kept.\x1b[0m');
|
|
1244
1244
|
} else if (created > 0) {
|
|
1245
|
-
log(` \x1b[1m${created} files created
|
|
1245
|
+
log(` \x1b[1m${created} files created:\x1b[0m`);
|
|
1246
|
+
for (const f of writtenFiles) {
|
|
1247
|
+
log(` \x1b[32m + ${f}\x1b[0m`);
|
|
1248
|
+
}
|
|
1246
1249
|
if (skipped > 0) {
|
|
1247
1250
|
log(` \x1b[2m${skipped} existing files preserved (not overwritten).\x1b[0m`);
|
|
1248
1251
|
}
|