ucn 3.8.12 → 3.8.14

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 (44) hide show
  1. package/.claude/skills/ucn/SKILL.md +3 -1
  2. package/.github/workflows/ci.yml +15 -3
  3. package/.github/workflows/publish.yml +20 -8
  4. package/README.md +1 -0
  5. package/cli/index.js +165 -246
  6. package/core/analysis.js +1400 -0
  7. package/core/build-worker.js +194 -0
  8. package/core/cache.js +105 -7
  9. package/core/callers.js +194 -64
  10. package/core/deadcode.js +22 -66
  11. package/core/discovery.js +9 -54
  12. package/core/execute.js +139 -54
  13. package/core/graph.js +615 -0
  14. package/core/output/analysis-ext.js +271 -0
  15. package/core/output/analysis.js +491 -0
  16. package/core/output/extraction.js +188 -0
  17. package/core/output/find.js +355 -0
  18. package/core/output/graph.js +399 -0
  19. package/core/output/refactoring.js +293 -0
  20. package/core/output/reporting.js +331 -0
  21. package/core/output/search.js +307 -0
  22. package/core/output/shared.js +271 -0
  23. package/core/output/tracing.js +416 -0
  24. package/core/output.js +15 -3293
  25. package/core/parallel-build.js +165 -0
  26. package/core/project.js +299 -3633
  27. package/core/registry.js +59 -0
  28. package/core/reporting.js +258 -0
  29. package/core/search.js +890 -0
  30. package/core/stacktrace.js +1 -1
  31. package/core/tracing.js +631 -0
  32. package/core/verify.js +10 -13
  33. package/eslint.config.js +43 -0
  34. package/jsconfig.json +10 -0
  35. package/languages/go.js +21 -2
  36. package/languages/html.js +8 -0
  37. package/languages/index.js +102 -40
  38. package/languages/java.js +13 -0
  39. package/languages/javascript.js +17 -1
  40. package/languages/python.js +14 -0
  41. package/languages/rust.js +13 -0
  42. package/languages/utils.js +1 -1
  43. package/mcp/server.js +45 -28
  44. package/package.json +8 -3
