@nerviq/cli 1.17.3 → 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 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.17.3",
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
@@ -576,10 +576,12 @@ const HELP = `
576
576
  Audit, align, and amplify every platform on every project.
577
577
  New here? Run: nerviq --beginner
578
578
 
579
- DISCOVER
580
- nerviq audit Quick scan: score + top 3 gaps (Harmony-first when 2+ platforms detected)
581
- nerviq audit --no-harmony-first Skip the cross-platform Harmony header
582
- nerviq audit --full Full audit with all checks, weakest areas, badge
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
583
585
  nerviq audit --platform X Audit specific platform (claude|codex|cursor|copilot|gemini|windsurf|aider|opencode)
584
586
  nerviq audit --json Machine-readable JSON output (for CI)
585
587
  nerviq audit --workspace packages/* Audit monorepo workspaces with stack-specific package profiles
@@ -600,9 +602,9 @@ const HELP = `
600
602
  nerviq check-health Detect regressions + platform format changes between snapshots
601
603
  nerviq doctor Self-diagnostics: Node, deps, freshness, MCP, hook runtime
602
604
 
603
- FIX
604
- nerviq fix Show fixable checks and manual-fix guidance
605
- 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)
606
608
  nerviq fix <key> --prompt Show AI agent prompt for a check (no auto-fix)
607
609
  nerviq fix --all-critical Fix all critical issues at once
608
610
  nerviq fix --dry-run Preview fixes without writing
