ucn 5.5.2 → 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 (74) hide show
  1. package/.claude/skills/ucn/SKILL.md +35 -12
  2. package/.claude/skills/ucn/references/commands.md +21 -17
  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 +71 -29
  7. package/cli/flags.js +184 -0
  8. package/cli/index.js +138 -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 +29 -65
  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/client-receivers.js +189 -0
  19. package/core/command-contracts.js +7 -6
  20. package/core/constructed-class.js +1195 -0
  21. package/core/contract-membership.js +142 -0
  22. package/core/deadcode.js +388 -85
  23. package/core/deletion.js +401 -0
  24. package/core/discovery.js +104 -9
  25. package/core/entrypoints.js +12 -6
  26. package/core/evidence-bands.js +68 -0
  27. package/core/evidence-bands.json +44 -0
  28. package/core/execute.js +172 -161
  29. package/core/hot-summary.js +241 -0
  30. package/core/index-ir.js +3 -1
  31. package/core/inprocess-clients.js +41 -15
  32. package/core/ir.js +5 -1
  33. package/core/member-signatures.js +789 -0
  34. package/core/output/analysis-ext.js +20 -10
  35. package/core/output/analysis.js +98 -60
  36. package/core/output/check.js +98 -37
  37. package/core/output/doctor.js +34 -1
  38. package/core/output/find.js +2 -1
  39. package/core/output/lines.js +63 -3
  40. package/core/output/public.js +30 -11
  41. package/core/output/refactoring.js +32 -0
  42. package/core/output/reporting.js +21 -2
  43. package/core/output/search.js +11 -2
  44. package/core/output/tracing.js +35 -7
  45. package/core/output-budget.js +124 -57
  46. package/core/parallel-build.js +3 -3
  47. package/core/project.js +106 -23
  48. package/core/provenance-type.js +12 -5
  49. package/core/public-command.js +35 -9
  50. package/core/python-modules.js +6 -0
  51. package/core/reflection.js +166 -23
  52. package/core/registry.js +623 -72
  53. package/core/reporting.js +69 -34
  54. package/core/request-helpers.js +63 -7
  55. package/core/review-items.js +75 -0
  56. package/core/search.js +89 -128
  57. package/core/shared.js +0 -45
  58. package/core/snapshot.js +350 -0
  59. package/core/source-universe.js +707 -0
  60. package/core/test-units.js +3624 -0
  61. package/core/tracing.js +150 -96
  62. package/core/verify.js +27 -11
  63. package/languages/adapter.js +8 -3
  64. package/languages/c-family.js +12 -0
  65. package/languages/csharp.js +634 -0
  66. package/languages/go.js +120 -0
  67. package/languages/index.js +422 -4
  68. package/languages/java.js +712 -0
  69. package/languages/javascript.js +1460 -2
  70. package/languages/lexical-scope.js +68 -0
  71. package/languages/python.js +1672 -1
  72. package/languages/rust.js +91 -0
  73. package/mcp/server.js +44 -110
  74. package/package.json +11 -6
@@ -19,7 +19,7 @@ UCN answers semantic questions about a codebase from a tree-sitter index: where
19
19
  2. `ucn find <name>` pins a definition. Pass its `path:line:name` handle to every later command. A plain name that matches several definitions carries an ambiguity warning in `show`, `source`, `trace`, `impact`, `tests`, `check` and `plan`; never treat the auto-selected definition as the repository-wide target.
20
20
  3. `ucn show <handle>` gives the summary, callers and callees. `--sections` replaces that selection; for example, `--sections=summary,source,tests` requests those three sections.
21
21
  4. Before a change: `ucn impact <handle>` and `ucn tests <handle> --depth=3`.
22
- 5. After a signature change: `ucn check <handle>`; before committing: target-less `ucn check`. Both compare a changed declaration with its parameters at `--base` (default `HEAD`), so the call sites bound to the old declaration are checked against the new one; a site that fits no remaining overload is a mismatch, never an excluded other-target line.
22
+ 5. After a signature change: `ucn check <handle>`; before committing: target-less `ucn check`. Both compare a changed declaration with its parameters at `--base` (default `HEAD`), so the call sites bound to the old declaration are checked against the new one; a site that fits no remaining overload is a mismatch, never an excluded other-target line. Target-less `check` gives each changed symbol a verdict by what the change did. `BLOCKED`: a break was observed (a call site that no longer fits, an import or bare call of a deleted name, an import of a name its module no longer exports, an import of a deleted file, an object literal declared as a type that gained a member it lacks); every site is listed. A site the toolchain does not look at (a line under `// @ts-ignore` or `# type: ignore`, a module loaded by a call the compiler does not resolve) is listed and is `REVIEW`, never what blocks. `REVIEW`: the change can reach existing code and the engine cannot settle it (a changed header, generator kind or body-inferred return; an added name already in use or a member that overrides, is required, is a new member of a TypeScript class other code can hold or enumerate, is an operator or conversion, or is invoked by a runtime or framework; a deletion a shield holds; a `(module)` item for changed exports, removed imports, loose module code or a new file an existing import can resolve to), with the reason named. `READY`: nothing that existed depends on it (a body edit that leaves the contract alone, new code under new names, a deletion with no static dependents). `TRUST` is the worst verdict. `--staged` describes the git index, not the worktree: files, `.gitignore` and `.ucn.json` come from the index, including paths marked skip-worktree or assume-unchanged; when the index cannot be exported, `check` and `impact` decline.
23
23
  6. `ucn source <handle>` or `ucn source path/to/file:10-30` for exact code, only when inspection is needed.
24
24
 
25
25
  The direct tests in `show <handle> --sections=tests` match `tests <handle>`. Equivalent file and line parameters preserve the same exact declaration, including overloads.
@@ -52,6 +52,7 @@ C++ integer literals with value zero keep pointer overloads reachable as null po
52
52
  | Select direct or transitive tests | `ucn tests <handle> [--depth=N]` |
53
53
  | Validate a symbol or pending diff | `ucn check [handle]` |
54
54
  | Preview a refactor | `ucn plan <handle> --rename-to=X` |
55
+ | Can this be deleted? | `ucn plan <handle> --delete` |
55
56
  | Inspect file dependencies | `ucn deps <file> [--direction=...]` |
56
57
  | Find circular imports | `ucn deps --cycles` (project-wide; no file target) |
57
58
  | Inspect public surface | `ucn api [file]` |
@@ -64,14 +65,14 @@ C++ integer literals with value zero keep pointer overloads reachable as null po
64
65
  Caller candidates in `show`, `impact`, `trace` and `check` carry evidence tiers:
65
66
 
66
67
  - `CONFIRMED`: binding, receiver, import, or ownership evidence for the pinned target.
