ucn 5.5.3 → 5.6.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.
Files changed (71) hide show
  1. package/.claude/skills/ucn/SKILL.md +35 -12
  2. package/.claude/skills/ucn/references/commands.md +18 -14
  3. package/.claude/skills/ucn/references/resolution-rules.md +4 -0
  4. package/.claude/skills/ucn/references/trust-contract.md +4 -4
  5. package/CHANGELOG.md +474 -0
  6. package/README.md +69 -29
  7. package/cli/flags.js +184 -0
  8. package/cli/index.js +137 -324
  9. package/core/account.js +44 -87
  10. package/core/analysis.js +273 -107
  11. package/core/ast-analysis.js +2 -0
  12. package/core/bridge.js +14 -0
  13. package/core/cache.js +101 -16
  14. package/core/callers.js +352 -70
  15. package/core/capture.js +331 -0
  16. package/core/change-kinds.js +888 -0
  17. package/core/check.js +1117 -139
  18. package/core/command-contracts.js +7 -6
  19. package/core/constructed-class.js +1195 -0
  20. package/core/contract-membership.js +142 -0
  21. package/core/deadcode.js +362 -84
  22. package/core/deletion.js +401 -0
  23. package/core/discovery.js +104 -9
  24. package/core/entrypoints.js +12 -6
  25. package/core/evidence-bands.js +68 -0
  26. package/core/evidence-bands.json +44 -0
  27. package/core/execute.js +172 -161
  28. package/core/hot-summary.js +241 -0
  29. package/core/index-ir.js +3 -1
  30. package/core/ir.js +5 -1
  31. package/core/member-signatures.js +789 -0
  32. package/core/output/analysis-ext.js +20 -10
  33. package/core/output/analysis.js +98 -60
  34. package/core/output/check.js +98 -37
  35. package/core/output/doctor.js +34 -1
  36. package/core/output/find.js +2 -1
  37. package/core/output/lines.js +63 -3
  38. package/core/output/public.js +29 -11
  39. package/core/output/refactoring.js +32 -0
  40. package/core/output/reporting.js +15 -2
  41. package/core/output/search.js +11 -2
  42. package/core/output/tracing.js +35 -7
  43. package/core/output-budget.js +124 -57
  44. package/core/parallel-build.js +3 -3
  45. package/core/project.js +106 -23
  46. package/core/provenance-type.js +12 -5
  47. package/core/public-command.js +35 -9
  48. package/core/python-modules.js +6 -0
  49. package/core/reflection.js +151 -23
  50. package/core/registry.js +623 -72
  51. package/core/reporting.js +69 -34
  52. package/core/review-items.js +75 -0
  53. package/core/search.js +89 -128
  54. package/core/shared.js +0 -45
  55. package/core/snapshot.js +350 -0
  56. package/core/source-universe.js +707 -0
  57. package/core/test-units.js +3624 -0
  58. package/core/tracing.js +150 -96
  59. package/core/verify.js +27 -11
  60. package/languages/adapter.js +8 -3
  61. package/languages/c-family.js +12 -0
  62. package/languages/csharp.js +634 -0
  63. package/languages/go.js +120 -0
  64. package/languages/index.js +422 -4
  65. package/languages/java.js +712 -0
  66. package/languages/javascript.js +1460 -2
  67. package/languages/lexical-scope.js +68 -0
  68. package/languages/python.js +1672 -1
  69. package/languages/rust.js +91 -0
  70. package/mcp/server.js +44 -110
  71. package/package.json +10 -5