@@ -0,0 +1,271 @@
1
+ /**
2
+ * core/output/analysis-ext.js - Extended analysis formatters (related, smart, diffImpact)
3
+ */
4
+
5
+ const { dynamicImportsNote, formatLineRanges } = require('./shared');
6
+
7
+ /**
8
+ * Format related command output - text
9
+ */
10
+ function formatRelated(related, options = {}) {
11
+ if (!related) {
12
+ return 'Function not found.';
13
+ }
14
+
15
+ const lines = [];
16
+
17
+ // Header
18
+ lines.push(`Related to ${related.target.name}`);
19
+ lines.push('═'.repeat(60));
20
+ lines.push(`${related.target.file}:${related.target.line}`);
21
+ lines.push('');
22
+
23
+ // Same file
24
+ let relatedTruncated = false;
25
+ if (related.sameFile.length > 0) {
26
+ const maxSameFile = options.top || (options.all ? Infinity : 8);
27
+ lines.push(`SAME FILE (${related.sameFile.length}):`);
28
+ for (const f of related.sameFile.slice(0, maxSameFile)) {
29
+ const params = f.params ? `(${f.params})` : '';
30
+ lines.push(` :${f.line} ${f.name}${params}`);
31
+ }
32
+ if (related.sameFile.length > maxSameFile) {
33
+ relatedTruncated = true;
34
+ lines.push(` ... and ${related.sameFile.length - maxSameFile} more`);
35
+ }
36
+ lines.push('');
37
+ }
38
+
39
+ // Similar names
40
+ if (related.similarNames.length > 0) {
41
+ const similarTotal = related.similarNamesTotal || related.similarNames.length;
42
+ const similarLabel = similarTotal > related.similarNames.length
43
+ ? `${related.similarNames.length} of ${similarTotal}` : `${related.similarNames.length}`;
44
+ lines.push(`SIMILAR NAMES (${similarLabel}):`);
45
+ for (const s of related.similarNames) {
46
+ lines.push(` ${s.name} - ${s.file}:${s.line}`);
47
+ lines.push(` shared: ${s.sharedParts.join(', ')}`);
48
+ }
49
+ if (similarTotal > related.similarNames.length) relatedTruncated = true;
50
+ lines.push('');
51
+ }
52
+
53
+ // Shared callers
54
+ if (related.sharedCallers.length > 0) {
55
+ const callersTotal = related.sharedCallersTotal || related.sharedCallers.length;
56
+ const callersLabel = callersTotal > related.sharedCallers.length
57
+ ? `${related.sharedCallers.length} of ${callersTotal}` : `${related.sharedCallers.length}`;
58
+ lines.push(`CALLED BY SAME FUNCTIONS (${callersLabel}):`);
59
+ for (const s of related.sharedCallers) {
60
+ lines.push(` ${s.name} - ${s.file}:${s.line} (${s.sharedCallerCount} shared callers)`);
61
+ }
62
+ if (callersTotal > related.sharedCallers.length) relatedTruncated = true;
63
+ lines.push('');
64
+ }
65
+
66
+ // Shared callees
67
+ if (related.sharedCallees.length > 0) {
68
+ const calleesTotal = related.sharedCalleesTotal || related.sharedCallees.length;
69
+ const calleesLabel = calleesTotal > related.sharedCallees.length
70
+ ? `${related.sharedCallees.length} of ${calleesTotal}` : `${related.sharedCallees.length}`;
71
+ lines.push(`CALLS SAME FUNCTIONS (${calleesLabel}):`);
72
+ for (const s of related.sharedCallees) {
73
+ lines.push(` ${s.name} - ${s.file}:${s.line} (${s.sharedCalleeCount} shared callees)`);
74
+ }
75
+ if (calleesTotal > related.sharedCallees.length) relatedTruncated = true;
76
+ }
77
+
78
+ if (relatedTruncated) {
79
+ const allHint = options.allHint || 'Use --all to show all.';
80
+ lines.push(`\nSome sections truncated. ${allHint}`);
81
+ }
82
+
83
+ return lines.join('\n');
84
+ }
85
+
86
+ /**
87
+ * Format related command output - JSON
88
+ */
89
+ function formatRelatedJson(related) {
90
+ if (!related) {
91
+ return JSON.stringify({ found: false, error: 'Function not found' }, null, 2);
92
+ }
93
+ return JSON.stringify(related, null, 2);
94
+ }
95
+
96
+ /**
97
+ * Format smart command output
98
+ * @param {object} smart - Smart extraction result
99
+ * @param {object} [options] - Formatting options
100
+ * @param {string} [options.uncertainHint] - Custom hint for uncertain calls
101
+ */
102
+ function formatSmart(smart, options = {}) {
103
+ if (!smart) return 'Function not found.';
104
+
105
+ const lines = [];
106
+ lines.push(`${smart.target.name} (${smart.target.file}:${smart.target.startLine})`);
107
+ lines.push('═'.repeat(60));
108
+
109
+ if (smart.meta) {
110
+ const notes = [];
111
+ if (smart.meta.dynamicImports) { const dn = dynamicImportsNote(smart.meta.dynamicImports, smart.meta); if (dn) notes.push(dn); }
112
+ if (smart.meta.uncertain) notes.push(`${smart.meta.uncertain} uncertain call(s) skipped`);
113
+ if (notes.length) {
114
+ const uncertainSuffix = smart.meta.uncertain && options.uncertainHint ? ` — ${options.uncertainHint}` : '';
115
+ lines.push(` Note: ${notes.join(', ')}${uncertainSuffix}`);
116
+ }
117
+ }
118
+
119
+ lines.push(smart.target.code);
120
+
121
+ if (smart.dependencies.length > 0) {
122
+ lines.push('\n─── DEPENDENCIES ───');
123
+ for (const dep of smart.dependencies) {
124
+ const weight = dep.weight && dep.weight !== 'normal' ? ` [${dep.weight}]` : '';
125
+ lines.push(`\n// ${dep.name}${weight} (${dep.relativePath}:${dep.startLine})`);
126
+ lines.push(dep.code);
127
+ }
128
+ }
129
+
130
+ if (smart.types && smart.types.length > 0) {
131
+ lines.push('\n─── TYPES ───');
132
+ for (const t of smart.types) {
133
+ lines.push(`\n// ${t.name} (${t.relativePath}:${t.startLine})`);
134
+ lines.push(t.code);
135
+ }
136
+ }
137
+
138
+ return lines.join('\n');
139
+ }
140
+
141
+ /**
142
+ * Format smart extraction result as JSON
143
+ * Includes function + all dependencies
144
+ */
145
+ function formatSmartJson(result) {
146
+ if (!result) return JSON.stringify({ found: false, error: 'Function not found' }, null, 2);
147
+ const meta = result.meta || { complete: true, skipped: 0, dynamicImports: 0, uncertain: 0 };
148
+ return JSON.stringify({
149
+ meta,
150
+ data: {
151
+ target: {
152
+ name: result.target.name,
153
+ file: result.target.file,
154
+ startLine: result.target.startLine,
155
+ endLine: result.target.endLine,
156
+ params: result.target.params,
157
+ returnType: result.target.returnType,
158
+ code: result.target.code
159
+ },
160
+ dependencies: result.dependencies.map(d => ({
161
+ name: d.name,
162
+ type: d.type,
163
+ file: d.file,
164
+ startLine: d.startLine,
165
+ endLine: d.endLine,
166
+ params: d.params,
167
+ weight: d.weight, // core, setup, utility
168
+ callCount: d.callCount,
169
+ code: d.code
170
+ })),
171
+ types: result.types || []
172
+ }
173
+ });
174
+ }
175
+
176
+ /**
177
+ * Format diff impact command output - text
178
+ */
179
+ function formatDiffImpact(result, options = {}) {
180
+ if (!result) return 'No diff data.';
181
+
182
+ const lines = [];
183
+ const MAX_CALLERS_PER_FN = options.all ? Infinity : 30;
184
+
185
+ lines.push(`Diff Impact Analysis (vs ${result.base})`);
186
+ lines.push('═'.repeat(60));
187
+
188
+ const s = result.summary || {};
189
+ const parts = [];
190
+ if (s.modifiedFunctions > 0) parts.push(`${s.modifiedFunctions} modified`);
191
+ if (s.deletedFunctions > 0) parts.push(`${s.deletedFunctions} deleted`);
192
+ if (s.newFunctions > 0) parts.push(`${s.newFunctions} new`);
193
+ parts.push(`${s.totalCallSites || 0} call sites across ${s.affectedFiles || 0} files`);
194
+ lines.push(parts.join(', '));
195
+ lines.push('');
196
+
197
+ // Modified functions
198
+ if (result.functions.length > 0) {
199
+ lines.push('MODIFIED FUNCTIONS:');
200
+ for (const fn of result.functions) {
201
+ lines.push(`\n ${fn.name}`);
202
+ lines.push(` ${fn.relativePath}:${fn.startLine}`);
203
+ lines.push(` ${fn.signature}`);
204
+ if (fn.addedLines.length > 0) {
205
+ lines.push(` Lines added: ${formatLineRanges(fn.addedLines)}`);
206
+ }
207
+ if (fn.deletedLines.length > 0) {
208
+ lines.push(` Lines deleted: ${formatLineRanges(fn.deletedLines)}`);
209
+ }
210
+
211
+ if (fn.callers.length > 0) {
212
+ const displayCallers = fn.callers.slice(0, MAX_CALLERS_PER_FN);
213
+ const truncated = fn.callers.length - displayCallers.length;
214
+ lines.push(` Callers (${fn.callers.length}):`);
215
+ for (const c of displayCallers) {
216
+ const caller = c.callerName ? `[${c.callerName}]` : '';
217
+ lines.push(` ${c.relativePath}:${c.line} ${caller}`);
218
+ lines.push(` ${c.content}`);
219
+ }
220
+ if (truncated > 0) {
221
+ lines.push(` ... ${truncated} more callers (use file= to scope diff to specific files, or use impact with class_name= for type-filtered results)`);
222
+ }
223
+ } else {
224
+ lines.push(' Callers: none found');
225
+ }
226
+ }
227
+ }
228
+
229
+ // New functions
230
+ if (result.newFunctions.length > 0) {
231
+ lines.push('\nNEW FUNCTIONS:');
232
+ for (const fn of result.newFunctions) {
233
+ lines.push(` ${fn.name} — ${fn.relativePath}:${fn.startLine}`);
234
+ lines.push(` ${fn.signature}`);
235
+ }
236
+ }
237
+
238
+ // Deleted functions
239
+ if (result.deletedFunctions.length > 0) {
240
+ lines.push('\nDELETED FUNCTIONS:');
241
+ for (const fn of result.deletedFunctions) {
242
+ lines.push(` ${fn.name} — ${fn.relativePath}:${fn.startLine}`);
243
+ }
244
+ }
245
+
246
+ // Module-level changes
247
+ if (result.moduleLevelChanges.length > 0) {
248
+ lines.push('\nMODULE-LEVEL CHANGES:');
249
+ for (const m of result.moduleLevelChanges) {
250
+ const changeParts = [];
251
+ if (m.addedLines.length > 0) changeParts.push(`+${m.addedLines.length} lines`);
252
+ if (m.deletedLines.length > 0) changeParts.push(`-${m.deletedLines.length} lines`);
253
+ lines.push(` ${m.relativePath}: ${changeParts.join(', ')}`);
254
+ }
255
+ }
256
+
257
+ return lines.join('\n');
258
+ }
259
+
260
+ function formatDiffImpactJson(result) {
261
+ return JSON.stringify(result, null, 2);
262
+ }
263
+
264
+ module.exports = {
265
+ formatRelated,
266
+ formatRelatedJson,
267
+ formatSmart,
268
+ formatSmartJson,
269
+ formatDiffImpact,
270
+ formatDiffImpactJson,
271
+ };