67
- - `UNVERIFIED`: call syntax that may reach the target, with the reason identity could not be established. Review it before a breaking change. When same-name definitions compete, `show` lists their stable handles once above the sites.
68
+ - `UNVERIFIED`: call syntax that may reach the target, with the reason identity could not be established. Review it before a breaking change. When same-name definitions compete, `show` lists their stable handles once above the sites. Each unverified list prints 10 rows; `--top=N` sets the count and `--all` prints every row, while the header always states the full count. A `by reason` line counts the reasons and says how often each was a real call on the release oracle board (`often`, `sometimes` or `rarely` with real/scored counts, `not measured` below the sample minimum). Use it to order a review: it describes the reason, never the site. JSON carries it as `meta.unverifiedReasons`.
68
69
  - Excluded sites (another definition's caller, an incompatible receiver, an external package, an arity mismatch) are counted as other-target lines in `ACCOUNT`, each with its reason in JSON.
69
70
 
70
71
  Caller, callee and trace views carry the applicable trust lines below, preserved through truncation. A source extraction, definition list or test-reference list does not provide a caller account; use `show` or `impact` when that account is needed.
71
72
 
72
- - `ACCOUNT` partitions every line where the name occurs: confirmed, unverified, non-call (definition, import, reference, other text), other-target, unaccounted. In mixed-language repositories it also counts occurrences in unsupported-language source files.
73
- - `CONTRACT` states what that partition covers. Semantic completeness is never claimed: aliases, generated code, reflection and runtime dispatch may exist.
74
- - `WARNING` lists unreadable, unparsed or partially indexed files and unsupported-language occurrence sites with their line text, so nothing text search would show is hidden. `usages` and `tests` carry the same disclosure as a note.
73
+ - `ACCOUNT` partitions every line where the name occurs: confirmed, unverified, non-call (definition, import, reference, other text), other-target, unanalyzed (a line in source UCN reads without analyzing: a bundle, a path excluded in `.ucn.json`, an oversized file, an unsupported language), unaccounted.
74
+ - `CONTRACT` is the state of that partition: `text-complete`, `text-complete over indexed source` when the name also occurs in unanalyzed source, or `degraded` when a path could not be read. Semantic completeness is never claimed: aliases, generated code, reflection and runtime dispatch may exist.
75
+ - `WARNING` lists what weakens the claim for this name: unparsed files that contain it, its occurrence sites in unanalyzed source with their line text, and paths that could not be read, so nothing text search would show is hidden. `usages` and `tests` carry the same disclosure as a note.
75
76
  - `FILTERED` means query options hid evidence. `CALLEE ACCOUNT` and `TREE ACCOUNT` do the same for callees and `trace` trees.
76
77
 
77
78
  A field, constant, enum member or module value adds `VALUE ACCESS SITES` and a `VALUE ACCOUNT` to `impact`. Conditional value reexports and unresolved implementation owners stay visible for review. Its rename plan edits proven references by the language's own lookup and lists unresolved bindings for review.
@@ -82,9 +83,31 @@ Return-type flow follows imported aliases and declared field owners. An unresolv
82
83
 
83
84
  Rust chains follow the produced value through recorded field hops and proven standard lock guards. A trait impl on an external type cannot prove that `self.method()` or `Self::method(self)` calls itself: an unseen inherent method may win, so the call requires review. Struct constructions name the type declaration, never its impl block.
84
85
 
85
- A property/getter/setter target adds `PROPERTY ACCESS SITES` to `impact` (reads and writes with access kind; receiver identity confirms, an unresolved receiver stays unverified). A type target (class, struct, interface, enum, trait, record, union, alias) adds `TYPE REFERENCE SITES`: every token naming the type outside its call sites (annotations, bases, casts, qualifiers, imports), decided by the language's own name lookup; constructions and static calls through the type stay in `CALL SITES`, and a `TYPE ACCOUNT` line partitions every name token (confirmed, unverified with a reason, excluded with a reason, at call sites, declaration). `DEPENDENCY SITES` counts each token once. Target-less `impact` and `check` diff the working tree against `HEAD` (`--staged` for the index), include untracked non-ignored source as whole-file additions, and put classes, interfaces, types and fields in declaration bands; a modified or deleted type or a deleted value makes `check` `BLOCKED`. A modified value requires review (`REVIEW_REQUIRED`) unless another finding blocks the check. Validate these changes with the language toolchain.
86
+ A property/getter/setter target adds `PROPERTY ACCESS SITES` to `impact` (reads and writes with access kind; receiver identity confirms, an unresolved receiver stays unverified). A type target (class, struct, interface, enum, trait, record, union, alias) adds `TYPE REFERENCE SITES`: every token naming the type outside its call sites (annotations, bases, casts, qualifiers, imports), decided by the language's own name lookup; constructions and static calls through the type stay in `CALL SITES`, and a `TYPE ACCOUNT` line partitions every name token (confirmed, unverified with a reason, excluded with a reason, at call sites, declaration). `DEPENDENCY SITES` counts each token once. Target-less `impact` and `check` diff the working tree against `HEAD` (`--staged` for the index), include untracked non-ignored source as whole-file additions, and put classes, interfaces, types and fields in declaration bands; a modified type or value is `REVIEW` (shape is not checked), and a deleted one is `BLOCKED` when an import still names it, otherwise `REVIEW`. Validate these changes with the language toolchain.
86
87
 
87
- `tests` distinguishes calls and unverified calls from imports, references, strings and test-case matches. Those other match kinds are discovery hints, not confirmed call edges.
88
+ `tests` finds property reads and nested-type references in the lines the test model identifies as test code, including files whose names do not follow test naming conventions. `tests` distinguishes calls and unverified calls from imports, references, strings and test-case matches. Those other match kinds are discovery hints, not confirmed call edges.
89
+
90
+ A test is a unit: its file, the suites around it, its name and its line, read from what the language's runner collects. Two declarations of one name are two tests. What a test executes is test code whatever its file is called: the body of a test a class inherits stands where its base class is declared, and a runner takes tests from files that are not named like test files. `tests`, the affected-test selection and `check` link a call the caller engine finds in such a file to the tests that execute its line. `tests` puts a `[test]` row above the calls a test holds, counts tests per file, and prints the command for the file (`run:`, or `no command:` with the reason); `--json` carries each unit with its own command (`units[].selector`).
91
+
92
+ - **Counts.** A count is of tests listed and not skipped. It is exact unless it reads `at least N` (`moreTests` in JSON): the file holds tests that are not listed, or a listed declaration stands for several of the runner's tests. `(K conditional)` (`conditionalTests`) says how many of the counted tests may not run. `tests`, the affected-test selection and `check` print the same counts.
93
+
94
+ - **What a name is.** A runner's function is recognised by what the name is bound to, by the language's own scopes, with every write to the name counted: a local, a parameter, a name assigned something else, or an import from a module that is not the runner's is not it; the runner taken through a project module is. A framework's annotation or attribute is the framework's by its package or namespace: a type of the project named `Test` or `Fact` is not one. In Python the last write a module or class makes to a name decides what pytest collects (a definition followed by `name = None` is gone; `__test__` and `pytestmark` have their last value). A test class or function bound by an import, by a star import of a project module, or by another name (`test_b = test_a`) is one more test, listed where it runs. `__test__` is read as pytest reads it: false switches off a class and every class deriving from it, the literal `True` collects a class or function whatever it is called. The switch is the state of the object, whoever holds it: a function its module switches off is a test again where a module that imports it switches it on, and a switch a module writes on a class or function it imported holds for that module (it is never joined with another file); in the module the object comes from, and in a third that takes it, the test then runs only sometimes, because the write is seen only where the writer was loaded first. Where a module writes a switch in a way that is not read (through another name, a member, `setattr`), what it holds that was switched off is listed as running only sometimes. A class is one of unittest's when a base, followed through the project, is unittest's `TestCase` (or a class from outside the project named like one).
95
+ - **What a class has.** What the runner asks of a class is answered by its lookup order, not by any class in it. pytest refuses a class whose constructor is not `object`'s: the first class in the order that binds `__init__` or `__new__` decides, so one that writes `object.__init__` back is collected again, and one bound to anything not read (or behind a base that is not read) leaves the class collected only sometimes. `typing.Protocol` gives a class a constructor. unittest runs the nearest `runTest` of a class with no other test. A method a deriving class overrides without the framework's mark is no test under JUnit 5, NUnit and MSTest; JUnit 4, TestNG and xUnit take the test from the nearest declaration that carries the mark and run it with the body the class has. An override is a method of the same signature: the same name and, parameter by parameter, the same type. A method of the same name with other parameters is another method, and the inherited test runs as it stands, with its own body. A type is what its name denotes where it is written, read by the language's own lookup and never by its spelling: `int` and `System.Int32` are one type, `int` and `int?` two, `Left.Token` and `Right.Token` two, and `Token` is whichever of them the file's imports, using directives, aliases and enclosing classes say. Java compares after erasure (type arguments are no part of a signature, a method's type parameter is its bound); C# compares type arguments, array ranks, tuple elements and `ref`. A C# type inside generic types is the nesting it is written in, each type with its own arguments: `Outer<int>.Inner<string>` and `Outer<int, string>.Inner` are two types. Where two types cannot be told apart or together (a type parameter of a class, a name two files take from different outside packages, a name a base class that is not read may declare, a type inside a generic type written through a class that inherits it), the test may be the class's or may not: it is listed as conditional, with `signature-not-resolved` and no command, and where the runner gives a test the body of the method that replaces it, a change to either body selects it. A method that says it overrides (`@Override`, `override`) is the one method of its name and parameter count the classes above declare, however its types are written. JUnit 5 decides by reflection: a test with a parameter typed by a type parameter is taken as replaced by any method of its name that could be passed its arguments, so under such a method it is conditional. Under NUnit only a declared `override` takes the test away (a method hiding it by `new` does not); MSTest takes one method of each name, so any method of the name does. What a method's modifiers do to its mark is each framework's rule too. TestNG runs a marked method of any visibility and a static one, and lets a deriving class replace a test only where the language lets it override the method: a private or a static test keeps its own body, one without a visibility is replaced from its own package only. JUnit 5 runs neither a private nor a static method. JUnit 4's own runner refuses a whole class for one `@Test` method that is not public or is static: none of the class's tests runs, its own or inherited, in it and in every class deriving from it, and they are listed as skipped. Under another runner (`@RunWith` on the class or on a class it derives from) the rules are that runner's and are not read: such a method is listed as conditional, and where a base that is not read may name a runner, so is every test of the class. A static test runs in every class that inherits it under xUnit, in its own class only under NUnit, and not at all under MSTest. What a marked method takes and gives back counts as well: JUnit 4 refuses the class for a marked method with a parameter or a result, and JUnit 3 takes neither as a test; JUnit 5 does not run a method that gives a result back, and a plain `@Test` with a parameter is conditional (what supplies the parameter is not read); a TestNG test with parameters and no data provider, or with a result, is conditional. A base written on a class is resolved the same way: a class of the project that shares its name with a type from outside (`extends Number`) is not the base, and of two project classes of one name the import decides. `@Disabled`, `@Ignore` and `[Ignore]` on a class hold for that class and the classes inside it, not for a class deriving from it (TestNG's `@Ignore` does reach them). TestNG's `@Test` on a class makes its public methods tests, and those of the classes deriving from it. MSTest wants `[TestClass]` on the class that runs a test, not on the one that declares it.
96
+ - **Inherited tests.** A test a class inherits is listed in the file of the class that runs it, as an `[inherited]` row naming where its body stands (`inheritedFrom`). JUnit 4 runner annotations are inherited through intermediate classes too. A custom runner, or a base whose runner cannot be read, makes a possible standard-runner refusal conditional for the class's own tests and its inherited tests.
97
+ - **`skipped`.** Declared and not executed, and not counted: skip and ignore marks read as values from the syntax (an empty or null reason does not skip; an `xfail(run=False)` skips where its condition holds, and with a condition that is not a literal the test is conditional), a test given no body, what a focus leaves out by the rule of the runner that runs the file, a test the project's pytest options deselect (`-m`, `--deselect`).
98
+ - **`conditional`.** May not run: a condition on the mark, a definition inside a branch, a decorator, attribute or annotation that is not the framework's own (an annotation the project declares stands for the ones it carries), a switch or mark whose last write is not a literal (in the class or in one it derives from), a statement that makes the test only sometimes (JavaScript: a branch, a handler, a loop, what stands after a `return`, a `throw` or `process.exit` of the same function), a table of parameters with no row in view that runs the body, and code in view that can stop the test before its body:
99
+ - Rows. A test that takes rows runs its body once for each row that runs. No row at all, or only rows left out by their own marks, and the test is `skipped`: an empty list, `pytest.param(..., marks=pytest.mark.skip)` in every row, `[InlineData(1, Skip = "...")]` or `[TestCase(1, Ignore = "...")]` on every row, an empty `@ValueSource`, a `@ParameterizedTest` with no source, an empty table given to `it.each`. Rows that are not written out are `conditional`: a list computed at run time (`@MethodSource`, `[MemberData]`, `[TestCaseSource]`, `[DynamicData]`, a TestNG data provider, `it.each(rows())`), a row under a name or made by a call that is not read. In Python a row is read through a name of the module bound once, a function of the module, and a name taken from another project module (followed through the modules that pass it on, by import or star import); a value written out, a class, a builtin and what a package's code gives back are plain rows.
100
+ - pytest: a hook of a conftest.py or of a plugin module the project names (one pytest calls for the whole run holds for every test of the runner's directory), a fixture every test gets or the test asks for (by parameter or `usefixtures`, through other fixtures), `setup_module`, `setUp` and the like in the class or a class it derives from, a module that skips when imported. A hook decides when it names a skip, raises, yields, returns a value where the value counts, or uses what pytest hands it other than through the few calls that only read. A skip inside a function that a hook, a fixture, a setup or the module calls is followed within the module and one project module further.
101
+ - JUnit and TestNG, NUnit, MSTest and xUnit: a setup method (or xUnit constructor) of the class or of one it derives from that names an assumption, an ignore or a skip, or throws. TestNG's `@Test`: `enabled = false` and `invocationCount = 0` skip, a member that is not read (a dependency, a group filter is not one) makes the test conditional.
102
+ - mocha, vitest, jest, node:test: a setup hook of a suite around the test whose body names `skip`, or is not written in place.
103
+ - Go: a `TestMain` that may not run the tests.
104
+ - **`at least`.** Tests the file holds that are not listed: a Python name bound to something that could be a function and is not read (a call, `globals()`, `setattr`), a star import of a module that is not read, a switch written in a way that is not read; a JavaScript call through a name that may be the runner's function, the runner's function handed to something, a function of another project module that declares tests when called; a Rust macro that makes tests; a class with a base that is not read (it may bring tests). And declarations that stand for several: a parametrized test, a theory or a table of cases, a title computed at run time, a declaration inside a function or a loop, a test under a pytest fixture every test gets that has a table of parameters (its own or one it asks for; in the module, a conftest.py, a plugin, the class or one it derives from), under a `pytest_generate_tests` hook, under a JUnit 4 runner named by `@RunWith`, a repeated test (`@RepeatedTest`, `invocationCount`, `[Repeat]`), NUnit parameters that carry their own values (`[Values]`, `[Range]`), an NUnit fixture built once for each `[TestFixture(...)]` with arguments, a Rust test under an attribute that is a macro. A unittest test under a fixture's table is conditional: the runner reports an error for it.
105
+ - **Commands.** A command is printed only when the runner is proven (the test file imports the runner, or the nearest manifest names it), its selection syntax is modeled (pytest, node:test, jest, vitest, mocha, go test, cargo test, dotnet test, Maven) and nothing in the runner's configuration that is not read can change what it selects. Configuration that is data is read: pytest's options and naming rules, jest's roots and path patterns, vitest's directory, what a mocha script loads. A configuration written as code is read only where the file is one object written out that nothing else in it can change: one export, no second write, no other use of the name that holds the object, no function of the file called, a wrapping `defineConfig` only when imported from the runner's own package, and only keys known to leave the selection alone beside the modeled ones (a YAML file: one key to a line). Otherwise the unit is listed with `selectorWithheld` and the reason: `no-runner-evidence`, `runner-not-modeled`, `ambiguous-runner`, `name-not-literal`, `name-not-unique`, `generated-cases`, `declared-indirectly`, `defined-conditionally`, `path-not-unique`, `runner-config-not-read`, `runner-options-not-read`, `outside-runner-config`, `deselected-by-options`, `build-constraint`, `conditional-compilation`, `collection-switch`, `constructor-not-read`, `base-not-resolved`, `signature-not-resolved`, `reported-again` (MSTest reports an inherited test a second time under a test of its name that hides it by `new`: listed as conditional), `declared-in-a-base-class` (Maven's filter does not find a TestNG test under the class that inherits it), `custom-targets`, `target-unknown`, `loader-unknown`. A file's `no command:` names one of these or a reason of its own: `collection-switched-off` (the runner is told not to collect the module), `conditional-tests` (every test of the file is left out of some builds), `no-test-class` (Maven: the file has no top-level test class), `outside-runner-root` (the file is not under the runner's directory), `no-package-manifest` (cargo: the manifest declares no package), `selector-failed` (the command could not be built). Commands for several files are joined where that changes nothing; a file with a mocha focus is run by itself, and files below a conftest.py whose hook decides for a whole run are not joined with files outside it. A command for a file runs that file's tests (go: its package; cargo: its module with the modules under it). The command runs the unit; whether it passes is the runner's to say.
106
+ - **Support.** Test-path files that declare no test and bind no runner (helpers, fixtures, shared setup), and Python modules whose name pytest does not collect, are listed as support and are never something to run. A file node runs under the project's script (named there, or one node looks for) is a test file though it declares none.
107
+
108
+ C, C++ and HTML tests are not read as units. Not read anywhere: what a package's code does (an installed pytest plugin, a vite plugin called in a configuration, a preset, a runner or an extension a test class names, the value a package's function gives back as a row), what a fixture or a setup reaches more than one project module away, what a function of another project module gives back as a row, a constructor one module writes on a class of another as seen from that other module, a using alias of a constructed type, an array or a tuple, and setup files a runner loads for every file (jest setup files, mocha root hooks). Type names are resolved on two assumptions: the code compiles (a name has one meaning where it is used), and the packages and namespaces the project declares types in, and the namespaces around those, hold the types its source declares. Where the runner's options are not read the tests are listed as declared, with no command.
109
+
110
+ `check` selects tests for every changed symbol by its declaration, counts each test once (`Tests potentially affected: N in M files`, `at least N` and `(K conditional)` as above) and prints the runner's command under `Run tests:`.
88
111
 
