@neurcode-ai/cli 0.9.49 → 0.9.59

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 (109) hide show
  1. package/dist/commands/fix.d.ts +1 -0
  2. package/dist/commands/fix.d.ts.map +1 -1
  3. package/dist/commands/fix.js +710 -29
  4. package/dist/commands/fix.js.map +1 -1
  5. package/dist/commands/generate.d.ts.map +1 -1
  6. package/dist/commands/generate.js +16 -1
  7. package/dist/commands/generate.js.map +1 -1
  8. package/dist/commands/patch-apply.d.ts +7 -0
  9. package/dist/commands/patch-apply.d.ts.map +1 -0
  10. package/dist/commands/patch-apply.js +85 -0
  11. package/dist/commands/patch-apply.js.map +1 -0
  12. package/dist/commands/start-intent.d.ts.map +1 -1
  13. package/dist/commands/start-intent.js +17 -2
  14. package/dist/commands/start-intent.js.map +1 -1
  15. package/dist/commands/verify.d.ts.map +1 -1
  16. package/dist/commands/verify.js +372 -71
  17. package/dist/commands/verify.js.map +1 -1
  18. package/dist/context-engine/graph.d.ts +6 -0
  19. package/dist/context-engine/graph.d.ts.map +1 -0
  20. package/dist/context-engine/graph.js +55 -0
  21. package/dist/context-engine/graph.js.map +1 -0
  22. package/dist/context-engine/index.d.ts +14 -0
  23. package/dist/context-engine/index.d.ts.map +1 -0
  24. package/dist/context-engine/index.js +26 -0
  25. package/dist/context-engine/index.js.map +1 -0
  26. package/dist/context-engine/scanner.d.ts +6 -0
  27. package/dist/context-engine/scanner.d.ts.map +1 -0
  28. package/dist/context-engine/scanner.js +62 -0
  29. package/dist/context-engine/scanner.js.map +1 -0
  30. package/dist/context-engine/scorer.d.ts +9 -0
  31. package/dist/context-engine/scorer.d.ts.map +1 -0
  32. package/dist/context-engine/scorer.js +112 -0
  33. package/dist/context-engine/scorer.js.map +1 -0
  34. package/dist/context-engine/suggestions.d.ts +12 -0
  35. package/dist/context-engine/suggestions.d.ts.map +1 -0
  36. package/dist/context-engine/suggestions.js +22 -0
  37. package/dist/context-engine/suggestions.js.map +1 -0
  38. package/dist/daemon/server.d.ts +23 -0
  39. package/dist/daemon/server.d.ts.map +1 -0
  40. package/dist/daemon/server.js +222 -0
  41. package/dist/daemon/server.js.map +1 -0
  42. package/dist/index.js +22 -0
  43. package/dist/index.js.map +1 -1
  44. package/dist/intent-engine/coverage.d.ts +69 -0
  45. package/dist/intent-engine/coverage.d.ts.map +1 -0
  46. package/dist/intent-engine/coverage.js +140 -0
  47. package/dist/intent-engine/coverage.js.map +1 -0
  48. package/dist/intent-engine/flow-rules.d.ts +21 -0
  49. package/dist/intent-engine/flow-rules.d.ts.map +1 -0
  50. package/dist/intent-engine/flow-rules.js +83 -0
  51. package/dist/intent-engine/flow-rules.js.map +1 -0
  52. package/dist/intent-engine/flow-validator.d.ts +29 -0
  53. package/dist/intent-engine/flow-validator.d.ts.map +1 -0
  54. package/dist/intent-engine/flow-validator.js +202 -0
  55. package/dist/intent-engine/flow-validator.js.map +1 -0
  56. package/dist/intent-engine/graph.d.ts +33 -0
  57. package/dist/intent-engine/graph.d.ts.map +1 -0
  58. package/dist/intent-engine/graph.js +67 -0
  59. package/dist/intent-engine/graph.js.map +1 -0
  60. package/dist/intent-engine/index.d.ts +35 -0
  61. package/dist/intent-engine/index.d.ts.map +1 -0
  62. package/dist/intent-engine/index.js +94 -0
  63. package/dist/intent-engine/index.js.map +1 -0
  64. package/dist/intent-engine/indexer.d.ts +18 -0
  65. package/dist/intent-engine/indexer.d.ts.map +1 -0
  66. package/dist/intent-engine/indexer.js +100 -0
  67. package/dist/intent-engine/indexer.js.map +1 -0
  68. package/dist/intent-engine/matcher.d.ts +35 -0
  69. package/dist/intent-engine/matcher.d.ts.map +1 -0
  70. package/dist/intent-engine/matcher.js +522 -0
  71. package/dist/intent-engine/matcher.js.map +1 -0
  72. package/dist/intent-engine/parser.d.ts +12 -0
  73. package/dist/intent-engine/parser.d.ts.map +1 -0
  74. package/dist/intent-engine/parser.js +93 -0
  75. package/dist/intent-engine/parser.js.map +1 -0
  76. package/dist/intent-engine/regression.d.ts +32 -0
  77. package/dist/intent-engine/regression.d.ts.map +1 -0
  78. package/dist/intent-engine/regression.js +166 -0
  79. package/dist/intent-engine/regression.js.map +1 -0
  80. package/dist/intent-engine/requirements.d.ts +22 -0
  81. package/dist/intent-engine/requirements.d.ts.map +1 -0
  82. package/dist/intent-engine/requirements.js +147 -0
  83. package/dist/intent-engine/requirements.js.map +1 -0
  84. package/dist/intent-engine/state.d.ts +44 -0
  85. package/dist/intent-engine/state.d.ts.map +1 -0
  86. package/dist/intent-engine/state.js +83 -0
  87. package/dist/intent-engine/state.js.map +1 -0
  88. package/dist/patch-engine/diff.d.ts +12 -0
  89. package/dist/patch-engine/diff.d.ts.map +1 -0
  90. package/dist/patch-engine/diff.js +74 -0
  91. package/dist/patch-engine/diff.js.map +1 -0
  92. package/dist/patch-engine/generator.d.ts +13 -0
  93. package/dist/patch-engine/generator.d.ts.map +1 -0
  94. package/dist/patch-engine/generator.js +51 -0
  95. package/dist/patch-engine/generator.js.map +1 -0
  96. package/dist/patch-engine/index.d.ts +47 -0
  97. package/dist/patch-engine/index.d.ts.map +1 -0
  98. package/dist/patch-engine/index.js +182 -0
  99. package/dist/patch-engine/index.js.map +1 -0
  100. package/dist/patch-engine/patterns.d.ts +4 -0
  101. package/dist/patch-engine/patterns.d.ts.map +1 -0
  102. package/dist/patch-engine/patterns.js +99 -0
  103. package/dist/patch-engine/patterns.js.map +1 -0
  104. package/dist/utils/ai-debt-budget.d.ts +3 -2
  105. package/dist/utils/ai-debt-budget.d.ts.map +1 -1
  106. package/dist/utils/ai-debt-budget.js +83 -2
  107. package/dist/utils/ai-debt-budget.js.map +1 -1
  108. package/package.json +8 -7
  109. package/LICENSE +0 -201
