@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 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
- const output = JSON.stringify(rules, null, 2);
1041
- if (options.out) {
1042
- require('fs').writeFileSync(options.out, output, 'utf8');
1043
- if (!options.json) {
1044
- console.log(`\n Rules exported to ${options.out} (${rules.totalRules} rules)\n`);
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
- console.log(output);
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",
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 date = entry.createdAt?.split('T')[0] || 'unknown';
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(` ${date} ${score}/100 (${passed}/${total} passing)`);
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 'npx nerviq augment';
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 'npx nerviq setup';
785
+ return `npx nerviq${platFlag} setup`;
785
786
  }
786
787
 
787
788
  if (result.score < 80) {
788
- return 'npx nerviq suggest-only';
789
+ return `npx nerviq${platFlag} suggest-only`;
789
790
  }
790
791
 
791
- return 'npx nerviq augment';
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
- console.log(` Score: ${colorize(`${result.score}/100`, 'bold')}`);
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
- console.log(' Domain Packs');
437
- if ((summary.domainPacks || []).length === 0) {
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
- for (const pack of summary.domainPacks || []) {
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(' MCP Packs');
446
- if ((summary.mcpPacks || []).length === 0) {
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
- for (const pack of summary.mcpPacks || []) {
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.\x1b[0m`);
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
  }