@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.
- package/bin/cli.js +71 -3
- package/package.json +1 -1
- 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
|
|
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.
|
|
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 };
|