@neurcode-ai/cli 0.9.48 → 0.9.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/dist/commands/fix.d.ts +13 -0
  2. package/dist/commands/fix.d.ts.map +1 -0
  3. package/dist/commands/fix.js +785 -0
  4. package/dist/commands/fix.js.map +1 -0
  5. package/dist/commands/generate.d.ts +7 -0
  6. package/dist/commands/generate.d.ts.map +1 -0
  7. package/dist/commands/generate.js +132 -0
  8. package/dist/commands/generate.js.map +1 -0
  9. package/dist/commands/patch-apply.d.ts +7 -0
  10. package/dist/commands/patch-apply.d.ts.map +1 -0
  11. package/dist/commands/patch-apply.js +85 -0
  12. package/dist/commands/patch-apply.js.map +1 -0
  13. package/dist/commands/plan-show.d.ts +6 -0
  14. package/dist/commands/plan-show.d.ts.map +1 -0
  15. package/dist/commands/plan-show.js +33 -0
  16. package/dist/commands/plan-show.js.map +1 -0
  17. package/dist/commands/start-intent.d.ts +6 -0
  18. package/dist/commands/start-intent.d.ts.map +1 -0
  19. package/dist/commands/start-intent.js +80 -0
  20. package/dist/commands/start-intent.js.map +1 -0
  21. package/dist/commands/verify.d.ts.map +1 -1
  22. package/dist/commands/verify.js +703 -186
  23. package/dist/commands/verify.js.map +1 -1
  24. package/dist/context-engine/graph.d.ts +6 -0
  25. package/dist/context-engine/graph.d.ts.map +1 -0
  26. package/dist/context-engine/graph.js +55 -0
  27. package/dist/context-engine/graph.js.map +1 -0
  28. package/dist/context-engine/index.d.ts +14 -0
  29. package/dist/context-engine/index.d.ts.map +1 -0
  30. package/dist/context-engine/index.js +26 -0
  31. package/dist/context-engine/index.js.map +1 -0
  32. package/dist/context-engine/scanner.d.ts +6 -0
  33. package/dist/context-engine/scanner.d.ts.map +1 -0
  34. package/dist/context-engine/scanner.js +62 -0
  35. package/dist/context-engine/scanner.js.map +1 -0
  36. package/dist/context-engine/scorer.d.ts +9 -0
  37. package/dist/context-engine/scorer.d.ts.map +1 -0
  38. package/dist/context-engine/scorer.js +112 -0
  39. package/dist/context-engine/scorer.js.map +1 -0
  40. package/dist/context-engine/suggestions.d.ts +12 -0
  41. package/dist/context-engine/suggestions.d.ts.map +1 -0
  42. package/dist/context-engine/suggestions.js +22 -0
  43. package/dist/context-engine/suggestions.js.map +1 -0
  44. package/dist/index.js +129 -55
  45. package/dist/index.js.map +1 -1
  46. package/dist/mcp/context-injector.d.ts +45 -0
  47. package/dist/mcp/context-injector.d.ts.map +1 -0
  48. package/dist/mcp/context-injector.js +587 -0
  49. package/dist/mcp/context-injector.js.map +1 -0
  50. package/dist/mcp/proximity.d.ts +3 -0
  51. package/dist/mcp/proximity.d.ts.map +1 -0
  52. package/dist/mcp/proximity.js +135 -0
  53. package/dist/mcp/proximity.js.map +1 -0
  54. package/dist/patch-engine/diff.d.ts +12 -0
  55. package/dist/patch-engine/diff.d.ts.map +1 -0
  56. package/dist/patch-engine/diff.js +74 -0
  57. package/dist/patch-engine/diff.js.map +1 -0
  58. package/dist/patch-engine/generator.d.ts +13 -0
  59. package/dist/patch-engine/generator.d.ts.map +1 -0
  60. package/dist/patch-engine/generator.js +51 -0
  61. package/dist/patch-engine/generator.js.map +1 -0
  62. package/dist/patch-engine/index.d.ts +47 -0
  63. package/dist/patch-engine/index.d.ts.map +1 -0
  64. package/dist/patch-engine/index.js +182 -0
  65. package/dist/patch-engine/index.js.map +1 -0
  66. package/dist/patch-engine/patterns.d.ts +4 -0
  67. package/dist/patch-engine/patterns.d.ts.map +1 -0
  68. package/dist/patch-engine/patterns.js +99 -0
  69. package/dist/patch-engine/patterns.js.map +1 -0
  70. package/dist/utils/git.d.ts +8 -0
  71. package/dist/utils/git.d.ts.map +1 -1
  72. package/dist/utils/git.js +55 -0
  73. package/dist/utils/git.js.map +1 -1
  74. package/dist/utils/plan-sync.d.ts +34 -0
  75. package/dist/utils/plan-sync.d.ts.map +1 -0
  76. package/dist/utils/plan-sync.js +265 -0
  77. package/dist/utils/plan-sync.js.map +1 -0
  78. package/package.json +1 -1