89
112
  Numeric evidence values are ordinal ranking weights, not probabilities. An observed-text zero is not a semantic zero or safe-delete proof.
90
113
 
@@ -103,9 +126,9 @@ ucn source handleRequest --raw # the code and nothing else, ready for
103
126
  ucn source src/server.js:40-80 --raw
104
127
  ```
105
128
 
106
- Records go to stdout; `ACCOUNT`/`CONTRACT`, notes and same-name disambiguation go to stderr as `# ` lines (MCP keeps them in the one text block; `--raw` appends its note as one trailing `# ` line there). `--lines` has no default row cap; explicit `--top`/`--limit` disclose what they omit; an explicit `--max-chars` fails before any output instead of truncating records. On `show --lines` caller or callee records, `grep -v '# unverified'` keeps the confirmed tier. `usages --lines` emits one record per source line and tags combined kinds; JSON keeps individual occurrences. `show --lines` accepts only the callers/callees sections (default callers); target-less `impact --lines` lists Git-diff callers with per-target accounting. `source --raw` extracts whole functions and large classes; `--max-lines` truncation is disclosed on stderr. Unusual path characters (backslash, tab, CR, LF) are escaped; use `--json` for exact filenames, and for `trace`/`tests` scripting. Closing the pipe with `head` is supported.
129
+ Records go to stdout; `ACCOUNT`/`CONTRACT`, notes and same-name disambiguation go to stderr as `# ` lines (MCP keeps them in the one text block; `--raw` appends its note as one trailing `# ` line there). `--lines` has no default row cap; explicit `--top`/`--limit` disclose what they omit. Under a character budget (an explicit `--max-chars`, or the MCP budget) a listing keeps the rows that fit, whole and in order, with every accounting line, and one `# truncated: X of Y rows omitted` line counts the rest. MCP explicitly withholds the answer if its accounting cannot fit; the CLI refuses when no row fits. Emitted newlines and mandatory raw notes count toward the CLI budget; CLI `source --raw` is never cut and fails before any output instead. MCP raw output remains subject to its transport budget and explicitly reports truncation; raise `max_chars` or narrow the extraction when the complete source is needed. On `show --lines` caller or callee records, `grep -v '# unverified'` keeps the confirmed tier. `usages --lines` emits one record per source line and tags combined kinds; JSON keeps individual occurrences. `show --lines` accepts only the callers/callees sections (default callers); target-less `impact --lines` lists Git-diff callers with per-target accounting. `source --raw` extracts whole functions and large classes; `--max-lines` truncation is disclosed on stderr, or after the source in the REPL. Unusual path characters (backslash, tab, CR, LF) are escaped; use `--json` for exact filenames, and for `trace`/`tests` scripting. Closing the pipe with `head` is supported.
107
130
 
