ucn 3.0.0

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.

Potentially problematic release.


This version of ucn might be problematic. Click here for more details.

Files changed (45) hide show
  1. package/.claude/skills/ucn/SKILL.md +77 -0
  2. package/LICENSE +21 -0
  3. package/README.md +135 -0
  4. package/cli/index.js +2437 -0
  5. package/core/discovery.js +513 -0
  6. package/core/imports.js +558 -0
  7. package/core/output.js +1274 -0
  8. package/core/parser.js +279 -0
  9. package/core/project.js +3261 -0
  10. package/index.js +52 -0
  11. package/languages/go.js +653 -0
  12. package/languages/index.js +267 -0
  13. package/languages/java.js +826 -0
  14. package/languages/javascript.js +1346 -0
  15. package/languages/python.js +667 -0
  16. package/languages/rust.js +950 -0
  17. package/languages/utils.js +457 -0
  18. package/package.json +42 -0
  19. package/test/fixtures/go/go.mod +3 -0
  20. package/test/fixtures/go/main.go +257 -0
  21. package/test/fixtures/go/service.go +187 -0
  22. package/test/fixtures/java/DataService.java +279 -0
  23. package/test/fixtures/java/Main.java +287 -0
  24. package/test/fixtures/java/Utils.java +199 -0
  25. package/test/fixtures/java/pom.xml +6 -0
  26. package/test/fixtures/javascript/main.js +109 -0
  27. package/test/fixtures/javascript/package.json +1 -0
  28. package/test/fixtures/javascript/service.js +88 -0
  29. package/test/fixtures/javascript/utils.js +67 -0
  30. package/test/fixtures/python/main.py +198 -0
  31. package/test/fixtures/python/pyproject.toml +3 -0
  32. package/test/fixtures/python/service.py +166 -0
  33. package/test/fixtures/python/utils.py +118 -0
  34. package/test/fixtures/rust/Cargo.toml +3 -0
  35. package/test/fixtures/rust/main.rs +253 -0
  36. package/test/fixtures/rust/service.rs +210 -0
  37. package/test/fixtures/rust/utils.rs +154 -0
  38. package/test/fixtures/typescript/main.ts +154 -0
  39. package/test/fixtures/typescript/package.json +1 -0
  40. package/test/fixtures/typescript/repository.ts +149 -0
  41. package/test/fixtures/typescript/types.ts +114 -0
  42. package/test/parser.test.js +3661 -0
  43. package/test/public-repos-test.js +477 -0
  44. package/test/systematic-test.js +619 -0
  45. package/ucn.js +8 -0
