ucn 4.2.3 → 5.0.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.
- package/.claude/skills/ucn/SKILL.md +89 -77
- package/.claude/skills/ucn/references/commands.md +62 -68
- package/.claude/skills/ucn/references/trust-contract.md +31 -6
- package/README.md +438 -305
- package/assets/demo.svg +31 -0
- package/cli/index.js +430 -1385
- package/core/account.js +144 -34
- package/core/analysis.js +182 -72
- package/core/ast-analysis.js +279 -0
- package/core/bridge.js +205 -24
- package/core/brief.js +27 -58
- package/core/build-worker.js +21 -140
- package/core/cache.js +513 -11
- package/core/callers.js +4920 -456
- package/core/check.js +13 -4
- package/core/command-contracts.js +402 -0
- package/core/compilation-database.js +276 -0
- package/core/confidence.js +4 -1
- package/core/deadcode.js +397 -19
- package/core/discovery.js +359 -46
- package/core/entrypoints.js +195 -41
- package/core/execute.js +887 -81
- package/core/graph-build.js +162 -7
- package/core/graph.js +53 -77
- package/core/imports.js +65 -6
- package/core/index-ir.js +138 -0
- package/core/ir.js +195 -0
- package/core/output/analysis.js +212 -22
- package/core/output/brief.js +23 -0
- package/core/output/check.js +4 -0
- package/core/output/doctor.js +37 -6
- package/core/output/endpoints.js +5 -2
- package/core/output/extraction.js +24 -12
- package/core/output/find.js +141 -36
- package/core/output/graph.js +11 -5
- package/core/output/public.js +462 -0
- package/core/output/refactoring.js +42 -10
- package/core/output/reporting.js +97 -20
- package/core/output/search.js +24 -16
- package/core/output/shared.js +22 -1
- package/core/output/tracing.js +30 -15
- package/core/output-budget.js +295 -0
- package/core/output.js +1 -0
- package/core/parallel-build.js +44 -11
- package/core/parser.js +3 -3
- package/core/project.js +384 -187
- package/core/public-command.js +47 -0
- package/core/registry.js +247 -117
- package/core/reporting.js +312 -290
- package/core/search.js +317 -185
- package/core/semantic-provider.js +110 -0
- package/core/stacktrace.js +25 -0
- package/core/tracing.js +101 -51
- package/core/trust-matrix.js +19 -40
- package/core/verify.js +534 -37
- package/languages/adapter.js +218 -0
- package/languages/c-family.js +2791 -0
- package/languages/c.js +3 -0
- package/languages/cpp.js +3 -0
- package/languages/csharp.js +1402 -0
- package/languages/go.js +60 -21
- package/languages/html.js +2 -2
- package/languages/index.js +85 -7
- package/languages/java.js +396 -13
- package/languages/javascript.js +199 -19
- package/languages/python.js +964 -22
- package/languages/rust.js +1317 -152
- package/languages/utils.js +40 -3
- package/mcp/server.js +254 -636
- package/package.json +39 -22
- package/eslint.config.js +0 -43
- package/jsconfig.json +0 -10
package/core/output/find.js
CHANGED
|
@@ -26,7 +26,7 @@ function firstSentenceShort(text) {
|
|
|
26
26
|
/**
|
|
27
27
|
* Format find command output
|
|
28
28
|
*/
|
|
29
|
-
function formatFind(symbols, query, top) {
|
|
29
|
+
function formatFind(symbols, query, top = undefined) {
|
|
30
30
|
if (symbols.length === 0) {
|
|
31
31
|
return `No symbols found for "${query}"`;
|
|
32
32
|
}
|
|
@@ -55,13 +55,21 @@ function formatFind(symbols, query, top) {
|
|
|
55
55
|
if (s.usageCounts !== undefined) {
|
|
56
56
|
const c = s.usageCounts;
|
|
57
57
|
const parts = [];
|
|
58
|
-
if (c.calls > 0
|
|
58
|
+
if (c.calls > 0 && c.confirmedCalls !== undefined) {
|
|
59
|
+
const tiers = [];
|
|
60
|
+
if (c.confirmedCalls > 0) tiers.push(`${c.confirmedCalls} confirmed`);
|
|
61
|
+
if (c.unverifiedCalls > 0) tiers.push(`${c.unverifiedCalls} unverified`);
|
|
62
|
+
parts.push(`${c.calls} target call candidate(s) (${tiers.join(', ')})`);
|
|
63
|
+
} else if (c.calls > 0) parts.push(`${c.calls} calls`);
|
|
59
64
|
if (c.definitions > 0) parts.push(`${c.definitions} def`);
|
|
60
65
|
if (c.imports > 0) parts.push(`${c.imports} imports`);
|
|
61
66
|
if (c.references > 0) parts.push(`${c.references} refs`);
|
|
67
|
+
const excluded = c.otherTargetCalls > 0
|
|
68
|
+
? `; ${c.otherTargetCalls} same-name call(s) excluded as another target`
|
|
69
|
+
: '';
|
|
62
70
|
lines.push(parts.length > 0
|
|
63
|
-
? ` (${c.total} usages: ${parts.join(', ')})`
|
|
64
|
-
: ` (${c.total} usages)`);
|
|
71
|
+
? ` (${c.total} usages: ${parts.join(', ')}${excluded})`
|
|
72
|
+
: ` (${c.total} usages${excluded})`);
|
|
65
73
|
} else if (s.usageCount !== undefined) {
|
|
66
74
|
lines.push(` (${s.usageCount} usages)`);
|
|
67
75
|
}
|
|
@@ -92,12 +100,12 @@ function formatFindJson(items) {
|
|
|
92
100
|
}
|
|
93
101
|
|
|
94
102
|
/**
|
|
95
|
-
* Format find results with
|
|
103
|
+
* Format find results with confidence and optional source.
|
|
96
104
|
* Returns a string. Used by CLI and interactive mode.
|
|
97
105
|
*
|
|
98
106
|
* @param {Array} symbols - Find result array
|
|
99
107
|
* @param {string} query - Original search query
|
|
100
|
-
* @param {object} options - { depth, top, all }
|
|
108
|
+
* @param {object} options - { depth, top, all, compact, withSource, limitHint }
|
|
101
109
|
*/
|
|
102
110
|
function formatFindDetailed(symbols, query, options = {}) {
|
|
103
111
|
const { top, all, compact } = options;
|
|
@@ -111,14 +119,25 @@ function formatFindDetailed(symbols, query, options = {}) {
|
|
|
111
119
|
}
|
|
112
120
|
|
|
113
121
|
const lines = [];
|
|
122
|
+
const sameNameDefinitionCounts = new Map();
|
|
123
|
+
for (const symbol of symbols) {
|
|
124
|
+
sameNameDefinitionCounts.set(
|
|
125
|
+
symbol.name,
|
|
126
|
+
(sameNameDefinitionCounts.get(symbol.name) || 0) + 1,
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
for (const [name, count] of Object.entries(symbols.findInfo?.nameWideDefinitionCounts || {})) {
|
|
130
|
+
sameNameDefinitionCounts.set(name, count);
|
|
131
|
+
}
|
|
114
132
|
const limit = all ? symbols.length : (top > 0 ? top : DEFAULT_LIMIT);
|
|
115
133
|
const showing = Math.min(limit, symbols.length);
|
|
116
|
-
const
|
|
134
|
+
const total = symbols.findInfo?.total ?? symbols.length;
|
|
135
|
+
const hidden = Math.max(0, total - showing);
|
|
117
136
|
|
|
118
137
|
if (hidden > 0) {
|
|
119
|
-
lines.push(`Found ${
|
|
138
|
+
lines.push(`Found ${total} match(es) for "${query}" (showing top ${showing}):`);
|
|
120
139
|
} else {
|
|
121
|
-
lines.push(`Found ${
|
|
140
|
+
lines.push(`Found ${total} match(es) for "${query}":`);
|
|
122
141
|
}
|
|
123
142
|
if (!compact) lines.push('─'.repeat(60));
|
|
124
143
|
|
|
@@ -144,7 +163,11 @@ function formatFindDetailed(symbols, query, options = {}) {
|
|
|
144
163
|
// One line per result: "<handle> <sig> <usages?> <doc snippet?>"
|
|
145
164
|
const parts = [`${loc} ${sig}${confStr}`];
|
|
146
165
|
if (s.usageCounts !== undefined && s.usageCounts.total > 0) {
|
|
147
|
-
|
|
166
|
+
const scope = sameNameDefinitionCounts.get(s.name) > 1 ? ' name-wide' : '';
|
|
167
|
+
const label = s.usageCounts.complete === false
|
|
168
|
+
? `${scope} indexed activity; refs excluded`
|
|
169
|
+
: `${scope} usages`;
|
|
170
|
+
parts.push(`(${s.usageCounts.total}${label})`);
|
|
148
171
|
} else if (s.usageCount !== undefined) {
|
|
149
172
|
parts.push(`(${s.usageCount} usages)`);
|
|
150
173
|
}
|
|
@@ -153,6 +176,12 @@ function formatFindDetailed(symbols, query, options = {}) {
|
|
|
153
176
|
if (snip) parts.push(`— ${snip}`);
|
|
154
177
|
}
|
|
155
178
|
lines.push(parts.join(' '));
|
|
179
|
+
if (options.withSource && s.code) {
|
|
180
|
+
lines.push(' ───');
|
|
181
|
+
for (const codeLine of String(s.code).split('\n')) {
|
|
182
|
+
lines.push(` ${codeLine}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
156
185
|
continue;
|
|
157
186
|
}
|
|
158
187
|
|
|
@@ -164,11 +193,24 @@ function formatFindDetailed(symbols, query, options = {}) {
|
|
|
164
193
|
if (s.usageCounts !== undefined) {
|
|
165
194
|
const c = s.usageCounts;
|
|
166
195
|
const parts = [];
|
|
167
|
-
if (c.calls > 0
|
|
196
|
+
if (c.calls > 0 && c.confirmedCalls !== undefined) {
|
|
197
|
+
const tiers = [];
|
|
198
|
+
if (c.confirmedCalls > 0) tiers.push(`${c.confirmedCalls} confirmed`);
|
|
199
|
+
if (c.unverifiedCalls > 0) tiers.push(`${c.unverifiedCalls} unverified`);
|
|
200
|
+
parts.push(`${c.calls} target call candidate(s) (${tiers.join(', ')})`);
|
|
201
|
+
} else if (c.calls > 0) parts.push(`${c.calls} calls`);
|
|
168
202
|
if (c.definitions > 0) parts.push(`${c.definitions} def`);
|
|
169
203
|
if (c.imports > 0) parts.push(`${c.imports} imports`);
|
|
170
204
|
if (c.references > 0) parts.push(`${c.references} refs`);
|
|
171
|
-
|
|
205
|
+
const scope = sameNameDefinitionCounts.get(s.name) > 1 ? 'name-wide ' : '';
|
|
206
|
+
const label = c.complete === false ? 'indexed activity' : 'usages';
|
|
207
|
+
const boundary = c.complete === false
|
|
208
|
+
? '; references not counted — use usages for the full inventory'
|
|
209
|
+
: '';
|
|
210
|
+
const excluded = c.otherTargetCalls > 0
|
|
211
|
+
? `; ${c.otherTargetCalls} same-name call(s) excluded as another target`
|
|
212
|
+
: '';
|
|
213
|
+
lines.push(` (${c.total} ${scope}${label}: ${parts.join(', ')}${boundary}${excluded})`);
|
|
172
214
|
} else if (s.usageCount !== undefined) {
|
|
173
215
|
lines.push(` (${s.usageCount} usages)`);
|
|
174
216
|
}
|
|
@@ -177,8 +219,14 @@ function formatFindDetailed(symbols, query, options = {}) {
|
|
|
177
219
|
lines.push(` ⚠ ${confidence.reasons.join(', ')}`);
|
|
178
220
|
}
|
|
179
221
|
|
|
180
|
-
|
|
181
|
-
|
|
222
|
+
if (options.withSource && s.code) {
|
|
223
|
+
lines.push(' ───');
|
|
224
|
+
for (const codeLine of String(s.code).split('\n')) {
|
|
225
|
+
lines.push(` ${codeLine}`);
|
|
226
|
+
}
|
|
227
|
+
// Legacy internal formatter mode. The v5 public command uses the
|
|
228
|
+
// explicit withSource contract instead of overloading graph depth.
|
|
229
|
+
} else if (depth === '2' || depth === 'full') {
|
|
182
230
|
try {
|
|
183
231
|
const content = fs.readFileSync(s.file, 'utf-8');
|
|
184
232
|
const fileLines = content.split('\n');
|
|
@@ -199,7 +247,8 @@ function formatFindDetailed(symbols, query, options = {}) {
|
|
|
199
247
|
}
|
|
200
248
|
|
|
201
249
|
if (hidden > 0) {
|
|
202
|
-
lines.push(`... ${hidden} more result(s).
|
|
250
|
+
lines.push(`... ${hidden} more result(s). ${options.limitHint ||
|
|
251
|
+
'Use --all to see all, or --top=N to see more.'}`);
|
|
203
252
|
}
|
|
204
253
|
|
|
205
254
|
return lines.join('\n');
|
|
@@ -245,11 +294,14 @@ function formatUsagesJson(usages, name) {
|
|
|
245
294
|
|
|
246
295
|
const calls = refs.filter(u => u.usageType === 'call');
|
|
247
296
|
const imports = refs.filter(u => u.usageType === 'import');
|
|
297
|
+
const textOccurrences = refs.filter(u => u.usageType === 'text');
|
|
298
|
+
const otherDefinitions = refs.filter(u => u.usageType === 'definition');
|
|
248
299
|
// Exhaustive complement (fix #241): a non-definition record that is
|
|
249
300
|
// neither call nor import lands in references — same-name definer sites
|
|
250
301
|
// (usageType 'definition', isDefinition false: shadowing locals, other
|
|
251
302
|
// defs of the name) used to inflate totals while rendering in NO band.
|
|
252
|
-
const references = refs.filter(u =>
|
|
303
|
+
const references = refs.filter(u =>
|
|
304
|
+
!['call', 'import', 'text', 'definition'].includes(u.usageType));
|
|
253
305
|
|
|
254
306
|
// Each usage record points at a call site. We emit a per-occurrence handle
|
|
255
307
|
// pointing at the SITE itself in the form "relativePath:line:callerName"
|
|
@@ -285,7 +337,11 @@ function formatUsagesJson(usages, name) {
|
|
|
285
337
|
callCount: sc ? sc.calls : calls.length,
|
|
286
338
|
importCount: sc ? sc.imports : imports.length,
|
|
287
339
|
referenceCount: sc ? sc.references : references.length,
|
|
288
|
-
|
|
340
|
+
otherDefinitionCount: sc ? (sc.otherDefinitions || 0) : otherDefinitions.length,
|
|
341
|
+
textCount: sc ? (sc.text || 0) : textOccurrences.length,
|
|
342
|
+
totalUsages: sc
|
|
343
|
+
? (sc.calls + sc.imports + sc.references + (sc.text || 0))
|
|
344
|
+
: refs.length,
|
|
289
345
|
definitions: definitions.map(d => {
|
|
290
346
|
const handle = formatSymbolHandle({ ...d, name: d.name || name });
|
|
291
347
|
return {
|
|
@@ -302,7 +358,12 @@ function formatUsagesJson(usages, name) {
|
|
|
302
358
|
}),
|
|
303
359
|
calls: calls.map(formatUsage),
|
|
304
360
|
imports: imports.map(formatUsage),
|
|
305
|
-
|
|
361
|
+
otherDefinitions: otherDefinitions.map(formatUsage),
|
|
362
|
+
references: references.map(formatUsage),
|
|
363
|
+
textOccurrences: textOccurrences.map(u => ({
|
|
364
|
+
...formatUsage(u),
|
|
365
|
+
textKind: u.textKind || 'other-text',
|
|
366
|
+
})),
|
|
306
367
|
}
|
|
307
368
|
});
|
|
308
369
|
}
|
|
@@ -315,15 +376,21 @@ function formatUsages(usages, name, options = {}) {
|
|
|
315
376
|
const defs = usages.filter(u => u.isDefinition);
|
|
316
377
|
const calls = usages.filter(u => u.usageType === 'call');
|
|
317
378
|
const imports = usages.filter(u => u.usageType === 'import');
|
|
318
|
-
|
|
319
|
-
const
|
|
379
|
+
const textOccurrences = usages.filter(u => u.usageType === 'text');
|
|
380
|
+
const otherDefinitions = usages.filter(u => !u.isDefinition && u.usageType === 'definition');
|
|
381
|
+
const refs = usages.filter(u => !u.isDefinition &&
|
|
382
|
+
!['call', 'import', 'text', 'definition'].includes(u.usageType));
|
|
320
383
|
|
|
321
384
|
// Under --limit the listed entries are truncated but the summary must
|
|
322
385
|
// describe the FULL result set (fix #237) — the handler attaches the
|
|
323
386
|
// full counts as a non-enumerable property.
|
|
324
387
|
const sc = usages.summaryCounts;
|
|
325
388
|
const lines = [];
|
|
326
|
-
|
|
389
|
+
// "syntax-classified": classification is by syntax alone — a method
|
|
390
|
+
// reference is a `reference` here even when the engine confirms it as a
|
|
391
|
+
// caller. ACCOUNT lines in show/impact are engine-adjudicated, so the
|
|
392
|
+
// two breakdowns can legitimately differ; both count lines.
|
|
393
|
+
lines.push(`Usages of "${name}" (syntax-classified): ${sc ? sc.definitions : defs.length} definitions, ${sc ? (sc.otherDefinitions || 0) : otherDefinitions.length} other definitions, ${sc ? sc.calls : calls.length} calls, ${sc ? sc.imports : imports.length} imports, ${sc ? sc.references : refs.length} references, ${sc ? (sc.text || 0) : textOccurrences.length} other-text`);
|
|
327
394
|
if (!compact) lines.push('═'.repeat(60));
|
|
328
395
|
|
|
329
396
|
function renderContextLines(usage) {
|
|
@@ -343,8 +410,12 @@ function formatUsages(usages, name, options = {}) {
|
|
|
343
410
|
}
|
|
344
411
|
|
|
345
412
|
if (defs.length > 0) {
|
|
413
|
+
// Many same-name definitions (jsoup has 25 `parse` overloads) can
|
|
414
|
+
// consume the whole output budget before the rarer sections render —
|
|
415
|
+
// cap the section display; the header count stays complete.
|
|
416
|
+
const defLimit = !options.all && defs.length > 10 ? 10 : defs.length;
|
|
346
417
|
lines.push(`${compact ? '' : '\n'}DEFINITIONS:`);
|
|
347
|
-
for (const d of defs) {
|
|
418
|
+
for (const d of defs.slice(0, defLimit)) {
|
|
348
419
|
if (compact) {
|
|
349
420
|
lines.push(` ${d.relativePath}:${d.line || d.startLine}${d.signature ? ' ' + d.signature : ''}`);
|
|
350
421
|
} else {
|
|
@@ -352,23 +423,16 @@ function formatUsages(usages, name, options = {}) {
|
|
|
352
423
|
if (d.signature) lines.push(` ${d.signature}`);
|
|
353
424
|
}
|
|
354
425
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
if (calls.length > 0) {
|
|
358
|
-
lines.push(`${compact ? '' : '\n'}CALLS:`);
|
|
359
|
-
for (const c of calls) {
|
|
360
|
-
if (compact) {
|
|
361
|
-
const expr = c.content ? c.content.trim().replace(/\s+/g, ' ').slice(0, 100) : '';
|
|
362
|
-
lines.push(` ${c.relativePath}:${c.line}: ${expr}`);
|
|
363
|
-
} else {
|
|
364
|
-
lines.push(` ${c.relativePath}:${c.line}`);
|
|
365
|
-
renderContextLines(c);
|
|
366
|
-
lines.push(` ${c.content.trim()}`);
|
|
367
|
-
renderAfterLines(c);
|
|
368
|
-
}
|
|
426
|
+
if (defLimit < defs.length) {
|
|
427
|
+
lines.push(` ... and ${defs.length - defLimit} more definitions (${options.allHint || 'use --all'})`);
|
|
369
428
|
}
|
|
370
429
|
}
|
|
371
430
|
|
|
431
|
+
// Section order is bulk-LAST: imports, references, and other-text are
|
|
432
|
+
// small, high-signal sections the ACCOUNT explicitly routes agents to;
|
|
433
|
+
// CALLS is the homogeneous bulk that dominates large outputs. Rendering
|
|
434
|
+
// CALLS last means the output budget truncates redundant bulk instead of
|
|
435
|
+
// silently swallowing the rare sections (the header still promised them).
|
|
372
436
|
if (imports.length > 0) {
|
|
373
437
|
lines.push(`${compact ? '' : '\n'}IMPORTS:`);
|
|
374
438
|
for (const i of imports) {
|
|
@@ -382,6 +446,14 @@ function formatUsages(usages, name, options = {}) {
|
|
|
382
446
|
}
|
|
383
447
|
}
|
|
384
448
|
|
|
449
|
+
if (otherDefinitions.length > 0) {
|
|
450
|
+
lines.push(`${compact ? '' : '\n'}OTHER DEFINITIONS (same spelling; not references to the selected target):`);
|
|
451
|
+
for (const d of otherDefinitions) {
|
|
452
|
+
const expression = d.content ? d.content.trim() : '';
|
|
453
|
+
lines.push(` ${d.relativePath}:${d.line}${expression ? `\n ${expression}` : ''}`);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
385
457
|
if (refs.length > 0) {
|
|
386
458
|
lines.push(`${compact ? '' : '\n'}REFERENCES:`);
|
|
387
459
|
for (const r of refs) {
|
|
@@ -397,6 +469,39 @@ function formatUsages(usages, name, options = {}) {
|
|
|
397
469
|
}
|
|
398
470
|
}
|
|
399
471
|
|
|
472
|
+
if (textOccurrences.length > 0) {
|
|
473
|
+
lines.push(`${compact ? '' : '\n'}OTHER TEXT (comments, strings, docstrings, or unclassified literal lines):`);
|
|
474
|
+
for (const occurrence of textOccurrences) {
|
|
475
|
+
const kind = occurrence.textKind || 'other-text';
|
|
476
|
+
if (compact) {
|
|
477
|
+
const expr = occurrence.content
|
|
478
|
+
? occurrence.content.trim().replace(/\s+/g, ' ').slice(0, 100)
|
|
479
|
+
: '';
|
|
480
|
+
lines.push(` ${occurrence.relativePath}:${occurrence.line} [${kind}]: ${expr}`);
|
|
481
|
+
} else {
|
|
482
|
+
lines.push(` ${occurrence.relativePath}:${occurrence.line} [${kind}]`);
|
|
483
|
+
renderContextLines(occurrence);
|
|
484
|
+
lines.push(` ${occurrence.content.trim()}`);
|
|
485
|
+
renderAfterLines(occurrence);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
if (calls.length > 0) {
|
|
491
|
+
lines.push(`${compact ? '' : '\n'}CALLS:`);
|
|
492
|
+
for (const c of calls) {
|
|
493
|
+
if (compact) {
|
|
494
|
+
const expr = c.content ? c.content.trim().replace(/\s+/g, ' ').slice(0, 100) : '';
|
|
495
|
+
lines.push(` ${c.relativePath}:${c.line}: ${expr}`);
|
|
496
|
+
} else {
|
|
497
|
+
lines.push(` ${c.relativePath}:${c.line}`);
|
|
498
|
+
renderContextLines(c);
|
|
499
|
+
lines.push(` ${c.content.trim()}`);
|
|
500
|
+
renderAfterLines(c);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
400
505
|
return lines.join('\n');
|
|
401
506
|
}
|
|
402
507
|
|
package/core/output/graph.js
CHANGED
|
@@ -23,7 +23,9 @@ function formatImports(imports, filePath) {
|
|
|
23
23
|
for (const imp of internal) {
|
|
24
24
|
lines.push(` ${imp.module}`);
|
|
25
25
|
if (imp.resolved) {
|
|
26
|
-
lines.push(` -> ${imp.resolved}
|
|
26
|
+
lines.push(` -> ${imp.resolved}${imp.indexed === false
|
|
27
|
+
? ' (not indexed; absent from dependency graph)'
|
|
28
|
+
: ''}`);
|
|
27
29
|
}
|
|
28
30
|
if (imp.names && imp.names.length > 0 && imp.names[0] !== '*') {
|
|
29
31
|
lines.push(` ${imp.names.join(', ')}`);
|
|
@@ -73,6 +75,7 @@ function formatImportsJson(imports, filePath) {
|
|
|
73
75
|
names: i.names,
|
|
74
76
|
type: i.type,
|
|
75
77
|
resolved: i.resolved || null,
|
|
78
|
+
indexed: i.resolved ? i.indexed !== false : false,
|
|
76
79
|
isDynamic: !!i.isDynamic,
|
|
77
80
|
line: i.line ?? null
|
|
78
81
|
}))
|
|
@@ -170,10 +173,6 @@ function formatApi(symbols, filePath) {
|
|
|
170
173
|
|
|
171
174
|
if (symbols.length === 0) {
|
|
172
175
|
lines.push(' (none found)');
|
|
173
|
-
if (filePath && filePath.endsWith('.py')) {
|
|
174
|
-
lines.push('');
|
|
175
|
-
lines.push('Note: Python requires __all__ for export detection. Use \'toc\' command to see all functions/classes.');
|
|
176
|
-
}
|
|
177
176
|
} else {
|
|
178
177
|
// Group by file, then dedup overloads within each file group.
|
|
179
178
|
const byFile = new Map();
|
|
@@ -194,6 +193,10 @@ function formatApi(symbols, filePath) {
|
|
|
194
193
|
lines.push('');
|
|
195
194
|
}
|
|
196
195
|
}
|
|
196
|
+
if (symbols.apiInfo?.pythonImplicitFiles > 0) {
|
|
197
|
+
lines.push('');
|
|
198
|
+
lines.push('Note: Python files without __all__ use the standard top-level non-underscore public-name convention; explicit `from X import Y as Y` re-exports are included.');
|
|
199
|
+
}
|
|
197
200
|
|
|
198
201
|
return lines.join('\n');
|
|
199
202
|
}
|
|
@@ -212,6 +215,9 @@ function formatApiJson(symbols, filePath) {
|
|
|
212
215
|
command: 'api',
|
|
213
216
|
count: deduped.length,
|
|
214
217
|
...(li && { total: li.total, truncated: true }),
|
|
218
|
+
...(symbols.apiInfo?.pythonImplicitFiles > 0 && {
|
|
219
|
+
note: 'Python files without __all__ use the top-level non-underscore public-name convention; explicit redundant-alias re-exports are included.',
|
|
220
|
+
}),
|
|
215
221
|
},
|
|
216
222
|
...(filePath && { file: filePath }),
|
|
217
223
|
data: {
|