ucn 4.1.3 → 4.2.2

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.
@@ -75,9 +75,9 @@ function isTestEntry(entry) {
75
75
  }
76
76
 
77
77
  /**
78
- * Render the conservation contract lines: ACCOUNT (always), WARNING (unparsed
79
- * files containing the symbol), FILTERED (display-filter hides). Returns [].
80
- * when no account is present (e.g. class-type context).
78
+ * Render the conservation contract lines: ACCOUNT and CONTRACT (always),
79
+ * WARNING (unparsed files containing the symbol), FILTERED (display-filter
80
+ * hides). Returns [] when no account is present (e.g. class-type context).
81
81
  */
82
82
  function formatAccountLines(account) {
83
83
  if (!account) return [];
@@ -93,6 +93,18 @@ function formatAccountLines(account) {
93
93
  line += ` (+${account.beyondText.count} beyond-text caller${account.beyondText.count === 1 ? '' : 's'})`;
94
94
  }
95
95
  lines.push(line);
96
+ const contract = account.contract || {};
97
+ const textComplete = contract.textComplete !== undefined
98
+ ? contract.textComplete
99
+ : Boolean(account.conserved) && !(account.unparsed && account.unparsed.fileCount > 0) &&
100
+ !(account.unreadableFiles && account.unreadableFiles.length > 0);
101
+ if (!textComplete) {
102
+ lines.push('CONTRACT: literal-name text partition is DEGRADED; review WARNING and unaccounted lines before acting. Semantic completeness is not claimed.');
103
+ } else if (contract.observedTextZero) {
104
+ lines.push('CONTRACT: observed-text zero only; every literal-name line was classified, but aliases, indirect calls, generated code, and runtime dispatch may exist. Not safe-delete proof.');
105
+ } else {
106
+ lines.push('CONTRACT: literal-name text partition complete; semantic completeness is not claimed (aliases, indirect calls, generated code, and runtime dispatch may exist).');
107
+ }
96
108
  if (account.unparsed && account.unparsed.fileCount > 0) {
97
109
  lines.push(`WARNING: ${account.unparsed.fileCount} unparsed file${account.unparsed.fileCount === 1 ? '' : 's'} ` +
98
110
  `contain${account.unparsed.fileCount === 1 ? 's' : ''} "${account.symbol}" ` +
@@ -184,6 +196,8 @@ function formatContextJson(context) {
184
196
  // Tier parity with the function-path callers list: class
185
197
  // usages are the confirmed-tier answer for type symbols.
186
198
  ...(c.confidence !== undefined && { confidence: c.confidence }),
199
+ ...(c.evidenceScore !== undefined && { evidenceScore: c.evidenceScore }),
200
+ ...(c.scoreKind && { scoreKind: c.scoreKind }),
187
201
  ...(c.resolution && { resolution: c.resolution }),
188
202
  ...(c.tier && { tier: c.tier })
189
203
  })),
@@ -193,6 +207,10 @@ function formatContextJson(context) {
193
207
  expression: c.content,
194
208
  callerName: c.callerName ?? null,
195
209
  tier: 'unverified',
210
+ ...(c.confidence !== undefined && { confidence: c.confidence }),
211
+ ...(c.evidenceScore !== undefined && { evidenceScore: c.evidenceScore }),
212
+ ...(c.scoreKind && { scoreKind: c.scoreKind }),
213
+ ...(c.resolution && { resolution: c.resolution }),
196
214
  ...(c.reason && { reason: c.reason }),
197
215
  ...(c.dispatchVia && { dispatchVia: c.dispatchVia }),
198
216
  ...(c.dispatchCandidates != null && { dispatchCandidates: c.dispatchCandidates }),
@@ -225,6 +243,8 @@ function formatContextJson(context) {
225
243
  ...(c.calledAs && { calledAs: c.calledAs }),
226
244
  ...(c.isFunctionReference && { functionReference: true }),
227
245
  ...(c.confidence != null && { confidence: c.confidence, resolution: c.resolution }),
246
+ ...(c.evidenceScore != null && { evidenceScore: c.evidenceScore }),
247
+ ...(c.scoreKind && { scoreKind: c.scoreKind }),
228
248
  ...(c.tier && { tier: c.tier }),
229
249
  ...(c.reachable !== undefined && { reachable: c.reachable }),
230
250
  })),