@@ -773,12 +775,12 @@ const BEGINNER_HELP = `
773
775
  nerviq augment Show an improvement plan without writing
774
776
  nerviq doctor Check install health, freshness, platform detection, MCP, and hook runtime
775
777
 
776
- SIMPLE PATH
777
- 1. nerviq audit
778
- 2. nerviq setup --auto
779
- 3. nerviq fix --all-critical --auto
780
- 4. nerviq augment
781
- 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
782
784
 
783
785
  WHEN YOU ARE READY
784
786
  nerviq --help Show the full command set
@@ -2093,73 +2095,34 @@ async function main() {
2093
2095
  });
2094
2096
  console.log(output);
2095
2097
  process.exit(0);
2096
- } else if (normalizedCommand === 'fix') {
2097
- // nerviq fix [key] [--all-critical] [--dry-run] [--auto] [--prompt]
2098
- const fixKey = parsed.extraArgs[0] || null;
2099
- const allCritical = flags.includes('--all-critical');
2100
- const promptOnly = flags.includes('--prompt');
2101
- const autoApply = options.auto;
2102
- const isDryRun = options.dryRun;
2103
-
2104
- // Step 1: Run silent audit to find failed checks (only actual failures, not skipped/null)
2105
- const auditResult = await audit({ dir: options.dir, silent: true, platform: options.platform });
2106
- const failedResults = (auditResult.results || []).filter(r => r.passed === false);
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);
2107
2110
 
2108
2111
  if (failedResults.length === 0) {
2109
2112
  console.log('\n ✅ All checks passing — nothing to fix.\n');
2110
2113
  process.exit(0);
2111
2114
  }
2112
2115
 
2113
- // Step 2: Determine which checks to fix
2114
- const { TECHNIQUES } = require('../src/techniques');
2115
- const { FIX_PROMPTS, formatFixPrompt } = require('../src/fix-prompts');
2116
- const fs = require('fs');
2117
- const pathMod = require('path');
2118
-
2119
- // Inline fixers for checks without templates but with trivial auto-fixes
2120
- const INLINE_FIXERS = {
2121
- gitIgnoreEnv: (dir) => {
2122
- const gitignorePath = pathMod.join(dir, '.gitignore');
2123
- const existing = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, 'utf8') : '';
2124
- if (!existing.includes('.env')) {
2125
- const lines = existing.endsWith('\n') || existing === '' ? '' : '\n';
2126
- fs.appendFileSync(gitignorePath, `${lines}.env\n.env.*\n`, 'utf8');
2127
- return true;
2128
- }
2129
- return false;
2130
- },
2131
- secretsProtection: (dir) => {
2132
- const settingsPath = pathMod.join(dir, '.claude', 'settings.json');
2133
- const settingsDir = pathMod.join(dir, '.claude');
2134
- if (!fs.existsSync(settingsDir)) fs.mkdirSync(settingsDir, { recursive: true });
2135
- let settings = {};
2136
- if (fs.existsSync(settingsPath)) {
2137
- try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); } catch { settings = {}; }
2138
- }
2139
- if (!settings.permissions) settings.permissions = {};
2140
- if (!settings.permissions.deny) settings.permissions.deny = [];
2141
- const denyEntries = ['.env', '.env.*', '**/.env', '**/*.pem', '**/secrets/**'];
2142
- for (const entry of denyEntries) {
2143
- if (!settings.permissions.deny.includes(entry)) settings.permissions.deny.push(entry);
2144
- }
2145
- // Remove overly broad allow:["*"] if present
2146
- if (Array.isArray(settings.permissions.allow) && settings.permissions.allow.includes('*')) {
2147
- settings.permissions.allow = settings.permissions.allow.filter(a => a !== '*');
2148
- if (settings.permissions.allow.length === 0) {
2149
- delete settings.permissions.allow;
2150
- }
2151
- }
2152
- fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
2153
- return true;
2154
- },
2155
- };
2156
-
2157
- let targetKeys = [];
2158
-
2159
- if (fixKey) {
2160
- // Fix a specific check
2161
- if (!failedResults.find(r => r.key === fixKey)) {
2162
- 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);
2163
2126
  if (passed) {
2164
2127
  console.log(`\n ✅ '${fixKey}' is already passing.\n`);
2165
2128
  } else {
@@ -2177,26 +2140,37 @@ async function main() {
2177
2140
  const failedCheck = failedResults.find(r => r.key === fixKey);
2178
2141
  console.log(`\n No AI prompt available for '${fixKey}'.`);
2179
2142
  console.log(` Manual fix: ${failedCheck ? failedCheck.fix : 'See nerviq audit --full.'}\n`);
2180
- }
2181
- process.exit(0);
2182
- }
2183
- targetKeys = [fixKey];
2184
- } else if (allCritical) {
2185
- targetKeys = failedResults.filter(r => r.impact === 'critical').map(r => r.key);
2186
- if (targetKeys.length === 0) {
2187
- console.log('\n ✅ No critical issues found.\n');
2188
- process.exit(0);
2189
- }
2190
- } else {
2191
- // No key specified — show fixable checks and exit
2192
- const INLINE_FIX_KEYS = new Set(Object.keys(INLINE_FIXERS));
2193
- const fixable = failedResults.filter(r => (TECHNIQUES[r.key] && TECHNIQUES[r.key].template) || INLINE_FIX_KEYS.has(r.key));
2194
- const nonFixable = failedResults.filter(r => !(TECHNIQUES[r.key] && TECHNIQUES[r.key].template) && !INLINE_FIX_KEYS.has(r.key));
2195
- console.log('');
2196
- console.log(` nerviq fix ${failedResults.length} failed checks\n`);
2197
- if (fixable.length > 0) {
2198
- console.log(` Auto-fixable (${fixable.length}):`);
2199
- for (const r of fixable) {
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) {
2200
2174
  const tier = r.impact === 'critical' ? '🔴' : r.impact === 'high' ? '🟡' : '🔵';
2201
2175
  console.log(` ${tier} nerviq fix ${r.key}`);
2202
2176
  }
@@ -2233,241 +2207,23 @@ async function main() {
2233
2207
  console.log(` nerviq fix ${fixable[0].key} Fix the first auto-fixable check`);
2234
2208
  console.log(` nerviq fix --all-critical Fix all critical issues at once`);
2235
2209
  }
2236
- console.log('');
2237
- process.exit(0);
2238
- }
2239
-
2240
- // Step 2.5: Predict impact and show preview before applying
2241
- const IMPACT_WEIGHTS = { critical: 15, high: 10, medium: 5, low: 2 };
2242
- const preScore = auditResult.score;
2243
- const applicableResults = (auditResult.results || []).filter(r => r.passed !== null);
2244
- const maxScore = applicableResults.reduce((sum, r) => sum + (IMPACT_WEIGHTS[r.impact] || 5), 0);
2245
-
2246
- // Compute predicted score by simulating target fixes as passing
2247
- const targetKeySet = new Set(targetKeys);
2248
- const INLINE_FIX_KEYS = new Set(Object.keys(INLINE_FIXERS));
2249
- const fixableTargets = targetKeys.filter(k => {
2250
- const tech = TECHNIQUES[k];
2251
- return (tech && tech.template) || INLINE_FIX_KEYS.has(k);
2252
- });
2253
- const fixableTargetSet = new Set(fixableTargets);
2254
- const simulatedEarned = applicableResults.reduce((sum, r) => {
2255
- const w = IMPACT_WEIGHTS[r.impact] || 5;
2256
- if (r.passed) return sum + w;
2257
- if (fixableTargetSet.has(r.key)) return sum + w;
2258
- return sum;
2259
- }, 0);
2260
- const predictedScore = maxScore > 0 ? Math.round((simulatedEarned / maxScore) * 100) : 0;
2261
- const predictedDelta = predictedScore - preScore;
2262
-
2263
- if (!autoApply && !isDryRun) {
2264
- console.log('');
2265
- if (allCritical && fixableTargets.length > 1) {
2266
- // Multi-fix summary
2267
- console.log(` ${fixableTargets.length} critical fixes available:`);
2268
- let runningEarned = applicableResults.reduce((s, r) => s + (r.passed ? (IMPACT_WEIGHTS[r.impact] || 5) : 0), 0);
2269
- let runningScore = maxScore > 0 ? Math.round((runningEarned / maxScore) * 100) : 0;
2270
- fixableTargets.forEach((k, idx) => {
2271
- const r = failedResults.find(fr => fr.key === k);
2272
- const w = IMPACT_WEIGHTS[r.impact] || 5;
2273
- const nextEarned = runningEarned + w;
2274
- const nextScore = maxScore > 0 ? Math.round((nextEarned / maxScore) * 100) : 0;
2275
- const d = nextScore - runningScore;
2276
- console.log(` ${idx + 1}. ${(r.key).padEnd(18)} ${runningScore} → ${nextScore} (+${d})`);
2277
- runningEarned = nextEarned;
2278
- runningScore = nextScore;
2279
- });
2280
- console.log('');
2281
- console.log(` Total: ${preScore} → ${predictedScore} (+${predictedDelta})`);
2282
- } else {
2283
- // Single fix preview
2284
- const targetCheck = failedResults.find(r => r.key === fixableTargets[0]) || failedResults.find(r => r.key === targetKeys[0]);
2285
- if (targetCheck) {
2286
- console.log(` Predicted impact: ${preScore} → ${predictedScore} (+${predictedDelta})`);
2287
- }
2288
- }
2289
-
2290
- // Prompt for confirmation
2291
- const readline = require('readline');
2292
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
2293
- const answer = await new Promise(resolve => {
2294
- rl.question(' Apply? (Y/n) ', resolve);
2295
- });
2296
- rl.close();
2297
- if (answer && answer.trim().toLowerCase() === 'n') {
2298
- for (const key of targetKeys) {
2299
- recordPattern(options.dir, key, 'rejected');
2300
- }
2301
- console.log('\n Aborted.\n');
2302
- process.exit(0);
2303
- }
2304
- }
2305
-
2306
- // Step 3: Create rollback snapshot before applying fixes
2307
- const isBatch = allCritical && targetKeys.length > 1;
2308
- let rollbackId = null;
2309
- const allCreatedFiles = [];
2310
- const fixResults = []; // { key, name, status, delta }
2311
-
2312
- const snapshotFiles = {};
2313
- if (!isDryRun && targetKeys.length > 0) {
2314
- // Snapshot existing files for rollback (before applying fixes)
2315
- for (const key of targetKeys) {
2316
- const technique = TECHNIQUES[key];
2317
- if (technique && technique.template && technique.template.path) {
2318
- const tplPath = pathMod.join(options.dir, technique.template.path);
2319
- if (fs.existsSync(tplPath)) {
2320
- snapshotFiles[technique.template.path] = fs.readFileSync(tplPath, 'utf8');
2321
- }
2322
- }
2323
- }
2324
- }
2325
-
2326
- // Step 3b: Apply fixes sequentially with progress
2327
- let fixed = 0;
2328
- let manual = 0;
2329
- let runningScore = preScore;
2330
-
2331
- for (let i = 0; i < targetKeys.length; i++) {
2332
- const key = targetKeys[i];
2333
- const technique = TECHNIQUES[key];
2334
- const failedCheck = failedResults.find(r => r.key === key);
2335
- const progress = isBatch ? `${i + 1}/${targetKeys.length}: ` : '';
2336
-
2337
- if (technique && technique.template) {
2338
- if (isDryRun) {
2339
- console.log(` [dry-run] Would fix: ${progress}${failedCheck.name} (${key})`);
2340
- fixResults.push({ key, name: failedCheck.name, status: 'dry-run', delta: 0 });
2341
- fixed++;
2342
- } else {
2343
- try {
2344
- if (isBatch) console.log(` Fixing ${progress}${key}...`);
2345
- const setupResult = await setup({ ...options, only: [key], silent: true });
2346
- if (setupResult && setupResult.writtenFiles) {
2347
- allCreatedFiles.push(...setupResult.writtenFiles);
2348
- }
2349
- const midResult = await audit({ dir: options.dir, silent: true, platform: options.platform });
2350
- const delta = midResult.score - runningScore;
2351
- fixResults.push({ key, name: failedCheck.name, status: 'fixed', delta });
2352
- runningScore = midResult.score;
2353
- if (!isBatch) console.log(` ✅ Fixed: ${failedCheck.name}`);
2354
- fixed++;
2355
- } catch (err) {
2356
- fixResults.push({ key, name: failedCheck.name, status: 'failed', delta: 0 });
2357
- if (isBatch) {
2358
- console.log(` ❌ Failed: ${key} — ${err.message}`);
2359
- console.log(` Stopping batch. ${fixed} fixes applied so far.`);
2360
- console.log(` Rollback: nerviq rollback --id ${rollbackId}`);
2361
- break;
2362
- } else {
2363
- console.log(` ❌ Failed: ${failedCheck.name} — ${err.message}`);
2364
- }
2365
- }
2366
- }
2367
- } else if (INLINE_FIXERS[key]) {
2368
- if (isDryRun) {
2369
- console.log(` [dry-run] Would fix: ${progress}${failedCheck.name} (${key})`);
2370
- fixResults.push({ key, name: failedCheck.name, status: 'dry-run', delta: 0 });
2371
- fixed++;
2372
- } else {
2373
- try {
2374
- if (isBatch) console.log(` Fixing ${progress}${key}...`);
2375
- const didFix = INLINE_FIXERS[key](options.dir);
2376
- if (didFix) {
2377
- const midResult = await audit({ dir: options.dir, silent: true, platform: options.platform });
2378
- const delta = midResult.score - runningScore;
2379
- fixResults.push({ key, name: failedCheck.name, status: 'fixed', delta });
2380
- runningScore = midResult.score;
2381
- if (!isBatch) console.log(` ✅ Fixed: ${failedCheck.name}`);
2382
- fixed++;
2383
- } else {
2384
- fixResults.push({ key, name: failedCheck.name, status: 'skipped', delta: 0 });
2385
- if (!isBatch) console.log(` ⏭️ Already fixed: ${failedCheck.name}`);
2386
- }
2387
- } catch (err) {
2388
- fixResults.push({ key, name: failedCheck.name, status: 'failed', delta: 0 });
2389
- if (isBatch) {
2390
- console.log(` ❌ Failed: ${key} — ${err.message}`);
2391
- console.log(` Stopping batch. ${fixed} fixes applied so far.`);
2392
- console.log(` Rollback: nerviq rollback --id ${rollbackId}`);
2393
- break;
2394
- }
2395
- }
2396
- }
2397
- } else {
2398
- if (!isBatch) {
2399
- const aiPrompt = FIX_PROMPTS[key];
2400
- if (aiPrompt) {
2401
- console.log(formatFixPrompt(key, aiPrompt));
2402
- } else {
2403
- console.log(` 📋 ${failedCheck.name} (manual fix needed)`);
2404
- console.log(` ${failedCheck.fix}`);
2405
- }
2406
- }
2407
- fixResults.push({ key, name: failedCheck.name, status: 'skipped', delta: 0 });
2408
- manual++;
2409
- }
2410
- }
2411
-
2412
- // Record accepted patterns for successfully fixed checks
2413
- if (!isDryRun) {
2414
- for (const key of targetKeys) {
2415
- const fr = fixResults.find(r => r.key === key);
2416
- recordPattern(options.dir, key, fr && fr.status === 'fixed' ? 'accepted' : 'rejected');
2417
- }
2418
- }
2419
-
2420
- // Write rollback artifact AFTER fixes are applied (with actual file lists)
2421
- if (!isDryRun && targetKeys.length > 0 && fixed > 0) {
2422
- const allPatchedFiles = Object.keys(snapshotFiles);
2423
- // Also track inline-fixer patched files
2424
- for (const fr of fixResults) {
2425
- if (fr.status === 'fixed' && INLINE_FIXERS[fr.key]) {
2426
- const inlinePath = fr.key === 'gitIgnoreEnv' ? '.gitignore' : fr.key === 'secretsProtection' ? '.claude/settings.json' : null;
2427
- if (inlinePath && !allPatchedFiles.includes(inlinePath)) {
2428
- allPatchedFiles.push(inlinePath);
2429
- }
2430
- }
2431
- }
2432
- const rollbackArtifact = writeRollbackArtifact(options.dir, {
2433
- sourcePlan: 'fix-batch',
2434
- preSnapshot: snapshotFiles,
2435
- createdFiles: allCreatedFiles,
2436
- patchedFiles: allPatchedFiles,
2437
- rollbackInstructions: ['Use nerviq rollback to undo these fixes'],
2438
- });
2439
- rollbackId = rollbackArtifact.id;
2440
- }
2441
-
2442
- // Step 4: Show batch summary or simple score impact
2443
- if (isBatch && fixResults.length > 0) {
2444
- console.log('');
2445
- console.log(' Batch fix complete:');
2446
- for (let i = 0; i < fixResults.length; i++) {
2447
- const r = fixResults[i];
2448
- const icon = r.status === 'fixed' ? '✅' : r.status === 'failed' ? '❌' : '⚠ ';
2449
- const deltaStr = r.status === 'fixed' ? ` (+${r.delta})` : r.status === 'skipped' ? ' (skipped — no auto-fix)' : r.status === 'failed' ? ' (failed)' : ' (dry-run)';
2450
- console.log(` ${icon} ${i + 1}. ${r.key.padEnd(20)}${deltaStr}`);
2451
- }
2452
- const totalDelta = runningScore - preScore;
2453
- console.log('');
2454
- console.log(` Score: ${preScore} → ${runningScore} (${totalDelta >= 0 ? '+' : ''}${totalDelta})`);
2455
- if (rollbackId && !isDryRun) {
2456
- console.log(` Rollback available: nerviq rollback --id ${rollbackId}`);
2457
- }
2458
- } else if (fixed > 0 && !isDryRun) {
2459
- const postResult = await audit({ dir: options.dir, silent: true, platform: options.platform });
2460
- const delta = postResult.score - preScore;
2461
- console.log('');
2462
- console.log(` Score: ${preScore} → ${postResult.score} (${delta >= 0 ? '+' : ''}${delta})`);
2463
- if (rollbackId) {
2464
- console.log(` Rollback available: nerviq rollback --id ${rollbackId}`);
2465
- }
2466
- }
2467
-
2468
- console.log(`\n ${fixed} fixed, ${manual} need manual action.\n`);
2469
-
2470
- } 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') {
2471
2227
  const { runInit } = require('../src/init');
2472
2228
  await runInit(options.dir, flags);
2473
2229
  process.exit(0);
@@ -2517,11 +2273,11 @@ async function main() {
2517
2273
  }
2518
2274
  // MOAT-01: Harmony-first default — when 2+ platforms and platform not explicit
2519
2275
  let harmonyFirstResult = null;
2520
- if (!options.platformExplicit && !options.noHarmonyFirst && !options.diffOnly && !options.driftMode && !options.workspace) {
2521
- const detected = detectPlatforms(options.dir) || [];
2522
- if (detected.length >= 2) {
2523
- try {
2524
- 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');
2525
2281
  harmonyFirstResult = await harmonyAudit({ dir: options.dir, silent: true });
2526
2282
  if (!options.json && harmonyFirstResult) {
2527
2283
  const hs = harmonyFirstResult.harmonyScore;
@@ -2535,15 +2291,46 @@ async function main() {
2535
2291
  }
2536
2292
  } catch {
2537
2293
  harmonyFirstResult = null;
2538
- }
2539
- }
2540
- }
2541
-
2542
- let result;
2543
- const renderAuditJsonLocally = options.json && (Boolean(options.driftMode) || Boolean(harmonyFirstResult));
2544
- if (options.diffOnly) {
2545
- const { getChangedFiles, buildDiffOnlyAuditView, printDiffOnlyAudit } = require('../src/diff-only');
2546
- const fullResult = await audit({ ...options, silent: true });
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 });
2547
2334
  const diffInfo = getChangedFiles(options.dir, {
2548
2335
  diffBase: options.diffBase,
2549
2336
  diffHead: options.diffHead,