@nerviq/cli 1.8.0 → 1.8.1

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.
Files changed (3) hide show
  1. package/bin/cli.js +71 -3
  2. package/package.json +1 -1
  3. package/src/init.js +184 -0
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', 'rollback', 'governance', 'benchmark', 'deep-review', 'interactive', 'watch', 'badge', 'insights', 'history', 'compare', 'trend', 'scan', 'feedback', 'doctor', 'convert', 'migrate', 'catalog', 'certify', 'serve', 'check-health', 'dashboard', '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', 'init', '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', 'dashboard', '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));
@@ -330,6 +330,7 @@ const HELP = `
330
330
  nerviq fix <key> Auto-fix a specific check (with score impact)
331
331
  nerviq fix --all-critical Fix all critical issues at once
332
332
  nerviq fix --dry-run Preview fixes without writing
333
+ nerviq fix --auto Apply fixes without confirmation prompt
333
334
  nerviq rollback Undo the most recent apply (delete created files)
334
335
  nerviq rollback --list Show available rollback points
335
336
  nerviq rollback --dry-run Preview what would be deleted
@@ -1283,9 +1284,10 @@ async function main() {
1283
1284
  console.log(output);
1284
1285
  process.exit(0);
1285
1286
  } else if (normalizedCommand === 'fix') {
1286
- // nerviq fix [key] [--all-critical] [--dry-run]
1287
+ // nerviq fix [key] [--all-critical] [--dry-run] [--auto]
1287
1288
  const fixKey = parsed.extraArgs[0] || null;
1288
1289
  const allCritical = flags.includes('--all-critical');
1290
+ const autoApply = options.auto || options.dryRun;
1289
1291
 
1290
1292
  // Step 1: Run silent audit to find failed checks (only actual failures, not skipped/null)
1291
1293
  const auditResult = await audit({ dir: options.dir, silent: true, platform: options.platform });
@@ -1388,8 +1390,70 @@ async function main() {
1388
1390
  process.exit(0);
1389
1391
  }
1390
1392
 
1391
- // Step 3: For each target, either use template, inline fix, or show manual instructions
1393
+ // Step 2.5: Predict impact and show preview before applying
1394
+ const IMPACT_WEIGHTS = { critical: 15, high: 10, medium: 5, low: 2 };
1392
1395
  const preScore = auditResult.score;
1396
+ const applicableResults = (auditResult.results || []).filter(r => r.passed !== null);
1397
+ const maxScore = applicableResults.reduce((sum, r) => sum + (IMPACT_WEIGHTS[r.impact] || 5), 0);
1398
+
1399
+ // Compute predicted score by simulating target fixes as passing
1400
+ const targetKeySet = new Set(targetKeys);
1401
+ const INLINE_FIX_KEYS = new Set(Object.keys(INLINE_FIXERS));
1402
+ const fixableTargets = targetKeys.filter(k => {
1403
+ const tech = TECHNIQUES[k];
1404
+ return (tech && tech.template) || INLINE_FIX_KEYS.has(k);
1405
+ });
1406
+ const fixableTargetSet = new Set(fixableTargets);
1407
+ const simulatedEarned = applicableResults.reduce((sum, r) => {
1408
+ const w = IMPACT_WEIGHTS[r.impact] || 5;
1409
+ if (r.passed) return sum + w;
1410
+ if (fixableTargetSet.has(r.key)) return sum + w;
1411
+ return sum;
1412
+ }, 0);
1413
+ const predictedScore = maxScore > 0 ? Math.round((simulatedEarned / maxScore) * 100) : 0;
1414
+ const predictedDelta = predictedScore - preScore;
1415
+
1416
+ if (!autoApply) {
1417
+ console.log('');
1418
+ if (allCritical && fixableTargets.length > 1) {
1419
+ // Multi-fix summary
1420
+ console.log(` ${fixableTargets.length} critical fixes available:`);
1421
+ let runningEarned = applicableResults.reduce((s, r) => s + (r.passed ? (IMPACT_WEIGHTS[r.impact] || 5) : 0), 0);
1422
+ let runningScore = maxScore > 0 ? Math.round((runningEarned / maxScore) * 100) : 0;
1423
+ fixableTargets.forEach((k, idx) => {
1424
+ const r = failedResults.find(fr => fr.key === k);
1425
+ const w = IMPACT_WEIGHTS[r.impact] || 5;
1426
+ const nextEarned = runningEarned + w;
1427
+ const nextScore = maxScore > 0 ? Math.round((nextEarned / maxScore) * 100) : 0;
1428
+ const d = nextScore - runningScore;
1429
+ console.log(` ${idx + 1}. ${(r.key).padEnd(18)} ${runningScore} → ${nextScore} (+${d})`);
1430
+ runningEarned = nextEarned;
1431
+ runningScore = nextScore;
1432
+ });
1433
+ console.log('');
1434
+ console.log(` Total: ${preScore} → ${predictedScore} (+${predictedDelta})`);
1435
+ } else {
1436
+ // Single fix preview
1437
+ const targetCheck = failedResults.find(r => r.key === fixableTargets[0]) || failedResults.find(r => r.key === targetKeys[0]);
1438
+ if (targetCheck) {
1439
+ console.log(` Predicted impact: ${preScore} → ${predictedScore} (+${predictedDelta})`);
1440
+ }
1441
+ }
1442
+
1443
+ // Prompt for confirmation
1444
+ const readline = require('readline');
1445
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
1446
+ const answer = await new Promise(resolve => {
1447
+ rl.question(' Apply? (Y/n) ', resolve);
1448
+ });
1449
+ rl.close();
1450
+ if (answer && answer.trim().toLowerCase() === 'n') {
1451
+ console.log('\n Aborted.\n');
1452
+ process.exit(0);
1453
+ }
1454
+ }
1455
+
1456
+ // Step 3: For each target, either use template, inline fix, or show manual instructions
1393
1457
  let fixed = 0;
1394
1458
  let manual = 0;
1395
1459
 
@@ -1437,6 +1501,10 @@ async function main() {
1437
1501
 
1438
1502
  console.log(`\n ${fixed} fixed, ${manual} need manual action.\n`);
1439
1503
 
1504
+ } else if (normalizedCommand === 'init') {
1505
+ const { runInit } = require('../src/init');
1506
+ await runInit(options.dir, flags);
1507
+ process.exit(0);
1440
1508
  } else if (normalizedCommand === 'setup') {
1441
1509
  await setup(options);
1442
1510
  if (options.snapshot) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nerviq/cli",
3
- "version": "1.8.0",
3
+ "version": "1.8.1",
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/init.js ADDED
@@ -0,0 +1,184 @@
1
+ 'use strict';
2
+
3
+ const readline = require('readline');
4
+ const { detectPlatforms } = require('./public-api');
5
+ const { audit } = require('./audit');
6
+ const { setup } = require('./setup');
7
+ const { ProjectContext } = require('./context');
8
+ const { STACKS } = require('./techniques');
9
+
10
+ const PLATFORM_LABELS = {
11
+ claude: 'Claude Code',
12
+ codex: 'Codex',
13
+ gemini: 'Gemini CLI',
14
+ copilot: 'GitHub Copilot',
15
+ cursor: 'Cursor',
16
+ windsurf: 'Windsurf',
17
+ aider: 'Aider',
18
+ opencode: 'OpenCode',
19
+ };
20
+
21
+ const ALL_PLATFORMS = Object.keys(PLATFORM_LABELS);
22
+
23
+ const TEAM_SIZES = ['solo', 'small', 'team', 'enterprise'];
24
+ const TEAM_LABELS = {
25
+ solo: 'Solo developer',
26
+ small: 'Small team (2-5)',
27
+ team: 'Team (6-20)',
28
+ enterprise: 'Enterprise (20+)',
29
+ };
30
+
31
+ function prompt(rl, question) {
32
+ return new Promise((resolve) => {
33
+ rl.question(question, (answer) => resolve(answer.trim()));
34
+ });
35
+ }
36
+
37
+ function detectStacks(dir) {
38
+ const ctx = new ProjectContext(dir);
39
+ return ctx.detectStacks(STACKS);
40
+ }
41
+
42
+ function labelPlatforms(platforms) {
43
+ return platforms.map((p) => PLATFORM_LABELS[p] || p).join(', ');
44
+ }
45
+
46
+ function parsePlatforms(input) {
47
+ return input
48
+ .split(/[,\s]+/)
49
+ .map((s) => s.trim().toLowerCase())
50
+ .filter((s) => ALL_PLATFORMS.includes(s));
51
+ }
52
+
53
+ async function runInit(dir, flags) {
54
+ const rl = readline.createInterface({
55
+ input: process.stdin,
56
+ output: process.stdout,
57
+ });
58
+
59
+ const dim = '\x1b[2m';
60
+ const bold = '\x1b[1m';
61
+ const cyan = '\x1b[36m';
62
+ const green = '\x1b[32m';
63
+ const reset = '\x1b[0m';
64
+
65
+ console.log('');
66
+ console.log(`${bold} Welcome to Nerviq${reset} — let's set up your AI coding agent governance.`);
67
+ console.log('');
68
+
69
+ // --- Question 1: Platforms ---
70
+ const detected = detectPlatforms(dir);
71
+ const detectedLabel = detected.length > 0
72
+ ? `${dim}[auto-detected: ${labelPlatforms(detected)}]${reset}`
73
+ : `${dim}[no platforms detected]${reset}`;
74
+ console.log(` ${bold}1.${reset} Which platform(s) do you use?`);
75
+ console.log(` ${detectedLabel}`);
76
+ const platformInput = await prompt(
77
+ rl,
78
+ ` ${dim}> Press Enter to confirm, or type: ${ALL_PLATFORMS.join(',')}${reset}\n > `,
79
+ );
80
+ const platforms = platformInput === ''
81
+ ? (detected.length > 0 ? detected : ['claude'])
82
+ : parsePlatforms(platformInput);
83
+ if (platforms.length === 0) platforms.push('claude');
84
+
85
+ console.log('');
86
+
87
+ // --- Question 2: Stack ---
88
+ const stacks = detectStacks(dir);
89
+ const stackLabels = stacks.map((s) => s.label);
90
+ const stackDetectedLabel = stackLabels.length > 0
91
+ ? `${dim}[auto-detected: ${stackLabels.join(', ')}]${reset}`
92
+ : `${dim}[no stack detected]${reset}`;
93
+ console.log(` ${bold}2.${reset} What's your primary stack?`);
94
+ console.log(` ${stackDetectedLabel}`);
95
+ const stackInput = await prompt(
96
+ rl,
97
+ ` ${dim}> Press Enter to confirm, or type your stack${reset}\n > `,
98
+ );
99
+ const stackDisplay = stackInput === ''
100
+ ? (stackLabels.length > 0 ? stackLabels.join(', ') : 'General')
101
+ : stackInput;
102
+
103
+ console.log('');
104
+
105
+ // --- Question 3: Team size ---
106
+ console.log(` ${bold}3.${reset} What's your team size?`);
107
+ const teamInput = await prompt(
108
+ rl,
109
+ ` ${dim}> solo / small (2-5) / team (6-20) / enterprise (20+)${reset}\n > `,
110
+ );
111
+ const teamKey = TEAM_SIZES.find((t) => teamInput.toLowerCase().startsWith(t)) || 'solo';
112
+ const teamLabel = TEAM_LABELS[teamKey];
113
+
114
+ rl.close();
115
+
116
+ console.log('');
117
+ console.log(` ${cyan}Setting up for: ${labelPlatforms(platforms)} | ${stackDisplay} | ${teamLabel}${reset}`);
118
+ console.log('');
119
+
120
+ // --- Run audit (before) ---
121
+ const primaryPlatform = platforms[0];
122
+ console.log(` ${dim}Running audit...${reset}`);
123
+ const preResult = await audit({ dir, silent: true, platform: primaryPlatform });
124
+ const preScore = preResult.score;
125
+ console.log(` Score: ${bold}${preScore}/100${reset}`);
126
+ console.log('');
127
+
128
+ // --- Run setup ---
129
+ console.log(` ${dim}Running setup...${reset}`);
130
+ const setupResult = await setup({
131
+ dir,
132
+ platform: primaryPlatform,
133
+ silent: true,
134
+ profile: 'safe-write',
135
+ mcpPacks: [],
136
+ });
137
+
138
+ for (const f of setupResult.writtenFiles) {
139
+ console.log(` ${green}✅${reset} Created ${f}`);
140
+ }
141
+ for (const f of setupResult.preservedFiles) {
142
+ console.log(` ${dim}⏭️ Kept ${f} (already exists)${reset}`);
143
+ }
144
+
145
+ // --- Run additional platform setups ---
146
+ for (const plat of platforms.slice(1)) {
147
+ try {
148
+ const extraResult = await setup({
149
+ dir,
150
+ platform: plat,
151
+ silent: true,
152
+ profile: 'safe-write',
153
+ mcpPacks: [],
154
+ });
155
+ for (const f of extraResult.writtenFiles) {
156
+ console.log(` ${green}✅${reset} Created ${f}`);
157
+ }
158
+ } catch {
159
+ // Platform setup not available, skip
160
+ }
161
+ }
162
+
163
+ // --- Run audit (after) ---
164
+ const postResult = await audit({ dir, silent: true, platform: primaryPlatform });
165
+ const postScore = postResult.score;
166
+ const delta = postScore - preScore;
167
+
168
+ if (delta > 0) {
169
+ console.log(` Score: ${bold}${postScore}/100${reset} (${green}+${delta}${reset})`);
170
+ } else {
171
+ console.log(` Score: ${bold}${postScore}/100${reset}`);
172
+ }
173
+
174
+ console.log('');
175
+ console.log(` ${bold}Next steps:${reset}`);
176
+ console.log(` - Review: ${cyan}nerviq audit --full${reset}`);
177
+ if (platforms.length > 1) {
178
+ console.log(` - Cross-platform: ${cyan}nerviq harmony-audit${reset}`);
179
+ }
180
+ console.log(` - Customize: ${cyan}nerviq augment${reset}`);
181
+ console.log('');
182
+ }
183
+
184
+ module.exports = { runInit };