ucn 5.5.3 → 5.6.1
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 +37 -12
- package/.claude/skills/ucn/references/commands.md +18 -14
- package/.claude/skills/ucn/references/resolution-rules.md +4 -0
- package/.claude/skills/ucn/references/trust-contract.md +4 -4
- package/CHANGELOG.md +485 -0
- package/README.md +66 -29
- package/cli/flags.js +184 -0
- package/cli/index.js +137 -324
- package/core/account.js +44 -87
- package/core/analysis.js +274 -108
- package/core/ast-analysis.js +2 -0
- package/core/bridge.js +14 -0
- package/core/cache.js +103 -16
- package/core/call-fit.js +130 -0
- package/core/callers.js +353 -71
- package/core/capture.js +363 -0
- package/core/change-kinds.js +1107 -0
- package/core/check.js +1150 -139
- package/core/command-contracts.js +8 -7
- package/core/constructed-class.js +1195 -0
- package/core/contract-membership.js +142 -0
- package/core/deadcode.js +455 -106
- package/core/deletion.js +421 -0
- package/core/discovery.js +104 -9
- package/core/entrypoint-toml.js +110 -0
- package/core/entrypoints.js +12 -6
- package/core/evidence-bands.js +68 -0
- package/core/evidence-bands.json +44 -0
- package/core/execute.js +172 -161
- package/core/hot-summary.js +241 -0
- package/core/index-ir.js +5 -3
- package/core/ir.js +6 -2
- package/core/member-signatures.js +789 -0
- package/core/outgoing-check.js +116 -0
- package/core/output/analysis-ext.js +20 -10
- package/core/output/analysis.js +98 -60
- package/core/output/check.js +101 -37
- package/core/output/doctor.js +34 -1
- package/core/output/find.js +2 -1
- package/core/output/lines.js +63 -3
- package/core/output/public.js +29 -11
- package/core/output/refactoring.js +32 -0
- package/core/output/reporting.js +19 -2
- package/core/output/search.js +14 -2
- package/core/output/tracing.js +36 -8
- package/core/output-budget.js +146 -61
- package/core/parallel-build.js +3 -3
- package/core/project.js +106 -23
- package/core/provenance-type.js +12 -5
- package/core/public-command.js +35 -9
- package/core/python-binding-identity.js +707 -0
- package/core/python-modules.js +6 -0
- package/core/reflection.js +248 -31
- package/core/registry.js +623 -72
- package/core/reporting.js +69 -34
- package/core/review-items.js +75 -0
- package/core/runtime-entrypoints.js +159 -0
- package/core/search.js +148 -164
- package/core/shared.js +0 -45
- package/core/snapshot.js +350 -0
- package/core/source-universe.js +707 -0
- package/core/test-units.js +3698 -0
- package/core/tracing.js +270 -658
- package/core/verify.js +63 -22
- package/languages/adapter.js +8 -3
- package/languages/c-family.js +12 -0
- package/languages/csharp.js +634 -0
- package/languages/go.js +120 -0
- package/languages/index.js +466 -4
- package/languages/java.js +712 -0
- package/languages/javascript.js +1498 -6
- package/languages/lexical-scope.js +68 -0
- package/languages/python.js +1708 -3
- package/languages/rust.js +209 -104
- package/languages/static-js.js +88 -0
- package/mcp/server.js +44 -110
- package/package.json +11 -5
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// A data-only TOML reader for the PEP 621 entrypoint subtree. Other values
|
|
4
|
+
// remain opaque; malformed or nonliteral entrypoint values return unknown.
|
|
5
|
+
function entrypointToml(source) {
|
|
6
|
+
const tokens = [];
|
|
7
|
+
let line = 1;
|
|
8
|
+
let offset = 0;
|
|
9
|
+
while (offset < source.length) {
|
|
10
|
+
const ch = source[offset];
|
|
11
|
+
if (ch === '\n') { tokens.push({ kind: 'newline', line }); offset++; line++; continue; }
|
|
12
|
+
if (/\s/.test(ch)) { offset++; continue; }
|
|
13
|
+
if (ch === '#') { while (offset < source.length && source[offset] !== '\n') offset++; continue; }
|
|
14
|
+
if ('[]=.,{}'.includes(ch)) { tokens.push({ kind: ch, line }); offset++; continue; }
|
|
15
|
+
if (ch === '"' || ch === "'") {
|
|
16
|
+
const startLine = line;
|
|
17
|
+
const triple = source.slice(offset, offset + 3) === ch.repeat(3);
|
|
18
|
+
const delimiter = triple ? ch.repeat(3) : ch;
|
|
19
|
+
offset += delimiter.length;
|
|
20
|
+
let value = '';
|
|
21
|
+
let valid = true;
|
|
22
|
+
let closed = false;
|
|
23
|
+
while (offset < source.length) {
|
|
24
|
+
if (source.slice(offset, offset + delimiter.length) === delimiter) {
|
|
25
|
+
offset += delimiter.length; closed = true; break;
|
|
26
|
+
}
|
|
27
|
+
const current = source[offset++];
|
|
28
|
+
if (current === '\n') line++;
|
|
29
|
+
if (current !== '\\' || ch === "'") { value += current; continue; }
|
|
30
|
+
const escaped = source[offset++];
|
|
31
|
+
const simple = { b: '\b', t: '\t', n: '\n', f: '\f', r: '\r', '"': '"', '\\': '\\' };
|
|
32
|
+
if (Object.hasOwn(simple, escaped)) value += simple[escaped];
|
|
33
|
+
else if (escaped === 'u' || escaped === 'U') {
|
|
34
|
+
const width = escaped === 'u' ? 4 : 8;
|
|
35
|
+
const digits = source.slice(offset, offset + width);
|
|
36
|
+
if (!/^[0-9a-fA-F]+$/.test(digits) || digits.length !== width || parseInt(digits, 16) > 0x10ffff) valid = false;
|
|
37
|
+
else value += String.fromCodePoint(parseInt(digits, 16));
|
|
38
|
+
offset += width;
|
|
39
|
+
} else if (triple && /\s/.test(escaped || '')) {
|
|
40
|
+
if (escaped === '\n') line++;
|
|
41
|
+
while (offset < source.length && /\s/.test(source[offset])) {
|
|
42
|
+
if (source[offset++] === '\n') line++;
|
|
43
|
+
}
|
|
44
|
+
} else valid = false;
|
|
45
|
+
}
|
|
46
|
+
tokens.push({ kind: 'string', value: valid && closed ? value.replace(/^\n/, '') : null, line: startLine });
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const start = offset;
|
|
50
|
+
while (offset < source.length && !/[\s[\]=.,{}#]/.test(source[offset])) offset++;
|
|
51
|
+
if (offset === start) offset++;
|
|
52
|
+
tokens.push({ kind: 'bare', value: source.slice(start, offset), line });
|
|
53
|
+
}
|
|
54
|
+
const rows = [];
|
|
55
|
+
let at = 0;
|
|
56
|
+
let section = [];
|
|
57
|
+
const keys = () => {
|
|
58
|
+
const parts = [];
|
|
59
|
+
while (tokens[at] && ['bare', 'string'].includes(tokens[at].kind)) {
|
|
60
|
+
parts.push(tokens[at++].value);
|
|
61
|
+
if (tokens[at]?.kind !== '.') break;
|
|
62
|
+
at++;
|
|
63
|
+
}
|
|
64
|
+
return parts;
|
|
65
|
+
};
|
|
66
|
+
const entryPath = parts => parts[0] === 'project' &&
|
|
67
|
+
(['scripts', 'gui-scripts'].includes(parts[1]) || parts[1] === 'entry-points');
|
|
68
|
+
const readValue = (parts, depth = 0) => {
|
|
69
|
+
const token = tokens[at++];
|
|
70
|
+
if (!token || depth > 32) { if (entryPath(parts)) rows.push({ unknown: true, line: token?.line }); return; }
|
|
71
|
+
if (token.kind === '{') {
|
|
72
|
+
while (tokens[at] && tokens[at].kind !== '}') {
|
|
73
|
+
if (['newline', ','].includes(tokens[at].kind)) { at++; continue; }
|
|
74
|
+
const nested = keys();
|
|
75
|
+
if (!nested.length || tokens[at++]?.kind !== '=') { if (entryPath(parts)) rows.push({ unknown: true, line: token.line }); break; }
|
|
76
|
+
readValue([...parts, ...nested], depth + 1);
|
|
77
|
+
}
|
|
78
|
+
if (tokens[at]?.kind === '}') at++;
|
|
79
|
+
} else if (token.kind === '[') {
|
|
80
|
+
let balance = 1;
|
|
81
|
+
while (tokens[at] && balance) {
|
|
82
|
+
const item = tokens[at++];
|
|
83
|
+
if (item.kind === '[') balance++;
|
|
84
|
+
if (item.kind === ']') balance--;
|
|
85
|
+
if (parts.join('.') === 'project.dynamic' && item.kind === 'string' &&
|
|
86
|
+
['scripts', 'gui-scripts', 'entry-points'].includes(item.value)) rows.push({ unknown: true, line: item.line });
|
|
87
|
+
}
|
|
88
|
+
if (entryPath(parts)) rows.push({ unknown: true, line: token.line });
|
|
89
|
+
} else if (entryPath(parts)) {
|
|
90
|
+
rows.push(token.kind === 'string' && token.value != null
|
|
91
|
+
? { value: token.value, line: token.line } : { unknown: true, line: token.line });
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
while (at < tokens.length) {
|
|
95
|
+
if (tokens[at].kind === 'newline') { at++; continue; }
|
|
96
|
+
if (tokens[at].kind === '[') {
|
|
97
|
+
at++;
|
|
98
|
+
section = keys();
|
|
99
|
+
while (tokens[at] && ![']', 'newline'].includes(tokens[at].kind)) at++;
|
|
100
|
+
if (tokens[at]?.kind === ']') at++;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const parts = keys();
|
|
104
|
+
if (parts.length && tokens[at]?.kind === '=') { at++; readValue([...section, ...parts]); }
|
|
105
|
+
else while (tokens[at] && tokens[at].kind !== 'newline') at++;
|
|
106
|
+
}
|
|
107
|
+
return rows;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
module.exports = { entrypointToml };
|
package/core/entrypoints.js
CHANGED
|
@@ -595,6 +595,12 @@ const FRAMEWORK_PATTERNS = [
|
|
|
595
595
|
|
|
596
596
|
];
|
|
597
597
|
|
|
598
|
+
// What the `type` and `framework` filters accept: the kinds and the labels a
|
|
599
|
+
// pattern above carries, and nothing else.
|
|
600
|
+
const ENTRYPOINT_TYPES = Object.freeze([...new Set(FRAMEWORK_PATTERNS.map(p => p.type))].sort());
|
|
601
|
+
const ENTRYPOINT_FRAMEWORKS = Object.freeze(
|
|
602
|
+
[...new Set(FRAMEWORK_PATTERNS.map(p => p.framework.toLowerCase()))].sort());
|
|
603
|
+
|
|
598
604
|
// ============================================================================
|
|
599
605
|
// DETECTION
|
|
600
606
|
// ============================================================================
|
|
@@ -864,11 +870,10 @@ function detectEntrypoints(index, options = {}) {
|
|
|
864
870
|
// Validate --type against the pattern registry up front — an unknown
|
|
865
871
|
// value used to fall through to the filter and silently return nothing.
|
|
866
872
|
if (options.type) {
|
|
867
|
-
|
|
868
|
-
if (!validTypes.has(options.type)) {
|
|
873
|
+
if (!ENTRYPOINT_TYPES.includes(options.type)) {
|
|
869
874
|
return {
|
|
870
875
|
error: 'invalid-type',
|
|
871
|
-
message: `Unknown type "${options.type}". Valid: ${
|
|
876
|
+
message: `Unknown type "${options.type}". Valid: ${ENTRYPOINT_TYPES.join(', ')}.`,
|
|
872
877
|
};
|
|
873
878
|
}
|
|
874
879
|
}
|
|
@@ -876,13 +881,12 @@ function detectEntrypoints(index, options = {}) {
|
|
|
876
881
|
// Same discipline for --framework (fix #243) — a typo like 'flsk'
|
|
877
882
|
// silently filtered everything to an empty result.
|
|
878
883
|
if (options.framework) {
|
|
879
|
-
const validFrameworks = new Set(FRAMEWORK_PATTERNS.map(p => p.framework.toLowerCase()));
|
|
880
884
|
const wanted = String(options.framework).split(',').map(s => s.trim().toLowerCase()).filter(Boolean);
|
|
881
|
-
const unknown = wanted.filter(f => !
|
|
885
|
+
const unknown = wanted.filter(f => !ENTRYPOINT_FRAMEWORKS.includes(f));
|
|
882
886
|
if (unknown.length > 0) {
|
|
883
887
|
return {
|
|
884
888
|
error: 'invalid-framework',
|
|
885
|
-
message: `Unknown framework "${unknown.join('", "')}". Valid: ${
|
|
889
|
+
message: `Unknown framework "${unknown.join('", "')}". Valid: ${ENTRYPOINT_FRAMEWORKS.join(', ')}.`,
|
|
886
890
|
};
|
|
887
891
|
}
|
|
888
892
|
}
|
|
@@ -1462,6 +1466,8 @@ function isFrameworkEntrypoint(symbol, index) {
|
|
|
1462
1466
|
}
|
|
1463
1467
|
|
|
1464
1468
|
module.exports = {
|
|
1469
|
+
ENTRYPOINT_TYPES,
|
|
1470
|
+
ENTRYPOINT_FRAMEWORKS,
|
|
1465
1471
|
FRAMEWORK_PATTERNS,
|
|
1466
1472
|
detectEntrypoints,
|
|
1467
1473
|
isFrameworkEntrypoint,
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* How often an unverified reason was a real edge on the release board.
|
|
5
|
+
*
|
|
6
|
+
* Display only. The table labels the per-reason summary line of an
|
|
7
|
+
* unverified section and orders that line; it decides no tier, filter,
|
|
8
|
+
* score or account, and nothing outside the output layer reads it (guarded
|
|
9
|
+
* by test/evidence-bands.test.js). Entries of a section keep their own
|
|
10
|
+
* order.
|
|
11
|
+
*
|
|
12
|
+
* The table (core/evidence-bands.json) is written by
|
|
13
|
+
* `node eval/run-unverified-census.js --bands` from the release board's
|
|
14
|
+
* oracle reports: per reason, the candidates the language's own tool scored
|
|
15
|
+
* and how many of them were real edges. A reason scored fewer times than
|
|
16
|
+
* the table's minimum has no row and is labelled "not measured".
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const DEFAULT_TABLE = require('./evidence-bands.json');
|
|
20
|
+
|
|
21
|
+
const BAND_RANK = Object.freeze({ often: 0, sometimes: 1, rarely: 2 });
|
|
22
|
+
const UNMEASURED_RANK = 3;
|
|
23
|
+
|
|
24
|
+
const codeUnitCompare = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
|
25
|
+
|
|
26
|
+
/** The measured row of a reason: { band, scored, real }, or null. */
|
|
27
|
+
function evidenceBand(reason, table = DEFAULT_TABLE) {
|
|
28
|
+
const row = table?.reasons?.[reason];
|
|
29
|
+
if (!row || BAND_RANK[row.band] === undefined) return null;
|
|
30
|
+
return { band: row.band, scored: row.scored, real: row.real };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The reasons of a list of unverified entries (or a `{ reason: count }`
|
|
35
|
+
* map) with their counts and bands: most often real first, then the larger
|
|
36
|
+
* count, then the name.
|
|
37
|
+
*/
|
|
38
|
+
function summarizeReasons(entries, table = DEFAULT_TABLE) {
|
|
39
|
+
const counts = new Map();
|
|
40
|
+
if (Array.isArray(entries)) {
|
|
41
|
+
for (const entry of entries) {
|
|
42
|
+
const reason = entry?.reason || 'unknown';
|
|
43
|
+
counts.set(reason, (counts.get(reason) || 0) + 1);
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
for (const [reason, count] of Object.entries(entries || {})) {
|
|
47
|
+
if (count > 0) counts.set(reason, count);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const rows = [...counts].map(([reason, count]) => ({ reason, count, ...evidenceBand(reason, table) }));
|
|
51
|
+
const rank = row => (row.band ? BAND_RANK[row.band] : UNMEASURED_RANK);
|
|
52
|
+
return rows.sort((a, b) => rank(a) - rank(b) || b.count - a.count || codeUnitCompare(a.reason, b.reason));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The summary line of an unverified section, or null when it would add
|
|
57
|
+
* nothing: one reason that is not measured.
|
|
58
|
+
*/
|
|
59
|
+
function reasonSummaryLine(entries, table = DEFAULT_TABLE) {
|
|
60
|
+
const rows = summarizeReasons(entries, table);
|
|
61
|
+
if (rows.length === 0 || (rows.length === 1 && !rows[0].band)) return null;
|
|
62
|
+
const parts = rows.map(row => (row.band
|
|
63
|
+
? `${row.reason} ${row.count} (${row.band}, ${row.real}/${row.scored})`
|
|
64
|
+
: `${row.reason} ${row.count} (not measured)`));
|
|
65
|
+
return ` by reason, with how often each was a real call on the release board: ${parts.join('; ')}`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = { evidenceBand, summarizeReasons, reasonSummaryLine };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"source": "unverified candidates of the release oracle board, scored by each language's own tool",
|
|
4
|
+
"boardDate": "2026-11-13",
|
|
5
|
+
"repositories": [
|
|
6
|
+
"cjson",
|
|
7
|
+
"clap",
|
|
8
|
+
"cobra",
|
|
9
|
+
"fmt",
|
|
10
|
+
"httpx",
|
|
11
|
+
"javapoet",
|
|
12
|
+
"newtonsoft-json",
|
|
13
|
+
"preact-signals",
|
|
14
|
+
"ripgrep",
|
|
15
|
+
"viper"
|
|
16
|
+
],
|
|
17
|
+
"minimumScored": 30,
|
|
18
|
+
"thresholds": {
|
|
19
|
+
"often": 0.5,
|
|
20
|
+
"sometimes": 0.15
|
|
21
|
+
},
|
|
22
|
+
"reasons": {
|
|
23
|
+
"method-ambiguous": {
|
|
24
|
+
"scored": 62,
|
|
25
|
+
"real": 21,
|
|
26
|
+
"band": "sometimes"
|
|
27
|
+
},
|
|
28
|
+
"overload-ambiguous": {
|
|
29
|
+
"scored": 591,
|
|
30
|
+
"real": 393,
|
|
31
|
+
"band": "often"
|
|
32
|
+
},
|
|
33
|
+
"possible-dispatch": {
|
|
34
|
+
"scored": 782,
|
|
35
|
+
"real": 59,
|
|
36
|
+
"band": "rarely"
|
|
37
|
+
},
|
|
38
|
+
"single-owner": {
|
|
39
|
+
"scored": 45,
|
|
40
|
+
"real": 45,
|
|
41
|
+
"band": "often"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|