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.
Files changed (72) hide show
  1. package/.claude/skills/ucn/SKILL.md +89 -77
  2. package/.claude/skills/ucn/references/commands.md +62 -68
  3. package/.claude/skills/ucn/references/trust-contract.md +31 -6
  4. package/README.md +438 -305
  5. package/assets/demo.svg +31 -0
  6. package/cli/index.js +430 -1385
  7. package/core/account.js +144 -34
  8. package/core/analysis.js +182 -72
  9. package/core/ast-analysis.js +279 -0
  10. package/core/bridge.js +205 -24
  11. package/core/brief.js +27 -58
  12. package/core/build-worker.js +21 -140
  13. package/core/cache.js +513 -11
  14. package/core/callers.js +4920 -456
  15. package/core/check.js +13 -4
  16. package/core/command-contracts.js +402 -0
  17. package/core/compilation-database.js +276 -0
  18. package/core/confidence.js +4 -1
  19. package/core/deadcode.js +397 -19
  20. package/core/discovery.js +359 -46
  21. package/core/entrypoints.js +195 -41
  22. package/core/execute.js +887 -81
  23. package/core/graph-build.js +162 -7
  24. package/core/graph.js +53 -77
  25. package/core/imports.js +65 -6
  26. package/core/index-ir.js +138 -0
  27. package/core/ir.js +195 -0
  28. package/core/output/analysis.js +212 -22
  29. package/core/output/brief.js +23 -0
  30. package/core/output/check.js +4 -0
  31. package/core/output/doctor.js +37 -6
  32. package/core/output/endpoints.js +5 -2
  33. package/core/output/extraction.js +24 -12
  34. package/core/output/find.js +141 -36
  35. package/core/output/graph.js +11 -5
  36. package/core/output/public.js +462 -0
  37. package/core/output/refactoring.js +42 -10
  38. package/core/output/reporting.js +97 -20
  39. package/core/output/search.js +24 -16
  40. package/core/output/shared.js +22 -1
  41. package/core/output/tracing.js +30 -15
  42. package/core/output-budget.js +295 -0
  43. package/core/output.js +1 -0
  44. package/core/parallel-build.js +44 -11
  45. package/core/parser.js +3 -3
  46. package/core/project.js +384 -187
  47. package/core/public-command.js +47 -0
  48. package/core/registry.js +247 -117
  49. package/core/reporting.js +312 -290
  50. package/core/search.js +317 -185
  51. package/core/semantic-provider.js +110 -0
  52. package/core/stacktrace.js +25 -0
  53. package/core/tracing.js +101 -51
  54. package/core/trust-matrix.js +19 -40
  55. package/core/verify.js +534 -37
  56. package/languages/adapter.js +218 -0
  57. package/languages/c-family.js +2791 -0
  58. package/languages/c.js +3 -0
  59. package/languages/cpp.js +3 -0
  60. package/languages/csharp.js +1402 -0
  61. package/languages/go.js +60 -21
  62. package/languages/html.js +2 -2
  63. package/languages/index.js +85 -7
  64. package/languages/java.js +396 -13
  65. package/languages/javascript.js +199 -19
  66. package/languages/python.js +964 -22
  67. package/languages/rust.js +1317 -152
  68. package/languages/utils.js +40 -3
  69. package/mcp/server.js +254 -636
  70. package/package.json +39 -22
  71. package/eslint.config.js +0 -43
  72. package/jsconfig.json +0 -10