@@ -0,0 +1,785 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fixCommand = fixCommand;
4
+ const fs_1 = require("fs");
5
+ const path_1 = require("path");
6
+ const cli_json_1 = require("../utils/cli-json");
7
+ const contracts_1 = require("@neurcode-ai/contracts");
8
+ const context_engine_1 = require("../context-engine");
9
+ const patch_engine_1 = require("../patch-engine");
10
+ const chalk = (0, cli_json_1.loadChalk)();
11
+ const MAX_SUGGESTIONS = 10;
12
+ // Minimum context-engine score for a target file to be trusted.
13
+ const MIN_TARGET_SCORE = 3;
14
+ // ---------------------------------------------------------------------------
15
+ // Reason templates (req 4) — policy-keyed, no runtime string assembly
16
+ // ---------------------------------------------------------------------------
17
+ const REASON_TEMPLATES = {
18
+ layering: 'UI layer should not directly access database',
19
+ validation: 'Input must be validated before request handling',
20
+ scope: 'Changes must stay within planned scope to prevent drift',
21
+ default: 'This change violates project architecture guidelines',
22
+ };
23
+ // ---------------------------------------------------------------------------
24
+ // Targeted suggestion helpers
25
+ // ---------------------------------------------------------------------------
26
+ function buildRepairIntent(issue, policy) {
27
+ const combined = `${issue} ${policy}`.toLowerCase();
28
+ // Violations with no useful cross-file redirect target
29
+ if (combined.includes('todo') || combined.includes('fixme'))
30
+ return null;
31
+ if (policy === 'scope_guard' || combined.includes('scope'))
32
+ return null;
33
+ if (policy === 'verify_runtime')
34
+ return null;
35
+ if (combined.includes('db') || combined.includes('database') ||
36
+ combined.includes('query') || combined.includes('sql') ||
37
+ combined.includes('data access') || combined.includes('direct access') ||
38
+ policy.includes('layer') || policy.includes('db') || policy.includes('layering')) {
39
+ return 'service layer database repository core';
40
+ }
41
+ if (combined.includes('validation') || combined.includes('validate') ||
42
+ combined.includes('input') || policy.includes('validation')) {
43
+ return 'validation schema middleware validator';
44
+ }
45
+ if (combined.includes('auth') || combined.includes('authentication') ||
46
+ combined.includes('token') || combined.includes('jwt') ||
47
+ policy.includes('auth')) {
48
+ return 'authentication middleware guard auth';
49
+ }
50
+ return issue;
51
+ }
52
+ function getModulePrefix(filePath) {
53
+ const parts = filePath.replace(/\\/g, '/').split('/').filter(Boolean);
54
+ return parts.slice(0, 2).join('/');
55
+ }
56
+ /**
57
+ * Find the best target file for a repair intent.
58
+ *
59
+ * Applies:
60
+ * - Same-module boost: +2 to candidates sharing a module prefix with any
61
+ * previously selected target (drives coherence across violations).
62
+ * - Safety threshold: only returns a result when adjusted score >= MIN_TARGET_SCORE.
63
+ */
64
+ function findTargetFile(sourceFile, repairIntent, graph, selectedTargets) {
65
+ const scored = (0, context_engine_1.scoreFiles)(repairIntent, graph);
66
+ const selectedPrefixes = new Set(selectedTargets.map(getModulePrefix));
67
+ // Exclude source file; apply same-module boost
68
+ const adjusted = scored
69
+ .filter((s) => s.file !== sourceFile)
70
+ .map((s) => ({
71
+ file: s.file,
72
+ score: s.score + (selectedPrefixes.has(getModulePrefix(s.file)) ? 2 : 0),
73
+ }));
74
+ adjusted.sort((a, b) => b.score - a.score);
75
+ const best = adjusted.find((s) => s.score >= MIN_TARGET_SCORE);
76
+ if (!best)
77
+ return undefined;
78
+ return { file: best.file, score: best.score };
79
+ }
80
+ function buildReason(issue, policy) {
81
+ const policyLower = policy.toLowerCase();
82
+ const combined = `${issue} ${policy}`.toLowerCase();
83
+ // Policy-name takes precedence
84
+ if (policyLower.includes('layer') || policyLower.includes('db') ||
85
+ policyLower.includes('database') || policyLower.includes('layering')) {
86
+ return REASON_TEMPLATES.layering;
87
+ }
88
+ if (policyLower.includes('validation') || policyLower.includes('validate') ||
89
+ policyLower.includes('input_validation')) {
90
+ return REASON_TEMPLATES.validation;
91
+ }
92
+ if (policyLower === 'scope_guard' || policyLower.includes('scope')) {
93
+ return REASON_TEMPLATES.scope;
94
+ }
95
+ // Issue-text fallback
96
+ if (combined.includes('db') || combined.includes('database') || combined.includes('query') || combined.includes('data access')) {
97
+ return REASON_TEMPLATES.layering;
98
+ }
99
+ if (combined.includes('validation') || combined.includes('validate') || combined.includes('input')) {
100
+ return REASON_TEMPLATES.validation;
101
+ }
102
+ return REASON_TEMPLATES.default;
103
+ }
104
+ function resolveConfidence(score) {
105
+ if (score >= 6)
106
+ return 'high';
107
+ if (score >= MIN_TARGET_SCORE)
108
+ return 'medium';
109
+ return 'low';
110
+ }
111
+ function buildActionVerb(issue, policy) {
112
+ const combined = `${issue} ${policy}`.toLowerCase();
113
+ if (combined.includes('db') || combined.includes('database') || combined.includes('query') || combined.includes('sql')) {
114
+ return 'Move DB query';
115
+ }
116
+ if (combined.includes('data access') || combined.includes('direct access') || policy.includes('layer') || policy.includes('layering')) {
117
+ return 'Move data access logic';
118
+ }
119
+ if (combined.includes('validation') || combined.includes('validate')) {
120
+ return 'Add input validation';
121
+ }
122
+ if (combined.includes('auth') || combined.includes('authentication')) {
123
+ return 'Move authentication logic';
124
+ }
125
+ return 'Move logic';
126
+ }
127
+ // ---------------------------------------------------------------------------
128
+ // Legacy generic action (used as fallback suggestedAction text)
129
+ // ---------------------------------------------------------------------------
130
+ function suggestAction(file, issue, policy, isScopeIssue) {
131
+ const combined = `${issue} ${policy}`.toLowerCase();
132
+ const withContextHint = (message) => `${message} (from current diff)`;
133
+ const withStartBy = (message) => `Start by ${message}`;
134
+ if (combined.includes('todo') || combined.includes('fixme') || combined.includes('todo_fixme')) {
135
+ return withContextHint(withStartBy(`removing or resolving TODO/FIXME in ${file} before merge to avoid technical debt`));
136
+ }
137
+ if ((combined.includes('direct db access') || combined.includes('database access') || combined.includes('db access'))
138
+ && (combined.includes('ui') || combined.includes('component') || combined.includes('frontend'))) {
139
+ return withContextHint(withStartBy(`moving data access from ${file} into a service layer (e.g., src/core/...) to keep business logic out of UI`));
140
+ }
141
+ if (combined.includes('validation') || combined.includes('missing validation') || combined.includes('input')) {
142
+ return withContextHint(withStartBy(`adding input validation in ${file} before request handling to prevent invalid data`));
143
+ }
144
+ if (isScopeIssue || combined.includes('scope')) {
145
+ return withContextHint(withStartBy(`updating the plan or reverting changes in ${file} to reduce architectural drift`));
146
+ }
147
+ if (combined.includes('direct db access') || combined.includes('database access') || combined.includes('db access')) {
148
+ return withContextHint(withStartBy(`moving direct database access from ${file} into a service layer to keep business logic out of UI`));
149
+ }
150
+ return withContextHint(withStartBy(`reviewing ${file} and aligning implementation with current project architecture`));
151
+ }
152
+ function asObjectRecord(value) {
153
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
154
+ return null;
155
+ }
156
+ return value;
157
+ }
158
+ function extractExpediteItems(payload) {
159
+ if (!payload)
160
+ return [];
161
+ const rawItems = payload.expediteItems;
162
+ if (!Array.isArray(rawItems))
163
+ return [];
164
+ const items = [];
165
+ for (const rawItem of rawItems) {
166
+ const item = asObjectRecord(rawItem);
167
+ if (!item)
168
+ continue;
169
+ const file = typeof item.file === 'string' && item.file.trim() ? item.file.trim() : 'unknown';
170
+ const message = typeof item.message === 'string' && item.message.trim() ? item.message.trim() : 'Expedite follow-up required';
171
+ const policy = typeof item.policy === 'string' && item.policy.trim() ? item.policy.trim() : 'expedite_followup';
172
+ items.push({ file, message, policy });
173
+ }
174
+ return items;
175
+ }
176
+ function suggestExpediteAction(file, issue, policy) {
177
+ const combined = `${issue} ${policy}`.toLowerCase();
178
+ if (combined.includes('validation') || combined.includes('input')) {
179
+ return `Minimal safe patch now: add a guard clause in ${file}. Follow-up: restore full validation rules in ${file}.`;
180
+ }
181
+ if (combined.includes('layer')
182
+ || combined.includes('direct db access')
183
+ || combined.includes('database access')
184
+ || combined.includes('ui')
185
+ || combined.includes('component')) {
186
+ return `Minimal safe patch now: route logic through an existing helper in ${file}. Follow-up: move business/data logic to a proper service layer file.`;
187
+ }
188
+ if (combined.includes('scope') || combined.includes('outside intended scope')) {
189
+ return `Minimal safe patch now: keep the change localized in ${file}. Follow-up: add ${file} to planned scope or refactor into an allowed file.`;
190
+ }
191
+ return `Minimal safe patch now: apply the smallest safe change in ${file}. Follow-up: clean up ${file} to restore full policy compliance.`;
192
+ }
193
+ function priorityRank(label) {
194
+ if (label === 'CRITICAL')
195
+ return 0;
196
+ if (label === 'WARNING')
197
+ return 1;
198
+ return 2;
199
+ }
200
+ function resolveViolationPriority(severity) {
201
+ if (severity === 'critical' || severity === 'high')
202
+ return 'CRITICAL';
203
+ return 'WARNING';
204
+ }
205
+ function dedupeSuggestions(suggestions) {
206
+ const out = [];
207
+ const seen = new Set();
208
+ for (const suggestion of suggestions) {
209
+ const key = `${suggestion.file.trim().toLowerCase()}|${suggestion.issue.trim().toLowerCase()}`;
210
+ if (seen.has(key))
211
+ continue;
212
+ seen.add(key);
213
+ out.push(suggestion);
214
+ }
215
+ return out;
216
+ }
217
+ // ---------------------------------------------------------------------------
218
+ // Core suggestion builder
219
+ // ---------------------------------------------------------------------------
220
+ function resolveTargetForViolation(file, message, policy, graph, selectedTargets,
221
+ // repairIntent → cached TargetResult for multi-violation coherence
222
+ targetHistory) {
223
+ const repairIntent = buildRepairIntent(message, policy);
224
+ if (!repairIntent) {
225
+ // No cross-file redirect needed (TODO, scope, etc.) — action is clear.
226
+ return { targetFile: undefined, reason: undefined, confidence: 'high', noStrongTarget: false };
227
+ }
228
+ const cached = targetHistory.get(repairIntent);
229
+ let result;
230
+ if (cached) {
231
+ // Fresh candidate for this specific violation's source file
232
+ const fresh = findTargetFile(file, repairIntent, graph, selectedTargets);
233
+ // Within-1-point tolerance: prefer the cached target for consistency (req 7)
234
+ if (fresh && Math.abs(fresh.score - cached.score) <= 1) {
235
+ result = cached;
236
+ }
237
+ else {
238
+ result = fresh ?? cached;
239
+ }
240
+ }
241
+ else {
242
+ result = findTargetFile(file, repairIntent, graph, selectedTargets);
243
+ if (result) {
244
+ targetHistory.set(repairIntent, result);
245
+ }
246
+ }
247
+ if (!result) {
248
+ return {
249
+ targetFile: undefined,
250
+ reason: undefined,
251
+ confidence: 'low',
252
+ noStrongTarget: true,
253
+ };
254
+ }
255
+ selectedTargets.push(result.file);
256
+ return {
257
+ targetFile: result.file,
258
+ reason: buildReason(message, policy),
259
+ confidence: resolveConfidence(result.score),
260
+ noStrongTarget: false,
261
+ };
262
+ }
263
+ function buildSuggestions(verifyOutput, graph) {
264
+ const suggestions = [];
265
+ // Shared state for same-module boost and coherence across all violations
266
+ const selectedTargets = [];
267
+ const targetHistory = new Map();
268
+ for (const violation of verifyOutput.violations) {
269
+ if (suggestions.length >= MAX_SUGGESTIONS)
270
+ break;
271
+ let targetFile;
272
+ let reason;
273
+ let confidence = 'high';
274
+ let noStrongTarget;
275
+ if (graph) {
276
+ const resolved = resolveTargetForViolation(violation.file, violation.message, violation.policy, graph, selectedTargets, targetHistory);
277
+ targetFile = resolved.targetFile;
278
+ reason = resolved.reason;
279
+ confidence = resolved.confidence;
280
+ noStrongTarget = resolved.noStrongTarget || undefined;
281
+ }
282
+ suggestions.push({
283
+ file: violation.file,
284
+ issue: violation.message,
285
+ policy: violation.policy,
286
+ suggestedAction: suggestAction(violation.file, violation.message, violation.policy, false),
287
+ targetFile,
288
+ reason,
289
+ confidence,
290
+ noStrongTarget,
291
+ source: 'violation',
292
+ priority: resolveViolationPriority(violation.severity),
293
+ });
294
+ }
295
+ for (const warning of verifyOutput.warnings) {
296
+ if (suggestions.length >= MAX_SUGGESTIONS)
297
+ break;
298
+ let targetFile;
299
+ let reason;
300
+ let confidence = 'high';
301
+ let noStrongTarget;
302
+ if (graph) {
303
+ const resolved = resolveTargetForViolation(warning.file, warning.message, warning.policy, graph, selectedTargets, targetHistory);
304
+ targetFile = resolved.targetFile;
305
+ reason = resolved.reason;
306
+ confidence = resolved.confidence;
307
+ noStrongTarget = resolved.noStrongTarget || undefined;
308
+ }
309
+ suggestions.push({
310
+ file: warning.file,
311
+ issue: warning.message,
312
+ policy: warning.policy,
313
+ suggestedAction: suggestAction(warning.file, warning.message, warning.policy, false),
314
+ targetFile,
315
+ reason,
316
+ confidence,
317
+ noStrongTarget,
318
+ source: 'warning',
319
+ priority: 'WARNING',
320
+ });
321
+ }
322
+ for (const scopeIssue of verifyOutput.scopeIssues) {
323
+ if (suggestions.length >= MAX_SUGGESTIONS)
324
+ break;
325
+ const message = scopeIssue.message || 'File modified outside approved scope';
326
+ suggestions.push({
327
+ file: scopeIssue.file,
328
+ issue: message,
329
+ policy: 'scope_guard',
330
+ suggestedAction: suggestAction(scopeIssue.file, message, 'scope_guard', true),
331
+ confidence: 'high',
332
+ source: 'scope',
333
+ priority: 'SCOPE',
334
+ });
335
+ }
336
+ return dedupeSuggestions(suggestions).slice(0, MAX_SUGGESTIONS);
337
+ }
338
+ function appendExpediteSuggestions(suggestions, expediteItems) {
339
+ if (expediteItems.length === 0)
340
+ return suggestions;
341
+ const expediteSuggestions = [];
342
+ for (const item of expediteItems) {
343
+ if (expediteSuggestions.length >= MAX_SUGGESTIONS)
344
+ break;
345
+ expediteSuggestions.push({
346
+ file: item.file,
347
+ issue: `[EXPEDITE] ${item.message}`,
348
+ policy: item.policy,
349
+ suggestedAction: suggestExpediteAction(item.file, item.message, item.policy),
350
+ confidence: 'medium',
351
+ source: 'expedite',
352
+ priority: 'WARNING',
353
+ });
354
+ }
355
+ return dedupeSuggestions([...expediteSuggestions, ...suggestions]).slice(0, MAX_SUGGESTIONS);
356
+ }
357
+ // ---------------------------------------------------------------------------
358
+ // Rendering helpers
359
+ // ---------------------------------------------------------------------------
360
+ function capitalize(s) {
361
+ return s.charAt(0).toUpperCase() + s.slice(1);
362
+ }
363
+ // ---------------------------------------------------------------------------
364
+ // Patch enrichment
365
+ // ---------------------------------------------------------------------------
366
+ function enrichSuggestionsWithPatches(suggestions, fileContents) {
367
+ for (const suggestion of suggestions) {
368
+ if (suggestion.file === 'unknown')
369
+ continue;
370
+ const content = fileContents[suggestion.file];
371
+ if (!content)
372
+ continue;
373
+ const patch = (0, patch_engine_1.generatePatchForSuggestion)(suggestion, content);
374
+ if (patch) {
375
+ suggestion.patch = { file: patch.file, diff: patch.diff };
376
+ suggestion.patchConfidence = patch.patchConfidence;
377
+ }
378
+ }
379
+ }
380
+ // ---------------------------------------------------------------------------
381
+ // Rendering helpers
382
+ // ---------------------------------------------------------------------------
383
+ function printTargetedAction(item) {
384
+ const verb = buildActionVerb(item.issue, item.policy);
385
+ console.log(` → ${verb} from:`);
386
+ console.log(` ${item.file}`);
387
+ console.log(` To:`);
388
+ console.log(` ${item.targetFile}`);
389
+ console.log(` Reason:`);
390
+ console.log(` ${item.reason}`);
391
+ console.log(` Confidence:`);
392
+ console.log(` ${capitalize(item.confidence)}`);
393
+ }
394
+ function printFallbackAction(item) {
395
+ console.log(' → No strong target file found.');
396
+ console.log(' Suggested action:');
397
+ console.log(` ${item.suggestedAction}`);
398
+ console.log(' Confidence:');
399
+ console.log(' Low');
400
+ }
401
+ function printPatch(patch, confidence) {
402
+ console.log(' Suggested patch (apply manually):');
403
+ for (const line of patch.diff.split('\n')) {
404
+ if (line.startsWith('---') || line.startsWith('+++')) {
405
+ console.log(chalk.dim(` ${line}`));
406
+ }
407
+ else if (line.startsWith('@@')) {
408
+ console.log(chalk.cyan(` ${line}`));
409
+ }
410
+ else if (line.startsWith('-')) {
411
+ console.log(chalk.red(` ${line}`));
412
+ }
413
+ else if (line.startsWith('+')) {
414
+ console.log(chalk.green(` ${line}`));
415
+ }
416
+ else {
417
+ console.log(chalk.dim(` ${line}`));
418
+ }
419
+ }
420
+ if (confidence) {
421
+ console.log(chalk.dim(` Patch Confidence: ${capitalize(confidence)}`));
422
+ }
423
+ console.log(chalk.dim(` Run: neurcode patch --file ${patch.file}`));
424
+ }
425
+ function printFixPlan(suggestions, context) {
426
+ console.log(chalk.bold('\nNeurcode Fix Plan (Prioritized)'));
427
+ console.log(chalk.dim('Based on latest Neurcode verify results\n'));
428
+ const uniqueFilesCount = new Set(suggestions.map((item) => item.file)).size;
429
+ const criticalCount = suggestions.filter((item) => item.priority === 'CRITICAL').length;
430
+ console.log(chalk.bold(`${suggestions.length} actionable items across ${uniqueFilesCount} files (${criticalCount} critical)`));
431
+ console.log(chalk.dim('Based on latest verification snapshot\n'));
432
+ console.log(chalk.dim('Based on full diff analysis of current changes\n'));
433
+ if (context.verifyMessage.includes('Expedite Mode used')) {
434
+ console.log(chalk.yellow('Expedite Mode used\n'));
435
+ }
436
+ if (suggestions.length === 0) {
437
+ if (context.verifyFailed) {
438
+ console.log(chalk.yellow('Verify failed, but no actionable items were derived from the current verify payload.'));
439
+ console.log(chalk.yellow(`Verify exited with code ${context.verifyExitCode}.`));
440
+ if (context.verifyMessage) {
441
+ console.log(chalk.dim(`Details: ${context.verifyMessage}`));
442
+ }
443
+ console.log('');
444
+ return;
445
+ }
446
+ console.log(chalk.green('No issues detected in current diff context.'));
447
+ if (context.diffEmpty) {
448
+ console.log(chalk.dim('Tip: Ensure changes are staged or run against a base branch.'));
449
+ }
450
+ console.log('');
451
+ return;
452
+ }
453
+ if (context.verifyFailed) {
454
+ console.log(chalk.yellow(`⚠️ Verify exited with code ${context.verifyExitCode}; showing best-effort fix plan from verify payload.`));
455
+ if (context.verifyMessage) {
456
+ console.log(chalk.dim(` ${context.verifyMessage}`));
457
+ }
458
+ console.log('');
459
+ }
460
+ const byFile = new Map();
461
+ for (const suggestion of suggestions) {
462
+ const key = suggestion.file || 'unknown';
463
+ const current = byFile.get(key) || [];
464
+ current.push(suggestion);
465
+ byFile.set(key, current);
466
+ }
467
+ const grouped = [...byFile.entries()].sort((left, right) => {
468
+ const leftRank = Math.min(...left[1].map((item) => priorityRank(item.priority)));
469
+ const rightRank = Math.min(...right[1].map((item) => priorityRank(item.priority)));
470
+ return leftRank - rightRank;
471
+ });
472
+ const colorPriority = (priority) => {
473
+ if (priority === 'CRITICAL')
474
+ return chalk.red(`[${priority}]`);
475
+ if (priority === 'WARNING')
476
+ return chalk.yellow(`[${priority}]`);
477
+ return chalk.cyan(`[${priority}]`);
478
+ };
479
+ for (let index = 0; index < grouped.length; index += 1) {
480
+ const [file, items] = grouped[index];
481
+ const filePriority = items.reduce((best, item) => (priorityRank(item.priority) < priorityRank(best) ? item.priority : best), 'SCOPE');
482
+ const sortedItems = [...items].sort((left, right) => priorityRank(left.priority) - priorityRank(right.priority));
483
+ const issueLabel = sortedItems.length === 1 ? 'issue' : 'issues';
484
+ console.log(`${colorPriority(filePriority)} ${chalk.cyan(file)} (${sortedItems.length} ${issueLabel})`);
485
+ for (const item of sortedItems) {
486
+ console.log(`* [${item.priority}] ${item.issue} (policy: ${item.policy})`);
487
+ if (item.targetFile && item.reason) {
488
+ printTargetedAction(item);
489
+ }
490
+ else if (item.noStrongTarget) {
491
+ printFallbackAction(item);
492
+ }
493
+ else {
494
+ console.log(` → ${item.suggestedAction}`);
495
+ }
496
+ if (item.patch?.diff) {
497
+ printPatch(item.patch, item.patchConfidence);
498
+ }
499
+ console.log('');
500
+ }
501
+ const showNextHint = index < 2 && index + 1 < grouped.length;
502
+ if (showNextHint) {
503
+ const nextFile = grouped[index + 1][0];
504
+ console.log(chalk.dim(`Next: ${nextFile}\n`));
505
+ }
506
+ }
507
+ console.log(chalk.bold('Fix highest priority issues first, then re-run `neurcode verify` to confirm resolution\n'));
508
+ }
509
+ function emitFixJson(payload) {
510
+ (0, cli_json_1.emitJson)(payload);
511
+ }
512
+ // ---------------------------------------------------------------------------
513
+ // Auto-fix
514
+ // ---------------------------------------------------------------------------
515
+ function isEligibleForAutoFix(suggestion) {
516
+ // Patch must exist and have already passed the safety gate (isPatchSafe is
517
+ // enforced during enrichSuggestionsWithPatches — no need to re-run it here).
518
+ return !!(suggestion.patch?.diff) && suggestion.patchConfidence === 'high';
519
+ }
520
+ async function runAutoFix(suggestions, verifyArgs, json) {
521
+ // Only talk about suggestions that have patches; skip everything else silently.
522
+ const patchable = suggestions.filter((s) => !!s.patch?.diff);
523
+ const results = [];
524
+ for (const suggestion of patchable) {
525
+ if (!isEligibleForAutoFix(suggestion)) {
526
+ results.push({
527
+ file: suggestion.file,
528
+ status: 'skipped',
529
+ reason: suggestion.patchConfidence ? `${suggestion.patchConfidence} confidence` : 'no patch',
530
+ });
531
+ continue;
532
+ }
533
+ const filePath = (0, path_1.resolve)(process.cwd(), suggestion.file);
534
+ if (!(0, fs_1.existsSync)(filePath)) {
535
+ results.push({ file: suggestion.file, status: 'skipped', reason: 'file not found' });
536
+ continue;
537
+ }
538
+ let content;
539
+ try {
540
+ // Re-read from disk so sequential patches in the same run see prior writes.
541
+ content = (0, fs_1.readFileSync)(filePath, 'utf-8');
542
+ }
543
+ catch {
544
+ results.push({ file: suggestion.file, status: 'skipped', reason: 'could not read file' });
545
+ continue;
546
+ }
547
+ // Apply the EXACT diff that was shown to the user — no re-generation.
548
+ const updatedContent = (0, patch_engine_1.applyUnifiedDiff)(content, suggestion.patch.diff);
549
+ if (updatedContent === null) {
550
+ results.push({ file: suggestion.file, status: 'skipped', reason: 'patch mismatch' });
551
+ continue;
552
+ }
553
+ try {
554
+ (0, fs_1.writeFileSync)(filePath, updatedContent, 'utf-8');
555
+ results.push({ file: suggestion.file, status: 'applied' });
556
+ }
557
+ catch (err) {
558
+ const msg = err instanceof Error ? err.message : 'write failed';
559
+ results.push({ file: suggestion.file, status: 'skipped', reason: msg });
560
+ }
561
+ }
562
+ const applied = results.filter((r) => r.status === 'applied');
563
+ const skipped = results.filter((r) => r.status === 'skipped');
564
+ // Re-run verify only when something was actually changed.
565
+ let verifyAfter;
566
+ if (applied.length > 0) {
567
+ const reRun = await (0, cli_json_1.runCliJson)(verifyArgs, { cwd: process.cwd() });
568
+ if (reRun.payload) {
569
+ try {
570
+ const reOutput = (0, contracts_1.parseVerifyOutput)(reRun.payload, 'neurcode-auto-fix-verify');
571
+ verifyAfter = {
572
+ exitCode: reRun.exitCode,
573
+ verdict: reOutput.verdict,
574
+ violations: reOutput.violations.length,
575
+ };
576
+ }
577
+ catch {
578
+ verifyAfter = { exitCode: reRun.exitCode, verdict: null, violations: -1 };
579
+ }
580
+ }
581
+ }
582
+ if (json) {
583
+ console.log(JSON.stringify({
584
+ success: true,
585
+ applied: applied.length,
586
+ skipped: skipped.length,
587
+ files: results,
588
+ verifyAfter,
589
+ timestamp: new Date().toISOString(),
590
+ }, null, 2));
591
+ return;
592
+ }
593
+ // Human output
594
+ if (applied.length === 0 && skipped.length === 0) {
595
+ console.log(chalk.dim('\nNo patchable suggestions found for --apply-safe.'));
596
+ console.log(chalk.dim('Run `neurcode fix` to see all issues.\n'));
597
+ return;
598
+ }
599
+ if (applied.length > 0) {
600
+ console.log(chalk.bold('\nAuto-fix applied:'));
601
+ for (const r of applied) {
602
+ console.log(chalk.green(` ✔ Applied patch to ${r.file}`));
603
+ }
604
+ }
605
+ if (skipped.length > 0) {
606
+ console.log(chalk.bold('\nSkipped:'));
607
+ for (const r of skipped) {
608
+ console.log(chalk.dim(` ✖ Skipped ${r.file}${r.reason ? ` (${r.reason})` : ''}`));
609
+ }
610
+ }
611
+ if (verifyAfter) {
612
+ const icon = verifyAfter.exitCode === 0 ? chalk.green('✔') : chalk.yellow('⚠');
613
+ const label = verifyAfter.verdict ?? (verifyAfter.exitCode === 0 ? 'PASS' : 'FAIL');
614
+ console.log(`\n${icon} Verify after auto-fix: ${chalk.bold(label)} — ${verifyAfter.violations} violation(s) remaining`);
615
+ }
616
+ console.log('');
617
+ }
618
+ function buildVerifyArgs(options) {
619
+ const args = ['verify'];
620
+ if (options.planId) {
621
+ args.push('--plan-id', options.planId);
622
+ }
623
+ if (options.projectId) {
624
+ args.push('--project-id', options.projectId);
625
+ }
626
+ if (options.policyOnly === true) {
627
+ args.push('--policy-only');
628
+ }
629
+ if (options.staged === true) {
630
+ args.push('--staged');
631
+ }
632
+ if (options.head === true) {
633
+ args.push('--head');
634
+ }
635
+ if (options.base) {
636
+ args.push('--base', options.base);
637
+ }
638
+ return args;
639
+ }
640
+ async function fixCommand(options) {
641
+ try {
642
+ const verifyRun = await (0, cli_json_1.runCliJson)(buildVerifyArgs(options), { cwd: process.cwd() });
643
+ const payload = verifyRun.payload;
644
+ if (!payload) {
645
+ const message = 'Could not parse verify output. Run `neurcode verify --json` and retry.';
646
+ if (options.json) {
647
+ emitFixJson({
648
+ success: false,
649
+ message,
650
+ timestamp: new Date().toISOString(),
651
+ verifyExitCode: verifyRun.exitCode,
652
+ verdict: null,
653
+ violations: 0,
654
+ scopeIssues: 0,
655
+ suggestions: [],
656
+ });
657
+ }
658
+ else {
659
+ console.log(chalk.bold('\nNeurcode Fix Plan (Prioritized)'));
660
+ console.log(chalk.dim('Based on latest Neurcode verify results\n'));
661
+ console.log(chalk.red(`${message}\n`));
662
+ }
663
+ process.exit(1);
664
+ }
665
+ let verifyOutput;
666
+ try {
667
+ verifyOutput = (0, contracts_1.parseVerifyOutput)(payload, 'neurcode-fix-verify-output');
668
+ }
669
+ catch (error) {
670
+ const message = error instanceof Error ? error.message : 'Invalid verify payload';
671
+ if (options.json) {
672
+ emitFixJson({
673
+ success: false,
674
+ message: `Verify output does not match contract: ${message}`,
675
+ timestamp: new Date().toISOString(),
676
+ verifyExitCode: verifyRun.exitCode,
677
+ verdict: null,
678
+ violations: 0,
679
+ scopeIssues: 0,
680
+ suggestions: [],
681
+ });
682
+ }
683
+ else {
684
+ console.log(chalk.bold('\nNeurcode Fix Plan (Prioritized)'));
685
+ console.log(chalk.dim('Based on latest Neurcode verify results\n'));
686
+ console.log(chalk.red(`Verify output does not match contract: ${message}\n`));
687
+ }
688
+ process.exit(1);
689
+ return;
690
+ }
691
+ // Scan project once; scoring and patch generation reuse this data.
692
+ let graph = null;
693
+ let fileContents = {};
694
+ try {
695
+ const scan = (0, context_engine_1.scanProject)(process.cwd());
696
+ graph = (0, context_engine_1.buildDependencyGraph)(scan);
697
+ fileContents = scan.fileContents;
698
+ }
699
+ catch {
700
+ // Non-fatal: fall back to generic suggestions without targeting or patches
701
+ }
702
+ const expediteModeUsed = payload.expediteMode === true || payload.expediteModeUsed === true;
703
+ const expediteItems = extractExpediteItems(payload);
704
+ let suggestions = buildSuggestions(verifyOutput, graph);
705
+ suggestions = appendExpediteSuggestions(suggestions, expediteItems);
706
+ enrichSuggestionsWithPatches(suggestions, fileContents);
707
+ if (verifyRun.exitCode !== 0 && suggestions.length === 0) {
708
+ suggestions = [
709
+ {
710
+ file: 'unknown',
711
+ issue: 'Verification failed but no actionable items were present in the verify payload',
712
+ policy: 'verify_runtime',
713
+ suggestedAction: 'Re-run `neurcode verify --json` and inspect the emitted payload',
714
+ confidence: 'low',
715
+ source: 'warning',
716
+ priority: 'WARNING',
717
+ },
718
+ ];
719
+ }
720
+ if (verifyOutput.violations.length > 0 && suggestions.length === 0) {
721
+ console.warn('Invariant violation: verify has issues but fix produced none');
722
+ }
723
+ if (options.applySafe) {
724
+ await runAutoFix(suggestions, buildVerifyArgs(options), options.json ?? false);
725
+ return;
726
+ }
727
+ if (!options.json) {
728
+ console.log(`Fix using verify payload: ${verifyOutput.violations.length} violations, ` +
729
+ `${verifyOutput.warnings.length} warnings, ${verifyOutput.scopeIssues.length} scope issues`);
730
+ }
731
+ const verifyFailed = verifyRun.exitCode !== 0;
732
+ const verifyMessage = verifyFailed
733
+ ? `${verifyOutput.summary.totalViolations} violations, ${verifyOutput.summary.totalWarnings} warnings, ` +
734
+ `${verifyOutput.summary.totalScopeIssues} scope issues`
735
+ : '';
736
+ const verifyMessageWithMode = expediteModeUsed
737
+ ? `${verifyMessage}${verifyMessage ? ' | ' : ''}Expedite Mode used`
738
+ : verifyMessage;
739
+ const diffEmpty = verifyOutput.summary.totalFilesChanged === 0
740
+ && verifyOutput.violations.length === 0
741
+ && verifyOutput.warnings.length === 0
742
+ && verifyOutput.scopeIssues.length === 0;
743
+ if (options.json) {
744
+ emitFixJson({
745
+ success: true,
746
+ message: suggestions.length > 0 ? 'Fix plan generated from latest verify result.' : 'No fix actions required.',
747
+ timestamp: new Date().toISOString(),
748
+ verifyExitCode: verifyRun.exitCode,
749
+ verdict: verifyOutput.verdict,
750
+ violations: verifyOutput.violations.length,
751
+ scopeIssues: verifyOutput.scopeIssues.length,
752
+ suggestions,
753
+ });
754
+ return;
755
+ }
756
+ printFixPlan(suggestions, {
757
+ diffEmpty,
758
+ verifyFailed,
759
+ verifyExitCode: verifyRun.exitCode,
760
+ verifyMessage: verifyMessageWithMode,
761
+ });
762
+ }
763
+ catch (error) {
764
+ const message = error instanceof Error ? error.message : 'Unknown fix command failure';
765
+ if (options.json) {
766
+ emitFixJson({
767
+ success: false,
768
+ message,
769
+ timestamp: new Date().toISOString(),
770
+ verifyExitCode: 1,
771
+ verdict: null,
772
+ violations: 0,
773
+ scopeIssues: 0,
774
+ suggestions: [],
775
+ });
776
+ }
777
+ else {
778
+ console.log(chalk.bold('\nNeurcode Fix Plan (Prioritized)'));
779
+ console.log(chalk.dim('Based on latest Neurcode verify results\n'));
780
+ console.log(chalk.red(`Failed to generate fix plan: ${message}\n`));
781
+ }
782
+ process.exit(1);
783
+ }
784
+ }
785
+ //# sourceMappingURL=fix.js.map