@nerviq/cli 1.17.2 → 1.18.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/README.md +3 -3
- package/bin/cli.js +140 -344
- package/package.json +60 -60
- package/src/codex/techniques.js +26 -7
- package/src/copilot/config-parser.js +280 -226
- package/src/copilot/context.js +218 -197
- package/src/copilot/techniques.js +219 -78
- package/src/fix-engine.js +783 -0
package/README.md
CHANGED
|
@@ -119,7 +119,7 @@ npx @nerviq/cli --beginner
|
|
|
119
119
|
| Tools & MCP | ~40 | .mcp.json, multi-server, Context7 |
|
|
120
120
|
| Governance & Compliance | ~30 | permission profiles, audit trails |
|
|
121
121
|
| DevOps & Infrastructure | ~30 | Docker, CI, Terraform, monitoring |
|
|
122
|
-
| Cross-Platform Intelligence | ~25 | harmony, synergy, drift detection |
|
|
122
|
+
| Cross-Platform Intelligence | ~25 | harmony (GA), synergy (Experimental), drift detection |
|
|
123
123
|
| Enterprise & Freshness | ~20 | freshness tracking, deprecation, SBOM |
|
|
124
124
|
| Memory & Context | ~15 | context management, compaction, @path |
|
|
125
125
|
| Prompting & Design | ~10 | XML tags, constraints, frontend patterns |
|
|
@@ -223,7 +223,7 @@ All successful operational responses are wrapped in a JSON envelope:
|
|
|
223
223
|
{
|
|
224
224
|
"data": {},
|
|
225
225
|
"meta": {
|
|
226
|
-
"version": "1.
|
|
226
|
+
"version": "1.18.0",
|
|
227
227
|
"timestamp": "2026-04-12T12:00:00.000Z"
|
|
228
228
|
}
|
|
229
229
|
}
|
|
@@ -353,7 +353,7 @@ Levels:
|
|
|
353
353
|
| `nerviq harmony-watch` | Live drift monitoring |
|
|
354
354
|
| `nerviq harmony-governance` | Unified platform governance |
|
|
355
355
|
| `nerviq harmony-add <platform>` | Add a new platform to your project |
|
|
356
|
-
| `nerviq synergy-report` | Multi-agent synergy analysis |
|
|
356
|
+
| `nerviq synergy-report` | Multi-agent synergy analysis (Experimental) |
|
|
357
357
|
| `nerviq catalog` | Show check catalog for all 8 platforms |
|
|
358
358
|
| `nerviq doctor` | Self-diagnostics for install health, freshness, platform detection, declared MCP servers, and hook runtime |
|
|
359
359
|
| `nerviq convert` | Convert config between platforms |
|
package/bin/cli.js
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
// macOS pipe-flush guard: when stdout is a pipe, Node defaults to
|
|
2
|
+
// non-blocking writes. `console.log(...) + process.exit(N)` then drops
|
|
3
|
+
// the trailing write (empty stdout on macOS Node 18, truncation at the
|
|
4
|
+
// 8192-byte pipe buffer on macOS Node 20). Flipping stdout+stderr to
|
|
5
|
+
// blocking mode at the *very start* — before any writes queue up —
|
|
6
|
+
// makes every subsequent console.log a synchronous syscall, so the
|
|
7
|
+
// data is in the kernel before we ever reach process.exit. Runs before
|
|
8
|
+
// require() so it covers warnings from Node's module loader too.
|
|
9
|
+
try { if (process.stdout?._handle?.setBlocking) process.stdout._handle.setBlocking(true); } catch {}
|
|
10
|
+
try { if (process.stderr?._handle?.setBlocking) process.stderr._handle.setBlocking(true); } catch {}
|
|
2
11
|
|
|
3
12
|
const { audit, detectPlatforms, getCatalog } = require('../src/public-api');
|
|
4
13
|
const { setup } = require('../src/setup');
|
|
@@ -567,10 +576,12 @@ const HELP = `
|
|
|
567
576
|
Audit, align, and amplify every platform on every project.
|
|
568
577
|
New here? Run: nerviq --beginner
|
|
569
578
|
|
|
570
|
-
DISCOVER
|
|
571
|
-
nerviq audit Quick scan: score + top 3 gaps (Harmony-first when 2+ platforms detected)
|
|
572
|
-
nerviq audit --
|
|
573
|
-
nerviq audit --
|
|
579
|
+
DISCOVER
|
|
580
|
+
nerviq audit Quick scan: score + top 3 gaps (Harmony-first when 2+ platforms detected)
|
|
581
|
+
nerviq audit --fix Audit, apply fixable critical fixes, then re-audit
|
|
582
|
+
nerviq audit --fix --dry-run Show proposed autofix diff without writing
|
|
583
|
+
nerviq audit --no-harmony-first Skip the cross-platform Harmony header
|
|
584
|
+
nerviq audit --full Full audit with all checks, weakest areas, badge
|
|
574
585
|
nerviq audit --platform X Audit specific platform (claude|codex|cursor|copilot|gemini|windsurf|aider|opencode)
|
|
575
586
|
nerviq audit --json Machine-readable JSON output (for CI)
|
|
576
587
|
nerviq audit --workspace packages/* Audit monorepo workspaces with stack-specific package profiles
|
|
@@ -591,9 +602,9 @@ const HELP = `
|
|
|
591
602
|
nerviq check-health Detect regressions + platform format changes between snapshots
|
|
592
603
|
nerviq doctor Self-diagnostics: Node, deps, freshness, MCP, hook runtime
|
|
593
604
|
|
|
594
|
-
FIX
|
|
595
|
-
nerviq fix Show fixable checks and manual-fix guidance
|
|
596
|
-
nerviq fix <key> Auto-fix a specific check (with score impact)
|
|
605
|
+
FIX
|
|
606
|
+
nerviq fix Show fixable checks and manual-fix guidance
|
|
607
|
+
nerviq fix <key> Auto-fix a specific check (with score impact)
|
|
597
608
|
nerviq fix <key> --prompt Show AI agent prompt for a check (no auto-fix)
|
|
598
609
|
nerviq fix --all-critical Fix all critical issues at once
|
|
599
610
|
nerviq fix --dry-run Preview fixes without writing
|
|
@@ -764,12 +775,12 @@ const BEGINNER_HELP = `
|
|
|
764
775
|
nerviq augment Show an improvement plan without writing
|
|
765
776
|
nerviq doctor Check install health, freshness, platform detection, MCP, and hook runtime
|
|
766
777
|
|
|
767
|
-
SIMPLE PATH
|
|
768
|
-
1. nerviq audit
|
|
769
|
-
2. nerviq setup --auto
|
|
770
|
-
3. nerviq
|
|
771
|
-
4. nerviq augment
|
|
772
|
-
5. nerviq doctor
|
|
778
|
+
SIMPLE PATH
|
|
779
|
+
1. nerviq audit
|
|
780
|
+
2. nerviq setup --auto
|
|
781
|
+
3. nerviq audit --fix --auto
|
|
782
|
+
4. nerviq augment
|
|
783
|
+
5. nerviq doctor
|
|
773
784
|
|
|
774
785
|
WHEN YOU ARE READY
|
|
775
786
|
nerviq --help Show the full command set
|
|
@@ -2084,73 +2095,34 @@ async function main() {
|
|
|
2084
2095
|
});
|
|
2085
2096
|
console.log(output);
|
|
2086
2097
|
process.exit(0);
|
|
2087
|
-
} else if (normalizedCommand === 'fix') {
|
|
2088
|
-
// nerviq fix [key] [--all-critical] [--dry-run] [--auto] [--prompt]
|
|
2089
|
-
const fixKey = parsed.extraArgs[0] || null;
|
|
2090
|
-
const allCritical = flags.includes('--all-critical');
|
|
2091
|
-
const promptOnly = flags.includes('--prompt');
|
|
2092
|
-
const autoApply = options.auto;
|
|
2093
|
-
const isDryRun = options.dryRun;
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
const
|
|
2098
|
+
} else if (normalizedCommand === 'fix') {
|
|
2099
|
+
// nerviq fix [key] [--all-critical] [--dry-run] [--auto] [--prompt]
|
|
2100
|
+
const fixKey = parsed.extraArgs[0] || null;
|
|
2101
|
+
const allCritical = flags.includes('--all-critical');
|
|
2102
|
+
const promptOnly = flags.includes('--prompt');
|
|
2103
|
+
const autoApply = options.auto;
|
|
2104
|
+
const isDryRun = options.dryRun;
|
|
2105
|
+
const { getFixableFailedResults, isFixableKey, applyFixes } = require('../src/fix-engine');
|
|
2106
|
+
|
|
2107
|
+
// Step 1: Run silent audit to find failed checks (only actual failures, not skipped/null)
|
|
2108
|
+
const auditResult = await audit({ dir: options.dir, silent: true, platform: options.platform });
|
|
2109
|
+
const failedResults = (auditResult.results || []).filter(r => r.passed === false);
|
|
2098
2110
|
|
|
2099
2111
|
if (failedResults.length === 0) {
|
|
2100
2112
|
console.log('\n ✅ All checks passing — nothing to fix.\n');
|
|
2101
2113
|
process.exit(0);
|
|
2102
2114
|
}
|
|
2103
2115
|
|
|
2104
|
-
// Step 2: Determine which checks to fix
|
|
2105
|
-
const {
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
const
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
const
|
|
2114
|
-
const existing = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, 'utf8') : '';
|
|
2115
|
-
if (!existing.includes('.env')) {
|
|
2116
|
-
const lines = existing.endsWith('\n') || existing === '' ? '' : '\n';
|
|
2117
|
-
fs.appendFileSync(gitignorePath, `${lines}.env\n.env.*\n`, 'utf8');
|
|
2118
|
-
return true;
|
|
2119
|
-
}
|
|
2120
|
-
return false;
|
|
2121
|
-
},
|
|
2122
|
-
secretsProtection: (dir) => {
|
|
2123
|
-
const settingsPath = pathMod.join(dir, '.claude', 'settings.json');
|
|
2124
|
-
const settingsDir = pathMod.join(dir, '.claude');
|
|
2125
|
-
if (!fs.existsSync(settingsDir)) fs.mkdirSync(settingsDir, { recursive: true });
|
|
2126
|
-
let settings = {};
|
|
2127
|
-
if (fs.existsSync(settingsPath)) {
|
|
2128
|
-
try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); } catch { settings = {}; }
|
|
2129
|
-
}
|
|
2130
|
-
if (!settings.permissions) settings.permissions = {};
|
|
2131
|
-
if (!settings.permissions.deny) settings.permissions.deny = [];
|
|
2132
|
-
const denyEntries = ['.env', '.env.*', '**/.env', '**/*.pem', '**/secrets/**'];
|
|
2133
|
-
for (const entry of denyEntries) {
|
|
2134
|
-
if (!settings.permissions.deny.includes(entry)) settings.permissions.deny.push(entry);
|
|
2135
|
-
}
|
|
2136
|
-
// Remove overly broad allow:["*"] if present
|
|
2137
|
-
if (Array.isArray(settings.permissions.allow) && settings.permissions.allow.includes('*')) {
|
|
2138
|
-
settings.permissions.allow = settings.permissions.allow.filter(a => a !== '*');
|
|
2139
|
-
if (settings.permissions.allow.length === 0) {
|
|
2140
|
-
delete settings.permissions.allow;
|
|
2141
|
-
}
|
|
2142
|
-
}
|
|
2143
|
-
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
|
|
2144
|
-
return true;
|
|
2145
|
-
},
|
|
2146
|
-
};
|
|
2147
|
-
|
|
2148
|
-
let targetKeys = [];
|
|
2149
|
-
|
|
2150
|
-
if (fixKey) {
|
|
2151
|
-
// Fix a specific check
|
|
2152
|
-
if (!failedResults.find(r => r.key === fixKey)) {
|
|
2153
|
-
const passed = (auditResult.results || []).find(r => r.key === fixKey && r.passed);
|
|
2116
|
+
// Step 2: Determine which checks to fix
|
|
2117
|
+
const { FIX_PROMPTS, formatFixPrompt } = require('../src/fix-prompts');
|
|
2118
|
+
|
|
2119
|
+
let targetKeys = [];
|
|
2120
|
+
const fixableFailedResults = getFixableFailedResults(failedResults, { mode: 'fix' });
|
|
2121
|
+
|
|
2122
|
+
if (fixKey) {
|
|
2123
|
+
// Fix a specific check
|
|
2124
|
+
if (!failedResults.find(r => r.key === fixKey)) {
|
|
2125
|
+
const passed = (auditResult.results || []).find(r => r.key === fixKey && r.passed);
|
|
2154
2126
|
if (passed) {
|
|
2155
2127
|
console.log(`\n ✅ '${fixKey}' is already passing.\n`);
|
|
2156
2128
|
} else {
|
|
@@ -2168,26 +2140,37 @@ async function main() {
|
|
|
2168
2140
|
const failedCheck = failedResults.find(r => r.key === fixKey);
|
|
2169
2141
|
console.log(`\n No AI prompt available for '${fixKey}'.`);
|
|
2170
2142
|
console.log(` Manual fix: ${failedCheck ? failedCheck.fix : 'See nerviq audit --full.'}\n`);
|
|
2171
|
-
}
|
|
2172
|
-
process.exit(0);
|
|
2173
|
-
}
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
if (
|
|
2189
|
-
console.log(
|
|
2190
|
-
|
|
2143
|
+
}
|
|
2144
|
+
process.exit(0);
|
|
2145
|
+
}
|
|
2146
|
+
if (!isFixableKey(fixKey, { mode: 'fix' })) {
|
|
2147
|
+
const aiPrompt = FIX_PROMPTS[fixKey];
|
|
2148
|
+
const failedCheck = failedResults.find(r => r.key === fixKey);
|
|
2149
|
+
if (aiPrompt) {
|
|
2150
|
+
console.log(formatFixPrompt(fixKey, aiPrompt));
|
|
2151
|
+
} else {
|
|
2152
|
+
console.log(`\n 📋 ${failedCheck.name} (manual fix needed)`);
|
|
2153
|
+
console.log(` ${failedCheck.fix}\n`);
|
|
2154
|
+
}
|
|
2155
|
+
process.exit(0);
|
|
2156
|
+
}
|
|
2157
|
+
targetKeys = [fixKey];
|
|
2158
|
+
} else if (allCritical) {
|
|
2159
|
+
targetKeys = getFixableFailedResults(failedResults, { mode: 'fix', criticalOnly: true }).map(r => r.key);
|
|
2160
|
+
if (targetKeys.length === 0) {
|
|
2161
|
+
console.log('\n ✅ No critical issues found.\n');
|
|
2162
|
+
process.exit(0);
|
|
2163
|
+
}
|
|
2164
|
+
} else {
|
|
2165
|
+
// No key specified — show fixable checks and exit
|
|
2166
|
+
const fixable = fixableFailedResults;
|
|
2167
|
+
const fixableKeySet = new Set(fixable.map(r => r.key));
|
|
2168
|
+
const nonFixable = failedResults.filter(r => !fixableKeySet.has(r.key));
|
|
2169
|
+
console.log('');
|
|
2170
|
+
console.log(` nerviq fix — ${failedResults.length} failed checks\n`);
|
|
2171
|
+
if (fixable.length > 0) {
|
|
2172
|
+
console.log(` Auto-fixable (${fixable.length}):`);
|
|
2173
|
+
for (const r of fixable) {
|
|
2191
2174
|
const tier = r.impact === 'critical' ? '🔴' : r.impact === 'high' ? '🟡' : '🔵';
|
|
2192
2175
|
console.log(` ${tier} nerviq fix ${r.key}`);
|
|
2193
2176
|
}
|
|
@@ -2224,241 +2207,23 @@ async function main() {
|
|
|
2224
2207
|
console.log(` nerviq fix ${fixable[0].key} Fix the first auto-fixable check`);
|
|
2225
2208
|
console.log(` nerviq fix --all-critical Fix all critical issues at once`);
|
|
2226
2209
|
}
|
|
2227
|
-
console.log('');
|
|
2228
|
-
process.exit(0);
|
|
2229
|
-
}
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
const fixableTargetSet = new Set(fixableTargets);
|
|
2245
|
-
const simulatedEarned = applicableResults.reduce((sum, r) => {
|
|
2246
|
-
const w = IMPACT_WEIGHTS[r.impact] || 5;
|
|
2247
|
-
if (r.passed) return sum + w;
|
|
2248
|
-
if (fixableTargetSet.has(r.key)) return sum + w;
|
|
2249
|
-
return sum;
|
|
2250
|
-
}, 0);
|
|
2251
|
-
const predictedScore = maxScore > 0 ? Math.round((simulatedEarned / maxScore) * 100) : 0;
|
|
2252
|
-
const predictedDelta = predictedScore - preScore;
|
|
2253
|
-
|
|
2254
|
-
if (!autoApply && !isDryRun) {
|
|
2255
|
-
console.log('');
|
|
2256
|
-
if (allCritical && fixableTargets.length > 1) {
|
|
2257
|
-
// Multi-fix summary
|
|
2258
|
-
console.log(` ${fixableTargets.length} critical fixes available:`);
|
|
2259
|
-
let runningEarned = applicableResults.reduce((s, r) => s + (r.passed ? (IMPACT_WEIGHTS[r.impact] || 5) : 0), 0);
|
|
2260
|
-
let runningScore = maxScore > 0 ? Math.round((runningEarned / maxScore) * 100) : 0;
|
|
2261
|
-
fixableTargets.forEach((k, idx) => {
|
|
2262
|
-
const r = failedResults.find(fr => fr.key === k);
|
|
2263
|
-
const w = IMPACT_WEIGHTS[r.impact] || 5;
|
|
2264
|
-
const nextEarned = runningEarned + w;
|
|
2265
|
-
const nextScore = maxScore > 0 ? Math.round((nextEarned / maxScore) * 100) : 0;
|
|
2266
|
-
const d = nextScore - runningScore;
|
|
2267
|
-
console.log(` ${idx + 1}. ${(r.key).padEnd(18)} ${runningScore} → ${nextScore} (+${d})`);
|
|
2268
|
-
runningEarned = nextEarned;
|
|
2269
|
-
runningScore = nextScore;
|
|
2270
|
-
});
|
|
2271
|
-
console.log('');
|
|
2272
|
-
console.log(` Total: ${preScore} → ${predictedScore} (+${predictedDelta})`);
|
|
2273
|
-
} else {
|
|
2274
|
-
// Single fix preview
|
|
2275
|
-
const targetCheck = failedResults.find(r => r.key === fixableTargets[0]) || failedResults.find(r => r.key === targetKeys[0]);
|
|
2276
|
-
if (targetCheck) {
|
|
2277
|
-
console.log(` Predicted impact: ${preScore} → ${predictedScore} (+${predictedDelta})`);
|
|
2278
|
-
}
|
|
2279
|
-
}
|
|
2280
|
-
|
|
2281
|
-
// Prompt for confirmation
|
|
2282
|
-
const readline = require('readline');
|
|
2283
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
2284
|
-
const answer = await new Promise(resolve => {
|
|
2285
|
-
rl.question(' Apply? (Y/n) ', resolve);
|
|
2286
|
-
});
|
|
2287
|
-
rl.close();
|
|
2288
|
-
if (answer && answer.trim().toLowerCase() === 'n') {
|
|
2289
|
-
for (const key of targetKeys) {
|
|
2290
|
-
recordPattern(options.dir, key, 'rejected');
|
|
2291
|
-
}
|
|
2292
|
-
console.log('\n Aborted.\n');
|
|
2293
|
-
process.exit(0);
|
|
2294
|
-
}
|
|
2295
|
-
}
|
|
2296
|
-
|
|
2297
|
-
// Step 3: Create rollback snapshot before applying fixes
|
|
2298
|
-
const isBatch = allCritical && targetKeys.length > 1;
|
|
2299
|
-
let rollbackId = null;
|
|
2300
|
-
const allCreatedFiles = [];
|
|
2301
|
-
const fixResults = []; // { key, name, status, delta }
|
|
2302
|
-
|
|
2303
|
-
const snapshotFiles = {};
|
|
2304
|
-
if (!isDryRun && targetKeys.length > 0) {
|
|
2305
|
-
// Snapshot existing files for rollback (before applying fixes)
|
|
2306
|
-
for (const key of targetKeys) {
|
|
2307
|
-
const technique = TECHNIQUES[key];
|
|
2308
|
-
if (technique && technique.template && technique.template.path) {
|
|
2309
|
-
const tplPath = pathMod.join(options.dir, technique.template.path);
|
|
2310
|
-
if (fs.existsSync(tplPath)) {
|
|
2311
|
-
snapshotFiles[technique.template.path] = fs.readFileSync(tplPath, 'utf8');
|
|
2312
|
-
}
|
|
2313
|
-
}
|
|
2314
|
-
}
|
|
2315
|
-
}
|
|
2316
|
-
|
|
2317
|
-
// Step 3b: Apply fixes sequentially with progress
|
|
2318
|
-
let fixed = 0;
|
|
2319
|
-
let manual = 0;
|
|
2320
|
-
let runningScore = preScore;
|
|
2321
|
-
|
|
2322
|
-
for (let i = 0; i < targetKeys.length; i++) {
|
|
2323
|
-
const key = targetKeys[i];
|
|
2324
|
-
const technique = TECHNIQUES[key];
|
|
2325
|
-
const failedCheck = failedResults.find(r => r.key === key);
|
|
2326
|
-
const progress = isBatch ? `${i + 1}/${targetKeys.length}: ` : '';
|
|
2327
|
-
|
|
2328
|
-
if (technique && technique.template) {
|
|
2329
|
-
if (isDryRun) {
|
|
2330
|
-
console.log(` [dry-run] Would fix: ${progress}${failedCheck.name} (${key})`);
|
|
2331
|
-
fixResults.push({ key, name: failedCheck.name, status: 'dry-run', delta: 0 });
|
|
2332
|
-
fixed++;
|
|
2333
|
-
} else {
|
|
2334
|
-
try {
|
|
2335
|
-
if (isBatch) console.log(` Fixing ${progress}${key}...`);
|
|
2336
|
-
const setupResult = await setup({ ...options, only: [key], silent: true });
|
|
2337
|
-
if (setupResult && setupResult.writtenFiles) {
|
|
2338
|
-
allCreatedFiles.push(...setupResult.writtenFiles);
|
|
2339
|
-
}
|
|
2340
|
-
const midResult = await audit({ dir: options.dir, silent: true, platform: options.platform });
|
|
2341
|
-
const delta = midResult.score - runningScore;
|
|
2342
|
-
fixResults.push({ key, name: failedCheck.name, status: 'fixed', delta });
|
|
2343
|
-
runningScore = midResult.score;
|
|
2344
|
-
if (!isBatch) console.log(` ✅ Fixed: ${failedCheck.name}`);
|
|
2345
|
-
fixed++;
|
|
2346
|
-
} catch (err) {
|
|
2347
|
-
fixResults.push({ key, name: failedCheck.name, status: 'failed', delta: 0 });
|
|
2348
|
-
if (isBatch) {
|
|
2349
|
-
console.log(` ❌ Failed: ${key} — ${err.message}`);
|
|
2350
|
-
console.log(` Stopping batch. ${fixed} fixes applied so far.`);
|
|
2351
|
-
console.log(` Rollback: nerviq rollback --id ${rollbackId}`);
|
|
2352
|
-
break;
|
|
2353
|
-
} else {
|
|
2354
|
-
console.log(` ❌ Failed: ${failedCheck.name} — ${err.message}`);
|
|
2355
|
-
}
|
|
2356
|
-
}
|
|
2357
|
-
}
|
|
2358
|
-
} else if (INLINE_FIXERS[key]) {
|
|
2359
|
-
if (isDryRun) {
|
|
2360
|
-
console.log(` [dry-run] Would fix: ${progress}${failedCheck.name} (${key})`);
|
|
2361
|
-
fixResults.push({ key, name: failedCheck.name, status: 'dry-run', delta: 0 });
|
|
2362
|
-
fixed++;
|
|
2363
|
-
} else {
|
|
2364
|
-
try {
|
|
2365
|
-
if (isBatch) console.log(` Fixing ${progress}${key}...`);
|
|
2366
|
-
const didFix = INLINE_FIXERS[key](options.dir);
|
|
2367
|
-
if (didFix) {
|
|
2368
|
-
const midResult = await audit({ dir: options.dir, silent: true, platform: options.platform });
|
|
2369
|
-
const delta = midResult.score - runningScore;
|
|
2370
|
-
fixResults.push({ key, name: failedCheck.name, status: 'fixed', delta });
|
|
2371
|
-
runningScore = midResult.score;
|
|
2372
|
-
if (!isBatch) console.log(` ✅ Fixed: ${failedCheck.name}`);
|
|
2373
|
-
fixed++;
|
|
2374
|
-
} else {
|
|
2375
|
-
fixResults.push({ key, name: failedCheck.name, status: 'skipped', delta: 0 });
|
|
2376
|
-
if (!isBatch) console.log(` ⏭️ Already fixed: ${failedCheck.name}`);
|
|
2377
|
-
}
|
|
2378
|
-
} catch (err) {
|
|
2379
|
-
fixResults.push({ key, name: failedCheck.name, status: 'failed', delta: 0 });
|
|
2380
|
-
if (isBatch) {
|
|
2381
|
-
console.log(` ❌ Failed: ${key} — ${err.message}`);
|
|
2382
|
-
console.log(` Stopping batch. ${fixed} fixes applied so far.`);
|
|
2383
|
-
console.log(` Rollback: nerviq rollback --id ${rollbackId}`);
|
|
2384
|
-
break;
|
|
2385
|
-
}
|
|
2386
|
-
}
|
|
2387
|
-
}
|
|
2388
|
-
} else {
|
|
2389
|
-
if (!isBatch) {
|
|
2390
|
-
const aiPrompt = FIX_PROMPTS[key];
|
|
2391
|
-
if (aiPrompt) {
|
|
2392
|
-
console.log(formatFixPrompt(key, aiPrompt));
|
|
2393
|
-
} else {
|
|
2394
|
-
console.log(` 📋 ${failedCheck.name} (manual fix needed)`);
|
|
2395
|
-
console.log(` ${failedCheck.fix}`);
|
|
2396
|
-
}
|
|
2397
|
-
}
|
|
2398
|
-
fixResults.push({ key, name: failedCheck.name, status: 'skipped', delta: 0 });
|
|
2399
|
-
manual++;
|
|
2400
|
-
}
|
|
2401
|
-
}
|
|
2402
|
-
|
|
2403
|
-
// Record accepted patterns for successfully fixed checks
|
|
2404
|
-
if (!isDryRun) {
|
|
2405
|
-
for (const key of targetKeys) {
|
|
2406
|
-
const fr = fixResults.find(r => r.key === key);
|
|
2407
|
-
recordPattern(options.dir, key, fr && fr.status === 'fixed' ? 'accepted' : 'rejected');
|
|
2408
|
-
}
|
|
2409
|
-
}
|
|
2410
|
-
|
|
2411
|
-
// Write rollback artifact AFTER fixes are applied (with actual file lists)
|
|
2412
|
-
if (!isDryRun && targetKeys.length > 0 && fixed > 0) {
|
|
2413
|
-
const allPatchedFiles = Object.keys(snapshotFiles);
|
|
2414
|
-
// Also track inline-fixer patched files
|
|
2415
|
-
for (const fr of fixResults) {
|
|
2416
|
-
if (fr.status === 'fixed' && INLINE_FIXERS[fr.key]) {
|
|
2417
|
-
const inlinePath = fr.key === 'gitIgnoreEnv' ? '.gitignore' : fr.key === 'secretsProtection' ? '.claude/settings.json' : null;
|
|
2418
|
-
if (inlinePath && !allPatchedFiles.includes(inlinePath)) {
|
|
2419
|
-
allPatchedFiles.push(inlinePath);
|
|
2420
|
-
}
|
|
2421
|
-
}
|
|
2422
|
-
}
|
|
2423
|
-
const rollbackArtifact = writeRollbackArtifact(options.dir, {
|
|
2424
|
-
sourcePlan: 'fix-batch',
|
|
2425
|
-
preSnapshot: snapshotFiles,
|
|
2426
|
-
createdFiles: allCreatedFiles,
|
|
2427
|
-
patchedFiles: allPatchedFiles,
|
|
2428
|
-
rollbackInstructions: ['Use nerviq rollback to undo these fixes'],
|
|
2429
|
-
});
|
|
2430
|
-
rollbackId = rollbackArtifact.id;
|
|
2431
|
-
}
|
|
2432
|
-
|
|
2433
|
-
// Step 4: Show batch summary or simple score impact
|
|
2434
|
-
if (isBatch && fixResults.length > 0) {
|
|
2435
|
-
console.log('');
|
|
2436
|
-
console.log(' Batch fix complete:');
|
|
2437
|
-
for (let i = 0; i < fixResults.length; i++) {
|
|
2438
|
-
const r = fixResults[i];
|
|
2439
|
-
const icon = r.status === 'fixed' ? '✅' : r.status === 'failed' ? '❌' : '⚠ ';
|
|
2440
|
-
const deltaStr = r.status === 'fixed' ? ` (+${r.delta})` : r.status === 'skipped' ? ' (skipped — no auto-fix)' : r.status === 'failed' ? ' (failed)' : ' (dry-run)';
|
|
2441
|
-
console.log(` ${icon} ${i + 1}. ${r.key.padEnd(20)}${deltaStr}`);
|
|
2442
|
-
}
|
|
2443
|
-
const totalDelta = runningScore - preScore;
|
|
2444
|
-
console.log('');
|
|
2445
|
-
console.log(` Score: ${preScore} → ${runningScore} (${totalDelta >= 0 ? '+' : ''}${totalDelta})`);
|
|
2446
|
-
if (rollbackId && !isDryRun) {
|
|
2447
|
-
console.log(` Rollback available: nerviq rollback --id ${rollbackId}`);
|
|
2448
|
-
}
|
|
2449
|
-
} else if (fixed > 0 && !isDryRun) {
|
|
2450
|
-
const postResult = await audit({ dir: options.dir, silent: true, platform: options.platform });
|
|
2451
|
-
const delta = postResult.score - preScore;
|
|
2452
|
-
console.log('');
|
|
2453
|
-
console.log(` Score: ${preScore} → ${postResult.score} (${delta >= 0 ? '+' : ''}${delta})`);
|
|
2454
|
-
if (rollbackId) {
|
|
2455
|
-
console.log(` Rollback available: nerviq rollback --id ${rollbackId}`);
|
|
2456
|
-
}
|
|
2457
|
-
}
|
|
2458
|
-
|
|
2459
|
-
console.log(`\n ${fixed} fixed, ${manual} need manual action.\n`);
|
|
2460
|
-
|
|
2461
|
-
} else if (normalizedCommand === 'init') {
|
|
2210
|
+
console.log('');
|
|
2211
|
+
process.exit(0);
|
|
2212
|
+
}
|
|
2213
|
+
|
|
2214
|
+
const outcome = await applyFixes({
|
|
2215
|
+
dir: options.dir,
|
|
2216
|
+
platform: options.platform,
|
|
2217
|
+
auditResult,
|
|
2218
|
+
targetKeys,
|
|
2219
|
+
auto: autoApply,
|
|
2220
|
+
dryRun: isDryRun,
|
|
2221
|
+
mode: 'fix',
|
|
2222
|
+
recordOutcomes: true,
|
|
2223
|
+
});
|
|
2224
|
+
process.exit(outcome.exitCode);
|
|
2225
|
+
|
|
2226
|
+
} else if (normalizedCommand === 'init') {
|
|
2462
2227
|
const { runInit } = require('../src/init');
|
|
2463
2228
|
await runInit(options.dir, flags);
|
|
2464
2229
|
process.exit(0);
|
|
@@ -2508,11 +2273,11 @@ async function main() {
|
|
|
2508
2273
|
}
|
|
2509
2274
|
// MOAT-01: Harmony-first default — when 2+ platforms and platform not explicit
|
|
2510
2275
|
let harmonyFirstResult = null;
|
|
2511
|
-
if (!options.platformExplicit && !options.noHarmonyFirst && !options.diffOnly && !options.driftMode && !options.workspace) {
|
|
2512
|
-
const detected = detectPlatforms(options.dir) || [];
|
|
2513
|
-
if (detected.length >= 2) {
|
|
2514
|
-
try {
|
|
2515
|
-
const { harmonyAudit } = require('../src/harmony/audit');
|
|
2276
|
+
if (!options.platformExplicit && !options.noHarmonyFirst && !options.diffOnly && !options.driftMode && !options.workspace) {
|
|
2277
|
+
const detected = detectPlatforms(options.dir) || [];
|
|
2278
|
+
if (detected.length >= 2) {
|
|
2279
|
+
try {
|
|
2280
|
+
const { harmonyAudit } = require('../src/harmony/audit');
|
|
2516
2281
|
harmonyFirstResult = await harmonyAudit({ dir: options.dir, silent: true });
|
|
2517
2282
|
if (!options.json && harmonyFirstResult) {
|
|
2518
2283
|
const hs = harmonyFirstResult.harmonyScore;
|
|
@@ -2526,15 +2291,46 @@ async function main() {
|
|
|
2526
2291
|
}
|
|
2527
2292
|
} catch {
|
|
2528
2293
|
harmonyFirstResult = null;
|
|
2529
|
-
}
|
|
2530
|
-
}
|
|
2531
|
-
}
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2297
|
+
|
|
2298
|
+
if (options.fix) {
|
|
2299
|
+
if (options.diffOnly) {
|
|
2300
|
+
console.error('\n Error: --diff-only cannot be combined with --fix.\n');
|
|
2301
|
+
process.exit(2);
|
|
2302
|
+
}
|
|
2303
|
+
|
|
2304
|
+
const { getFixableFailedResults, applyFixes } = require('../src/fix-engine');
|
|
2305
|
+
const auditResult = await audit({ ...options, silent: true });
|
|
2306
|
+
const failedResults = (auditResult.results || []).filter((item) => item.passed === false);
|
|
2307
|
+
const targetKeys = getFixableFailedResults(failedResults, { mode: 'audit', criticalOnly: true }).map((item) => item.key);
|
|
2308
|
+
|
|
2309
|
+
if (targetKeys.length === 0) {
|
|
2310
|
+
console.log('');
|
|
2311
|
+
console.log(' אין תיקוני critical אוטומטיים זמינים למסלול audit --fix.');
|
|
2312
|
+
console.log(' נסו `nerviq fix <key> --prompt` עבור תיקון ידני, או הריצו audit מלא כדי לראות את כל הפערים.');
|
|
2313
|
+
console.log('');
|
|
2314
|
+
process.exit(2);
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
const outcome = await applyFixes({
|
|
2318
|
+
dir: options.dir,
|
|
2319
|
+
platform: options.platform,
|
|
2320
|
+
auditResult,
|
|
2321
|
+
targetKeys,
|
|
2322
|
+
auto: options.auto,
|
|
2323
|
+
dryRun: options.dryRun,
|
|
2324
|
+
mode: 'audit',
|
|
2325
|
+
});
|
|
2326
|
+
process.exit(outcome.exitCode);
|
|
2327
|
+
}
|
|
2328
|
+
|
|
2329
|
+
let result;
|
|
2330
|
+
const renderAuditJsonLocally = options.json && (Boolean(options.driftMode) || Boolean(harmonyFirstResult));
|
|
2331
|
+
if (options.diffOnly) {
|
|
2332
|
+
const { getChangedFiles, buildDiffOnlyAuditView, printDiffOnlyAudit } = require('../src/diff-only');
|
|
2333
|
+
const fullResult = await audit({ ...options, silent: true });
|
|
2538
2334
|
const diffInfo = getChangedFiles(options.dir, {
|
|
2539
2335
|
diffBase: options.diffBase,
|
|
2540
2336
|
diffHead: options.diffHead,
|