@@ -1,10 +1,370 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.fixCommand = fixCommand;
4
+ const fs_1 = require("fs");
5
+ const path_1 = require("path");
4
6
  const cli_json_1 = require("../utils/cli-json");
5
- const contracts_1 = require("@neurcode-ai/contracts");
7
+ const context_engine_1 = require("../context-engine");
8
+ const patch_engine_1 = require("../patch-engine");
6
9
  const chalk = (0, cli_json_1.loadChalk)();
7
10
  const MAX_SUGGESTIONS = 10;
11
+ // Minimum context-engine score for a target file to be trusted.
12
+ const MIN_TARGET_SCORE = 3;
13
+ // ---------------------------------------------------------------------------
14
+ // Intent issue → FixSuggestion conversion
15
+ // ---------------------------------------------------------------------------
16
+ const INTENT_ISSUE_ACTIONS = {
17
+ 'intent:missing-input-validation': 'Add input validation using zod/joi/yup at the API boundary — validate req.body before processing',
18
+ 'intent:missing-role-checks': 'Add role-based access checks — use middleware or inline checks (req.user.role === "admin") before sensitive operations',
19
+ 'intent:missing-token-expiry': 'Set token expiry in jwt.sign() and implement refresh-token rotation — e.g., expiresIn: "15m"',
20
+ 'intent:missing-api-validation': 'Add a validation schema for all route handlers that read req.body — use zod.parse() or equivalent',
21
+ 'intent:missing-error-handling': 'Wrap async handlers in try/catch and return structured error responses — use next(error) for Express',
22
+ 'intent:missing-payment-idempotency': 'Add idempotency key handling to payment operations — store and check keys before processing charges',
23
+ 'intent:missing-webhook-verification': 'Verify webhook signatures before processing — use stripe.webhooks.constructEvent() or equivalent',
24
+ 'intent:db-in-ui': 'Extract the database call into a service/repository module — UI components must not import DB clients directly',
25
+ 'intent:auth-logic-in-ui': 'Move JWT signing/verification and bcrypt calls into the API/service layer — UI components must not perform cryptographic operations',
26
+ 'intent:partial-auth-no-token': 'Complete the auth flow by adding token issuance — call jwt.sign() with user payload after successful credential validation',
27
+ };
28
+ function intentIssuesToFixSuggestions(intentIssues) {
29
+ return intentIssues.map((issue) => {
30
+ const suggestedAction = INTENT_ISSUE_ACTIONS[issue.rule]
31
+ ?? `Resolve intent gap: ${issue.message}`;
32
+ const priority = issue.severity === 'high' ? 'CRITICAL' : 'WARNING';
33
+ const file = issue.files?.[0] ?? 'intent-analysis';
34
+ return {
35
+ file,
36
+ issue: issue.message,
37
+ policy: issue.rule,
38
+ suggestedAction,
39
+ confidence: 'medium',
40
+ source: 'warning',
41
+ priority,
42
+ ...(issue.files && issue.files.length > 1 ? { reason: `Also affects: ${issue.files.slice(1).join(', ')}` } : {}),
43
+ };
44
+ });
45
+ }
46
+ // ---------------------------------------------------------------------------
47
+ // Flow issue → FixSuggestion conversion (V5)
48
+ // ---------------------------------------------------------------------------
49
+ const FLOW_ISSUE_ACTIONS = {
50
+ 'flow:token-not-validated-in-middleware': {
51
+ action: 'Add jwt.verify() call in an auth middleware to validate tokens on every protected request',
52
+ targetHint: 'middleware/auth.ts',
53
+ },
54
+ 'flow:middleware-not-applied-to-routes': {
55
+ action: 'Wire authMiddleware into route definitions with router.use(authMiddleware) before protected handlers',
56
+ targetHint: 'routes/index.ts',
57
+ },
58
+ 'flow:auth-in-ui-layer': {
59
+ action: 'Move JWT signing/verification and bcrypt calls into the API or service layer — UI components must not perform cryptographic operations',
60
+ targetHint: 'services/auth.service.ts',
61
+ },
62
+ 'flow:validation-before-handler': {
63
+ action: 'Apply a validation middleware (e.g. validateBody(schema)) as the first argument before your route handler',
64
+ targetHint: 'middleware/validate.ts',
65
+ },
66
+ 'flow:service-not-separated': {
67
+ action: 'Extract DB calls from the route handler into a dedicated service or repository module and import it',
68
+ targetHint: 'services/',
69
+ },
70
+ 'flow:webhook-handler-without-verification': {
71
+ action: 'Verify webhook signature before processing — call stripe.webhooks.constructEvent() or use createHmac() on the raw body',
72
+ targetHint: 'routes/webhook.ts',
73
+ },
74
+ 'flow:payment-no-idempotency-gate': {
75
+ action: 'Add idempotency key handling before executing the charge — check for existing transaction with the same key before calling stripe.paymentIntents.create()',
76
+ targetHint: 'services/payment.service.ts',
77
+ },
78
+ 'flow:file-stored-without-type-check': {
79
+ action: 'Validate MIME type before storing — use fileFilter in multer config or check mimetype against an allowlist before writing to disk/S3',
80
+ targetHint: 'middleware/upload.ts',
81
+ },
82
+ };
83
+ function flowIssuesToFixSuggestions(flowIssues, diffFilePaths) {
84
+ return flowIssues.map((issue) => {
85
+ const guidance = FLOW_ISSUE_ACTIONS[issue.rule];
86
+ const priority = issue.severity === 'high' ? 'CRITICAL' : 'WARNING';
87
+ // Target: first known file from the issue, then fallback to diff paths or hint
88
+ const knownFile = issue.files?.[0];
89
+ const hintBasename = guidance?.targetHint ?? '';
90
+ const targetFile = knownFile ??
91
+ diffFilePaths.find((p) => hintBasename && p.includes(hintBasename.replace(/\//g, ''))) ??
92
+ diffFilePaths[0] ??
93
+ hintBasename;
94
+ const suggestedAction = guidance
95
+ ? `${guidance.action}`
96
+ : `Resolve flow gap: ${issue.message}`;
97
+ return {
98
+ file: targetFile,
99
+ issue: issue.message,
100
+ policy: issue.rule,
101
+ suggestedAction,
102
+ confidence: 'medium',
103
+ source: 'warning',
104
+ priority,
105
+ ...(issue.files && issue.files.length > 1
106
+ ? { reason: `Also affects: ${issue.files.slice(1, 4).join(', ')}${issue.files.length > 4 ? ' ...' : ''}` }
107
+ : {}),
108
+ };
109
+ });
110
+ }
111
+ // ---------------------------------------------------------------------------
112
+ // Regression → FixSuggestion conversion (V6)
113
+ // ---------------------------------------------------------------------------
114
+ function regressionToFixSuggestions(regressions, diffFilePaths) {
115
+ return regressions.map((reg) => {
116
+ // Extract the component name from the rule, if present
117
+ // e.g. "regression:component:token-expiry" → "token-expiry"
118
+ const ruleSegments = reg.rule.split(':');
119
+ const componentKey = ruleSegments.length >= 3 ? ruleSegments.slice(2).join(':') : '';
120
+ const label = componentKey
121
+ ? componentKey.split('-').map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(' ')
122
+ : '';
123
+ // Best-effort file target: prefer a file in the diff that matches the component
124
+ const targetFile = (componentKey && diffFilePaths.find((p) => p.includes(componentKey.replace(/-/g, '')))) ??
125
+ (componentKey && diffFilePaths.find((p) => new RegExp(componentKey.split('-')[0], 'i').test(p))) ??
126
+ diffFilePaths[0] ??
127
+ 'regression-analysis';
128
+ const suggestedAction = reg.type === 'component-regression'
129
+ ? `Restore ${label || componentKey} implementation — check recent commits for accidental removal`
130
+ : reg.type === 'critical-regression'
131
+ ? `Immediately restore ${label || componentKey} — this is a security-critical component that must not be absent`
132
+ : reg.type === 'flow-regression'
133
+ ? `Fix the newly introduced flow gap: ${reg.message.replace('New flow issue introduced: ', '')}`
134
+ : `Restore system coverage — identify and re-implement the components removed in recent changes`;
135
+ return {
136
+ file: targetFile,
137
+ issue: reg.message,
138
+ policy: reg.rule,
139
+ suggestedAction,
140
+ confidence: 'medium',
141
+ source: 'violation',
142
+ priority: 'CRITICAL',
143
+ };
144
+ });
145
+ }
146
+ // ---------------------------------------------------------------------------
147
+ // Coverage gap → FixSuggestion conversion
148
+ // ---------------------------------------------------------------------------
149
+ // Per-component guidance for missing components discovered via coverage analysis.
150
+ const COMPONENT_ACTIONS = {
151
+ 'input-validation': {
152
+ action: 'Add input validation at the API boundary',
153
+ hint: 'Use zod.parse(), joi.validate(), or express-validator on req.body before processing',
154
+ },
155
+ 'token-generation': {
156
+ action: 'Implement token generation',
157
+ hint: 'Call jwt.sign(payload, secret, { expiresIn: "15m" }) after successful authentication',
158
+ },
159
+ 'token-expiry': {
160
+ action: 'Add token expiry and refresh-token logic',
161
+ hint: 'Set expiresIn in jwt.sign() and implement a /refresh endpoint that issues new access tokens',
162
+ },
163
+ 'role-check': {
164
+ action: 'Implement role-based access checks',
165
+ hint: 'Add a checkRole() middleware or inline req.user.role guard before sensitive endpoints',
166
+ },
167
+ 'middleware-protection': {
168
+ action: 'Add auth middleware to protected routes',
169
+ hint: 'Apply verifyToken middleware to all routes that require authentication',
170
+ },
171
+ 'password-hashing': {
172
+ action: 'Hash passwords before storage',
173
+ hint: 'Use bcrypt.hash(password, 12) on registration and bcrypt.compare() on login',
174
+ },
175
+ 'error-handling': {
176
+ action: 'Add consistent error handling to API handlers',
177
+ hint: 'Wrap async handlers in try/catch and call next(error) or return structured error responses',
178
+ },
179
+ 'service-layer-separation': {
180
+ action: 'Extract business logic into a service layer',
181
+ hint: 'Create a *Service class or module and import it in route handlers — do not put DB queries in controllers',
182
+ },
183
+ 'auth-middleware': {
184
+ action: 'Wire auth middleware to the router',
185
+ hint: 'Use router.use(authMiddleware) or pass the middleware as a route-level guard',
186
+ },
187
+ 'response-schema': {
188
+ action: 'Define and enforce a response schema',
189
+ hint: 'Use a zod/joi schema or a response DTO class to ensure consistent API response shape',
190
+ },
191
+ 'idempotency': {
192
+ action: 'Add idempotency key handling to payment operations',
193
+ hint: 'Accept an idempotency key header, store it, and reject duplicate requests',
194
+ },
195
+ 'webhook-verification': {
196
+ action: 'Verify webhook signatures before processing',
197
+ hint: 'Use stripe.webhooks.constructEvent() or an HMAC check on the raw request body',
198
+ },
199
+ 'secure-data-handling': {
200
+ action: 'Ensure sensitive payment data is not logged or stored in plain text',
201
+ hint: 'Mask PAN digits, never log card numbers, and use tokenisation where possible',
202
+ },
203
+ 'transaction-handling': {
204
+ action: 'Wrap multi-step DB operations in transactions',
205
+ hint: 'Use prisma.$transaction() or BEGIN/COMMIT to keep multi-step writes atomic',
206
+ },
207
+ 'migration-safety': {
208
+ action: 'Add migration rollback support',
209
+ hint: 'Ensure every migration has a corresponding down() function',
210
+ },
211
+ 'connection-pooling': {
212
+ action: 'Configure a connection pool',
213
+ hint: 'Set pool.min / pool.max in your DB client config to avoid connection exhaustion',
214
+ },
215
+ 'input-sanitization': {
216
+ action: 'Sanitize user input before processing',
217
+ hint: 'Use DOMPurify, sanitize-html, or a validation library to strip unsafe content',
218
+ },
219
+ };
220
+ function coverageGapsToFixSuggestions(summary, diffFilePaths) {
221
+ if (summary.missing.length === 0)
222
+ return [];
223
+ // Find the most relevant file for the domain (e.g. src/api/auth.ts for auth)
224
+ const domainRe = new RegExp(`\\b${summary.domain}\\b`, 'i');
225
+ const bestFile = diffFilePaths.find((p) => domainRe.test(p)) ??
226
+ diffFilePaths.find((p) => /\/(api|routes?|handler|controller)/i.test(p)) ??
227
+ diffFilePaths[0] ??
228
+ `src/${summary.domain}/index.ts`;
229
+ // V4: critical missing components come first, marked CRITICAL priority
230
+ const criticalMissingSet = new Set(summary.criticalMissing ?? []);
231
+ const criticalKeys = summary.missing.filter((k) => criticalMissingSet.has(k));
232
+ const otherKeys = summary.missing.filter((k) => !criticalMissingSet.has(k));
233
+ const orderedKeys = [...criticalKeys, ...otherKeys];
234
+ return orderedKeys.map((key) => {
235
+ const isCriticalKey = criticalMissingSet.has(key);
236
+ const guidance = COMPONENT_ACTIONS[key];
237
+ const label = key.split('-').map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
238
+ const suggestedAction = guidance
239
+ ? `${guidance.action} — ${guidance.hint}`
240
+ : `Implement ${label} for the ${summary.domain} domain`;
241
+ return {
242
+ file: bestFile,
243
+ issue: `${isCriticalKey ? '[CRITICAL] ' : ''}${label} not detected in ${summary.domain} implementation (coverage: ${summary.coveragePct}%)`,
244
+ policy: `intent:coverage:${key}`,
245
+ suggestedAction,
246
+ confidence: 'medium',
247
+ source: 'warning',
248
+ priority: (isCriticalKey ? 'CRITICAL' : 'WARNING'),
249
+ };
250
+ });
251
+ }
252
+ // ---------------------------------------------------------------------------
253
+ // Reason templates (req 4) — policy-keyed, no runtime string assembly
254
+ // ---------------------------------------------------------------------------
255
+ const REASON_TEMPLATES = {
256
+ layering: 'UI layer should not directly access database',
257
+ validation: 'Input must be validated before request handling',
258
+ scope: 'Changes must stay within planned scope to prevent drift',
259
+ default: 'This change violates project architecture guidelines',
260
+ };
261
+ // ---------------------------------------------------------------------------
262
+ // Targeted suggestion helpers
263
+ // ---------------------------------------------------------------------------
264
+ function buildRepairIntent(issue, policy) {
265
+ const combined = `${issue} ${policy}`.toLowerCase();
266
+ // Violations with no useful cross-file redirect target
267
+ if (combined.includes('todo') || combined.includes('fixme'))
268
+ return null;
269
+ if (policy === 'scope_guard' || combined.includes('scope'))
270
+ return null;
271
+ if (policy === 'verify_runtime')
272
+ return null;
273
+ if (combined.includes('db') || combined.includes('database') ||
274
+ combined.includes('query') || combined.includes('sql') ||
275
+ combined.includes('data access') || combined.includes('direct access') ||
276
+ policy.includes('layer') || policy.includes('db') || policy.includes('layering')) {
277
+ return 'service layer database repository core';
278
+ }
279
+ if (combined.includes('validation') || combined.includes('validate') ||
280
+ combined.includes('input') || policy.includes('validation')) {
281
+ return 'validation schema middleware validator';
282
+ }
283
+ if (combined.includes('auth') || combined.includes('authentication') ||
284
+ combined.includes('token') || combined.includes('jwt') ||
285
+ policy.includes('auth')) {
286
+ return 'authentication middleware guard auth';
287
+ }
288
+ return issue;
289
+ }
290
+ function getModulePrefix(filePath) {
291
+ const parts = filePath.replace(/\\/g, '/').split('/').filter(Boolean);
292
+ return parts.slice(0, 2).join('/');
293
+ }
294
+ /**
295
+ * Find the best target file for a repair intent.
296
+ *
297
+ * Applies:
298
+ * - Same-module boost: +2 to candidates sharing a module prefix with any
299
+ * previously selected target (drives coherence across violations).
300
+ * - Safety threshold: only returns a result when adjusted score >= MIN_TARGET_SCORE.
301
+ */
302
+ function findTargetFile(sourceFile, repairIntent, graph, selectedTargets) {
303
+ const scored = (0, context_engine_1.scoreFiles)(repairIntent, graph);
304
+ const selectedPrefixes = new Set(selectedTargets.map(getModulePrefix));
305
+ // Exclude source file; apply same-module boost
306
+ const adjusted = scored
307
+ .filter((s) => s.file !== sourceFile)
308
+ .map((s) => ({
309
+ file: s.file,
310
+ score: s.score + (selectedPrefixes.has(getModulePrefix(s.file)) ? 2 : 0),
311
+ }));
312
+ adjusted.sort((a, b) => b.score - a.score);
313
+ const best = adjusted.find((s) => s.score >= MIN_TARGET_SCORE);
314
+ if (!best)
315
+ return undefined;
316
+ return { file: best.file, score: best.score };
317
+ }
318
+ function buildReason(issue, policy) {
319
+ const policyLower = policy.toLowerCase();
320
+ const combined = `${issue} ${policy}`.toLowerCase();
321
+ // Policy-name takes precedence
322
+ if (policyLower.includes('layer') || policyLower.includes('db') ||
323
+ policyLower.includes('database') || policyLower.includes('layering')) {
324
+ return REASON_TEMPLATES.layering;
325
+ }
326
+ if (policyLower.includes('validation') || policyLower.includes('validate') ||
327
+ policyLower.includes('input_validation')) {
328
+ return REASON_TEMPLATES.validation;
329
+ }
330
+ if (policyLower === 'scope_guard' || policyLower.includes('scope')) {
331
+ return REASON_TEMPLATES.scope;
332
+ }
333
+ // Issue-text fallback
334
+ if (combined.includes('db') || combined.includes('database') || combined.includes('query') || combined.includes('data access')) {
335
+ return REASON_TEMPLATES.layering;
336
+ }
337
+ if (combined.includes('validation') || combined.includes('validate') || combined.includes('input')) {
338
+ return REASON_TEMPLATES.validation;
339
+ }
340
+ return REASON_TEMPLATES.default;
341
+ }
342
+ function resolveConfidence(score) {
343
+ if (score >= 6)
344
+ return 'high';
345
+ if (score >= MIN_TARGET_SCORE)
346
+ return 'medium';
347
+ return 'low';
348
+ }
349
+ function buildActionVerb(issue, policy) {
350
+ const combined = `${issue} ${policy}`.toLowerCase();
351
+ if (combined.includes('db') || combined.includes('database') || combined.includes('query') || combined.includes('sql')) {
352
+ return 'Move DB query';
353
+ }
354
+ if (combined.includes('data access') || combined.includes('direct access') || policy.includes('layer') || policy.includes('layering')) {
355
+ return 'Move data access logic';
356
+ }
357
+ if (combined.includes('validation') || combined.includes('validate')) {
358
+ return 'Add input validation';
359
+ }
360
+ if (combined.includes('auth') || combined.includes('authentication')) {
361
+ return 'Move authentication logic';
362
+ }
363
+ return 'Move logic';
364
+ }
365
+ // ---------------------------------------------------------------------------
366
+ // Legacy generic action (used as fallback suggestedAction text)
367
+ // ---------------------------------------------------------------------------
8
368
  function suggestAction(file, issue, policy, isScopeIssue) {
9
369
  const combined = `${issue} ${policy}`.toLowerCase();
10
370
  const withContextHint = (message) => `${message} (from current diff)`;
@@ -92,16 +452,80 @@ function dedupeSuggestions(suggestions) {
92
452
  }
93
453
  return out;
94
454
  }
95
- function buildSuggestions(verifyOutput) {
455
+ // ---------------------------------------------------------------------------
456
+ // Core suggestion builder
457
+ // ---------------------------------------------------------------------------
458
+ function resolveTargetForViolation(file, message, policy, graph, selectedTargets,
459
+ // repairIntent → cached TargetResult for multi-violation coherence
460
+ targetHistory) {
461
+ const repairIntent = buildRepairIntent(message, policy);
462
+ if (!repairIntent) {
463
+ // No cross-file redirect needed (TODO, scope, etc.) — action is clear.
464
+ return { targetFile: undefined, reason: undefined, confidence: 'high', noStrongTarget: false };
465
+ }
466
+ const cached = targetHistory.get(repairIntent);
467
+ let result;
468
+ if (cached) {
469
+ // Fresh candidate for this specific violation's source file
470
+ const fresh = findTargetFile(file, repairIntent, graph, selectedTargets);
471
+ // Within-1-point tolerance: prefer the cached target for consistency (req 7)
472
+ if (fresh && Math.abs(fresh.score - cached.score) <= 1) {
473
+ result = cached;
474
+ }
475
+ else {
476
+ result = fresh ?? cached;
477
+ }
478
+ }
479
+ else {
480
+ result = findTargetFile(file, repairIntent, graph, selectedTargets);
481
+ if (result) {
482
+ targetHistory.set(repairIntent, result);
483
+ }
484
+ }
485
+ if (!result) {
486
+ return {
487
+ targetFile: undefined,
488
+ reason: undefined,
489
+ confidence: 'low',
490
+ noStrongTarget: true,
491
+ };
492
+ }
493
+ selectedTargets.push(result.file);
494
+ return {
495
+ targetFile: result.file,
496
+ reason: buildReason(message, policy),
497
+ confidence: resolveConfidence(result.score),
498
+ noStrongTarget: false,
499
+ };
500
+ }
501
+ function buildSuggestions(verifyOutput, graph) {
96
502
  const suggestions = [];
503
+ // Shared state for same-module boost and coherence across all violations
504
+ const selectedTargets = [];
505
+ const targetHistory = new Map();
97
506
  for (const violation of verifyOutput.violations) {
98
507
  if (suggestions.length >= MAX_SUGGESTIONS)
99
508
  break;
509
+ let targetFile;
510
+ let reason;
511
+ let confidence = 'high';
512
+ let noStrongTarget;
513
+ if (graph) {
514
+ const resolved = resolveTargetForViolation(violation.file, violation.message, violation.policy, graph, selectedTargets, targetHistory);
515
+ targetFile = resolved.targetFile;
516
+ reason = resolved.reason;
517
+ confidence = resolved.confidence;
518
+ noStrongTarget = resolved.noStrongTarget || undefined;
519
+ }
100
520
  suggestions.push({
101
521
  file: violation.file,
102
522
  issue: violation.message,
103
523
  policy: violation.policy,
104
524
  suggestedAction: suggestAction(violation.file, violation.message, violation.policy, false),
525
+ targetFile,
526
+ reason,
527
+ confidence,
528
+ noStrongTarget,
105
529
  source: 'violation',
106
530
  priority: resolveViolationPriority(violation.severity),
107
531
  });
@@ -109,11 +533,26 @@ function buildSuggestions(verifyOutput) {
109
533
  for (const warning of verifyOutput.warnings) {
110
534
  if (suggestions.length >= MAX_SUGGESTIONS)
111
535
  break;
536
+ let targetFile;
537
+ let reason;
538
+ let confidence = 'high';
539
+ let noStrongTarget;
540
+ if (graph) {
541
+ const resolved = resolveTargetForViolation(warning.file, warning.message, warning.policy, graph, selectedTargets, targetHistory);
542
+ targetFile = resolved.targetFile;
543
+ reason = resolved.reason;
544
+ confidence = resolved.confidence;
545
+ noStrongTarget = resolved.noStrongTarget || undefined;
546
+ }
112
547
  suggestions.push({
113
548
  file: warning.file,
114
549
  issue: warning.message,
115
550
  policy: warning.policy,
116
551
  suggestedAction: suggestAction(warning.file, warning.message, warning.policy, false),
552
+ targetFile,
553
+ reason,
554
+ confidence,
555
+ noStrongTarget,
117
556
  source: 'warning',
118
557
  priority: 'WARNING',
119
558
  });
@@ -127,6 +566,7 @@ function buildSuggestions(verifyOutput) {
127
566
  issue: message,
128
567
  policy: 'scope_guard',
129
568
  suggestedAction: suggestAction(scopeIssue.file, message, 'scope_guard', true),
569
+ confidence: 'high',
130
570
  source: 'scope',
131
571
  priority: 'SCOPE',
132
572
  });
@@ -145,12 +585,81 @@ function appendExpediteSuggestions(suggestions, expediteItems) {
145
585
  issue: `[EXPEDITE] ${item.message}`,
146
586
  policy: item.policy,
147
587
  suggestedAction: suggestExpediteAction(item.file, item.message, item.policy),
588
+ confidence: 'medium',
148
589
  source: 'expedite',
149
590
  priority: 'WARNING',
150
591
  });
151
592
  }
152
593
  return dedupeSuggestions([...expediteSuggestions, ...suggestions]).slice(0, MAX_SUGGESTIONS);
153
594
  }
595
+ // ---------------------------------------------------------------------------
596
+ // Rendering helpers
597
+ // ---------------------------------------------------------------------------
598
+ function capitalize(s) {
599
+ return s.charAt(0).toUpperCase() + s.slice(1);
600
+ }
601
+ // ---------------------------------------------------------------------------
602
+ // Patch enrichment
603
+ // ---------------------------------------------------------------------------
604
+ function enrichSuggestionsWithPatches(suggestions, fileContents) {
605
+ for (const suggestion of suggestions) {
606
+ if (suggestion.file === 'unknown')
607
+ continue;
608
+ const content = fileContents[suggestion.file];
609
+ if (!content)
610
+ continue;
611
+ const patch = (0, patch_engine_1.generatePatchForSuggestion)(suggestion, content);
612
+ if (patch) {
613
+ suggestion.patch = { file: patch.file, diff: patch.diff };
614
+ suggestion.patchConfidence = patch.patchConfidence;
615
+ }
616
+ }
617
+ }
618
+ // ---------------------------------------------------------------------------
619
+ // Rendering helpers
620
+ // ---------------------------------------------------------------------------
621
+ function printTargetedAction(item) {
622
+ const verb = buildActionVerb(item.issue, item.policy);
623
+ console.log(` → ${verb} from:`);
624
+ console.log(` ${item.file}`);
625
+ console.log(` To:`);
626
+ console.log(` ${item.targetFile}`);
627
+ console.log(` Reason:`);
628
+ console.log(` ${item.reason}`);
629
+ console.log(` Confidence:`);
630
+ console.log(` ${capitalize(item.confidence)}`);
631
+ }
632
+ function printFallbackAction(item) {
633
+ console.log(' → No strong target file found.');
634
+ console.log(' Suggested action:');
635
+ console.log(` ${item.suggestedAction}`);
636
+ console.log(' Confidence:');
637
+ console.log(' Low');
638
+ }
639
+ function printPatch(patch, confidence) {
640
+ console.log(' Suggested patch (apply manually):');
641
+ for (const line of patch.diff.split('\n')) {
642
+ if (line.startsWith('---') || line.startsWith('+++')) {
643
+ console.log(chalk.dim(` ${line}`));
644
+ }
645
+ else if (line.startsWith('@@')) {
646
+ console.log(chalk.cyan(` ${line}`));
647
+ }
648
+ else if (line.startsWith('-')) {
649
+ console.log(chalk.red(` ${line}`));
650
+ }
651
+ else if (line.startsWith('+')) {
652
+ console.log(chalk.green(` ${line}`));
653
+ }
654
+ else {
655
+ console.log(chalk.dim(` ${line}`));
656
+ }
657
+ }
658
+ if (confidence) {
659
+ console.log(chalk.dim(` Patch Confidence: ${capitalize(confidence)}`));
660
+ }
661
+ console.log(chalk.dim(` Run: neurcode patch --file ${patch.file}`));
662
+ }
154
663
  function printFixPlan(suggestions, context) {
155
664
  console.log(chalk.bold('\nNeurcode Fix Plan (Prioritized)'));
156
665
  console.log(chalk.dim('Based on latest Neurcode verify results\n'));
@@ -213,7 +722,18 @@ function printFixPlan(suggestions, context) {
213
722
  console.log(`${colorPriority(filePriority)} ${chalk.cyan(file)} (${sortedItems.length} ${issueLabel})`);
214
723
  for (const item of sortedItems) {
215
724
  console.log(`* [${item.priority}] ${item.issue} (policy: ${item.policy})`);
216
- console.log(` → ${item.suggestedAction}`);
725
+ if (item.targetFile && item.reason) {
726
+ printTargetedAction(item);
727
+ }
728
+ else if (item.noStrongTarget) {
729
+ printFallbackAction(item);
730
+ }
731
+ else {
732
+ console.log(` → ${item.suggestedAction}`);
733
+ }
734
+ if (item.patch?.diff) {
735
+ printPatch(item.patch, item.patchConfidence);
736
+ }
217
737
  console.log('');
218
738
  }
219
739
  const showNextHint = index < 2 && index + 1 < grouped.length;
@@ -227,6 +747,112 @@ function printFixPlan(suggestions, context) {
227
747
  function emitFixJson(payload) {
228
748
  (0, cli_json_1.emitJson)(payload);
229
749
  }
750
+ // ---------------------------------------------------------------------------
751
+ // Auto-fix
752
+ // ---------------------------------------------------------------------------
753
+ function isEligibleForAutoFix(suggestion) {
754
+ // Patch must exist and have already passed the safety gate (isPatchSafe is
755
+ // enforced during enrichSuggestionsWithPatches — no need to re-run it here).
756
+ return !!(suggestion.patch?.diff) && suggestion.patchConfidence === 'high';
757
+ }
758
+ async function runAutoFix(suggestions, verifyArgs, json) {
759
+ // Only talk about suggestions that have patches; skip everything else silently.
760
+ const patchable = suggestions.filter((s) => !!s.patch?.diff);
761
+ const results = [];
762
+ for (const suggestion of patchable) {
763
+ if (!isEligibleForAutoFix(suggestion)) {
764
+ results.push({
765
+ file: suggestion.file,
766
+ status: 'skipped',
767
+ reason: suggestion.patchConfidence ? `${suggestion.patchConfidence} confidence` : 'no patch',
768
+ });
769
+ continue;
770
+ }
771
+ const filePath = (0, path_1.resolve)(process.cwd(), suggestion.file);
772
+ if (!(0, fs_1.existsSync)(filePath)) {
773
+ results.push({ file: suggestion.file, status: 'skipped', reason: 'file not found' });
774
+ continue;
775
+ }
776
+ let content;
777
+ try {
778
+ // Re-read from disk so sequential patches in the same run see prior writes.
779
+ content = (0, fs_1.readFileSync)(filePath, 'utf-8');
780
+ }
781
+ catch {
782
+ results.push({ file: suggestion.file, status: 'skipped', reason: 'could not read file' });
783
+ continue;
784
+ }
785
+ // Apply the EXACT diff that was shown to the user — no re-generation.
786
+ const updatedContent = (0, patch_engine_1.applyUnifiedDiff)(content, suggestion.patch.diff);
787
+ if (updatedContent === null) {
788
+ results.push({ file: suggestion.file, status: 'skipped', reason: 'patch mismatch' });
789
+ continue;
790
+ }
791
+ try {
792
+ (0, fs_1.writeFileSync)(filePath, updatedContent, 'utf-8');
793
+ results.push({ file: suggestion.file, status: 'applied' });
794
+ }
795
+ catch (err) {
796
+ const msg = err instanceof Error ? err.message : 'write failed';
797
+ results.push({ file: suggestion.file, status: 'skipped', reason: msg });
798
+ }
799
+ }
800
+ const applied = results.filter((r) => r.status === 'applied');
801
+ const skipped = results.filter((r) => r.status === 'skipped');
802
+ // Re-run verify only when something was actually changed.
803
+ let verifyAfter;
804
+ if (applied.length > 0) {
805
+ const reRun = await (0, cli_json_1.runCliJson)(verifyArgs, { cwd: process.cwd() });
806
+ if (reRun.payload) {
807
+ try {
808
+ const reOutput = reRun.payload;
809
+ verifyAfter = {
810
+ exitCode: reRun.exitCode,
811
+ verdict: reOutput.verdict,
812
+ violations: Array.isArray(reOutput.violations) ? reOutput.violations.length : 0,
813
+ };
814
+ }
815
+ catch {
816
+ verifyAfter = { exitCode: reRun.exitCode, verdict: null, violations: -1 };
817
+ }
818
+ }
819
+ }
820
+ if (json) {
821
+ console.log(JSON.stringify({
822
+ success: true,
823
+ applied: applied.length,
824
+ skipped: skipped.length,
825
+ files: results,
826
+ verifyAfter,
827
+ timestamp: new Date().toISOString(),
828
+ }, null, 2));
829
+ return;
830
+ }
831
+ // Human output
832
+ if (applied.length === 0 && skipped.length === 0) {
833
+ console.log(chalk.dim('\nNo patchable suggestions found for --apply-safe.'));
834
+ console.log(chalk.dim('Run `neurcode fix` to see all issues.\n'));
835
+ return;
836
+ }
837
+ if (applied.length > 0) {
838
+ console.log(chalk.bold('\nAuto-fix applied:'));
839
+ for (const r of applied) {
840
+ console.log(chalk.green(` ✔ Applied patch to ${r.file}`));
841
+ }
842
+ }
843
+ if (skipped.length > 0) {
844
+ console.log(chalk.bold('\nSkipped:'));
845
+ for (const r of skipped) {
846
+ console.log(chalk.dim(` ✖ Skipped ${r.file}${r.reason ? ` (${r.reason})` : ''}`));
847
+ }
848
+ }
849
+ if (verifyAfter) {
850
+ const icon = verifyAfter.exitCode === 0 ? chalk.green('✔') : chalk.yellow('⚠');
851
+ const label = verifyAfter.verdict ?? (verifyAfter.exitCode === 0 ? 'PASS' : 'FAIL');
852
+ console.log(`\n${icon} Verify after auto-fix: ${chalk.bold(label)} — ${verifyAfter.violations} violation(s) remaining`);
853
+ }
854
+ console.log('');
855
+ }
230
856
  function buildVerifyArgs(options) {
231
857
  const args = ['verify'];
232
858
  if (options.planId) {
@@ -274,36 +900,81 @@ async function fixCommand(options) {
274
900
  }
275
901
  process.exit(1);
276
902
  }
277
- let verifyOutput;
903
+ const verifyOutput = payload;
904
+ // Scan project once; scoring and patch generation reuse this data.
905
+ let graph = null;
906
+ let fileContents = {};
278
907
  try {
279
- verifyOutput = (0, contracts_1.parseVerifyOutput)(payload, 'neurcode-fix-verify-output');
908
+ const scan = (0, context_engine_1.scanProject)(process.cwd());
909
+ graph = (0, context_engine_1.buildDependencyGraph)(scan);
910
+ fileContents = scan.fileContents;
280
911
  }
281
- catch (error) {
282
- const message = error instanceof Error ? error.message : 'Invalid verify payload';
283
- if (options.json) {
284
- emitFixJson({
285
- success: false,
286
- message: `Verify output does not match contract: ${message}`,
287
- timestamp: new Date().toISOString(),
288
- verifyExitCode: verifyRun.exitCode,
289
- verdict: null,
290
- violations: 0,
291
- scopeIssues: 0,
292
- suggestions: [],
293
- });
294
- }
295
- else {
296
- console.log(chalk.bold('\nNeurcode Fix Plan (Prioritized)'));
297
- console.log(chalk.dim('Based on latest Neurcode verify results\n'));
298
- console.log(chalk.red(`Verify output does not match contract: ${message}\n`));
299
- }
300
- process.exit(1);
301
- return;
912
+ catch {
913
+ // Non-fatal: fall back to generic suggestions without targeting or patches
302
914
  }
303
915
  const expediteModeUsed = payload.expediteMode === true || payload.expediteModeUsed === true;
304
916
  const expediteItems = extractExpediteItems(payload);
305
- let suggestions = buildSuggestions(verifyOutput);
917
+ let suggestions = buildSuggestions(verifyOutput, graph);
306
918
  suggestions = appendExpediteSuggestions(suggestions, expediteItems);
919
+ enrichSuggestionsWithPatches(suggestions, fileContents);
920
+ // ── Intent-aware suggestions ──────────────────────────────────────────
921
+ // 1. Convert intent issues (missing/misplaced/partial) to fix suggestions.
922
+ const rawIntentIssues = Array.isArray(payload.intentIssues)
923
+ ? payload.intentIssues
924
+ : [];
925
+ if (rawIntentIssues.length > 0) {
926
+ const intentSuggestions = intentIssuesToFixSuggestions(rawIntentIssues);
927
+ suggestions = [...suggestions, ...intentSuggestions];
928
+ }
929
+ // 2. Coverage-gap suggestions — one suggestion per missing component.
930
+ // These are distinct from issue-based suggestions: they tell the developer
931
+ // exactly which implementation components are still absent.
932
+ const rawIntentSummary = payload.intentSummary;
933
+ if (rawIntentSummary && rawIntentSummary.missing.length > 0) {
934
+ const diffPaths = [
935
+ ...(Array.isArray(verifyOutput.violations) ? verifyOutput.violations.map((v) => v.file).filter(Boolean) : []),
936
+ ...(Array.isArray(verifyOutput.warnings) ? verifyOutput.warnings.map((w) => w.file).filter(Boolean) : []),
937
+ ...(Array.isArray(verifyOutput.scopeIssues) ? verifyOutput.scopeIssues.map((s) => s.file).filter(Boolean) : []),
938
+ ].filter((f) => f && f !== 'unknown' && !f.startsWith('.neurcode'));
939
+ const uniquePaths = [...new Set(diffPaths)];
940
+ const coverageSuggestions = coverageGapsToFixSuggestions(rawIntentSummary, uniquePaths);
941
+ // Only add coverage suggestions that aren't already covered by intent issue suggestions
942
+ const existingPolicies = new Set(suggestions.map((s) => s.policy));
943
+ const newCoverageSuggestions = coverageSuggestions.filter((s) => !existingPolicies.has(s.policy));
944
+ suggestions = [...suggestions, ...newCoverageSuggestions];
945
+ }
946
+ // 3. V6: Regression suggestions — always top-priority (structural degradation).
947
+ const rawRegressions = Array.isArray(payload.regressions)
948
+ ? payload.regressions
949
+ : [];
950
+ if (rawRegressions.length > 0) {
951
+ const regressionDiffPaths = [
952
+ ...(Array.isArray(verifyOutput.violations) ? verifyOutput.violations.map((v) => v.file).filter(Boolean) : []),
953
+ ].filter((f) => f && f !== 'unknown' && !f.startsWith('.neurcode'));
954
+ const uniqueRegressionPaths = [...new Set(regressionDiffPaths)];
955
+ const regressionSuggestions = regressionToFixSuggestions(rawRegressions, uniqueRegressionPaths);
956
+ // Regressions are prepended before everything else
957
+ const existingRegressionPolicies = new Set(suggestions.map((s) => s.policy));
958
+ const newRegressionSuggestions = regressionSuggestions.filter((s) => !existingRegressionPolicies.has(s.policy));
959
+ suggestions = [...newRegressionSuggestions, ...suggestions];
960
+ }
961
+ // 4. V5: Flow issue suggestions — wiring and connectivity gaps.
962
+ const rawFlowIssues = Array.isArray(payload.flowIssues)
963
+ ? payload.flowIssues
964
+ : [];
965
+ if (rawFlowIssues.length > 0) {
966
+ const flowDiffPaths = [
967
+ ...(Array.isArray(verifyOutput.violations) ? verifyOutput.violations.map((v) => v.file).filter(Boolean) : []),
968
+ ...(Array.isArray(verifyOutput.scopeIssues) ? verifyOutput.scopeIssues.map((s) => s.file).filter(Boolean) : []),
969
+ ].filter((f) => f && f !== 'unknown' && !f.startsWith('.neurcode'));
970
+ const uniqueFlowPaths = [...new Set(flowDiffPaths)];
971
+ const flowSuggestions = flowIssuesToFixSuggestions(rawFlowIssues, uniqueFlowPaths);
972
+ // Deduplicate: don't re-add if a coverage/intent suggestion already covers the same rule
973
+ const existingFlowPolicies = new Set(suggestions.map((s) => s.policy));
974
+ const newFlowSuggestions = flowSuggestions.filter((s) => !existingFlowPolicies.has(s.policy));
975
+ // Flow suggestions go first (they are structural) before coverage gaps
976
+ suggestions = [...newFlowSuggestions, ...suggestions];
977
+ }
307
978
  if (verifyRun.exitCode !== 0 && suggestions.length === 0) {
308
979
  suggestions = [
309
980
  {
@@ -311,6 +982,7 @@ async function fixCommand(options) {
311
982
  issue: 'Verification failed but no actionable items were present in the verify payload',
312
983
  policy: 'verify_runtime',
313
984
  suggestedAction: 'Re-run `neurcode verify --json` and inspect the emitted payload',
985
+ confidence: 'low',
314
986
  source: 'warning',
315
987
  priority: 'WARNING',
316
988
  },
@@ -319,14 +991,23 @@ async function fixCommand(options) {
319
991
  if (verifyOutput.violations.length > 0 && suggestions.length === 0) {
320
992
  console.warn('Invariant violation: verify has issues but fix produced none');
321
993
  }
994
+ if (options.applySafe) {
995
+ await runAutoFix(suggestions, buildVerifyArgs(options), options.json ?? false);
996
+ return;
997
+ }
322
998
  if (!options.json) {
999
+ const intentNote = rawIntentIssues.length > 0 ? `, ${rawIntentIssues.length} intent issues` : '';
1000
+ const flowNote = rawFlowIssues.length > 0 ? `, ${rawFlowIssues.length} flow issues` : '';
1001
+ const regressionNote = rawRegressions.length > 0 ? `, ${rawRegressions.length} regressions` : '';
323
1002
  console.log(`Fix using verify payload: ${verifyOutput.violations.length} violations, ` +
324
- `${verifyOutput.warnings.length} warnings, ${verifyOutput.scopeIssues.length} scope issues`);
1003
+ `${verifyOutput.warnings.length} warnings, ${verifyOutput.scopeIssues.length} scope issues${intentNote}${flowNote}${regressionNote}`);
325
1004
  }
326
1005
  const verifyFailed = verifyRun.exitCode !== 0;
1006
+ const flowSuffix = rawFlowIssues.length > 0 ? `, ${rawFlowIssues.length} flow issues` : '';
1007
+ const intentSuffix = rawIntentIssues.length > 0 ? `, ${rawIntentIssues.length} intent issues` : '';
327
1008
  const verifyMessage = verifyFailed
328
1009
  ? `${verifyOutput.summary.totalViolations} violations, ${verifyOutput.summary.totalWarnings} warnings, ` +
329
- `${verifyOutput.summary.totalScopeIssues} scope issues`
1010
+ `${verifyOutput.summary.totalScopeIssues} scope issues${intentSuffix}${flowSuffix}`
330
1011
  : '';
331
1012
  const verifyMessageWithMode = expediteModeUsed
332
1013
  ? `${verifyMessage}${verifyMessage ? ' | ' : ''}Expedite Mode used`