package/cli/flags.js ADDED
@@ -0,0 +1,184 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * CLI flag parsing, written from the registry's parameter table.
5
+ *
6
+ * Which flags set a parameter, which of them take a value, and the type and
7
+ * bounds of a value are the table's. A parameter the table gains is parsed
8
+ * here without a change to this file. Flags of the CLI process itself
9
+ * (--json, --no-cache, --workers and the like) are not parameters of a
10
+ * command and stay with the CLI entry point.
11
+ */
12
+
13
+ const {
14
+ PARAMS,
15
+ cliSpellings,
16
+ paramRangeError,
17
+ } = require('../core/registry');
18
+
19
+ /** A flag whose value the table refuses. Not a UCN error: reported before any index is built. */
20
+ class FlagValidationError extends Error {
21
+ constructor(msg) { super(msg); this.name = 'FlagValidationError'; }
22
+ }
23
+
24
+ /**
25
+ * The value written for a value flag, or undefined when the flag is absent:
26
+ * `--flag=value` first, else `--flag value` when the next token is not a
27
+ * flag. `{ missing: true }` when the flag is written with no value.
28
+ */
29
+ function writtenValue(tokens, flag) {
30
+ const prefix = `${flag}=`;
31
+ const equals = tokens.find(token => token.startsWith(prefix));
32
+ if (equals !== undefined) return equals.slice(prefix.length);
33
+ const at = tokens.indexOf(flag);
34
+ if (at === -1) return undefined;
35
+ if (at + 1 < tokens.length && !tokens[at + 1].startsWith('-')) return tokens[at + 1];
36
+ return { missing: true };
37
+ }
38
+
39
+ /** Every value of a repeatable flag, comma-separated values split: the `=` forms first, then the spaced ones. */
40
+ function repeatedValues(tokens, flags) {
41
+ const values = [];
42
+ for (const token of tokens) {
43
+ for (const flag of flags) {
44
+ if (token.startsWith(`${flag}=`)) values.push(...token.slice(flag.length + 1).split(','));
45
+ }
46
+ }
47
+ for (const flag of flags) {
48
+ for (let i = 0; i < tokens.length; i++) {
49
+ if (tokens[i] === flag && i + 1 < tokens.length && !tokens[i + 1].startsWith('-')) {
50
+ values.push(...tokens[i + 1].split(','));
51
+ }
52
+ }
53
+ }
54
+ return values;
55
+ }
56
+
57
+ /**
58
+ * A boolean parameter as the tokens set it, or undefined. A negative
59
+ * spelling wins over an affirmative one. An explicit value describes the
60
+ * written spelling: `--no-flag=false` enables the parameter.
61
+ */
62
+ function writtenBoolean(tokens, spellings, problems) {
63
+ let value;
64
+ for (const [flags, enabled] of [[spellings.on, true], [spellings.off, false]]) {
65
+ for (const flag of flags) {
66
+ if (tokens.includes(flag)) value = enabled;
67
+ // Validate every occurrence, including a malformed repeat.
68
+ for (const token of tokens.filter(token => token.startsWith(`${flag}=`))) {
69
+ const written = token.slice(flag.length + 1);
70
+ if (written === 'true') value = enabled;
71
+ else if (written === 'false') value = !enabled;
72
+ else problems.push(`Invalid ${flag} value: must be true or false (got "${written}")`);
73
+ }
74
+ }
75
+ }
76
+ return value;
77
+ }
78
+
79
+ /** CLI process controls use the same boolean syntax as command parameters. */
80
+ function parseProcessFlags(tokens) {
81
+ const flags = {};
82
+ const problems = [];
83
+ const spellings = {
84
+ help: { on: ['--help', '-h'], off: [] },
85
+ version: { on: ['--version', '-v'], off: [] },
86
+ mcp: { on: ['--mcp'], off: [] },
87
+ json: { on: ['--json'], off: [] },
88
+ quiet: { on: ['--quiet'], off: ['--verbose', '--no-quiet'] },
89
+ cache: { on: [], off: ['--no-cache'] },
90
+ clearCache: { on: ['--clear-cache'], off: [] },
91
+ interactive: { on: ['--interactive', '-i'], off: [] },
92
+ };
93
+ for (const [name, names] of Object.entries(spellings)) {
94
+ const value = writtenBoolean(tokens, names, problems);
95
+ if (value !== undefined) flags[name] = value;
96
+ }
97
+ Object.defineProperty(flags, 'problems', { value: problems, enumerable: false });
98
+ return flags;
99
+ }
100
+
101
+ /**
102
+ * Parse the flags that set a parameter. The result holds one key per
103
+ * parameter the tokens set, with a value of the table's type; a parameter
104
+ * that is not written has no key. Values the table refuses are collected,
105
+ * not thrown: `validateFlags` raises the first, after the caller has had
106
+ * the chance to report an unknown flag.
107
+ */
108
+ function parseFlags(tokens) {
109
+ const flags = {};
110
+ const problems = [];
111
+ for (const [param, spec] of Object.entries(PARAMS)) {
112
+ const spellings = cliSpellings(param);
113
+ if (spec.type === 'boolean') {
114
+ const value = writtenBoolean(tokens, spellings, problems);
115
+ if (value !== undefined) flags[param] = value;
116
+ continue;
117
+ }
118
+ if (spec.repeat) {
119
+ const values = repeatedValues(tokens, spellings.value);
120
+ if (values.length > 0) flags[param] = values;
121
+ for (const flag of spellings.value) {
122
+ const bare = tokens.some((token, i) => token === flag &&
123
+ !(i + 1 < tokens.length && !tokens[i + 1].startsWith('-')));
124
+ if (bare) problems.push(`${flag} requires a value.`);
125
+ }
126
+ continue;
127
+ }
128
+ for (const flag of spellings.value) {
129
+ const written = writtenValue(tokens, flag);
130
+ if (written === undefined) continue;
131
+ if (written.missing) {
132
+ problems.push(`${flag} requires a value.`);
133
+ break;
134
+ }
135
+ if (spec.type === 'integer' || spec.type === 'number') {
136
+ const problem = numberProblem(param, spec, written, flag);
137
+ if (problem) problems.push(problem);
138
+ else flags[param] = Number(written.trim());
139
+ } else {
140
+ flags[param] = written;
141
+ }
142
+ break;
143
+ }
144
+ }
145
+ Object.defineProperty(flags, 'problems', { value: problems, enumerable: false });
146
+ return flags;
147
+ }
148
+
149
+ /**
150
+ * Why a written number is not one the table takes, or null. Integers use the
151
+ * canonical base-10 spelling: Number() also reads exponent, decimal and
152
+ * hexadecimal forms (`1e1`, `2.5`, `0x10`), and a malformed line pin must
153
+ * not select a real definition by accident.
154
+ */
155
+ function numberProblem(param, spec, written, flag) {
156
+ if (spec.type === 'integer' && !/^\d+$/.test(written.trim())) {
157
+ const label = spec.min === 0 ? 'non-negative integer' : 'positive integer';
158
+ return `Invalid ${flag} value: must be a ${label} (got "${written}")`;
159
+ }
160
+ return paramRangeError(param, spec.type === 'integer' ? Number(written.trim()) : written, flag);
161
+ }
162
+
163
+ /** Raise the first value the table refused. */
164
+ function validateFlags(flags) {
165
+ if (flags.problems && flags.problems.length > 0) throw new FlagValidationError(flags.problems[0]);
166
+ }
167
+
168
+ /**
169
+ * A non-negative integer of a process flag (`--workers`), or undefined when
170
+ * the flag is absent.
171
+ */
172
+ function processInteger(tokens, flag, { max = 10000000 } = {}) {
173
+ const written = writtenValue(tokens, flag);
174
+ if (written === undefined) return undefined;
175
+ if (written.missing) throw new FlagValidationError(`${flag} requires a value.`);
176
+ if (!/^\d+$/.test(written.trim())) {
177
+ throw new FlagValidationError(`Invalid ${flag} value: must be a non-negative integer (got "${written}")`);
178
+ }
179
+ const n = Number(written.trim());
180
+ if (n > max) throw new FlagValidationError(`Invalid ${flag} value: ${n} exceeds maximum (${max})`);
181
+ return n;
182
+ }
183
+
184
+ module.exports = { FlagValidationError, parseFlags, parseProcessFlags, validateFlags, processInteger };