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/languages/utils.js
CHANGED
|
@@ -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,
|
|
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, '\\$&'),
|
|
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);
|