108
- Exit codes: 0 success; 1 for an empty `--lines` listing, a blocking `check` (target-less `check` with `TRUST: BLOCKED`, or `check <handle>` reporting a mismatch or an unverified call site), or a JSON command error (`meta.ok: false` plus `error`); 2 for a text-mode command error or a `check` that could not run. An exception inside UCN prints `Internal error: ...` on every surface (CLI JSON adds `meta.internalError: true`); it says nothing about the code, so rerun with a narrower target or report it.
131
+ Exit codes: 0 success; 1 for an empty `--lines` listing, a blocking `check` (target-less `check` with `TRUST: BLOCKED`, or with `TRUST: REVIEW_REQUIRED` under `--strict`; `check <handle>` reporting a mismatch or an unverified call site), or a JSON command error (`meta.ok: false` plus `error`); 2 for a text-mode command error or a `check` that could not run. An exception inside UCN prints `Internal error: ...` on every surface (CLI JSON adds `meta.internalError: true`); it says nothing about the code, so rerun with a narrower target or report it.
109
132
 
110
133
  File export queries report source-read and import-parser failures as errors. A failed query does not establish an empty public API.
111
134
 
@@ -113,13 +136,13 @@ File export queries report source-read and import-parser failures as errors. A f
113
136
 
114
137
  CLI, MCP and interactive mode run the same 18 commands through one registry, the same handlers and the same formatters. CLI spells multiword commands with hyphens (`audit-async`), MCP uses snake case (`audit_async`) through one `ucn` tool. `--json` returns `{ meta, data }`: `meta.command` keeps the surface spelling and may add `canonicalCommand`, `meta.contract` carries the truth boundary, and public `file` paths are relative to `meta.pathBase`. Definition handles start at a decorator when present; `nameLine` is the declaration token's line.
115
138
 
116
- Text output has a character budget on both CLI and MCP: 3K for broad sweeps (`repo`, `entrypoints`, `endpoints`, `deadcode`, `deps`, `check`, `audit-async`), 10K for every other command, 100K ceiling (`--max-chars=N` / `max_chars`; `--all` lifts caps where a command supports it). Room for every trust line is reserved before any body text. A truncated answer keeps its head, a notice with the full size and the limit, and the trust lines; `show` and `impact` keep a head of every tier with a `+N more` line each, so a long confirmed list never hides the unverified band. A budget too small for the trust lines withholds the answer and names the budget that would carry them (`answer withheld; 0 of 3 trust line(s) shown. Raise --max-chars to at least 498.`). Narrow with a handle, `--file`, `--in` or `--sections` before raising the budget.
139
+ Text output has a character budget on both CLI and MCP: 3K for broad sweeps (`repo`, `entrypoints`, `endpoints`, `deadcode`, `deps`, `check`, `audit-async`), 10K for every other command, 100K ceiling (`--max-chars=N` / `max_chars`; `--all` lifts caps where a command supports it). Room for every trust line is reserved before any body text. A truncated answer keeps its head, a notice with the full size and the limit, and the trust lines; `show` and `impact` keep a head of every tier with a `+N more` line each, so a long confirmed list never hides the unverified band. A budget too small for the trust lines withholds the answer and names the budget that would carry them (`answer withheld; 0 of 3 trust line(s) shown. Raise --max-chars to at least 498.`). Formatted reports on CLI and MCP choose the same whole body lines under the same budget, reserving the larger space their native guidance needs. Their full-size notices count their own text. Narrow with a handle, `--file`, `--in` or `--sections` before raising the budget. `ucn --help` lists every value set, numeric bound and default (`Values, bounds and defaults`); the MCP input schema states the same ones, and a value outside them is refused with the valid list.
117
140
 
118
141
  ## Index and discovery
119
142
 
