@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 +3 -3
- package/bin/cli.js +130 -343
- package/package.json +60 -60
- 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
|
@@ -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 --
|
|
582
|
-
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
|
|
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
|
|
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
|
-
|
|
2105
|
-
|
|
2106
|
-
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);
|
|
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 {
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
const
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
const
|
|
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
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
if (
|
|
2198
|
-
console.log(
|
|
2199
|
-
|
|
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
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
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
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
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,
|