@@ -236,6 +256,8 @@ function formatContextJson(context) {
236
256
  ...(c.calledAs && { calledAs: c.calledAs }),
237
257
  ...(c.isFunctionReference && { functionReference: true }),
238
258
  ...(c.confidence != null && { confidence: c.confidence, resolution: c.resolution }),
259
+ ...(c.evidenceScore != null && { evidenceScore: c.evidenceScore }),
260
+ ...(c.scoreKind && { scoreKind: c.scoreKind }),
239
261
  tier: 'unverified',
240
262
  ...(c.reason && { reason: c.reason }),
241
263
  ...(c.dispatchVia && { dispatchVia: c.dispatchVia }),
@@ -250,6 +272,8 @@ function formatContextJson(context) {
250
272
  params: c.params, // FULL params
251
273
  weight: c.weight || 'normal', // Dependency weight: core, setup, utility
252
274
  ...(c.confidence != null && { confidence: c.confidence, resolution: c.resolution }),
275
+ ...(c.evidenceScore != null && { evidenceScore: c.evidenceScore }),
276
+ ...(c.scoreKind && { scoreKind: c.scoreKind }),
253
277
  ...(c.tier && { tier: c.tier }),
254
278
  ...(c.sites && { sites: c.sites }),
255
279
  ...(c.functionReference && { functionReference: true }),
@@ -377,7 +401,7 @@ function formatContext(ctx, options = {}) {
377
401
  if (ctx.meta) {
378
402
  const notes = [];
379
403
  if (ctx.meta.dynamicImports) { const dn = dynamicImportsNote(ctx.meta.dynamicImports, ctx.meta); if (dn) notes.push(dn); }
380
- if (ctx.meta.confidenceFiltered) notes.push(`${ctx.meta.confidenceFiltered} edge(s) below confidence threshold hidden`);
404
+ if (ctx.meta.confidenceFiltered) notes.push(`${ctx.meta.confidenceFiltered} edge(s) below evidence threshold hidden`);
381
405
  if (notes.length) {
382
406
  lines.push(` Note: ${notes.join(', ')}`);
383
407
  }
@@ -401,7 +425,7 @@ function formatContext(ctx, options = {}) {
401
425
  : `CALLERS — CONFIRMED (${callers.length}):`;
402
426
  lines.push(`${compact ? '' : '\n'}${tierHeader}`);
403
427
  const callerEvidence = options.showConfidence !== false ? formatEvidenceLine(callers) : null;
404
- if (callerEvidence && !compact) lines.push(callerEvidence);
428
+ if (callerEvidence) lines.push(callerEvidence);
405
429
  const callerReach = reachabilityDisplay(callers, hasEntrypoints, 'caller');
406
430
  const renderCaller = (c) => {
407
431
  const callerName = c.callerName ? ` [${c.callerName}]` : '';
@@ -470,7 +494,7 @@ function formatContext(ctx, options = {}) {
470
494
  const callees = ctx.callees || [];
471
495
  lines.push(`${compact ? '' : '\n'}CALLEES (${callees.length}):`);
472
496
  const calleeEvidence = options.showConfidence !== false ? formatEvidenceLine(callees) : null;
473
- if (calleeEvidence && !compact) lines.push(calleeEvidence);
497
+ if (calleeEvidence) lines.push(calleeEvidence);
474
498
  const calleeReach = reachabilityDisplay(callees, hasEntrypoints, 'callee');
475
499
  for (const c of callees) {
476
500
  const weight = c.weight && c.weight !== 'normal' ? ` [${c.weight}]` : '';
@@ -709,14 +733,18 @@ function formatAbout(about, options = {}) {
709
733
  }
710
734
  }
711
735
  if (about.confidenceFiltered) {
712
- lines.push(` Note: ${about.confidenceFiltered} edge(s) below confidence threshold hidden`);
736
+ lines.push(` Note: ${about.confidenceFiltered} edge(s) below evidence threshold hidden`);
713
737
  }
714
738
 
715
739
  // Usage summary (fast-path approximation; ACCOUNT below is the exact
716
740
  // text-ground truth — both are labeled to avoid confusion)
717
741
  lines.push('');
718
- lines.push(`USAGES: ${about.totalUsages} total`);
719
- lines.push(` ${about.usages.calls} calls, ${about.usages.imports} imports, ${about.usages.references} references`);
742
+ if (compact) {
743
+ lines.push(`USAGES: ${about.totalUsages} total (${about.usages.calls} calls, ${about.usages.imports} imports, ${about.usages.references} references)`);
744
+ } else {
745
+ lines.push(`USAGES: ${about.totalUsages} total`);
746
+ lines.push(` ${about.usages.calls} calls, ${about.usages.imports} imports, ${about.usages.references} references`);
747
+ }
720
748
 
721
749
  // Callers — CONFIRMED tier, prod before test
722
750
  const hasEntrypoints = about.hasEntrypoints !== false;
@@ -740,7 +768,7 @@ function formatAbout(about, options = {}) {
740
768
  const caller = c.callerName ? `[${c.callerName}]` : '';
741
769
  const unreachableMark = (aboutCallerReach.perLine && c.reachable === false) ? ' [unreachable]' : '';
742
770
  lines.push(` ${c.file}:${c.line} ${caller}${unreachableMark}`);
743
- lines.push(` ${c.expression}`);
771
+ if (!compact && c.expression) lines.push(` ${c.expression}`);
744
772
  };
745
773
  for (const c of prodTop) renderAboutCaller(c);
746
774
  if (testTop.length > 0) {
@@ -773,7 +801,7 @@ function formatAbout(about, options = {}) {
773
801
  for (const u of aboutUnverified.top) {
774
802
  const caller = u.callerName ? ` [${u.callerName}]` : '';
775
803
  const reason = u.reason ? ` (${unverifiedReasonLabel(u)})` : '';
776
- const expr = u.expression ? `: ${u.expression.replace(/\s+/g, ' ').slice(0, 100)}` : '';
804
+ const expr = !compact && u.expression ? `: ${u.expression.replace(/\s+/g, ' ').slice(0, 100)}` : '';
777
805
  lines.push(` ${u.file}:${u.line}${caller}${expr}${reason}`);
778
806
  }
779
807
  if (aboutUnverified.total > aboutUnverified.top.length) {
@@ -799,13 +827,13 @@ function formatAbout(about, options = {}) {
799
827
  const sideEffects = (c.sideEffects && c.sideEffects.length) ? ` {${c.sideEffects.join(',')}}` : '';
800
828
  const unreachableMark = (aboutCalleeReach.perLine && c.reachable === false) ? ' [unreachable]' : '';
801
829
  lines.push(` ${c.name}${weight}${returnSuffix}${sideEffects} - ${c.file}:${c.line} (${c.callCount}x)${unreachableMark}`);
802
- if (c.docstring) {
830
+ if (!compact && c.docstring) {
803
831
  const snip = calleeDocstringSnippet(c.docstring);
804
832
  if (snip) lines.push(` "${snip}"`);
805
833
  }
806
834
 
807
835
  // Inline expansion: show first 3 lines of callee code
808
- if (expand && root && c.file && c.startLine) {
836
+ if (!compact && expand && root && c.file && c.startLine) {
809
837
  try {
810
838
  const filePath = path.isAbsolute(c.file) ? c.file : path.join(root, c.file);
811
839
  const content = fs.readFileSync(filePath, 'utf-8');
@@ -840,6 +868,13 @@ function formatAbout(about, options = {}) {
840
868
  // Tests
841
869
  if (about.tests.totalMatches > 0) {
842
870
  lines.push('');
871
+ if (compact) {
872
+ const shown = about.tests.files.length > 0 ? `: ${about.tests.files.join(', ')}` : '';
873
+ const partial = about.tests.fileCount > about.tests.files.length
874
+ ? ` (showing ${about.tests.files.length} of ${about.tests.fileCount} files)`
875
+ : '';
876
+ lines.push(`TESTS: ${about.tests.totalMatches} matches${partial}${shown}`);
877
+ } else {
843
878
  if (about.tests.fileCount > about.tests.files.length) {
844
879
  lines.push(`TESTS: ${about.tests.totalMatches} matches in ${about.tests.fileCount} file(s), showing ${about.tests.files.length}:`);
845
880
  aboutTruncated = true;
@@ -849,6 +884,7 @@ function formatAbout(about, options = {}) {
849
884
  for (const f of about.tests.files) {
850
885
  lines.push(` ${f}`);
851
886
  }
887
+ }
852
888
  }
853
889
 
854
890
  // Other definitions
@@ -881,10 +917,13 @@ function formatAbout(about, options = {}) {
881
917
  }
882
918
 
883
919
  // Code
884
- if (about.code) {
920
+ if (about.code && !compact) {
885
921
  lines.push('');
886
922
  lines.push('─── CODE ───');
887
923
  lines.push(about.code);
924
+ } else if (about.code && compact) {
925
+ lines.push('');
926
+ lines.push(`SOURCE: omitted in compact mode; use fn ${sym.handle || sym.name} to extract it.`);
888
927
  }
889
928
 
890
929
  if (aboutTruncated) {
@@ -13,6 +13,16 @@ function formatCheck(result) {
13
13
  const lines = [];
14
14
  lines.push(`Pre-commit Check vs ${result.base}${result.staged ? ' (staged)' : ''}`);
15
15
  lines.push('═'.repeat(60));
16
+ if (result.trust) {
17
+ lines.push(`TRUST: ${result.trust.status} — UCN evidence is not semantic proof; compiler and tests are required`);
18
+ const trustDetails = [];
19
+ if (result.trust.incompleteAccounts) trustDetails.push(`${result.trust.incompleteAccounts} incomplete account(s)`);
20
+ if (result.trust.unverifiedCallSites) trustDetails.push(`${result.trust.unverifiedCallSites} unverified caller(s)`);
21
+ if (result.trust.signatureMismatches) trustDetails.push(`${result.trust.signatureMismatches} signature mismatch(es)`);
22
+ if (result.trust.filteredEdges) trustDetails.push(`${result.trust.filteredEdges} filtered edge(s)`);
23
+ if (result.trust.usageReviewSymbols) trustDetails.push(`${result.trust.usageReviewSymbols} symbol(s) need usages review`);
24
+ if (trustDetails.length > 0) lines.push(` ${trustDetails.join(' · ')}`);
25
+ }
16
26
 
17
27
  // Changed functions section
18
28
  const items = result.changed || [];
@@ -29,6 +39,7 @@ function formatCheck(result) {
29
39
  if (it.kind && it.kind !== 'changed') tags.push(it.kind.toUpperCase());
30
40
  if (it.signatureMismatches > 0) tags.push(`SIG-DRIFT(${it.signatureMismatches})`);
31
41
  if (it.orphan) tags.push('ORPHAN');
42
+ if (it.account && !it.account.textComplete) tags.push('ACCOUNT-INCOMPLETE');
32
43
  const tagStr = tags.length ? ' [' + tags.join(', ') + ']' : '';
33
44
  let callers = it.callerCount != null ? `${it.callerCount} caller${it.callerCount === 1 ? '' : 's'}` : '';
34
45
  if (it.unverifiedCallerCount > 0) {
@@ -4,10 +4,6 @@
4
4
 
5
5
  'use strict';
6
6
 
7
- function pad(n, width = 4) {
8
- return String(n).padStart(width);
9
- }
10
-
11
7
  function formatDoctor(result) {
12
8
  if (!result) return 'No project to analyze.';
13
9
  const lines = [];
@@ -32,23 +28,31 @@ function formatDoctor(result) {
32
28
  const buildHint = result.cache.buildMs ? `, ${result.cache.buildMs}ms build` : '';
33
29
  lines.push(`Cache: ${state}${buildHint}`);
34
30
  }
31
+ if (result.commandTrust) {
32
+ const c = result.commandTrust;
33
+ lines.push(`Command proofs: ${c.classified}/${c.commands} classified, ${c.oracleBacked} external-oracle-backed, ${c.unclassified} unclassified`);
34
+ lines.push(' Classification describes shipped proof coverage, not this repository\'s runtime accuracy.');
35
+ }
35
36
 
36
- // Coverage (if computed)
37
- if (result.coverage && result.coverage.total > 0) {
37
+ // Evidence profile (if computed). Confidence scores are rule labels, not
38
+ // empirically calibrated probabilities, so never call this accuracy.
39
+ const profile = result.evidenceProfile || result.coverage;
40
+ if (profile && profile.total > 0) {
38
41
  lines.push('');
39
- lines.push('Resolution coverage (sampled):');
40
- const c = result.coverage;
42
+ lines.push('Resolution evidence profile (sampled; not semantic accuracy):');
43
+ const c = profile;
41
44
  const total = c.total || 1;
42
- lines.push(` High confidence (>0.8): ${c.high} (${(c.high / total * 100).toFixed(1)}%)`);
43
- lines.push(` Medium (0.5-0.8): ${c.medium} (${(c.medium / total * 100).toFixed(1)}%)`);
44
- lines.push(` Low (<0.5): ${c.low} (${(c.low / total * 100).toFixed(1)}%)`);
45
- lines.push(` Sampled ${c.sampled} symbols → ${c.total} edges examined`);
46
- } else if (result.coverage) {
45
+ lines.push(` Confirmed evidence: ${c.confirmed} (${(c.confirmed / total * 100).toFixed(1)}%)`);
46
+ lines.push(` Unverified: ${c.unverified} (${(c.unverified / total * 100).toFixed(1)}%)`);
47
+ lines.push(` Score bands: high ${c.high}, medium ${c.medium}, low ${c.low}`);
48
+ lines.push(` Sampled ${c.sampled}/${c.candidateSymbols} pinned definitions → ${c.total} unique edge candidates`);
49
+ lines.push(` Sample quality: ${c.adequate ? 'adequate' : 'insufficient'}${c.representative ? ', all indexed languages represented' : ''}`);
50
+ } else if (profile) {
47
51
  lines.push('');
48
- lines.push('Resolution coverage: no edges in sample likely a small or isolated project.');
52
+ lines.push('Resolution evidence profile: no caller edges in the stratified sample.');
49
53
  } else {
50
54
  lines.push('');
51
- lines.push('Resolution coverage: not computed (use --deep for sampled analysis)');
55
+ lines.push('Resolution evidence profile: not computed (use --deep)');
52
56
  }
53
57
 
54
58
  // Blind spots
@@ -60,8 +64,12 @@ function formatDoctor(result) {
60
64
  ['Eval/exec calls', bs.evalCalls],
61
65
  ['Reflection', bs.reflection],
62
66
  ['Parse failures', bs.parseFailures],
67
+ ['Parser recovery', bs.parseRecoveries],
63
68
  ];
64
- const unitFor = { 'Dynamic imports': 'import', 'Eval/exec calls': 'use', 'Reflection': 'use', 'Parse failures': 'failure' };
69
+ const unitFor = {
70
+ 'Dynamic imports': 'import', 'Eval/exec calls': 'use', Reflection: 'use',
71
+ 'Parse failures': 'failure',
72
+ };
65
73
  let anyBlindSpot = false;
66
74
  for (const [label, info] of bsLines) {
67
75
  if (info && info.count > 0) {
@@ -72,7 +80,11 @@ function formatDoctor(result) {
72
80
  // never present the display cap as the population (field-report #2).
73
81
  const fileCount = info.fileCount != null ? info.fileCount : info.files.length;
74
82
  const unit = unitFor[label] || 'use';
75
- lines.push(` ${label}: ${info.count} ${unit}${info.count === 1 ? '' : 's'} in ${fileCount} file${fileCount === 1 ? '' : 's'}`);
83
+ if (label === 'Parser recovery') {
84
+ lines.push(` ${label}: ${fileCount} recovered file${fileCount === 1 ? '' : 's'} (results may be partial)`);
85
+ } else {
86
+ lines.push(` ${label}: ${info.count} ${unit}${info.count === 1 ? '' : 's'} in ${fileCount} file${fileCount === 1 ? '' : 's'}`);
87
+ }
76
88
  const shownFiles = info.files.slice(0, 3);
77
89
  const sample = shownFiles.map(f => ` - ${f}`).join('\n');
78
90
  const moreFiles = fileCount - shownFiles.length;
@@ -82,9 +94,18 @@ function formatDoctor(result) {
82
94
  }
83
95
  if (!anyBlindSpot) lines.push(' (none detected)');
84
96
 
85
- // Verdict
97
+ // Task-specific readiness. One scalar cannot honestly describe navigation,
98
+ // refactoring, and deletion risk.
86
99
  lines.push('');
87
- lines.push(`Trust level: ${result.trust}${result.trustReason ? ' — ' + result.trustReason : ''}`);
100
+ if (result.dimensions) {
101
+ lines.push('Readiness:');
102
+ for (const key of ['navigation', 'refactor', 'deletion']) {
103
+ const d = result.dimensions[key];
104
+ if (d) lines.push(` ${key}: ${d.level} — ${d.reason}`);
105
+ }
106
+ lines.push(` semantic recall: ${result.dimensions.semanticRecall.level} — ${result.dimensions.semanticRecall.reason}`);
107
+ }
108
+ lines.push(`Overall (${result.trustScope || 'legacy'}): ${result.trust}${result.trustReason ? ' — ' + result.trustReason : ''}`);
88
109
  return lines.join('\n');
89
110
  }
90
111
 
@@ -15,8 +15,6 @@
15
15
  const { advisoryLine } = require('./shared');
16
16
  const { codeUnitCompare } = require('../shared');
17
17
 
18
- const SEP_TIER = { exact: 'EXACT', partial: 'PARTIAL', uncertain: 'UNCERTAIN' };
19
-
20
18
  function pad(s, n) {
21
19
  if (typeof s !== 'string') s = String(s);
22
20
  return s.length >= n ? s : s + ' '.repeat(n - s.length);
@@ -151,6 +151,10 @@ function formatStructuralSearchJson(result) {
151
151
  * Format example result as text
152
152
  */
153
153
  function formatExample(result, name) {
154
+ if (result && !result.best && result.unverifiedCalls > 0) {
155
+ const n = result.unverifiedCalls;
156
+ return `No confirmed call example for "${name}". ${n} unverified candidate${n === 1 ? '' : 's'} require receiver/binding review (use impact or usages).`;
157
+ }
154
158
  // MEDIUM-8: when only test-file callers exist and the user didn't ask
155
159
  // for them, surface that fact explicitly instead of saying nothing was
156
160
  // found.
@@ -172,6 +176,7 @@ function formatExample(result, name) {
172
176
  lines.push(`${best.relativePath}:${best.line}`);
173
177
  const exAdvisory = advisoryLine(result.advisory);
174
178
  if (exAdvisory) lines.push(exAdvisory);
179
+ lines.push(`Evidence: ${best.evidenceTier || 'unverified'}${best.resolution ? ` (${best.resolution})` : ''}`);
175
180
  lines.push('');
176
181
 
177
182
  if (best.before) {
@@ -224,6 +229,7 @@ function formatExampleDiverse(result, name) {
224
229
  lines.push(`[${i + 1}] ${shape} — ${c.count} call${c.count === 1 ? '' : 's'} in this cluster`);
225
230
  if (!rep) continue;
226
231
  lines.push(` ${rep.relativePath || rep.file}:${rep.line}`);
232
+ lines.push(` evidence: ${rep.evidenceTier || 'unverified'}${rep.resolution ? ` (${rep.resolution})` : ''}`);
227
233
 
228
234
  if (rep.before) {
229
235
  for (let j = 0; j < rep.before.length; j++) {
@@ -256,10 +262,17 @@ function formatExampleJson(result, name) {
256
262
  // Surface the excluded-test-usages count in JSON too (fix #237 — the
257
263
  // handler note that used to carry it duplicated the text body).
258
264
  const excluded = result?.excludedTestCalls || 0;
265
+ const unverified = result?.unverifiedCalls || 0;
259
266
  return JSON.stringify({
260
267
  found: false, query: name,
261
268
  ...(excluded > 0 && { excludedTestCalls: excluded }),
262
- error: excluded > 0
269
+ ...(unverified > 0 && {
270
+ unverifiedCalls: unverified,
271
+ unverifiedCandidates: result.unverifiedCandidates || [],
272
+ }),
273
+ error: unverified > 0
274
+ ? `No confirmed call example for "${name}". ${unverified} unverified candidate${unverified === 1 ? '' : 's'} require receiver/binding review.`
275
+ : excluded > 0
263
276
  ? `No call examples found for "${name}" (excluded ${excluded} test-file usage${excluded === 1 ? '' : 's'} — pass --include-tests to include them)`
264
277
  : `No call examples found for "${name}"`,
265
278
  }, null, 2);
@@ -271,12 +284,17 @@ function formatExampleJson(result, name) {
271
284
  query: name,
272
285
  ...(result.advisory && { advisory: result.advisory }),
273
286
  totalCalls: result.totalCalls,
287
+ confirmedCalls: result.confirmedCalls || 0,
288
+ unverifiedCalls: result.unverifiedCalls || 0,
274
289
  best: {
275
290
  file: best.relativePath || best.file,
276
291
  line: best.line,
277
292
  content: best.content,
278
293
  score: best.score,
279
294
  reasons: best.reasons || [],
295
+ evidenceTier: best.evidenceTier || 'unverified',
296
+ ...(best.resolution && { resolution: best.resolution }),
297
+ ...(best.reason && { reason: best.reason }),
280
298
  ...(best.before && best.before.length > 0 && { before: best.before }),
281
299
  ...(best.after && best.after.length > 0 && { after: best.after })
282
300
  }
@@ -383,6 +401,11 @@ function formatTests(tests, name) {
383
401
  } else {
384
402
  const totalMatches = tests.reduce((sum, t) => sum + t.matches.length, 0);
385
403
  lines.push(`Found ${totalMatches} matches in ${tests.length} test file(s):\n`);
404
+ const unverified = tests.reduce((sum, t) => sum +
405
+ t.matches.filter(m => ['unverified-call', 'unverified-reference'].includes(m.matchType)).length, 0);
406
+ if (unverified > 0) {
407
+ lines.push(`UNVERIFIED: ${unverified} possible test call${unverified === 1 ? '' : 's'} need receiver/binding review.\n`);
408
+ }
386
409
 
387
410
  for (const testFile of tests) {
388
411
  lines.push(testFile.file);
@@ -390,6 +413,8 @@ function formatTests(tests, name) {
390
413
  const typeLabel = match.matchType === 'test-case' ? '[test]' :
391
414
  match.matchType === 'import' ? '[import]' :
392
415
  match.matchType === 'call' ? '[call]' :
416
+ match.matchType === 'unverified-call' ? '[unverified-call]' :
417
+ match.matchType === 'unverified-reference' ? '[unverified-ref]' :
393
418
  match.matchType === 'string-ref' ? '[string]' : '[ref]';
394
419
  lines.push(` ${match.line}: ${typeLabel} ${match.content}`);
395
420
  }
package/core/parser.js CHANGED
@@ -6,7 +6,6 @@
6
6
  */
7
7
 
8
8
  const fs = require('fs');
9
- const path = require('path');
10
9
  const { detectLanguage, getParser, getLanguageModule, isSupported } = require('../languages');
11
10
 
12
11
  /**
@@ -58,6 +57,7 @@ const { detectLanguage, getParser, getLanguageModule, isSupported } = require('.
58
57
  * @property {StateDef[]} stateObjects - All state objects
59
58
  * @property {Array} imports - Import statements (from imports.js)
60
59
  * @property {Array} exports - Export statements (from imports.js)
60
+ * @property {boolean} [parseRecovery] - Tree-sitter recovered from syntax errors; results may be partial
61
61
  */
62
62
 
63
63
  /**
package/core/project.js CHANGED
@@ -9,7 +9,7 @@ const fs = require('fs');
9
9
  const path = require('path');
10
10
  const crypto = require('crypto');
11
11
  const { expandGlob, findProjectRoot, detectProjectPattern, isTestFile, parseGitignore, DEFAULT_IGNORES, compareNames } = require('./discovery');
12
- const { extractImports, extractExports, resolveImport } = require('./imports');
12
+ const { extractImports, extractExports } = require('./imports');
13
13
  const { parse, cleanHtmlScriptTags } = require('./parser');
14
14
  const { detectLanguage, getParser, getLanguageModule, safeParse, langTraits, PARSE_OPTIONS } = require('../languages');
15
15
  const { getTokenTypeAtPosition } = require('../languages/utils');
@@ -54,6 +54,8 @@ class ProjectIndex {
54
54
  this._opUsagesCache = null; // per-operation findUsagesInCode cache (Map<"file:name", usages[]>)
55
55
  this._opTreeCache = null; // per-operation parsed-tree cache (Map<filePath, tree|null>, bounded FIFO)
56
56
  this._opLinesCache = null; // per-operation split-lines cache (Map<filePath, string[]>, bounded FIFO)
57
+ this._opInnerSymbolRangesCache = null; // per-operation sorted class-method ranges by file
58
+ this._opFlowTypeOriginCache = null; // per-operation annotation type identity results
57
59
  this.calleeIndex = null; // name -> Set<filePath> — inverted call index (built lazily)
58
60
  }
59
61
 
@@ -83,6 +85,15 @@ class ProjectIndex {
83
85
  this._opEnclosingFnCache = new Map();
84
86
  this._opTreeCache = new Map();
85
87
  this._opLinesCache = new Map();
88
+ // Query-time semantic derivations that depend only on one file's
89
+ // immutable call records. Reachability may ask findCallees() for
90
+ // thousands of symbols in the same few large files; rebuilding
91
+ // these structures per symbol turns the walk quadratic.
92
+ this._opReturnTypeFlowCache = new Map();
93
+ this._opCallsByLineCache = new Map();
94
+ this._opInnerSymbolRangesCache = new Map();
95
+ this._opFlowTypeOriginCache = new Map();
96
+ this._opImportReachCache = new Map();
86
97
  this._opDepth = 0;
87
98
  }
88
99
  this._opDepth++;
@@ -99,6 +110,11 @@ class ProjectIndex {
99
110
  this._opEnclosingFnCache = null;
100
111
  this._opTreeCache = null;
101
112
  this._opLinesCache = null;
113
+ this._opReturnTypeFlowCache = null;
114
+ this._opCallsByLineCache = null;
115
+ this._opInnerSymbolRangesCache = null;
116
+ this._opFlowTypeOriginCache = null;
117
+ this._opImportReachCache = null;
102
118
  // Free cached file content from callsCache entries (retained during
103
119
  // operation for _readFile caching, not needed between operations)
104
120
  for (const entry of this.callsCache.values()) {
@@ -120,7 +136,7 @@ class ProjectIndex {
120
136
  if (!this._opTreeCache) return null;
121
137
  const cached = this._opTreeCache.get(filePath);
122
138
  if (cached !== undefined) return cached;
123
- let tree = null;
139
+ let tree;
124
140
  try {
125
141
  const parser = getParser(language);
126
142
  tree = parser ? safeParse(parser, content, undefined, PARSE_OPTIONS) : null;
@@ -495,6 +511,7 @@ class ProjectIndex {
495
511
  exportDetails: exports,
496
512
  symbols: [],
497
513
  bindings: [],
514
+ ...(parsed.parseRecovery && { parseRecovery: true }),
498
515
  ...(importAliases && { importAliases }),
499
516
  // Module-scope assignment targets (fix #217): names a module can
500
517
  // expose WITHOUT a def/class/import binding (`render = impl`,
@@ -518,6 +535,8 @@ class ProjectIndex {
518
535
  params: item.params,
519
536
  paramsStructured: item.paramsStructured,
520
537
  returnType: item.returnType,
538
+ ...(item.returnedFunctionResult && { returnedFunctionResult: item.returnedFunctionResult }),
539
+ ...(item.isFunctionVariable && { isFunctionVariable: true }),
521
540
  ...(item.paramTypes && { paramTypes: item.paramTypes }),
522
541
  ...(item.isAsync && { isAsync: true }),
523
542
  ...(item.isGenerator && { isGenerator: true }),
@@ -550,7 +569,9 @@ class ProjectIndex {
550
569
  // just the traitImpl flag (fix #210).
551
570
  ...(item.traitName && { traitName: item.traitName }),
552
571
  ...(item.isSignature && { isSignature: true }),
553
- ...(item.memberAssigned && { memberAssigned: true })
572
+ ...(item.memberAssigned && { memberAssigned: true }),
573
+ ...(item.registryMember && { registryMember: true }),
574
+ ...(item.registryContainer && { registryContainer: item.registryContainer })
554
575
  };
555
576
  fileEntry.symbols.push(symbol);
556
577
  // Property-assignment defs (fix #269: Reply.prototype.serialize
@@ -1418,7 +1439,7 @@ class ProjectIndex {
1418
1439
  // Match both "TypeName" and "*TypeName" receivers (for Go/Rust pointer receivers)
1419
1440
  const baseTypeName = typeName.replace(/^\*/, '');
1420
1441
 
1421
- for (const [name, symbols] of this.symbols) {
1442
+ for (const [, symbols] of this.symbols) {
1422
1443
  for (const symbol of symbols) {
1423
1444
  // Skip non-method types (fields, properties, etc.)
1424
1445
  if (symbol.type === 'field' || symbol.type === 'property') {
package/core/registry.js CHANGED
@@ -125,7 +125,7 @@ const FLAG_APPLICABILITY = {
125
125
  reverseTrace: ['name', 'file', 'exclude', 'className', 'line', 'includeMethods', 'includeUncertain', 'depth', 'all', 'minConfidence', 'expandUnverified'],
126
126
  smart: ['name', 'file', 'exclude', 'className', 'line', 'includeMethods', 'includeUncertain', 'withTypes', 'minConfidence'],
127
127
  trace: ['name', 'file', 'exclude', 'className', 'line', 'includeMethods', 'includeUncertain', 'depth', 'all', 'minConfidence'],
128
- example: ['name', 'file', 'className', 'diverse', 'top', 'includeTests'],
128
+ example: ['name', 'file', 'className', 'line', 'diverse', 'top', 'includeTests'],
129
129
  related: ['name', 'file', 'className', 'line', 'top', 'all'],
130
130
  brief: ['name', 'file', 'className', 'line', 'git'],
131
131
  // Finding code
@@ -162,7 +162,7 @@ const FLAG_APPLICABILITY = {
162
162
  stacktrace: ['stack'],
163
163
  api: ['file', 'limit'],
164
164
  stats: ['functions', 'hot', 'top'],
165
- doctor: ['file', 'in', 'limit', 'deep'],
165
+ doctor: ['file', 'in', 'deep'],
166
166
  orient: ['top'],
167
167
  auditAsync: ['file', 'exclude', 'limit'],
168
168
  };