120
143
  Indexes live in a per-user, project-keyed cache outside the repository (`UCN_CACHE_DIR` overrides the root; `--no-cache` bypasses it; `--clear-cache` removes the project's cache; a `.ucn-cache` directory left in a project is migrated on first use). MCP keeps the process and index warm across calls. Concurrent cold starts share one build: the first process takes a build lock and the others wait for its cache (with `--verbose`, `Waiting for another ucn process building the index...` on stderr); a lock left by a dead process is broken automatically.
121
144
 
122
- Discovery applies the project's `.gitignore` files (root and nested) with git's own matching rules; git-tracked files are indexed even when a rule matches them, and a failing `git ls-files` is disclosed as a partial index (`git-listing-failed` in `repo --sections=health` and in caller contracts), never a silently smaller one. `*.min.js`, `*.bundle.js` and `*.map` are skipped and disclosed, which makes observed-text completeness partial; `--include-bundled` (MCP `include_bundled=true`) indexes the JavaScript bundles, respecting user exclusions and bypassing the shared cache. Source maps stay unindexed; inspect them with text tools.
145
+ Discovery applies the project's `.gitignore` files (root and nested) with git's own matching rules; git-tracked files are indexed even when a rule matches them, and a failing `git ls-files` is disclosed as a partial index (`git-listing-failed` in `repo --sections=health` and in caller contracts), never a silently smaller one. Source UCN reads without analyzing (`*.min.js`, `*.bundle.js`, paths excluded in `.ucn.json`, files over the size limit, unsupported languages) is searched for each name: an answer lists such an occurrence and limits its claim to indexed source only when the name occurs there, and says nothing otherwise. A path that cannot be read degrades every caller account. `*.map` files are data, outside every claim. `--include-bundled` (MCP `include_bundled=true`) indexes the JavaScript bundles, respecting user exclusions and bypassing the shared cache. `.ucn.json` keys: `exclude` (not indexed, still searched per name), `outsideProject` (not part of the project; listed in `repo --sections=health`), `kind` (`"application"`: nothing outside the repository consumes its exports, so `deadcode` and `plan --delete` assess exported symbols), `maxFiles`, `maxDepth`, `maxFileSize`, `textScanBudgetBytes`, `aliases`, `includePaths`.
123
146
 
124
147
  C/C++ uses `compile_commands.json` when present for header classification and include paths. Project C/C++ function-like macros and Rust `macro_rules!` invocations are expanded, and what they generate is indexed at the invocation (`[via macro NAME]`). This is portable AST analysis, not a compiler build: macros, templates, generated code, reflection and external dependency semantics can stay unverified, and no conditional branch is asserted as the one a build activates.
125
148
 
@@ -135,7 +158,7 @@ C/C++ uses `compile_commands.json` when present for header classification and in
135
158
 
136
159
  ## Deletion protocol
137
160
 
138
- `deadcode` is a candidate generator, never deletion proof. Before deleting, inspect `usages`, `impact`, `entrypoints`, `api`, and `repo --sections=health --deep`, then corroborate with the compiler/type checker and tests. Members that reflection (exact names or literal-fragment patterns), computed dispatch, runtime protocols (Python dunders, Java serialization callbacks, C# pattern members, JS `toJSON`/`then`), unknown decorators, event-handler assignment, an outside base class, or macro expansion may reach are withheld or warned; the `deadcode` notes in the command reference list them. Never delete solely from `deadcode` or an observed-text-zero result.
161
+ `ucn plan <handle> --delete` answers for one declaration: `no-static-dependents`, `has-static-dependents` with the first use, or `review` with the reason. It applies the rule `deadcode` applies to every name and is static evidence, not a guarantee. `deadcode` is a candidate generator, never deletion proof. Before deleting, inspect `usages`, `impact`, `entrypoints`, `api`, and `repo --sections=health --deep`, then corroborate with the compiler/type checker and tests. Members that reflection (exact names or literal-fragment patterns), computed dispatch, runtime protocols (Python dunders, Java serialization callbacks, C# pattern members, JS `toJSON`/`then`), unknown decorators, event-handler assignment, an outside base class, or macro expansion may reach are withheld or warned; the `deadcode` notes in the command reference list them. Never delete solely from `deadcode` or an observed-text-zero result.
139
162
 
140
163
  ## Efficient use
141
164
 
@@ -11,9 +11,9 @@ Use text search (grep/ripgrep) for simple literals, error messages, configuratio
11
11
  | `show <handle>` | Symbol summary, callers, and callees. `--sections=summary,callers,callees,source,dependencies,tests,types,example,related` selects projections. |
12
12
  | `find <name>` | Locate definitions and their stable handles. Text shows the top 5 in detail and counts the rest; JSON returns up to 500; `--limit=N` raises both. `--type=type`, `--with-source`, `--compact`, `--exact`. |
13
13
  | `usages <name>` | Every line where the name occurs: definitions, calls, imports, type references, and comment/string/docstring text (`OTHER TEXT`), test files included. `--code-only`, `--exclude-tests`, `--context=N`. |
14
- | `search [term]` | Literal text by default; `--regex` for RE2-compatible linear-time regular expressions; structural filters `--type`, `--param`, `--receiver`, `--returns`, `--decorator`, `--exported`, `--unused`. |
14
+ | `search [term]` | Literal text by default; `--regex` for RE2-compatible linear-time regular expressions; structural filters `--type` (`function`, `class`, `call`, `method`, `type`, `constructor`, `state`, `field`, `constant`, `macro`, `variable`, `interface`, `enum`, `struct`, `trait`, `record`, `namespace`), `--param`, `--receiver`, `--returns`, `--decorator`, `--exported`, `--unused`. |
15
15
  | `source <handle\|file:range>` | Extract a function, class-like declaration, or exact line range (`src/a.js:10-30`, `src/a.js --range=10-30`, or `--file=PATH --range=N-M`; MCP `name` or `file` with `range`). `--raw`, `--max-lines=N`. |
16
- | `trace <handle>` | Callees by default (depth 3). `--direction=callers` for blast radius, `--to=entrypoints` for paths to roots, `--depth=N`, `--expand-unverified`. |
16
+ | `trace <handle>` | Callees by default (depth 3). `--direction=callers` for blast radius, `--to=entrypoints` for paths to roots (depth 5), `--depth=N`, `--expand-unverified`. |
17
17
 
18
18
  ## Change and validation
19
19
 
@@ -22,7 +22,7 @@ Use text search (grep/ripgrep) for simple literals, error messages, configuratio
22
22
  | `impact [handle]` | Direct impact of one symbol; without a handle, the Git diff (`--base=<ref>`, `--staged`). Property targets add an access-site band, type targets a reference-site band. |
23
23
  | `tests <handle\|file>` | Statically linked direct tests; `--depth=N` adds transitively affected tests; `--calls-only` keeps direct calls and test-case matches. |
24
24
  | `check [handle]` | Call-site validation for one symbol against its declaration at `--base` (default `HEAD`); without a handle, the composed pre-commit check of the diff (`--base`, `--staged`). |
25
- | `plan <handle>` | Preview `--rename-to=X`, `--add-param=p` (`--default-value=v`), or `--remove-param=p`. Never edits files. A Python param added beside `*args` or keyword-only params is keyword-only (after them, before `**kwargs`); calls passing arguments where a param is inserted before a rest param are listed. |
25
+ | `plan <handle>` | Preview `--rename-to=X`, `--add-param=p` (`--default-value=v`), or `--remove-param=p`. Never edits files. A Python param added beside `*args` or keyword-only params is keyword-only (after them, before `**kwargs`); calls passing arguments where a param is inserted before a rest param are listed. `--delete` assesses deleting the declaration with the rule `deadcode` applies to every name: `no-static-dependents` (no use in project source outside its own declarations and no shield applies), `has-static-dependents` (the name is called or used outside the declaration; the first site is printed; evidence is by name, as in `deadcode`), or `review` with the reason (exported from a library, bound to an external or project contract, registered, reflected, source that could not be searched, used only inside the declaration itself). Static evidence, not a guarantee: run the compiler and the tests. `"kind": "application"` in `.ucn.json` lets exported symbols be assessed. |
26
26
 
27
27
  `show <handle> --sections=tests` and `tests <handle>` select direct tests for the same declaration. Equivalent `--file` and `--line` parameters preserve that pin, including overloaded methods.
28
28
 
@@ -33,8 +33,8 @@ Use text search (grep/ripgrep) for simple literals, error messages, configuratio
33
33
  | `repo` | Orientation. `--sections=summary,files,stats,health`; `--deep` adds readiness evidence; `--hot`, `--functions`, `--detailed`, `--top-level`, `--in=DIR`. |
34
34
  | `deps <file>` | File dependency graph: `--direction=imports\|importers\|both`, `--depth=N`, `--detailed`. `deps --cycles` (no file target) reports circular imports. |
35
35
  | `api [file]` | Exported/public surface of a project or file. An exact file includes tests; broader scans exclude tests with a count unless `--include-tests`. |
36
- | `entrypoints` | Framework, route, task, test, and runtime entry points. `--type`, `--framework`; tests excluded unless `--include-tests`. |
37
- | `endpoints` | Server/client HTTP surface. `--bridge`, `--server-only`, `--client-only`, `--unmatched`, `--method`, `--prefix`, `--hide-uncertain`, `--exclude-tests`, `--in=DIR`. |
36
+ | `entrypoints` | Framework, route, task, test, and runtime entry points. `--type` (`cli`, `di`, `events`, `http`, `jobs`, `runtime`, `test`), `--framework` (comma-separated labels); tests excluded unless `--include-tests`. |
37
+ | `endpoints` | Server/client HTTP surface. `--bridge`, `--server-only`, `--client-only`, `--unmatched`, `--method` (an HTTP verb, `ALL` or `USE`, any letter case), `--framework` (one label a row carries), `--prefix`, `--hide-uncertain`, `--exclude-tests`, `--in=DIR`. |
38
38
 
39
39
  ## Focused audits and runtime evidence
40
40
 
@@ -55,25 +55,27 @@ Use text search (grep/ripgrep) for simple literals, error messages, configuratio
55
55
  | `--in=<directory>` | Limit query scope to a directory. |
56
56
  | `--exclude=<patterns>` | Exclude matching paths (`--not` is an alias). |
57
57
  | `--limit=N` | Default maximum of 500 results for `find`, text `search`, `deadcode`, `api`, and `repo` files; structural `search` defaults to 50. Explicit limits override these caps and disclose omissions; `usages` and `--lines` are uncapped by default. |
58
- | `--top=N` | Cap listed callers/callees and ranked results; the header discloses the cut (`3 shown of 21`). Caller lists have no default cap. |
58
+ | `--top=N` | Cap listed callers/callees and ranked results; the header discloses the cut (`3 shown of 21`). Caller lists have no default cap. Also the rows of each unverified list (10 by default; `impact` takes `--limit` the same way). On `search` it bounds matches as `--limit` does. |
59
59
  | `--context=N` | Lines of context around `search`/`usages` matches. |
60
60
  | `--include-tests` / `--exclude-tests` | Lift or apply the language's test-file exclusions where a command filters them (`--exclude-tests`: `usages`, `endpoints`, `entrypoints`). `usages` ignores `--include-tests` (tests are listed by default) and says so. |
61
- | `--include-bundled` | Index `*.min.js` and `*.bundle.js`, respecting user exclusions and bypassing the shared cache. By default these and `*.map` are disclosed as skipped sources and completeness is partial. Source maps remain unindexed. MCP: `include_bundled=true`. |
62
- | `--depth=N` | Trace, dependency, or test traversal depth. |
61
+ | `--include-bundled` | Index `*.min.js` and `*.bundle.js`, respecting user exclusions and bypassing the shared cache. By default they are searched for each name and listed only when the name occurs in them. `*.map` files are data and stay unindexed. MCP: `include_bundled=true`. |
62
+ | `--depth=N` | Trace, dependency, or test traversal depth, 0 to 100. Omitted: `trace` 3, `trace --to=entrypoints` 5, `deps` 2, `tests` 0 (direct links only). |
63
63
  | `--direction=<value>` | Trace (`callees`/`callers`) or dependency (`imports`/`importers`/`both`) direction. |
64
- | `--all` | Lift result and formatter caps where a command supports it; the truncation notice says when it does. |
65
- | `--max-chars=N` | Text budget in characters (10K targeted / 3K broad by default; 100K ceiling), including notices and trust lines. Trust lines are reserved first; a limit too small for them withholds the answer and names the budget that carries them. MCP: `max_chars`. JSON is not transport-truncated. |
64
+ | `--all` | Lift result and formatter caps where a command supports it, including every unverified list of `show` and `impact`; the truncation notice says when it does. |
65
+ | `--max-chars=N` | Text budget in characters (10K targeted / 3K broad by default; 100K ceiling), including notices and trust lines. Trust lines are reserved first; a limit too small for them withholds the answer and names the budget that carries them. MCP: `max_chars`. CLI JSON is uncapped even with an explicit budget; `meta.maxChars` is `"not-applied"`. |
66
66
  | `--compact` / `--no-compact` | Token-efficient or full presentation (`show`/`impact` default compact, `usages` full). |
67
67
  | `--range=N-M` | Explicit line range with `source <file>` or `source --file=<path>`. |
68
68
  | `--json` | Stable `{ meta, data }` envelope; `meta.contract` carries the truth boundary, decision safety, and next actions. |
69
- | `--lines` | `find`/`usages`/`search`/`show`/`impact`: uncapped `path:line:text` records with tier tags after a tab. `show` accepts the callers/callees sections (default callers). Accounting and notes go to stderr. Exit 0 for records, 1 for none, 2 for errors. |
70
- | `--raw` | `source`: code only, including full large classes. Notes and `--max-lines` truncation travel on stderr. In shell modes an explicit `--max-chars` fails before stdout instead of truncating. |
69
+ | `--lines` | `find`/`usages`/`search`/`show`/`impact`: uncapped `path:line:text` records with tier tags after a tab. `show` accepts the callers/callees sections (default callers). Accounting and notes go to stderr. Exit 0 for records, 1 for none, 2 for errors. Under an explicit `--max-chars` (over MCP, under its budget) the rows that fit are printed whole, every accounting line is kept, and one `# truncated: X of Y rows omitted` line counts the rest; a budget no row fits in is a CLI error. MCP may omit every row while keeping all accounting; if the accounting cannot fit, it explicitly withholds the answer. The CLI budget includes emitted newlines. |
70
+ | `--raw` | `source`: code only, including full large classes. Notes and `--max-lines` truncation travel on stderr (in the REPL, after the source). Source, terminal newline and mandatory notes all count toward the budget. CLI raw source is never cut by a character budget: an explicit `--max-chars` it exceeds fails before stdout. MCP raw output retains its transport budget and reports truncation explicitly; raise `max_chars` or narrow the extraction for complete source. |
71
71
  | `--expand-unverified` | `trace --direction=callers`: follow possible edges; descendants stay marked possible. |
72
72
  | `--base=<ref>` / `--staged` | Git scope for target-less `impact` and `check`. `check <handle> --base=<ref>` reads the old declaration at that ref. `impact <handle>` takes neither; `check <handle>` does not take `--staged`. |
73
+ | `--strict` | Target-less `check`: `REVIEW_REQUIRED` fails too (exit 1, `trust.passes: false`), not only `BLOCKED`. |
73
74
  | `--no-cache` / `--clear-cache` | Bypass or clear the project's per-user cache (`--clear-cache --all` clears every project). `UCN_CACHE_DIR` overrides the cache root. |
74
75
  | `--workers=N` | Build workers; `0` disables the parallel build. |
75
76
  | `--code-only` | Exclude comments and strings in `search`/`usages`. |
76
77
  | `--regex` | Interpret a `search` term as a regular expression; without it the term is literal. |
78
+ | Flag forms | A value flag is written `--flag=value` or `--flag value`, and is refused without a value. A boolean flag is `--flag`, `--flag=true` or `--flag=false`, including process controls such as `--json`. Negative spellings accept the same values: `--no-compact=false` enables compact output. A negative spelling wins over an affirmative spelling. Closed scalar values are exact strings; surrounding whitespace is refused. Value sets, numeric bounds and defaults are the same on the CLI and over MCP; `ucn --help` lists them all, and a value outside them is refused with the valid list. |
77
79
 
78
80
  ## Target forms and exit codes
79
81
 
@@ -83,7 +85,7 @@ ucn [target] <command> [argument] [flags]
83
85
 
84
86
  Omit the target for the current project. A target may be a file, directory, or quoted glob such as `"src/**/*.py"`.
85
87
 
86
- Text and shell command errors exit 2. JSON command errors exit 1 with `meta.ok: false` and an `error` field; successful empty JSON results exit 0. Target-less `check` exits 1 for `TRUST: BLOCKED`, 0 for other completed checks, and 2 when the check could not run; `check <handle>` exits 1 when it reports a mismatch or an unverified call site. An exception inside UCN is an internal error: the message starts `Internal error:` on every surface and CLI JSON adds `meta.internalError: true`; the exit code is that of any command error.
88
+ Text and shell command errors exit 2. JSON command errors exit 1 with `meta.ok: false` and an `error` field; successful empty JSON results exit 0. Target-less `check` exits 1 for `TRUST: BLOCKED` (a break was observed), 0 for `REVIEW_REQUIRED` and `READY_FOR_TOOLCHAIN`, and 2 when the check could not run; `--strict` makes `REVIEW_REQUIRED` exit 1 too (JSON: `trust.passes`); `check <handle>` exits 1 when it reports a mismatch or an unverified call site. An exception inside UCN is an internal error: the message starts `Internal error:` on every surface and CLI JSON adds `meta.internalError: true`; the exit code is that of any command error.
87
89
 
88
90
  ## Stable symbol identity
89
91
 
@@ -117,7 +119,7 @@ A field, constant, enum member or module value adds a `VALUE ACCESS SITES` depen
117
119
 
118
120
  When the selected definition is a property/getter/setter, `impact` adds a separate `PROPERTY ACCESS SITES` band: confirmed reads/writes have receiver identity, matching attribute syntax with an unresolved receiver stays unverified. Reads and writes carry the access kind and token column; getter/setter handles describe the same property, and unambiguous inherited properties share the owner evidence. These are change dependencies, not caller edges, so the caller `ACCOUNT` stays call-shaped. When the selected definition is a type (class, struct, interface, enum, trait, record, union or type alias), `impact` adds a `TYPE REFERENCE SITES` band: every token naming the type on the name's lines, decided by the same lookup a type rename uses (lexical shadows, type parameters, imports, packages, namespaces, include closures). A token the lookup proves names the type is confirmed with its kind (`type`, `value`, `import`, `member`, `attribute`) and column; one it cannot settle is unverified with its reason; one naming something else is excluded with its reason. Constructions and static member calls through the type (`new Foo()`, `Foo(..)`, `Key.get(..)`) are call sites, listed once under `CALL SITES`. `TYPE ACCOUNT` partitions every name token: confirmed, unverified, excluded (by reason), at call sites, declaration. `DEPENDENCY SITES` counts each token once.
119
121
 
120
- Target-less `impact` and `check` diff the working tree against `HEAD` and include untracked, non-ignored source files as whole-file additions, so new modules are checked before `git add`; `--staged` keeps its index-only meaning and excludes all untracked paths. The changed-path note also counts untracked documentation and configuration. Classes, interfaces, types, and fields appear in separate declaration bands with their dependency sites. Target-less `impact --lines` lists Git-diff callers with per-target accounting.
122
+ Target-less `impact` and `check` diff the working tree against `HEAD` and include untracked, non-ignored source files as whole-file additions, so new modules are checked before `git add`; `--staged` describes the git index and nothing else: changed declarations, their callers, `.gitignore` rules and `.ucn.json` are read from the index (a worktree-only `.ucn.json` is not applied, and the answer says so), so a staged break with an unstaged repair is still a break. When the worktree differs from the index the staged tree is exported and indexed for the run; if that cannot be done the check reports that it did not run. The changed-path note also counts untracked documentation and configuration. Classes, interfaces, types, and fields appear in separate declaration bands with their dependency sites. Target-less `impact --lines` lists Git-diff callers with per-target accounting.
121
123
 
122
124
  ### tests
123
125
 
@@ -161,10 +163,12 @@ Open external interfaces, incomplete ownership, unresolved dispatch, an inexact
161
163
 
162
164
  ### repo
163
165
 
164
- `repo` ranks HOT production functions by confirmed production callers, exactly (test call sites are not scanned for it). Each function appears once: overload signatures and C/C++ prototypes count with their implementation. Exact ranking has a work budget (call records examined); only a very large repository exceeds it, and then the header says the ranking is approximate while every shown count stays exact; narrow with `--in=<dir>` for an exact list. `repo --sections=stats --hot` ranks by all callers under a larger budget, disclosed the same way.
166
+ `repo` ranks HOT production functions by confirmed production callers, exactly (test call sites are not scanned for it). Each function appears once: overload signatures and C/C++ prototypes count with their implementation. Exact ranking has a work budget (call records examined); only a very large repository exceeds it, and then the header says the ranking is approximate while every shown count stays exact; narrow with `--in=<dir>` for an exact list. `repo --sections=stats --hot` ranks by all callers under a larger budget, disclosed the same way. A ranking is computed once per index state and stored with the index: asked again with the same options on an unchanged project it is read back, and any indexed change ranks afresh. The stored answer is the one a fresh ranking gives.
165
167
 
166
168
  `repo` readiness is task-specific. Its headline is navigation readiness; refactor, deletion, semantic recall, and the sampled evidence mix are separate dimensions. The confirmed/unverified percentage is a classification profile, not an accuracy grade. Skipped unsupported source is listed with a text-search and language-tool handoff; `--sections=health` also reports `git-listing-failed` and unexpandable macro invocations.
167
169
 
170
+ The eval and reflection counts are call sites in the syntax tree: `eval`/`exec`/`new Function` for code execution; `getattr`-family builtins, `Reflect.*`, Go's `reflect` package (under the name the file imports it by), `Class.forName` and the name-taking reflection members of Java and C# for reflection. A spelling in a comment or a string, a same-named method of another object (`node.eval(ctx)`) and a same-named definition are not counted. An import by name (`importlib.import_module(name)`) is counted with dynamic imports. Receivers are not typed, so a same-named member of another class that takes an argument (`message.getField(descriptor)`) counts.
171
+
168
172
  Summary/stats `buildTime` is the duration of the last index build (discovery, parsing, and graphs), retained in the cache. It excludes cache loading/saving and query execution, so it is not command wall time; `buildTimeNote` states this boundary.
169
173
 
170
174
  ### deps
@@ -177,7 +181,7 @@ In JS/TS and Python, where an import runs the imported module, cycles distinguis
177
181
 
178
182
  Route tables count too: Django URLconfs (`path`/`re_path`/`include`, composed from `ROOT_URLCONF`), DRF routers, Starlette `Route`/`Mount`/`Host` lists, Flask `add_url_rule`, aiohttp `add_routes`/`router.add_*`. Methods come from the view (`require_*` decorators, class-view handlers, viewset actions); an unresolved view serves `ALL`. Tags: `[regex]` (a `re_path` shown in path form, an unconvertible part as `{?...}`), `[drf-router]`, `[static]`, and `[include]`/`[mount]` for a target that cannot be resolved (shown as `prefix/{?target}`, matched only as uncertain). Django composes as Django does: an include prefix and the included pattern are concatenated as written (`path("api", include(...))` + `"items/"` serves `/apiitems/`); a `re_path` without `^` or, for a view, without `$` matches any text there, shown as `{?*}` (matched as a catch-all); a regex group that can match `/` is written `<path:name>`.
179
183
 
180
- Client receivers are recognized by evidence (a receiver typed to an HTTP client class, or bound to a pytest fixture that constructs one), not only by name. Request-configuration calls (`request(OpenAPI, { method, url })`, `client.post({ url })`, `session.request(method=..., url=...)`, generated OpenAPI SDKs) count as clients when the callee's definition reaches an HTTP call (fetch, axios, requests, ...). Request-shaped calls on an unrecognized receiver or an unproven helper are listed under `Possible client requests` (JSON `uncertainRequests`, with the reason), counted in `meta`, never in the inventory.
184
+ Client receivers require HTTP import, constructor, or factory evidence; names such as `client`, `session`, or `api` alone stay possible and never create bridges. Python follows returned clients and fixture chains, including imported/class fixtures and fixtures returning factories; JavaScript/TypeScript follows imported client values and factories such as `axios.create`, `ky.create`, and `got.extend`. Request-configuration calls (`request(OpenAPI, { method, url })`, `client.post({ url })`, `session.request(method=..., url=...)`, generated OpenAPI SDKs) count as clients when the callee's definition reaches an HTTP call (fetch, axios, requests, ...). Request-shaped calls on an unrecognized receiver or an unproven helper are listed under `Possible client requests` (JSON `uncertainRequests`, with the reason), counted in `meta`, never in the inventory.
181
185
 
182
186
  A request through an in-process test client built from an app (`TestClient(app)`, a fixture's client, `app.test_client()`, `AsyncClient(transport=ASGITransport(app=app))`) bridges only to routes of that app or mounted under it; an app declared as a function or class serves the request itself and matches no route. A supertest client (`request(app).get('/x')`, `request.agent(app)`, a client bound to a local; the name bound to the `supertest` module, whatever it is called) is one too, its app followed through imports and `app.callback()` / `http.createServer(app)`; `request(this.app)` in a mocha test or hook takes the app its suite's `before`/`beforeEach` hook stores on `this` (the nearest suite assigning it, which writes it nowhere else and registers nothing through `this.app`). When the app does not resolve, every path match stays, marked `[unscoped]` (JSON `unscoped: true`). A HEAD request matches a GET route where the framework serves HEAD with GET (Starlette, Flask, Express).
183
187
 
@@ -187,7 +191,7 @@ Route paths include router mount prefixes composed from the code: FastAPI `inclu
187
191
 
188
192
  Constructor declarations are not claims of their own (`N constructor(s) not listed`, JSON `excludedConstructors`): reflection, frameworks and the initializer the compiler generates when one is deleted can depend on them without a construction call. An unused type still appears as a whole-type candidate.
189
193
 
190
- `deadcode` is a candidate generator; every candidate is review-only. Computed dispatch such as `handlers[key]()` is a reported blind spot; registry members reached by a modeled computed receiver are withheld. Statically named reflection such as `getattr(obj, "run")` is positive liveness evidence, so every matching member spelling is withheld. A reflective name built from literal fragments around a runtime part (`getattr(self, "_get_%s_perms" % src)`, f-strings, `.format`, `"get" + name` in `getMethod`, `MethodByName(fmt.Sprintf(...))`, `obj["on" + evt]`) is a pattern: members it can spell on a receiver that may hold their class are withheld and counted per pattern, and `show`/`impact`/`plan` list the site as an unverified `reflection-pattern` caller. `eval(name)`/`globals()[name]` reach only their own module's top-level names; a fully dynamic one (`eval(code)`) withholds them all and is listed as a caller of none. A reflective name that is a parameter of the enclosing function, never reassigned (`registry[type](..)` in a call, `getattr(self, name)`, `getMethod(name)`), takes the string literals the function's callers pass at that position: those members are withheld and listed as `reflection-pattern` callers. Reflection with no specific literal part is counted and warned because it cannot be attributed to one member.
194
+ `deadcode` is a candidate generator; every candidate is review-only. Computed dispatch such as `handlers[key]()` is a reported blind spot; registry members reached by a modeled computed receiver are withheld. Statically named reflection such as `getattr(obj, "run")` is positive liveness evidence, so every matching member spelling is withheld. A reflective name built from literal fragments around a runtime part (`getattr(self, "_get_%s_perms" % src)`, f-strings, `.format`, `"get" + name` in `getMethod`, `MethodByName(fmt.Sprintf(...))`, `obj["on" + evt]`) is a pattern: members it can spell on a receiver that may hold their class are withheld and counted per pattern, and `show`/`impact`/`plan` list the site as an unverified `reflection-pattern` caller. `eval(name)`/`globals()[name]` reach only their own module's top-level names; a fully dynamic one (`eval(code)`) withholds them all and is listed as a caller of none. A reflective name that is a parameter of the enclosing function, never reassigned (`registry[type](..)` in a call, `getattr(self, name)`, `getMethod(name)`), takes the string literals the function's callers pass at that position: those members are withheld and listed as `reflection-pattern` callers. Reflection with no specific literal part is counted and warned because it cannot be attributed to one member. When a file contains dynamic reflection, `deadcode` also conservatively withholds candidates in that file whose exact spelling occurs as an AST string literal. Text and JSON (`reflection.withheldByTable`) list each name and literal location. This does not prove flow: even an unrelated literal in that file can withhold a candidate; another file without dynamic reflection is unaffected.
191
195
 
192
196
  Members the runtime or compiler invokes by name are hidden as runtime callbacks: Python dunders and Enum hooks (`_generate_next_value_`, `_missing_`) on Enum subclasses, Java serialization callbacks and `serialVersionUID` on Serializable types (also when written `implements java.io.Serializable` or inherited) and, as possible members with the protocol's signature, on classes that derive from another out-of-project type (`extends IOException`, `extends ArrayList`; `plan` keeps their edits and lists the definition for review), C# pattern members (`GetEnumerator`, `GetAwaiter`, `Deconstruct`), C# compiler-required types the project declares for a feature it uses (`System.Runtime.CompilerServices.IsExternalInit` with `init` accessors or positional records, `RequiredMemberAttribute` with `required` members, `System.Index`/`Range` with `^i`/`a..b`; unused when the feature is absent), JS `toJSON`/`then`/`[Symbol.*]`, C++ `begin`/`end`. Unknown decorators/annotations and member-assigned event handlers are also withheld by default because they can be the registration itself. Members of a class deriving from an outside base (underscore hooks such as a stream's `_transform` included), members of an object literal declared with an outside type (`ProxyHandler` traps) and members of impls of an outside Rust trait (`impl FromIterator for A`, however many project types implement it) may be called by that outside code and are hidden as external-contract surface.
193
197
 
@@ -44,6 +44,10 @@ C++ default initialization (`T v;`, `static T v;`, `T a[2];`, `new T`) runs the
44
44
 
45
45
  A construction through a method's own receiver, Python `cls(..)` in a classmethod, `type(self)(..)` and `self.__class__(..)`, and JS/TS `new this(..)` in a static method or `new this.constructor(..)`, constructs the method's class: a confirmed caller of that class and of its constructor (signature checks include it), never an edit in a class rename, and an unverified `possible-dispatch` candidate of a subclass's constructor. Constructor identity never crosses languages: a TypeScript class's constructor never lists a construction of a Python subclass of a same-named class.
46
46
 
47
+ A receiver made by a constructor expression (Python `User(..).save()` and `u = User(..)` then `u.save()`, JS/TS `new User(..).save()`) has the class the expression's name denotes where it is written: the scopes around the site, then the module's own binding, then the import it leads to, by local name. Same-named classes elsewhere are other targets. A function's own import of the name is its binding inside that function. The name types nothing, and the call stays unverified, when it is a parameter or a local value, is bound more than once or assigned again (a module assignment, a `global` write, a reassigned `let`), names a function, or is an attribute of a value (`registry.User(..)`). Members are looked up through that class's own ancestry on both sides: a member no project ancestor declares is external when a base is outside the project and unverified when a base is computed (`extends mixin(Base)`).
48
+
49
+ An exact receiver also needs a construction-result proof, whether the class is local, imported by name or read through a module. A Python allocator, a project metaclass that customizes creation or calling, an unresolved or computed base, a JS/TS constructor returning another value, or a class decorator whose result is not settled keeps the call unverified. A project decorator must preserve the class under its actual arguments: rebinding its parameter or helpers, changing a method or allocation slot, or passing the class to an unread helper defeats that proof. Keyword arguments are checked against the helper's parameter; unmodeled argument wrappers, container escapes and captured identities leave the proof undecided. Inherited helpers and builtin property getters/setters are read from the constructed class, including its overrides; harmless data initialization preserves exactness. A getter returning the instance still carries that identity into its use. Custom descriptors and attribute lookup or mutation hooks withdraw exactness. Passing the instance to a method on another object remains unmodeled. Simple identity decorators and option factories remain readable. Module-member writes, including known-key subscription writes and `setattr`, also prevent an exact construction claim. A base, metaclass or decorator outside the project is taken to keep the class. `with Name(..) as v` types `v` only when the enter method returns its unchanged instance; a decorated enter method, rebound receiver or project helper that changes its class or a method leaves the call unverified. A helper-written method or enter-method mutation withdraws exactness for the receiver as a whole, including its other methods. Where a class assigns an instance attribute under a method's name (`self.save = other`), calls of that name remain `callable-value` candidates of the method.
50
+
47
51
  JavaScript constructor identity uses the value of a non-computed method key, including string literals and escapes. Static methods and computed keys do not define an instance constructor. Renaming a JavaScript constructor handle renames its proven owning class while preserving the fixed constructor key. C# static initializers are separate from instance constructor overloads.
48
52
 
49
53
  A C# or C++ construction through a generic type parameter requires review, even when a project class has the same name. TypeScript construction uses the separate value binding of a class.
@@ -18,9 +18,9 @@ Use this reference when an agent, CI job, or script will make decisions from UCN
18
18
 
19
19
  Caller, callee and trace views carry the applicable trust lines below, preserved through truncation. Source, definition and test-reference lists do not supply a caller account; request `show` or `impact` when one is needed.
20
20
 
21
- - `ACCOUNT` partitions every line where the name occurs: confirmed, unverified, non-call (import, definition, reference, other text), other-target, unaccounted. In mixed-language repositories it also counts occurrences in unsupported-language source files.
22
- - `CONTRACT` states the scope of that partition. When unsupported-language files contain the name, it says so and limits the partition-complete claim to supported languages. Semantic completeness is never claimed.
23
- - `WARNING` identifies unreadable, unparsed, or partially indexed files (including `git-listing-failed`), lines in syntax-error recovery regions, and unsupported-language occurrence sites (file:line plus the line text). `usages` and `tests` carry the same disclosure as a note.
21
+ - `ACCOUNT` partitions every line where the name occurs: confirmed, unverified, non-call (import, definition, reference, other text), other-target, unanalyzed (a line in source UCN reads without analyzing), unaccounted.
22
+ - `CONTRACT` is the state of that partition: `text-complete` (every literal-name line of project source was classified), `text-complete, zero observed; not deletion proof`, `text-complete over indexed source; N lines in unanalyzed source` (the name also occurs in a bundle, a path excluded in `.ucn.json`, an oversized file or an unsupported language, listed in the WARNING), or `degraded; see WARNING`. No state is semantic completeness: aliases, indirect calls, generated code and runtime dispatch may exist.
23
+ - `WARNING` identifies what weakens the claim for this name: unparsed files that contain it, lines in syntax-error recovery regions, its occurrence sites in unanalyzed source (file:line plus the line text), and paths that could not be searched at all (including `git-listing-failed`), which degrade every account. Unanalyzed source the name does not occur in is not mentioned. `usages` and `tests` carry the same disclosure as a note.
24
24
  - `FILTERED` means query options hid evidence.
25
25
  - `CALLEE ACCOUNT` partitions a function's call sites (confirmed, unverified, external/builtin, excluded, filtered); `TREE ACCOUNT` does the same for the interior of a caller-direction `trace`.
26
26
 
@@ -32,7 +32,7 @@ Required automation checks:
32
32
 
33
33
  1. `account.conserved` is true.
34
34
  2. `account.contract.textComplete` is true.
35
- 3. No unreadable or unparsed files are reported, and no ground line sits in a syntax-error recovery region (`account.recovered`, WARNING line).
35
+ 3. No unreadable or unparsed files are reported, `account.opaque.count` is 0, and no ground line sits in a syntax-error recovery region (`account.recovered`, WARNING line).
36
36
  4. No contract metadata was lost to truncation.
37
37
  5. Unverified and excluded reasons are retained for review.
38
38