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,619 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Systematic Test Script for UCN
5
+ *
6
+ * Tests all UCN commands across all supported languages to identify bugs.
7
+ * Run with: node test/systematic-test.js [--verbose] [--language=<lang>] [--command=<cmd>]
8
+ */
9
+
10
+ const { execSync, spawn } = require('child_process');
11
+ const path = require('path');
12
+ const fs = require('fs');
13
+
14
+ // Configuration
15
+ const UCN_PATH = path.join(__dirname, '..', 'ucn.js');
16
+ const FIXTURES_PATH = path.join(__dirname, 'fixtures');
17
+ const TIMEOUT = 30000; // 30 seconds
18
+
19
+ // Parse command line arguments
20
+ const args = process.argv.slice(2);
21
+ const verbose = args.includes('--verbose') || args.includes('-v');
22
+ const filterLanguage = args.find(a => a.startsWith('--language='))?.split('=')[1];
23
+ const filterCommand = args.find(a => a.startsWith('--command='))?.split('=')[1];
24
+
25
+ // Colors for output
26
+ const colors = {
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
+ // Test results
37
+ const results = {
38
+ passed: 0,
39
+ failed: 0,
40
+ skipped: 0,
41
+ bugs: [],
42
+ };
43
+
44
+ // Language configurations
45
+ const languages = {
46
+ javascript: {
47
+ path: path.join(FIXTURES_PATH, 'javascript'),
48
+ extension: '.js',
49
+ symbols: {
50
+ functions: ['processData', 'validateInput', 'transformOutput', 'fetchAndProcess', 'helper', 'formatData'],
51
+ classes: ['DataProcessor', 'Service'],
52
+ methods: ['fetch', 'process', 'buildUrl'],
53
+ },
54
+ files: ['main.js', 'utils.js', 'service.js'],
55
+ },
56
+ typescript: {
57
+ path: path.join(FIXTURES_PATH, 'typescript'),
58
+ extension: '.ts',
59
+ symbols: {
60
+ functions: ['filterTasks', 'createTask', 'generateId', 'withRetry', 'processTask', 'createConfig'],
61
+ classes: ['TaskManager', 'Repository', 'DataService', 'Logger'],
62
+ interfaces: ['Task', 'Config', 'IRepository'],
63
+ enums: ['Status', 'LogLevel'],
64
+ },
65
+ files: ['main.ts', 'repository.ts', 'types.ts'],
66
+ },
67
+ python: {
68
+ path: path.join(FIXTURES_PATH, 'python'),
69
+ extension: '.py',
70
+ symbols: {
71
+ functions: ['create_task', 'filter_by_status', 'filter_by_priority', 'format_data', 'validate_input', 'deep_merge'],
72
+ classes: ['TaskManager', 'Task', 'DataService', 'CacheService', 'ApiClient'],
73
+ decorators: ['with_logging', 'with_retry'],
74
+ },
75
+ files: ['main.py', 'utils.py', 'service.py'],
76
+ },
77
+ go: {
78
+ path: path.join(FIXTURES_PATH, 'go'),
79
+ extension: '.go',
80
+ symbols: {
81
+ functions: ['NewTaskManager', 'ValidateTask', 'CreateTask', 'FilterByStatus', 'FormatTask', 'NewDataService'],
82
+ structs: ['Task', 'TaskManager', 'TaskProcessor', 'DataService', 'CacheService'],
83
+ methods: ['AddTask', 'GetTask', 'GetTasks', 'Save', 'Find'],
84
+ },
85
+ files: ['main.go', 'service.go'],
86
+ },
87
+ rust: {
88
+ path: path.join(FIXTURES_PATH, 'rust'),
89
+ extension: '.rs',
90
+ symbols: {
91
+ functions: ['validate_task', 'create_task', 'filter_by_status', 'format_task', 'format_data', 'snake_to_camel'],
92
+ structs: ['Task', 'TaskManager', 'TaskProcessor', 'DataService', 'CacheService', 'Config'],
93
+ traits: ['Entity', 'Repository'],
94
+ enums: ['Status'],
95
+ },
96
+ files: ['main.rs', 'service.rs', 'utils.rs'],
97
+ },
98
+ java: {
99
+ path: path.join(FIXTURES_PATH, 'java'),
100
+ extension: '.java',
101
+ symbols: {
102
+ functions: ['createTask', 'validateTask', 'filterByStatus', 'filterByPriority', 'formatTask', 'formatData'],
103
+ classes: ['Main', 'Task', 'TaskManager', 'TaskProcessor', 'DataService', 'CacheService', 'Utils'],
104
+ interfaces: ['Repository'],
105
+ enums: ['Status'],
106
+ },
107
+ files: ['Main.java', 'DataService.java', 'Utils.java'],
108
+ },
109
+ };
110
+
111
+ // Commands to test with their expected behaviors
112
+ const commands = [
113
+ // Basic commands
114
+ { name: 'toc', args: [], description: 'Table of contents', expectOutput: true },
115
+ { name: 'stats', args: [], description: 'Project statistics', expectOutput: true },
116
+
117
+ // Find commands
118
+ { name: 'find', args: ['$FUNCTION'], description: 'Find symbol definition', expectOutput: true },
119
+ { name: 'usages', args: ['$FUNCTION'], description: 'Find symbol usages', expectOutput: true },
120
+ { name: 'search', args: ['TODO'], description: 'Text search', expectOutput: false }, // May not have TODO
121
+
122
+ // Extraction commands
123
+ { name: 'fn', args: ['$FUNCTION'], description: 'Extract function', expectOutput: true },
124
+ { name: 'class', args: ['$CLASS'], description: 'Extract class', expectOutput: true },
125
+ { name: 'lines', args: ['1-10'], description: 'Extract lines', expectOutput: true, fileMode: true },
126
+
127
+ // Analysis commands
128
+ { name: 'context', args: ['$FUNCTION'], description: 'Show callers and callees', expectOutput: true },
129
+ { name: 'about', args: ['$FUNCTION'], description: 'Full symbol information', expectOutput: true },
130
+ { name: 'smart', args: ['$FUNCTION'], description: 'Function with dependencies', expectOutput: true },
131
+ { name: 'impact', args: ['$FUNCTION'], description: 'Impact analysis', expectOutput: true },
132
+ { name: 'trace', args: ['$FUNCTION', '--depth=2'], description: 'Call tree', expectOutput: true },
133
+ { name: 'related', args: ['$FUNCTION'], description: 'Related functions', expectOutput: false }, // May not find related
134
+
135
+ // Additional analysis commands
136
+ { name: 'typedef', args: ['$CLASS'], description: 'Find type definition', expectOutput: false }, // May not have types
137
+ { name: 'example', args: ['$FUNCTION'], description: 'Best usage example', expectOutput: false }, // May not find example
138
+ { name: 'verify', args: ['$FUNCTION'], description: 'Verify call sites', expectOutput: false }, // May not need verify
139
+
140
+ // Dependency commands
141
+ { name: 'imports', args: ['$FILE'], description: 'File imports', expectOutput: false }, // May not have imports
142
+ { name: 'exporters', args: ['$FILE'], description: 'Who imports file', expectOutput: false },
143
+ { name: 'who-imports', args: ['$FILE'], description: 'Who imports (alias)', expectOutput: false },
144
+ { name: 'graph', args: ['$FILE', '--depth=2'], description: 'Dependency graph', expectOutput: false },
145
+
146
+ // Refactoring commands
147
+ { name: 'deadcode', args: [], description: 'Find unused functions', expectOutput: false },
148
+ { name: 'api', args: [], description: 'Public API', expectOutput: true },
149
+ { name: 'tests', args: ['$FUNCTION'], description: 'Find related tests', expectOutput: false },
150
+
151
+ // JSON output
152
+ { name: 'find', args: ['$FUNCTION', '--json'], description: 'Find with JSON output', expectOutput: true, checkJson: true },
153
+ { name: 'toc', args: ['--json'], description: 'TOC with JSON output', expectOutput: true, checkJson: true },
154
+ ];
155
+
156
+ /**
157
+ * Run a UCN command and capture output
158
+ */
159
+ function runUcn(targetPath, command, args = [], options = {}) {
160
+ const fullArgs = ['node', UCN_PATH, targetPath, command, ...args];
161
+ const cmdStr = fullArgs.join(' ');
162
+
163
+ if (verbose) {
164
+ console.log(`${colors.dim} Running: ${cmdStr}${colors.reset}`);
165
+ }
166
+
167
+ try {
168
+ const output = execSync(cmdStr, {
169
+ timeout: TIMEOUT,
170
+ encoding: 'utf8',
171
+ stdio: ['pipe', 'pipe', 'pipe'],
172
+ cwd: __dirname,
173
+ });
174
+ return { success: true, output: output.trim(), command: cmdStr };
175
+ } catch (error) {
176
+ const stderr = error.stderr?.toString() || '';
177
+ const stdout = error.stdout?.toString() || '';
178
+ return {
179
+ success: false,
180
+ output: stdout,
181
+ error: stderr || error.message,
182
+ command: cmdStr,
183
+ exitCode: error.status,
184
+ };
185
+ }
186
+ }
187
+
188
+ /**
189
+ * Replace placeholders in command arguments
190
+ */
191
+ function replaceArgs(args, symbols, files, lang) {
192
+ return args.map(arg => {
193
+ if (arg === '$FUNCTION') {
194
+ return symbols.functions?.[0] || symbols.methods?.[0] || 'main';
195
+ }
196
+ if (arg === '$CLASS') {
197
+ return symbols.classes?.[0] || symbols.structs?.[0] || 'Main';
198
+ }
199
+ if (arg === '$FILE') {
200
+ return files[0];
201
+ }
202
+ return arg;
203
+ });
204
+ }
205
+
206
+ /**
207
+ * Test a single command
208
+ */
209
+ function testCommand(lang, langConfig, cmd) {
210
+ const testName = `${lang}/${cmd.name}`;
211
+
212
+ if (filterCommand && cmd.name !== filterCommand) {
213
+ return { skipped: true, name: testName };
214
+ }
215
+
216
+ const args = replaceArgs(cmd.args, langConfig.symbols, langConfig.files, lang);
217
+ const targetPath = cmd.fileMode
218
+ ? path.join(langConfig.path, langConfig.files[0])
219
+ : langConfig.path;
220
+
221
+ const result = runUcn(targetPath, cmd.name, args);
222
+
223
+ // Check for success
224
+ if (!result.success) {
225
+ // Check if it's an expected failure (like no results found)
226
+ const isExpectedFailure =
227
+ result.error?.includes('No matches found') ||
228
+ result.error?.includes('not found') ||
229
+ result.error?.includes('No usages found') ||
230
+ result.error?.includes('No tests found') ||
231
+ result.error?.includes('No related') ||
232
+ result.error?.includes('No imports') ||
233
+ result.error?.includes('No deadcode');
234
+
235
+ if (isExpectedFailure && !cmd.expectOutput) {
236
+ return { passed: true, name: testName, note: 'Expected no results' };
237
+ }
238
+
239
+ return {
240
+ passed: false,
241
+ name: testName,
242
+ error: result.error,
243
+ command: result.command,
244
+ exitCode: result.exitCode,
245
+ };
246
+ }
247
+
248
+ // Check for output if expected
249
+ if (cmd.expectOutput && !result.output) {
250
+ return {
251
+ passed: false,
252
+ name: testName,
253
+ error: 'Expected output but got none',
254
+ command: result.command,
255
+ };
256
+ }
257
+
258
+ // Check JSON validity if required
259
+ if (cmd.checkJson) {
260
+ try {
261
+ JSON.parse(result.output);
262
+ } catch (e) {
263
+ return {
264
+ passed: false,
265
+ name: testName,
266
+ error: `Invalid JSON output: ${e.message}`,
267
+ command: result.command,
268
+ output: result.output.substring(0, 200),
269
+ };
270
+ }
271
+ }
272
+
273
+ // Check for error messages in output (potential bugs)
274
+ const errorPatterns = [
275
+ /undefined/i,
276
+ /null is not/i,
277
+ /cannot read property/i,
278
+ /is not a function/i,
279
+ /TypeError/,
280
+ /ReferenceError/,
281
+ /SyntaxError/,
282
+ ];
283
+
284
+ for (const pattern of errorPatterns) {
285
+ if (pattern.test(result.output)) {
286
+ return {
287
+ passed: false,
288
+ name: testName,
289
+ error: `Output contains error pattern: ${pattern}`,
290
+ command: result.command,
291
+ output: result.output.substring(0, 500),
292
+ };
293
+ }
294
+ }
295
+
296
+ return { passed: true, name: testName };
297
+ }
298
+
299
+ /**
300
+ * Test a language
301
+ */
302
+ function testLanguage(lang, langConfig) {
303
+ console.log(`\n${colors.blue}Testing ${lang.toUpperCase()}${colors.reset}`);
304
+ console.log(`${colors.dim} Path: ${langConfig.path}${colors.reset}`);
305
+
306
+ // Check if fixtures exist
307
+ if (!fs.existsSync(langConfig.path)) {
308
+ console.log(`${colors.yellow} ⚠ Fixtures not found, skipping${colors.reset}`);
309
+ results.skipped += commands.length;
310
+ return;
311
+ }
312
+
313
+ // Test each command
314
+ for (const cmd of commands) {
315
+ const result = testCommand(lang, langConfig, cmd);
316
+
317
+ if (result.skipped) {
318
+ results.skipped++;
319
+ continue;
320
+ }
321
+
322
+ if (result.passed) {
323
+ results.passed++;
324
+ if (verbose) {
325
+ const note = result.note ? ` (${result.note})` : '';
326
+ console.log(` ${colors.green}✓${colors.reset} ${cmd.name}: ${cmd.description}${note}`);
327
+ }
328
+ } else {
329
+ results.failed++;
330
+ results.bugs.push({
331
+ language: lang,
332
+ command: cmd.name,
333
+ description: cmd.description,
334
+ error: result.error,
335
+ fullCommand: result.command,
336
+ output: result.output,
337
+ exitCode: result.exitCode,
338
+ });
339
+ console.log(` ${colors.red}✗${colors.reset} ${cmd.name}: ${cmd.description}`);
340
+ if (verbose) {
341
+ console.log(` ${colors.red}Error: ${result.error}${colors.reset}`);
342
+ if (result.output) {
343
+ console.log(` ${colors.dim}Output: ${result.output.substring(0, 200)}${colors.reset}`);
344
+ }
345
+ }
346
+ }
347
+ }
348
+ }
349
+
350
+ /**
351
+ * Run additional edge case tests
352
+ */
353
+ function testEdgeCases() {
354
+ console.log(`\n${colors.blue}Testing EDGE CASES${colors.reset}`);
355
+
356
+ const edgeCases = [
357
+ // Non-existent symbol
358
+ {
359
+ name: 'Non-existent symbol',
360
+ run: () => runUcn(languages.javascript.path, 'find', ['nonExistentSymbol12345']),
361
+ expect: (r) => !r.success || r.output.includes('No symbols found') || r.output.includes('No matches') || r.output === '',
362
+ },
363
+ // Empty arguments
364
+ {
365
+ name: 'Empty find argument',
366
+ run: () => runUcn(languages.javascript.path, 'find', ['']),
367
+ expect: (r) => true, // Should handle gracefully
368
+ },
369
+ // Special characters in search
370
+ {
371
+ name: 'Special chars in search',
372
+ run: () => runUcn(languages.javascript.path, 'search', ['[test]']),
373
+ expect: (r) => r.success || r.error?.includes('regex') || !r.error?.includes('SyntaxError'),
374
+ },
375
+ // Very long symbol name
376
+ {
377
+ name: 'Very long symbol name',
378
+ run: () => runUcn(languages.javascript.path, 'find', ['a'.repeat(1000)]),
379
+ expect: (r) => r.success || !r.error?.includes('Maximum call stack'),
380
+ },
381
+ // Invalid path
382
+ {
383
+ name: 'Invalid path',
384
+ run: () => runUcn('/nonexistent/path', 'toc', []),
385
+ expect: (r) => !r.success && (r.error?.includes('not found') || r.error?.includes('ENOENT') || r.error?.includes('No supported files')),
386
+ },
387
+ // Depth edge cases
388
+ {
389
+ name: 'Depth = 0',
390
+ run: () => runUcn(languages.javascript.path, 'trace', ['processData', '--depth=0']),
391
+ expect: (r) => r.success,
392
+ },
393
+ {
394
+ name: 'Negative depth',
395
+ run: () => runUcn(languages.javascript.path, 'trace', ['processData', '--depth=-1']),
396
+ expect: (r) => r.success || !r.error?.includes('crash'),
397
+ },
398
+ // Unicode in search
399
+ {
400
+ name: 'Unicode in search',
401
+ run: () => runUcn(languages.javascript.path, 'search', ['日本語']),
402
+ expect: (r) => r.success || r.output === '',
403
+ },
404
+ // Multiple flags
405
+ {
406
+ name: 'Multiple flags combined',
407
+ run: () => runUcn(languages.javascript.path, 'find', ['processData', '--json', '--exact', '--top=1']),
408
+ expect: (r) => r.success,
409
+ },
410
+ // Flag variations
411
+ {
412
+ name: 'Exclude flag',
413
+ run: () => runUcn(languages.javascript.path, 'find', ['processData', '--exclude=test']),
414
+ expect: (r) => r.success,
415
+ },
416
+ {
417
+ name: 'In flag (path filter)',
418
+ run: () => runUcn(languages.javascript.path, 'toc', ['--in=.']),
419
+ expect: (r) => r.success,
420
+ },
421
+ {
422
+ name: 'Include tests flag',
423
+ run: () => runUcn(languages.javascript.path, 'usages', ['processData', '--include-tests']),
424
+ expect: (r) => r.success,
425
+ },
426
+ // Context output with expand
427
+ {
428
+ name: 'Context with expand flag',
429
+ run: () => runUcn(languages.javascript.path, 'context', ['processData', '--expand']),
430
+ expect: (r) => r.success,
431
+ },
432
+ // About with code-only flag
433
+ {
434
+ name: 'About with code-only',
435
+ run: () => runUcn(languages.javascript.path, 'about', ['processData', '--code-only']),
436
+ expect: (r) => r.success || r.output.includes('processData'),
437
+ },
438
+ // Smart with types
439
+ {
440
+ name: 'Smart with types',
441
+ run: () => runUcn(languages.typescript.path, 'smart', ['filterTasks', '--with-types']),
442
+ expect: (r) => r.success,
443
+ },
444
+ // File mode commands
445
+ {
446
+ name: 'Single file toc',
447
+ run: () => runUcn(path.join(languages.javascript.path, 'main.js'), 'toc', []),
448
+ expect: (r) => r.success && r.output.includes('processData'),
449
+ },
450
+ {
451
+ name: 'Single file find',
452
+ run: () => runUcn(path.join(languages.javascript.path, 'main.js'), 'find', ['processData']),
453
+ expect: (r) => r.success,
454
+ },
455
+ // Glob mode - note: path needs quoting to prevent shell expansion
456
+ {
457
+ name: 'Glob pattern find',
458
+ run: () => {
459
+ const globPath = `"${path.join(languages.javascript.path, '*.js')}"`;
460
+ return runUcn(globPath, 'find', ['helper']);
461
+ },
462
+ expect: (r) => r.success,
463
+ },
464
+ // Lines command edge cases
465
+ {
466
+ name: 'Lines with out-of-bounds range',
467
+ run: () => runUcn(path.join(languages.javascript.path, 'main.js'), 'lines', ['9999-10000']),
468
+ expect: (r) => !r.success && r.error?.includes('out of bounds'),
469
+ },
470
+ {
471
+ name: 'Lines with reversed range',
472
+ run: () => runUcn(path.join(languages.javascript.path, 'main.js'), 'lines', ['10-5']),
473
+ expect: (r) => r.success && r.output.includes('5'),
474
+ },
475
+ {
476
+ name: 'Lines with non-numeric range',
477
+ run: () => runUcn(path.join(languages.javascript.path, 'main.js'), 'lines', ['abc-def']),
478
+ expect: (r) => !r.success && r.error?.includes('Invalid line range'),
479
+ },
480
+ // Graph with negative depth
481
+ {
482
+ name: 'Graph with negative depth',
483
+ run: () => runUcn(languages.javascript.path, 'graph', ['main.js', '--depth=-5']),
484
+ expect: (r) => r.success && r.output.includes('main.js'),
485
+ },
486
+ // Double-dash separator
487
+ {
488
+ name: 'Double-dash flag separator',
489
+ run: () => runUcn(languages.javascript.path, 'find', ['--', '--test']),
490
+ expect: (r) => r.success && !r.error?.includes('Unknown flag'),
491
+ },
492
+ // Plan command
493
+ {
494
+ name: 'Plan with rename',
495
+ run: () => runUcn(languages.javascript.path, 'plan', ['processData', '--rename-to=processInput']),
496
+ expect: (r) => r.success && r.output.includes('Refactoring plan'),
497
+ },
498
+ // Verify command
499
+ {
500
+ name: 'Verify function calls',
501
+ run: () => runUcn(languages.javascript.path, 'verify', ['processData']),
502
+ expect: (r) => r.success,
503
+ },
504
+ ];
505
+
506
+ for (const testCase of edgeCases) {
507
+ const result = testCase.run();
508
+ const passed = testCase.expect(result);
509
+
510
+ if (passed) {
511
+ results.passed++;
512
+ if (verbose) {
513
+ console.log(` ${colors.green}✓${colors.reset} ${testCase.name}`);
514
+ }
515
+ } else {
516
+ results.failed++;
517
+ const errorMsg = result.error || `Expected test to pass but got: ${JSON.stringify({ success: result.success, output: result.output?.substring(0, 100) })}`;
518
+ results.bugs.push({
519
+ language: 'edge-case',
520
+ command: testCase.name,
521
+ error: errorMsg,
522
+ fullCommand: result.command,
523
+ });
524
+ console.log(` ${colors.red}✗${colors.reset} ${testCase.name}`);
525
+ if (verbose) {
526
+ console.log(` ${colors.red}Error: ${errorMsg}${colors.reset}`);
527
+ }
528
+ }
529
+ }
530
+ }
531
+
532
+ /**
533
+ * Print summary report
534
+ */
535
+ function printSummary() {
536
+ console.log(`\n${colors.cyan}${'='.repeat(60)}${colors.reset}`);
537
+ console.log(`${colors.cyan}TEST SUMMARY${colors.reset}`);
538
+ console.log(`${colors.cyan}${'='.repeat(60)}${colors.reset}`);
539
+
540
+ console.log(`\n ${colors.green}Passed:${colors.reset} ${results.passed}`);
541
+ console.log(` ${colors.red}Failed:${colors.reset} ${results.failed}`);
542
+ console.log(` ${colors.yellow}Skipped:${colors.reset} ${results.skipped}`);
543
+ console.log(` Total: ${results.passed + results.failed + results.skipped}`);
544
+
545
+ if (results.bugs.length > 0) {
546
+ console.log(`\n${colors.red}BUGS FOUND (${results.bugs.length}):${colors.reset}`);
547
+ console.log(`${'-'.repeat(60)}`);
548
+
549
+ // Group bugs by language
550
+ const bugsByLang = {};
551
+ for (const bug of results.bugs) {
552
+ if (!bugsByLang[bug.language]) {
553
+ bugsByLang[bug.language] = [];
554
+ }
555
+ bugsByLang[bug.language].push(bug);
556
+ }
557
+
558
+ for (const [lang, bugs] of Object.entries(bugsByLang)) {
559
+ console.log(`\n${colors.yellow}${lang.toUpperCase()}:${colors.reset}`);
560
+ for (const bug of bugs) {
561
+ console.log(` • ${bug.command}: ${bug.description || bug.error}`);
562
+ console.log(` ${colors.dim}Command: ${bug.fullCommand}${colors.reset}`);
563
+ if (bug.error && bug.error !== bug.description) {
564
+ console.log(` ${colors.red}Error: ${bug.error}${colors.reset}`);
565
+ }
566
+ if (bug.exitCode !== undefined) {
567
+ console.log(` ${colors.dim}Exit code: ${bug.exitCode}${colors.reset}`);
568
+ }
569
+ }
570
+ }
571
+
572
+ // Save bugs to file
573
+ const bugsFile = path.join(__dirname, 'bugs-report.json');
574
+ fs.writeFileSync(bugsFile, JSON.stringify(results.bugs, null, 2));
575
+ console.log(`\n${colors.dim}Bug report saved to: ${bugsFile}${colors.reset}`);
576
+ } else {
577
+ console.log(`\n${colors.green}No bugs found! All tests passed.${colors.reset}`);
578
+ }
579
+
580
+ console.log(`\n${colors.cyan}${'='.repeat(60)}${colors.reset}`);
581
+ }
582
+
583
+ /**
584
+ * Main function
585
+ */
586
+ function main() {
587
+ console.log(`${colors.cyan}UCN Systematic Test Suite${colors.reset}`);
588
+ console.log(`${colors.dim}Testing all commands across all supported languages${colors.reset}`);
589
+ console.log(`${colors.dim}UCN Path: ${UCN_PATH}${colors.reset}`);
590
+ console.log(`${colors.dim}Fixtures Path: ${FIXTURES_PATH}${colors.reset}`);
591
+
592
+ if (filterLanguage) {
593
+ console.log(`${colors.yellow}Filtering by language: ${filterLanguage}${colors.reset}`);
594
+ }
595
+ if (filterCommand) {
596
+ console.log(`${colors.yellow}Filtering by command: ${filterCommand}${colors.reset}`);
597
+ }
598
+
599
+ // Test each language
600
+ for (const [lang, config] of Object.entries(languages)) {
601
+ if (filterLanguage && lang !== filterLanguage) {
602
+ continue;
603
+ }
604
+ testLanguage(lang, config);
605
+ }
606
+
607
+ // Test edge cases
608
+ if (!filterLanguage && !filterCommand) {
609
+ testEdgeCases();
610
+ }
611
+
612
+ // Print summary
613
+ printSummary();
614
+
615
+ // Exit with error code if there were failures
616
+ process.exit(results.failed > 0 ? 1 : 0);
617
+ }
618
+
619
+ main();
package/ucn.js ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * UCN entry point wrapper
5
+ * Redirects to the actual CLI implementation
6
+ */
7
+
8
+ require('./cli/index.js');