@@ -0,0 +1,477 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Public Repository Test Script for UCN
5
+ *
6
+ * Tests UCN against real public repositories to find edge cases and bugs.
7
+ * Run with: node test/public-repos-test.js [--verbose] [--keep]
8
+ */
9
+
10
+ const { execSync, spawnSync } = require('child_process');
11
+ const path = require('path');
12
+ const fs = require('fs');
13
+ const os = require('os');
14
+
15
+ // Configuration
16
+ const UCN_PATH = path.join(__dirname, '..', 'ucn.js');
17
+ const TEMP_DIR = path.join(os.tmpdir(), 'ucn-test-repos');
18
+ const TIMEOUT = 60000; // 60 seconds
19
+
20
+ // Parse args
21
+ const args = process.argv.slice(2);
22
+ const verbose = args.includes('--verbose') || args.includes('-v');
23
+ const keepRepos = args.includes('--keep');
24
+
25
+ // Colors
26
+ const c = {
27
+ reset: '\x1b[0m',
28
+ red: '\x1b[31m',
29
+ green: '\x1b[32m',
30
+ yellow: '\x1b[33m',
31
+ blue: '\x1b[34m',
32
+ cyan: '\x1b[36m',
33
+ dim: '\x1b[2m',
34
+ };
35
+
36
+ // Results tracking
37
+ const results = {
38
+ passed: 0,
39
+ failed: 0,
40
+ bugs: [],
41
+ };
42
+
43
+ // Public repositories to test - small, well-known repos
44
+ const repos = {
45
+ javascript: [
46
+ {
47
+ name: 'preact-signals',
48
+ url: 'https://github.com/preactjs/signals',
49
+ dir: 'packages/core/src',
50
+ symbols: ['signal', 'computed', 'effect', 'batch'],
51
+ },
52
+ ],
53
+ typescript: [
54
+ {
55
+ name: 'zod',
56
+ url: 'https://github.com/colinhacks/zod',
57
+ dir: 'src',
58
+ symbols: ['ZodType', 'string', 'parse', 'safeParse'],
59
+ },
60
+ ],
61
+ python: [
62
+ {
63
+ name: 'httpx',
64
+ url: 'https://github.com/encode/httpx',
65
+ dir: 'httpx',
66
+ symbols: ['Client', 'get', 'post', 'request'],
67
+ },
68
+ ],
69
+ go: [
70
+ {
71
+ name: 'cobra',
72
+ url: 'https://github.com/spf13/cobra',
73
+ dir: '.',
74
+ symbols: ['Command', 'Execute', 'AddCommand', 'Flags'],
75
+ },
76
+ ],
77
+ rust: [
78
+ {
79
+ name: 'ripgrep',
80
+ url: 'https://github.com/BurntSushi/ripgrep',
81
+ dir: 'crates/core',
82
+ symbols: ['Searcher', 'search', 'new', 'build'],
83
+ },
84
+ ],
85
+ java: [
86
+ {
87
+ name: 'gson',
88
+ url: 'https://github.com/google/gson',
89
+ dir: 'gson/src/main/java/com/google/gson',
90
+ symbols: ['Gson', 'toJson', 'fromJson', 'JsonElement'],
91
+ },
92
+ ],
93
+ };
94
+
95
+ // Commands to test
96
+ const commands = [
97
+ { name: 'toc', args: [] },
98
+ { name: 'stats', args: [] },
99
+ { name: 'find', args: ['$SYM'] },
100
+ { name: 'usages', args: ['$SYM'] },
101
+ { name: 'context', args: ['$SYM'] },
102
+ { name: 'about', args: ['$SYM'] },
103
+ { name: 'smart', args: ['$SYM'] },
104
+ { name: 'impact', args: ['$SYM'] },
105
+ { name: 'trace', args: ['$SYM', '--depth=2'] },
106
+ { name: 'api', args: [] },
107
+ { name: 'deadcode', args: [] },
108
+ { name: 'find', args: ['$SYM', '--json'], id: 'find-json' },
109
+ { name: 'toc', args: ['--json'], id: 'toc-json' },
110
+ { name: 'search', args: ['TODO'] },
111
+ { name: 'fn', args: ['$SYM'] },
112
+ ];
113
+
114
+ /**
115
+ * Clone a repository
116
+ */
117
+ function cloneRepo(url, name) {
118
+ const repoPath = path.join(TEMP_DIR, name);
119
+
120
+ if (fs.existsSync(repoPath)) {
121
+ if (verbose) console.log(`${c.dim} Using cached: ${name}${c.reset}`);
122
+ return repoPath;
123
+ }
124
+
125
+ console.log(`${c.dim} Cloning: ${name}...${c.reset}`);
126
+ try {
127
+ execSync(`git clone --depth 1 ${url} ${repoPath}`, {
128
+ stdio: verbose ? 'inherit' : 'pipe',
129
+ timeout: 120000,
130
+ });
131
+ return repoPath;
132
+ } catch (e) {
133
+ console.log(`${c.yellow} ⚠ Failed to clone ${name}: ${e.message}${c.reset}`);
134
+ return null;
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Run UCN command
140
+ */
141
+ function runUcn(targetPath, command, args = []) {
142
+ const fullArgs = ['node', UCN_PATH, targetPath, command, ...args, '--no-cache'];
143
+ const cmdStr = fullArgs.join(' ');
144
+
145
+ if (verbose) {
146
+ console.log(`${c.dim} Running: ${cmdStr}${c.reset}`);
147
+ }
148
+
149
+ try {
150
+ const result = spawnSync('node', [UCN_PATH, targetPath, command, ...args, '--no-cache'], {
151
+ timeout: TIMEOUT,
152
+ encoding: 'utf8',
153
+ maxBuffer: 50 * 1024 * 1024, // 50MB buffer
154
+ });
155
+
156
+ if (result.error) {
157
+ return { success: false, error: result.error.message, command: cmdStr };
158
+ }
159
+
160
+ if (result.status !== 0) {
161
+ const stderr = result.stderr || '';
162
+ // Check for expected "no results" type errors
163
+ const isExpectedNoResult =
164
+ stderr.includes('No matches found') ||
165
+ stderr.includes('not found') ||
166
+ stderr.includes('No usages found') ||
167
+ stderr.includes('No tests found') ||
168
+ stderr.includes('No deadcode') ||
169
+ stderr.includes('No imports');
170
+
171
+ return {
172
+ success: isExpectedNoResult,
173
+ output: result.stdout,
174
+ error: stderr,
175
+ command: cmdStr,
176
+ exitCode: result.status,
177
+ isExpectedNoResult,
178
+ };
179
+ }
180
+
181
+ return { success: true, output: result.stdout, command: cmdStr };
182
+ } catch (e) {
183
+ return { success: false, error: e.message, command: cmdStr };
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Check for error patterns in output
189
+ */
190
+ function checkForErrors(output) {
191
+ if (!output) return null;
192
+
193
+ const patterns = [
194
+ { pattern: /TypeError: .+/g, name: 'TypeError' },
195
+ { pattern: /ReferenceError: .+/g, name: 'ReferenceError' },
196
+ { pattern: /SyntaxError: .+/g, name: 'SyntaxError' },
197
+ { pattern: /Cannot read propert(y|ies) .+ of (undefined|null)/gi, name: 'Property access on null/undefined' },
198
+ { pattern: /is not a function/gi, name: 'Not a function' },
199
+ { pattern: /undefined is not/gi, name: 'Undefined error' },
200
+ { pattern: /FATAL ERROR/gi, name: 'Fatal error' },
201
+ { pattern: /Maximum call stack/gi, name: 'Stack overflow' },
202
+ { pattern: /heap out of memory/gi, name: 'Memory error' },
203
+ ];
204
+
205
+ for (const { pattern, name } of patterns) {
206
+ const match = output.match(pattern);
207
+ if (match) {
208
+ return { type: name, match: match[0] };
209
+ }
210
+ }
211
+
212
+ return null;
213
+ }
214
+
215
+ /**
216
+ * Test a repository
217
+ */
218
+ function testRepo(lang, repo) {
219
+ console.log(`\n${c.blue}Testing ${lang}/${repo.name}${c.reset}`);
220
+
221
+ const repoPath = cloneRepo(repo.url, repo.name);
222
+ if (!repoPath) {
223
+ results.failed++;
224
+ results.bugs.push({
225
+ language: lang,
226
+ repo: repo.name,
227
+ error: 'Failed to clone repository',
228
+ });
229
+ return;
230
+ }
231
+
232
+ const targetPath = path.join(repoPath, repo.dir);
233
+ if (!fs.existsSync(targetPath)) {
234
+ console.log(`${c.yellow} ⚠ Directory not found: ${repo.dir}${c.reset}`);
235
+ return;
236
+ }
237
+
238
+ // Test each command
239
+ for (const cmd of commands) {
240
+ const cmdArgs = cmd.args.map(a => (a === '$SYM' ? repo.symbols[0] : a));
241
+ const testId = cmd.id || `${cmd.name}(${cmdArgs.join(',')})`;
242
+
243
+ const result = runUcn(targetPath, cmd.name, cmdArgs);
244
+
245
+ // Check for crashes/errors
246
+ const errorInOutput = checkForErrors(result.output);
247
+ const errorInStderr = checkForErrors(result.error);
248
+ const foundError = errorInOutput || errorInStderr;
249
+
250
+ if (foundError) {
251
+ results.failed++;
252
+ results.bugs.push({
253
+ language: lang,
254
+ repo: repo.name,
255
+ command: cmd.name,
256
+ args: cmdArgs,
257
+ error: `${foundError.type}: ${foundError.match}`,
258
+ fullCommand: result.command,
259
+ });
260
+ console.log(` ${c.red}✗${c.reset} ${testId}: ${foundError.type}`);
261
+ if (verbose) {
262
+ console.log(` ${c.red}${foundError.match}${c.reset}`);
263
+ }
264
+ continue;
265
+ }
266
+
267
+ // Check for non-zero exit without expected "no results"
268
+ if (!result.success && !result.isExpectedNoResult) {
269
+ results.failed++;
270
+ results.bugs.push({
271
+ language: lang,
272
+ repo: repo.name,
273
+ command: cmd.name,
274
+ args: cmdArgs,
275
+ error: result.error || 'Command failed',
276
+ fullCommand: result.command,
277
+ exitCode: result.exitCode,
278
+ });
279
+ console.log(` ${c.red}✗${c.reset} ${testId}`);
280
+ if (verbose) {
281
+ console.log(` ${c.red}${result.error || 'Failed'}${c.reset}`);
282
+ }
283
+ continue;
284
+ }
285
+
286
+ // Check JSON validity for JSON commands
287
+ if (cmd.args.includes('--json') && result.output) {
288
+ try {
289
+ JSON.parse(result.output);
290
+ } catch (e) {
291
+ results.failed++;
292
+ results.bugs.push({
293
+ language: lang,
294
+ repo: repo.name,
295
+ command: cmd.name,
296
+ args: cmdArgs,
297
+ error: `Invalid JSON: ${e.message}`,
298
+ fullCommand: result.command,
299
+ output: result.output.substring(0, 200),
300
+ });
301
+ console.log(` ${c.red}✗${c.reset} ${testId}: Invalid JSON`);
302
+ continue;
303
+ }
304
+ }
305
+
306
+ results.passed++;
307
+ if (verbose) {
308
+ console.log(` ${c.green}✓${c.reset} ${testId}`);
309
+ }
310
+ }
311
+ }
312
+
313
+ /**
314
+ * Additional stress tests
315
+ */
316
+ function runStressTests() {
317
+ console.log(`\n${c.blue}Running STRESS TESTS${c.reset}`);
318
+
319
+ const jsFixture = path.join(__dirname, 'fixtures', 'javascript');
320
+
321
+ const tests = [
322
+ // Large depth values
323
+ {
324
+ name: 'Very large depth',
325
+ run: () => runUcn(jsFixture, 'trace', ['processData', '--depth=100']),
326
+ check: r => !checkForErrors(r.output) && !checkForErrors(r.error),
327
+ },
328
+ // Many concurrent operations simulation (sequential but rapid)
329
+ {
330
+ name: 'Rapid sequential commands',
331
+ run: () => {
332
+ for (let i = 0; i < 10; i++) {
333
+ const r = runUcn(jsFixture, 'toc', []);
334
+ if (!r.success) return r;
335
+ }
336
+ return { success: true };
337
+ },
338
+ check: r => r.success,
339
+ },
340
+ // Large symbol names in search
341
+ {
342
+ name: 'Pattern-like symbol',
343
+ run: () => runUcn(jsFixture, 'find', ['.*']),
344
+ check: r => !checkForErrors(r.output),
345
+ },
346
+ // Empty string searches
347
+ {
348
+ name: 'Empty search',
349
+ run: () => runUcn(jsFixture, 'search', ['']),
350
+ check: r => !checkForErrors(r.output),
351
+ },
352
+ // Newlines in search
353
+ {
354
+ name: 'Newline in search',
355
+ run: () => runUcn(jsFixture, 'search', ['test\ntest']),
356
+ check: r => !checkForErrors(r.output),
357
+ },
358
+ // Null bytes
359
+ {
360
+ name: 'Null byte in search',
361
+ run: () => runUcn(jsFixture, 'search', ['test\x00test']),
362
+ check: r => !checkForErrors(r.output),
363
+ },
364
+ ];
365
+
366
+ for (const test of tests) {
367
+ const result = test.run();
368
+ const passed = test.check(result);
369
+
370
+ if (passed) {
371
+ results.passed++;
372
+ if (verbose) {
373
+ console.log(` ${c.green}✓${c.reset} ${test.name}`);
374
+ }
375
+ } else {
376
+ results.failed++;
377
+ results.bugs.push({
378
+ language: 'stress',
379
+ command: test.name,
380
+ error: result.error || 'Test failed',
381
+ fullCommand: result.command,
382
+ });
383
+ console.log(` ${c.red}✗${c.reset} ${test.name}`);
384
+ }
385
+ }
386
+ }
387
+
388
+ /**
389
+ * Print summary
390
+ */
391
+ function printSummary() {
392
+ console.log(`\n${c.cyan}${'='.repeat(60)}${c.reset}`);
393
+ console.log(`${c.cyan}PUBLIC REPO TEST SUMMARY${c.reset}`);
394
+ console.log(`${c.cyan}${'='.repeat(60)}${c.reset}`);
395
+
396
+ console.log(`\n ${c.green}Passed:${c.reset} ${results.passed}`);
397
+ console.log(` ${c.red}Failed:${c.reset} ${results.failed}`);
398
+ console.log(` Total: ${results.passed + results.failed}`);
399
+
400
+ if (results.bugs.length > 0) {
401
+ console.log(`\n${c.red}BUGS FOUND (${results.bugs.length}):${c.reset}`);
402
+ console.log(`${'-'.repeat(60)}`);
403
+
404
+ // Group by language
405
+ const byLang = {};
406
+ for (const bug of results.bugs) {
407
+ const key = bug.repo ? `${bug.language}/${bug.repo}` : bug.language;
408
+ if (!byLang[key]) byLang[key] = [];
409
+ byLang[key].push(bug);
410
+ }
411
+
412
+ for (const [key, bugs] of Object.entries(byLang)) {
413
+ console.log(`\n${c.yellow}${key}:${c.reset}`);
414
+ for (const bug of bugs) {
415
+ console.log(` • ${bug.command}: ${bug.error}`);
416
+ if (bug.fullCommand) {
417
+ console.log(` ${c.dim}${bug.fullCommand}${c.reset}`);
418
+ }
419
+ }
420
+ }
421
+
422
+ // Save to file
423
+ const bugsFile = path.join(__dirname, 'public-repos-bugs.json');
424
+ fs.writeFileSync(bugsFile, JSON.stringify(results.bugs, null, 2));
425
+ console.log(`\n${c.dim}Bug report saved to: ${bugsFile}${c.reset}`);
426
+ } else {
427
+ console.log(`\n${c.green}No bugs found!${c.reset}`);
428
+ }
429
+
430
+ console.log(`${c.cyan}${'='.repeat(60)}${c.reset}`);
431
+ }
432
+
433
+ /**
434
+ * Cleanup
435
+ */
436
+ function cleanup() {
437
+ if (!keepRepos && fs.existsSync(TEMP_DIR)) {
438
+ console.log(`\n${c.dim}Cleaning up temp directory...${c.reset}`);
439
+ fs.rmSync(TEMP_DIR, { recursive: true, force: true });
440
+ }
441
+ }
442
+
443
+ /**
444
+ * Main
445
+ */
446
+ async function main() {
447
+ console.log(`${c.cyan}UCN Public Repository Test Suite${c.reset}`);
448
+ console.log(`${c.dim}Testing against real-world repositories${c.reset}`);
449
+
450
+ // Create temp directory
451
+ if (!fs.existsSync(TEMP_DIR)) {
452
+ fs.mkdirSync(TEMP_DIR, { recursive: true });
453
+ }
454
+
455
+ // Test each language
456
+ for (const [lang, repoList] of Object.entries(repos)) {
457
+ for (const repo of repoList) {
458
+ testRepo(lang, repo);
459
+ }
460
+ }
461
+
462
+ // Run stress tests
463
+ runStressTests();
464
+
465
+ // Print summary
466
+ printSummary();
467
+
468
+ // Cleanup
469
+ cleanup();
470
+
471
+ process.exit(results.failed > 0 ? 1 : 0);
472
+ }
473
+
474
+ main().catch(e => {
475
+ console.error(`${c.red}Fatal error: ${e.message}${c.reset}`);
476
+ process.exit(1);
477
+ });