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/core/check.js
CHANGED
|
@@ -55,12 +55,17 @@ function check(index, options = {}) {
|
|
|
55
55
|
file: options.file,
|
|
56
56
|
});
|
|
57
57
|
} catch (e) {
|
|
58
|
-
//
|
|
58
|
+
// The gate could not run at all. This must stay distinguishable from a
|
|
59
|
+
// clean tree in every machine-readable field (status, ok), not just the
|
|
60
|
+
// free-text reason — CI gating on exit code or `empty` must never read
|
|
61
|
+
// "could not run" as "passed".
|
|
62
|
+
const message = e && e.message ? e.message : 'diff failed';
|
|
59
63
|
return {
|
|
60
64
|
base: options.base || 'HEAD',
|
|
61
65
|
staged: !!options.staged,
|
|
62
|
-
|
|
63
|
-
|
|
66
|
+
ok: false,
|
|
67
|
+
status: /not a git repositor/i.test(message) ? 'not-a-repo' : 'diff-failed',
|
|
68
|
+
error: message,
|
|
64
69
|
};
|
|
65
70
|
}
|
|
66
71
|
|
|
@@ -78,8 +83,10 @@ function check(index, options = {}) {
|
|
|
78
83
|
return {
|
|
79
84
|
base: options.base || 'HEAD',
|
|
80
85
|
staged: !!options.staged,
|
|
86
|
+
ok: true,
|
|
87
|
+
status: 'clean',
|
|
81
88
|
empty: true,
|
|
82
|
-
reason:
|
|
89
|
+
reason: 'no changes detected',
|
|
83
90
|
};
|
|
84
91
|
}
|
|
85
92
|
|
|
@@ -272,6 +279,8 @@ function check(index, options = {}) {
|
|
|
272
279
|
return {
|
|
273
280
|
base: options.base || 'HEAD',
|
|
274
281
|
staged: !!options.staged,
|
|
282
|
+
ok: true,
|
|
283
|
+
status: 'checked',
|
|
275
284
|
changed: items,
|
|
276
285
|
totalChanged: allChanged.length + deleted.length,
|
|
277
286
|
truncated: !!(limit && allChanged.length > limit),
|
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Human- and agent-facing command contracts.
|
|
5
|
+
*
|
|
6
|
+
* Registry.js owns command/parameter spelling. trust-matrix.js owns the proof
|
|
7
|
+
* and decision-safety classification. This module owns the question each
|
|
8
|
+
* command answers, its explicit modes, boundaries, defaults, and workflow
|
|
9
|
+
* guidance. CLI help, MCP discovery, tracked docs, and contract tests consume
|
|
10
|
+
* these records so the public surface stays honest across every adapter.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const { CANONICAL_COMMANDS, FLAG_APPLICABILITY, toCliName } = require('./registry');
|
|
14
|
+
const { COMMAND_TRUST_MATRIX } = require('./trust-matrix');
|
|
15
|
+
|
|
16
|
+
function contract(spec) {
|
|
17
|
+
return Object.freeze({
|
|
18
|
+
...spec,
|
|
19
|
+
primaryQuestion: spec.primaryQuestion || spec.question,
|
|
20
|
+
modes: Object.freeze(spec.modes || []),
|
|
21
|
+
defaults: Object.freeze(spec.defaults || []),
|
|
22
|
+
nonGoals: Object.freeze(spec.nonGoals || []),
|
|
23
|
+
invalidCombinations: Object.freeze(spec.invalidCombinations || []),
|
|
24
|
+
examples: Object.freeze(spec.examples || []),
|
|
25
|
+
next: Object.freeze(spec.next || []),
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const COMMAND_CONTRACTS = Object.freeze({
|
|
30
|
+
show: contract({
|
|
31
|
+
question: 'What must I know about this exact symbol?',
|
|
32
|
+
purpose: 'Return a compact symbol briefing with selectable evidence projections.',
|
|
33
|
+
target: 'Required symbol name or stable `file:line:name` handle.',
|
|
34
|
+
modes: [
|
|
35
|
+
{ name: 'projection', when: '`sections` selects summary, callers, callees, source, dependencies, tests, types, example, or related.', answer: 'Only the requested sections are rendered; caller-bearing sections retain accounting metadata.' },
|
|
36
|
+
],
|
|
37
|
+
defaults: ['`sections=summary,callers,callees`', 'Compact relationship formatting.'],
|
|
38
|
+
truth: 'The definition is index-backed. Caller/callee relationships are tiered by target-identity evidence and conserve the observed literal-name account where provided.',
|
|
39
|
+
nonGoals: ['Runtime behavior or framework reflection.', 'Semantic completeness beyond indexed/static evidence.'],
|
|
40
|
+
invalidCombinations: ['A missing symbol target is rejected.', 'Unknown section names are rejected.'],
|
|
41
|
+
examples: ['ucn show src/parser.ts:42:parseRequest', 'ucn show parseRequest --sections=summary,source,tests'],
|
|
42
|
+
jsonData: '`data.summary` plus the requested projection fields; relationships include evidence/account metadata.',
|
|
43
|
+
output: 'Targeted output; use `sections` before increasing output limits.',
|
|
44
|
+
next: ['`impact <handle>` before a change.', '`source <handle>` for exact code.', '`tests <handle> --depth=3` for test planning.'],
|
|
45
|
+
benchmark: 'A03',
|
|
46
|
+
}),
|
|
47
|
+
find: contract({
|
|
48
|
+
question: 'Which indexed definition or type does this name identify?',
|
|
49
|
+
purpose: 'Locate definitions, disambiguate duplicates, and produce reusable symbol identity.',
|
|
50
|
+
target: 'Required name; filters may narrow by file, class, directory, or kind.',
|
|
51
|
+
modes: [
|
|
52
|
+
{ name: 'definition', when: 'Default or `type` is a symbol kind such as function/class.', answer: 'Matching indexed definitions with location and signature metadata.' },
|
|
53
|
+
{ name: 'type lookup', when: '`type=type`.', answer: 'Type-definition records rather than general name matches.' },
|
|
54
|
+
],
|
|
55
|
+
defaults: ['Substring matching unless `exact=true`.', 'Tests excluded unless `includeTests=true`.', '`limit` is the single result cap; `withSource=true` attaches exact bodies.'],
|
|
56
|
+
truth: 'Results are exact records from the static symbol index; usage counts are static indexed occurrences.',
|
|
57
|
+
nonGoals: ['Text search inside comments or configuration.', 'Compiler overload resolution across external dependencies.'],
|
|
58
|
+
invalidCombinations: ['A missing name is rejected.'],
|
|
59
|
+
examples: ['ucn find parseRequest --exact --limit=20', 'ucn find Result --type=type --with-source'],
|
|
60
|
+
jsonData: 'Array of symbol or type records with stable file/line identity.',
|
|
61
|
+
output: 'Symbol query; narrow by `file`, `in`, `className`, or `type` when ambiguous.',
|
|
62
|
+
next: ['Pass the resulting handle to `show`, `impact`, or `source`.'],
|
|
63
|
+
benchmark: 'A02',
|
|
64
|
+
}),
|
|
65
|
+
usages: contract({
|
|
66
|
+
question: 'Where does this literal name occur in indexed code, and what kind of use is each site?',
|
|
67
|
+
purpose: 'Inventory definitions, imports, calls, references, and literal comment/string/docstring text without pretending every occurrence binds to one target.',
|
|
68
|
+
target: 'Required literal symbol name.',
|
|
69
|
+
modes: [
|
|
70
|
+
{ name: 'inventory', when: 'Always.', answer: 'Occurrence records classified by usage kind.' },
|
|
71
|
+
],
|
|
72
|
+
defaults: ['Tests excluded unless `includeTests=true`.', 'Comments/strings included unless `codeOnly=true`.'],
|
|
73
|
+
truth: 'The command reports the indexed literal-name occurrence universe, not exact semantic references to one definition. Classification is by syntax alone; show/impact ACCOUNT lines are engine-adjudicated, so the two breakdowns can legitimately differ (a method reference is a `reference` here but may be a confirmed caller there).',
|
|
74
|
+
nonGoals: ['Alias-only references whose source line does not contain the queried name.', 'Safe-delete proof.'],
|
|
75
|
+
invalidCombinations: ['A missing name is rejected.'],
|
|
76
|
+
examples: ['ucn usages parseRequest --include-tests', 'ucn usages parseRequest --code-only --file=src'],
|
|
77
|
+
jsonData: 'Definition/reference records plus separately classified text occurrences, each with file, line, and content.',
|
|
78
|
+
output: 'Broad output; narrow with `file`, `in`, `exclude`, or `limit`.',
|
|
79
|
+
next: ['Use `find` plus `impact` when target identity matters.', 'Use `search` for arbitrary text.'],
|
|
80
|
+
benchmark: 'A04',
|
|
81
|
+
}),
|
|
82
|
+
search: contract({
|
|
83
|
+
question: 'Where does text or an AST-indexed shape match?',
|
|
84
|
+
purpose: 'Search literal/regex text or filter indexed structural records.',
|
|
85
|
+
target: 'Text mode accepts a term; structural mode is selected by structural filters.',
|
|
86
|
+
modes: [
|
|
87
|
+
{ name: 'text', when: 'A term is supplied without structural filters.', answer: 'File/line text matches, optionally excluding comments and strings.' },
|
|
88
|
+
{ name: 'structural', when: '`type`, `param`, `receiver`, `returns`, `decorator`, `exported`, or `unused` is supplied.', answer: 'AST/index records matching the requested shape.' },
|
|
89
|
+
],
|
|
90
|
+
defaults: ['Text term is literal unless `regex=true`.', 'Case-insensitive unless `caseSensitive=true`.'],
|
|
91
|
+
truth: 'Text mode is text-ground exact for the configured regex/literal semantics; structural mode is an index-shape query, not compiler binding.',
|
|
92
|
+
nonGoals: ['Exact target references.', 'Data flow or arbitrary semantic predicates.'],
|
|
93
|
+
invalidCombinations: ['A request with neither a term nor structural filters is rejected.', '`receiver` requires a call-oriented structural query.'],
|
|
94
|
+
examples: ['ucn search "$scope.$apply" --code-only', 'ucn search "TODO|FIXME" --regex', 'ucn search --type=call --receiver=db'],
|
|
95
|
+
jsonData: 'Text mode returns files with line matches; structural mode returns indexed match records and mode metadata.',
|
|
96
|
+
output: 'Project scan; narrow with `file`, `in`, `exclude`, `type`, or `limit`.',
|
|
97
|
+
next: [
|
|
98
|
+
'Use `find` to pin a matched definition.',
|
|
99
|
+
'Use `source` to inspect a selected result.',
|
|
100
|
+
'Use grep/ripgrep for simple literals, messages, configuration, filenames, Markdown, or unsupported languages.',
|
|
101
|
+
],
|
|
102
|
+
benchmark: 'A05',
|
|
103
|
+
}),
|
|
104
|
+
source: contract({
|
|
105
|
+
question: 'What is the exact indexed source for this symbol or line range?',
|
|
106
|
+
purpose: 'Extract a function, class-like declaration, or literal file range without reading the whole file.',
|
|
107
|
+
target: 'Required symbol/handle, `file:range`, or `file` plus `range`.',
|
|
108
|
+
modes: [
|
|
109
|
+
{ name: 'symbol', when: 'A name or stable handle is supplied.', answer: 'Exact declaration source with resolved symbol identity.' },
|
|
110
|
+
{ name: 'range', when: 'A file and line/range are supplied.', answer: 'Exact requested file lines.' },
|
|
111
|
+
],
|
|
112
|
+
defaults: ['Large declarations respect the `maxLines` safety limit.'],
|
|
113
|
+
truth: 'Returned code is sliced from the validated indexed project file and reports its extraction mode.',
|
|
114
|
+
nonGoals: ['Source-map reconstruction.', 'Generated or dependency source outside the project root.'],
|
|
115
|
+
invalidCombinations: ['A line range without a file is rejected.', 'A symbol target and range cannot be combined.', 'Paths outside the project root are rejected.'],
|
|
116
|
+
examples: ['ucn source src/parser.ts:42:parseRequest', 'ucn source src/parser.ts:40-80'],
|
|
117
|
+
jsonData: 'Symbol mode returns resolved entries and code; range mode returns numbered lines.',
|
|
118
|
+
output: 'Targeted extraction; use `maxLines` for large class-like declarations.',
|
|
119
|
+
next: ['Use `show` for relationships.', 'Use `impact` before editing the extracted symbol.'],
|
|
120
|
+
benchmark: 'A06',
|
|
121
|
+
}),
|
|
122
|
+
trace: contract({
|
|
123
|
+
question: 'What static call path goes down, up, or toward an entry point?',
|
|
124
|
+
purpose: 'Traverse tiered call relationships while preserving uncertainty at each expansion.',
|
|
125
|
+
target: 'Required symbol name or stable handle.',
|
|
126
|
+
modes: [
|
|
127
|
+
{ name: 'callees', when: '`direction=callees` (default).', answer: 'Downstream call tree.' },
|
|
128
|
+
{ name: 'callers', when: '`direction=callers`.', answer: 'Upstream caller tree.' },
|
|
129
|
+
{ name: 'entrypoint paths', when: '`direction=callers` and `to=entrypoints`.', answer: 'Caller paths toward detected static roots.' },
|
|
130
|
+
],
|
|
131
|
+
defaults: ['Depth is bounded.', 'Unverified branches are shown but not recursively expanded unless `expandUnverified=true`.'],
|
|
132
|
+
truth: 'Each visible edge carries static evidence; tree-account metadata reconciles indexed call sites where available.',
|
|
133
|
+
nonGoals: ['A complete runtime call graph.', 'Dynamic/reflection edges not represented in the index.'],
|
|
134
|
+
invalidCombinations: ['`to=entrypoints` requires caller direction.', 'A missing symbol is rejected.'],
|
|
135
|
+
examples: ['ucn trace parseRequest --direction=callees --depth=3', 'ucn trace parseRequest --direction=callers --to=entrypoints'],
|
|
136
|
+
jsonData: 'Root identity, direction/depth metadata, and a nested evidence-bearing tree.',
|
|
137
|
+
output: 'Targeted graph output; control breadth with `depth` and `expandUnverified`.',
|
|
138
|
+
next: ['Use `impact` for editable call sites.', 'Use `tests` to find test paths.'],
|
|
139
|
+
benchmark: 'A07',
|
|
140
|
+
}),
|
|
141
|
+
impact: contract({
|
|
142
|
+
question: 'What indexed code may be affected by changing this symbol or Git diff?',
|
|
143
|
+
purpose: 'List direct symbol call sites or compose repository impact from changed lines.',
|
|
144
|
+
target: 'Optional symbol/handle. Omitting it selects Git-diff mode.',
|
|
145
|
+
modes: [
|
|
146
|
+
{ name: 'symbol', when: 'A symbol target is supplied.', answer: 'Tiered direct call sites grouped by file with patterns/accounting.' },
|
|
147
|
+
{ name: 'diff', when: 'No symbol is supplied; `base` or `staged` selects the diff.', answer: 'Changed definitions and their composed impact.' },
|
|
148
|
+
],
|
|
149
|
+
defaults: ['Symbol output is compact.', 'Diff base defaults to the repository default used by the Git analysis layer.'],
|
|
150
|
+
truth: 'Symbol impact is static tiered caller evidence; diff impact composes indexed changed definitions and does not imply runtime reachability completeness.',
|
|
151
|
+
nonGoals: ['Automatic edits.', 'Behavioral equivalence or deployment impact.'],
|
|
152
|
+
invalidCombinations: [
|
|
153
|
+
'A named symbol cannot also select staged/diff scope.',
|
|
154
|
+
'`base` and `staged` cannot both select the diff.',
|
|
155
|
+
],
|
|
156
|
+
examples: ['ucn impact src/parser.ts:42:parseRequest', 'ucn impact --staged'],
|
|
157
|
+
jsonData: 'Symbol mode returns call sites/accounting; diff mode returns changed functions and aggregate impact.',
|
|
158
|
+
output: 'Potentially broad; narrow symbol mode by file/exclude and diff mode by a focused base.',
|
|
159
|
+
next: ['Use `plan` for a concrete refactor preview.', 'Use `tests` for affected tests.', 'Use `check` after editing.'],
|
|
160
|
+
benchmark: 'A08',
|
|
161
|
+
}),
|
|
162
|
+
tests: contract({
|
|
163
|
+
question: 'Which indexed tests directly or transitively exercise this target?',
|
|
164
|
+
purpose: 'Select test evidence for a symbol and show whether it is direct or reached through callers.',
|
|
165
|
+
target: 'Required symbol name or stable handle.',
|
|
166
|
+
modes: [
|
|
167
|
+
{ name: 'direct', when: 'Depth is omitted or zero.', answer: 'Test files containing direct imports, calls, or matching test references.' },
|
|
168
|
+
{ name: 'affected', when: '`depth>0`.', answer: 'Tests reached through the caller graph up to the requested depth.' },
|
|
169
|
+
],
|
|
170
|
+
defaults: ['Direct mode by default.'],
|
|
171
|
+
truth: 'Selections are static code/reference or caller-path evidence; absence is not proof that no runtime test covers the symbol.',
|
|
172
|
+
nonGoals: ['Runtime coverage percentages.', 'Executing the tests.'],
|
|
173
|
+
invalidCombinations: ['A missing symbol is rejected.', 'Depth must be a non-negative integer.', '`callsOnly` applies only to direct mode.'],
|
|
174
|
+
examples: ['ucn tests parseRequest', 'ucn tests parseRequest --depth=3'],
|
|
175
|
+
jsonData: 'Direct mode returns test files and matches; affected mode returns root/path and tiered test results.',
|
|
176
|
+
output: 'Broad command; control traversal with `depth`, `file`, and `exclude`.',
|
|
177
|
+
next: ['Run the selected tests with the project test runner.', 'Use `trace --direction=callers` to inspect paths without static test links.'],
|
|
178
|
+
benchmark: 'A09',
|
|
179
|
+
}),
|
|
180
|
+
check: contract({
|
|
181
|
+
question: 'What indexed inconsistency should be fixed before I commit?',
|
|
182
|
+
purpose: 'Validate a symbol signature against call sites or compose checks over a Git diff.',
|
|
183
|
+
target: 'Optional symbol/handle. Omitting it selects diff/precommit mode.',
|
|
184
|
+
modes: [
|
|
185
|
+
{ name: 'symbol', when: 'A symbol target is supplied.', answer: 'Argument-count compatibility, uncertain sites, and the caller account.' },
|
|
186
|
+
{ name: 'diff', when: 'No target is supplied; `base` or `staged` selects changes.', answer: 'Composed diff impact, signature checks, and affected tests.' },
|
|
187
|
+
],
|
|
188
|
+
defaults: ['Symbol mode checks static arity, not compiler type compatibility.'],
|
|
189
|
+
truth: 'Findings are static diagnostics with explicit uncertainty; a clean result is not a compiler/test pass.',
|
|
190
|
+
nonGoals: ['Full type checking.', 'Linting or executing project tests.'],
|
|
191
|
+
invalidCombinations: [
|
|
192
|
+
'A named symbol cannot also select staged/diff scope.',
|
|
193
|
+
'`base` and `staged` cannot both select the diff.',
|
|
194
|
+
],
|
|
195
|
+
examples: ['ucn check publishOrderCreated', 'ucn check --staged'],
|
|
196
|
+
jsonData: 'Symbol mode returns signature, valid/mismatched/uncertain sites and accounting; diff mode returns composed diagnostics.',
|
|
197
|
+
output: 'Broad diagnostic; scope to a symbol or focused Git diff.',
|
|
198
|
+
next: ['Run the language compiler/type checker and selected tests.', 'Use `plan` before a signature refactor.'],
|
|
199
|
+
benchmark: 'A10',
|
|
200
|
+
}),
|
|
201
|
+
plan: contract({
|
|
202
|
+
question: 'What exact indexed edits would this proposed refactor require?',
|
|
203
|
+
purpose: 'Preview rename or parameter-shape edits without mutating files.',
|
|
204
|
+
target: 'Required symbol name or stable handle plus one refactor operation.',
|
|
205
|
+
modes: [
|
|
206
|
+
{ name: 'rename', when: '`renameTo` is supplied.', answer: 'Selected declaration plus indexed call/import/export edit previews.' },
|
|
207
|
+
{ name: 'add parameter', when: '`addParam` is supplied.', answer: 'Selected declaration and call-site preview, optionally with a default.' },
|
|
208
|
+
{ name: 'remove parameter', when: '`removeParam` is supplied.', answer: 'Selected declaration and affected call-site preview.' },
|
|
209
|
+
],
|
|
210
|
+
defaults: ['Preview only; no file writes.'],
|
|
211
|
+
truth: 'Changes are derived from indexed definitions/usages, include the selected declaration, and retain unverified/blocked or needs-review evidence separately.',
|
|
212
|
+
nonGoals: ['Applying edits.', 'Guaranteeing the preview compiles.'],
|
|
213
|
+
invalidCombinations: ['Exactly one of rename, add-parameter, or remove-parameter is required.', '`defaultValue` only applies to add-parameter.'],
|
|
214
|
+
examples: ['ucn plan parseRequest --rename-to=parseIncoming', 'ucn plan parseRequest --add-param=context --default-value=null'],
|
|
215
|
+
jsonData: 'Before/after signatures, concrete declaration/call/import/export previews, changeSummary, needsReview markers, unverified sites, and account metadata.',
|
|
216
|
+
output: 'Targeted refactor preview; inspect every unverified or warning entry.',
|
|
217
|
+
next: ['Use `impact` before editing.', 'Use `check` and the compiler after applying the change manually.'],
|
|
218
|
+
benchmark: 'A11',
|
|
219
|
+
}),
|
|
220
|
+
repo: contract({
|
|
221
|
+
question: 'What is this repository, and is UCN ready for my task?',
|
|
222
|
+
purpose: 'Compose repository orientation, file inventory, statistics, and health/readiness.',
|
|
223
|
+
target: 'Project directory; optional file/directory filters narrow the index view.',
|
|
224
|
+
modes: [
|
|
225
|
+
{ name: 'summary', when: 'Default or `sections` includes summary.', answer: 'Languages, files, symbols, hot code, entry points, and trust headline.' },
|
|
226
|
+
{ name: 'files', when: '`sections` includes files.', answer: 'Table of contents.' },
|
|
227
|
+
{ name: 'stats', when: '`sections` includes stats.', answer: 'Repository and optional function/hot statistics.' },
|
|
228
|
+
{ name: 'health', when: '`sections` includes health or `deep=true`.', answer: 'Index blind spots, cache state, command proof classification, and readiness dimensions.' },
|
|
229
|
+
],
|
|
230
|
+
defaults: ['Compact summary.', '`deep=false`, so evidence readiness remains unknown until sampled.'],
|
|
231
|
+
truth: 'Counts and health are static index diagnostics. Trust level is task readiness, not measured accuracy.',
|
|
232
|
+
nonGoals: ['A compiler build.', 'A universal repository quality score.'],
|
|
233
|
+
invalidCombinations: ['Unknown section names are rejected.'],
|
|
234
|
+
examples: ['ucn repo', 'ucn repo --sections=summary,health --deep'],
|
|
235
|
+
jsonData: 'Selected summary/files/stats/health projections under one envelope.',
|
|
236
|
+
output: 'Broad output; use `sections`, `in`, and `limit` before increasing caps.',
|
|
237
|
+
next: ['Use the suggested `find`/`show` target.', 'Resolve health warnings before a risky change.'],
|
|
238
|
+
benchmark: 'A01',
|
|
239
|
+
}),
|
|
240
|
+
deps: contract({
|
|
241
|
+
question: 'What does this file import, what imports it, or which static cycles exist?',
|
|
242
|
+
purpose: 'Inspect the static project dependency graph.',
|
|
243
|
+
target: 'File target for graph modes; cycles mode can scan the project.',
|
|
244
|
+
modes: [
|
|
245
|
+
{ name: 'imports', when: '`direction=imports`.', answer: 'Downstream imported files.' },
|
|
246
|
+
{ name: 'importers', when: '`direction=importers`.', answer: 'Upstream importing files.' },
|
|
247
|
+
{ name: 'both', when: '`direction=both`.', answer: 'Both graph directions.' },
|
|
248
|
+
{ name: 'cycles', when: '`cycles=true`.', answer: 'Detected static circular dependencies.' },
|
|
249
|
+
],
|
|
250
|
+
defaults: ['Direction defaults to both.', 'Traversal depth is bounded.'],
|
|
251
|
+
truth: 'Edges are resolved static import/include relationships inside the indexed project.',
|
|
252
|
+
nonGoals: ['Dynamic imports that cannot be resolved statically.', 'Package-manager or runtime dependency graphs.'],
|
|
253
|
+
invalidCombinations: ['A file is required outside cycles mode.', 'Cycle mode does not combine with a file-direction question.'],
|
|
254
|
+
examples: ['ucn deps src/server.ts --direction=both --depth=2', 'ucn deps --cycles'],
|
|
255
|
+
jsonData: 'Graph mode returns nodes/edges by direction; cycles mode returns cycle records.',
|
|
256
|
+
output: 'Broad graph output; use `depth=1`, one direction, or a file target.',
|
|
257
|
+
next: ['Use `api` on a dependency boundary.', 'Use `show` on a symbol crossing the edge.'],
|
|
258
|
+
benchmark: 'A12',
|
|
259
|
+
}),
|
|
260
|
+
api: contract({
|
|
261
|
+
question: 'What static public surface does this project or file export?',
|
|
262
|
+
purpose: 'List indexed exports with signatures.',
|
|
263
|
+
target: 'Optional file target; omission scans the project.',
|
|
264
|
+
modes: [
|
|
265
|
+
{ name: 'file', when: 'A file is supplied.', answer: 'Exports from that file.' },
|
|
266
|
+
{ name: 'project', when: 'No file is supplied.', answer: 'Indexed project exports up to the result limit.' },
|
|
267
|
+
],
|
|
268
|
+
defaults: ['Static exports only.'],
|
|
269
|
+
truth: 'The result is the index-visible export/public declaration universe.',
|
|
270
|
+
nonGoals: ['External consumer inventory.', 'Runtime exports or reflection.'],
|
|
271
|
+
invalidCombinations: [],
|
|
272
|
+
examples: ['ucn api src/payments/stripe.ts', 'ucn api --limit=200'],
|
|
273
|
+
jsonData: 'Array of exported symbol records with signatures and source identity.',
|
|
274
|
+
output: 'Project mode can be broad; narrow by file or limit.',
|
|
275
|
+
next: ['Use `usages` and `impact` before changing a public symbol.'],
|
|
276
|
+
benchmark: 'A13',
|
|
277
|
+
}),
|
|
278
|
+
entrypoints: contract({
|
|
279
|
+
question: 'Which static roots can invoke indexed project code?',
|
|
280
|
+
purpose: 'Detect framework registrations, tests, mains, and other static entry-point patterns.',
|
|
281
|
+
target: 'Project scope with optional file, framework, or type filters.',
|
|
282
|
+
modes: [
|
|
283
|
+
{ name: 'inventory', when: 'Always; filters select a subset.', answer: 'Detected roots with framework, pattern, registration site, and evidence.' },
|
|
284
|
+
],
|
|
285
|
+
defaults: ['Includes supported framework and runtime patterns.'],
|
|
286
|
+
truth: 'Positive findings match declared static framework/name/file patterns and are advisory.',
|
|
287
|
+
nonGoals: ['All runtime registration/reflection roots.', 'Proof that an unlisted function is unreachable.'],
|
|
288
|
+
invalidCombinations: ['Unsupported `type` or `framework` filters are rejected or return an explicit empty filtered inventory.'],
|
|
289
|
+
examples: ['ucn entrypoints', 'ucn entrypoints --type=http --framework=express'],
|
|
290
|
+
jsonData: 'Array of entry-point records with pattern/framework evidence and registration location.',
|
|
291
|
+
output: 'Broad inventory; narrow by file, type, framework, or exclude.',
|
|
292
|
+
next: ['Use `trace --direction=callers --to=entrypoints` for a target path.', 'Use `endpoints` for HTTP boundaries.'],
|
|
293
|
+
benchmark: 'A14',
|
|
294
|
+
}),
|
|
295
|
+
endpoints: contract({
|
|
296
|
+
question: 'Which supported HTTP server/client boundaries exist, and which ones match?',
|
|
297
|
+
purpose: 'Extract framework-specific routes and client requests and optionally bridge them.',
|
|
298
|
+
target: 'Project scope with optional file/framework/path/method filters.',
|
|
299
|
+
modes: [
|
|
300
|
+
{ name: 'inventory', when: 'Default.', answer: 'Server routes and client requests.' },
|
|
301
|
+
{ name: 'bridge', when: '`bridge=true`.', answer: 'Exact/parameterized route-request matches plus unmatched sides.' },
|
|
302
|
+
{ name: 'unmatched', when: '`unmatched=true`.', answer: 'Only unmatched supported boundaries.' },
|
|
303
|
+
],
|
|
304
|
+
defaults: ['Framework-specific static extraction.', 'Interpolated-path uncertainty remains visible unless hidden explicitly.'],
|
|
305
|
+
truth: 'Findings are static matches for supported framework call/decorator shapes; bridge confidence is match quality, not runtime probability.',
|
|
306
|
+
nonGoals: ['Network discovery.', 'Frameworks not represented by an endpoint adapter.'],
|
|
307
|
+
invalidCombinations: ['`serverOnly` and `clientOnly` cannot both describe a useful result.'],
|
|
308
|
+
examples: ['ucn endpoints', 'ucn endpoints --bridge --prefix=/api'],
|
|
309
|
+
jsonData: 'Routes, requests, bridges, unmatched records, and aggregate framework metadata.',
|
|
310
|
+
output: 'Broad inventory; narrow by method, prefix, framework, file, or one side.',
|
|
311
|
+
next: ['Use `show` on a handler/caller.', 'Use `entrypoints` for non-HTTP roots.'],
|
|
312
|
+
benchmark: 'A15',
|
|
313
|
+
}),
|
|
314
|
+
deadcode: contract({
|
|
315
|
+
question: 'Which indexed symbols are conservative cleanup candidates?',
|
|
316
|
+
purpose: 'Find symbols with no modeled usage while protecting common entry/public/decorated shapes by default.',
|
|
317
|
+
target: 'Project scope with optional file/directory and exclusion filters.',
|
|
318
|
+
modes: [
|
|
319
|
+
{ name: 'candidates', when: 'Always; include flags can reveal protected categories.', answer: 'Static zero-usage candidates and usage count.' },
|
|
320
|
+
],
|
|
321
|
+
defaults: ['Exported and decorated symbols excluded.', 'Tests excluded unless requested.'],
|
|
322
|
+
truth: 'A result means no modeled usage survived the command policy; known computed-dispatch registry members are withheld, and the result is explicitly not safe-delete proof.',
|
|
323
|
+
nonGoals: ['External consumers, reflection, generated registration, unresolved computed dispatch, or runtime reachability proof.'],
|
|
324
|
+
invalidCombinations: [],
|
|
325
|
+
examples: ['ucn deadcode --exclude=test', 'ucn deadcode --include-exported --limit=100'],
|
|
326
|
+
jsonData: 'Candidate records plus computed-dispatch/deletion-safety metadata and any registry members withheld from candidates.',
|
|
327
|
+
output: 'Broad candidate list; narrow by file/in/exclude/limit.',
|
|
328
|
+
next: ['Review `usages`, `impact`, `entrypoints`, and `api`, then run compiler/tests before deletion.'],
|
|
329
|
+
benchmark: 'A16',
|
|
330
|
+
}),
|
|
331
|
+
auditAsync: contract({
|
|
332
|
+
question: 'Which supported async call sites deserve missing-await review?',
|
|
333
|
+
purpose: 'Find calls to indexed async functions whose syntax does not await/return/otherwise consume the promise.',
|
|
334
|
+
target: 'Project scope with optional file and exclusion filters.',
|
|
335
|
+
modes: [
|
|
336
|
+
{ name: 'missing-await candidates', when: 'Always.', answer: 'Caller/callee locations for supported async syntax.' },
|
|
337
|
+
],
|
|
338
|
+
defaults: ['Advisory findings only.'],
|
|
339
|
+
truth: 'Positive findings are static syntax/index candidates for supported languages; framework promise handling can make them benign.',
|
|
340
|
+
nonGoals: ['Complete async correctness.', 'Languages or call shapes without implemented async analysis.'],
|
|
341
|
+
invalidCombinations: [],
|
|
342
|
+
examples: ['ucn audit-async', 'ucn audit-async --file=src/api'],
|
|
343
|
+
jsonData: 'Issue array plus total issue and affected-file counts.',
|
|
344
|
+
output: 'Broad audit; narrow by file, exclude, or limit.',
|
|
345
|
+
next: ['Use `show`/`source` on the caller and callee.', 'Run the language compiler/linter.'],
|
|
346
|
+
benchmark: 'A17',
|
|
347
|
+
}),
|
|
348
|
+
stacktrace: contract({
|
|
349
|
+
question: 'Which indexed source locations best match these runtime frames?',
|
|
350
|
+
purpose: 'Parse supported stack formats and enrich frames with source context and enclosing symbols.',
|
|
351
|
+
target: 'Required stack trace text.',
|
|
352
|
+
modes: [
|
|
353
|
+
{ name: 'frame resolution', when: 'Always.', answer: 'Resolved and unresolved frames in input order.' },
|
|
354
|
+
],
|
|
355
|
+
defaults: ['Best-effort path/function matching.', 'Context lines are included for resolved frames.'],
|
|
356
|
+
truth: 'Found frames are matched to current indexed source; unresolved frames remain visible and confidence is advisory match quality.',
|
|
357
|
+
nonGoals: ['Source-map loading.', 'Guaranteeing source matches the deployed artifact.'],
|
|
358
|
+
invalidCombinations: ['Empty stack text is rejected.'],
|
|
359
|
+
examples: ['ucn stacktrace "at handle (src/server.ts:42:7)"'],
|
|
360
|
+
jsonData: 'Advisory marker, frame count, and ordered frame records with resolution/context.',
|
|
361
|
+
output: 'Targeted runtime evidence; large multi-frame traces remain bounded by transport limits.',
|
|
362
|
+
next: ['Use `show` on the resolved function.', 'Use `tests` and `impact` when fixing the failure.'],
|
|
363
|
+
benchmark: 'A18',
|
|
364
|
+
}),
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
function validateCommandContracts() {
|
|
368
|
+
const failures = [];
|
|
369
|
+
const contractNames = Object.keys(COMMAND_CONTRACTS);
|
|
370
|
+
for (const command of CANONICAL_COMMANDS) {
|
|
371
|
+
const spec = COMMAND_CONTRACTS[command];
|
|
372
|
+
if (!spec) {
|
|
373
|
+
failures.push(`${command}: missing contract`);
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
for (const field of ['question', 'purpose', 'target', 'truth', 'jsonData',
|
|
377
|
+
'output', 'benchmark']) {
|
|
378
|
+
if (typeof spec[field] !== 'string' || !spec[field].trim()) {
|
|
379
|
+
failures.push(`${command}: missing ${field}`);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
for (const field of ['modes', 'defaults', 'nonGoals', 'invalidCombinations',
|
|
383
|
+
'examples', 'next']) {
|
|
384
|
+
if (!Array.isArray(spec[field])) failures.push(`${command}: ${field} must be an array`);
|
|
385
|
+
}
|
|
386
|
+
if (!spec.modes.length) failures.push(`${command}: requires an explicit mode`);
|
|
387
|
+
if (!spec.examples.length) failures.push(`${command}: requires an example`);
|
|
388
|
+
for (const example of spec.examples) {
|
|
389
|
+
if (!example.startsWith(`ucn ${toCliName(command)}`)) {
|
|
390
|
+
failures.push(`${command}: example does not use its CLI command: ${example}`);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
if (!COMMAND_TRUST_MATRIX[command]) failures.push(`${command}: missing trust row`);
|
|
394
|
+
if (!FLAG_APPLICABILITY[command]) failures.push(`${command}: missing flag applicability`);
|
|
395
|
+
}
|
|
396
|
+
for (const extra of contractNames.filter(name => !CANONICAL_COMMANDS.includes(name))) {
|
|
397
|
+
failures.push(`${extra}: contract is not a public command`);
|
|
398
|
+
}
|
|
399
|
+
return failures;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
module.exports = { COMMAND_CONTRACTS, validateCommandContracts };
|