@@ -70,6 +70,16 @@ function parseStructuredParams(paramsNode, language) {
70
70
 
71
71
  const params = [];
72
72
 
73
+ // Paren-less JS/TS arrows expose their sole parameter directly instead
74
+ // of wrapping it in formal_parameters.
75
+ if ((language === 'javascript' || language === 'typescript' || language === 'tsx') &&
76
+ paramsNode.type === 'identifier') {
77
+ const paramInfo = {};
78
+ parseJSParam(paramsNode, paramInfo);
79
+ if (paramInfo.name) params.push(paramInfo);
80
+ return params;
81
+ }
82
+
73
83
  for (let i = 0; i < paramsNode.namedChildCount; i++) {
74
84
  const param = paramsNode.namedChild(i);
75
85
  const paramInfo = {};
@@ -171,6 +181,13 @@ function parsePythonParam(param, info) {
171
181
  const typeNode = param.childForFieldName('type');
172
182
  if (nameNode) info.name = nameNode.text;
173
183
  if (typeNode) info.type = typeNode.text;
184
+ // Python wraps annotated splats in typed_parameter, with the actual
185
+ // `*args` / `**kwargs` node as its first named child. Treating those
186
+ // as ordinary required parameters makes every short call look broken.
187
+ if (nameNode && (nameNode.type === 'list_splat_pattern' ||
188
+ nameNode.type === 'dictionary_splat_pattern')) {
189
+ info.rest = true;
190
+ }
174
191
  } else if (param.type === 'default_parameter' || param.type === 'typed_default_parameter') {
175
192
  const nameNode = param.childForFieldName('name');
176
193
  const valueNode = param.childForFieldName('value');
@@ -625,7 +642,7 @@ function isMatchInCommentOrString(rootNode, line, lineContent, term) {
625
642
  * @param {string} content - File content
626
643
  * @param {string} term - Search term
627
644
  * @param {object} parser - Tree-sitter parser
628
- * @param {object} options - { codeOnly: boolean }
645
+ * @param {object} options - { codeOnly: boolean, regex?: boolean, caseSensitive?: boolean }
629
646
  * @returns {Array<{line: number, content: string, column: number}>}
630
647
  */
631
648
  function findMatchesWithASTFilter(content, term, parser, options = {}) {
@@ -636,21 +653,41 @@ function findMatchesWithASTFilter(content, term, parser, options = {}) {
636
653
 
637
654
  // Default: regex mode ON. Use raw pattern unless regex=false.
638
655
  let regex;
656
+ const compiledRegex = options.compiledRegex || null;
657
+ const flags = options.caseSensitive ? 'g' : 'gi';
639
658
  if (options.regex !== false) {
640
- try { regex = new RegExp(term, 'gi'); } catch (e) { regex = new RegExp(term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi'); }
659
+ try { regex = new RegExp(term, flags); } catch (e) { regex = new RegExp(term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), flags); }
641
660
  } else {
642
- regex = new RegExp(term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi');
661
+ regex = new RegExp(term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), flags);
643
662
  }
644
663
 
645
664
  lines.forEach((line, idx) => {
646
665
  const lineNum = idx + 1;
647
666
  let match;
648
667
 
668
+ if (compiledRegex) {
669
+ const matcher = compiledRegex.matcher(line);
670
+ while (matcher.find()) {
671
+ const column = matcher.start();
672
+ if (options.codeOnly) {
673
+ const tokenType = getTokenTypeAtPosition(tree.rootNode, lineNum, column);
674
+ if (tokenType !== 'code') continue;
675
+ }
676
+ matches.push({ line: lineNum, content: line, column });
677
+ break; // Result contract is one record per matching line.
678
+ }
679
+ return;
680
+ }
681
+
649
682
  // Reset regex for each line
650
683
  regex.lastIndex = 0;
651
684
 
652
685
  while ((match = regex.exec(line)) !== null) {
653
686
  const column = match.index;
687
+ // RegExp.exec does not advance after an empty match. Advance it
688
+ // explicitly before any filtering/continue path so patterns such
689
+ // as `.*`, `x*`, `^`, and `\b` cannot wedge CLI or MCP searches.
690
+ if (match[0].length === 0) regex.lastIndex = match.index + 1;
654
691
 
655
692
  if (options.codeOnly) {
656
693
  const tokenType = getTokenTypeAtPosition(tree.rootNode, lineNum, column);