ucn 5.4.1 → 5.5.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.
- package/.claude/skills/ucn/SKILL.md +58 -236
- package/.claude/skills/ucn/references/commands.md +144 -68
- package/.claude/skills/ucn/references/resolution-rules.md +171 -0
- package/.claude/skills/ucn/references/trust-contract.md +27 -33
- package/README.md +18 -12
- package/cli/index.js +42 -16
- package/core/accessors.js +118 -3
- package/core/account.js +30 -0
- package/core/analysis.js +508 -264
- package/core/ast-analysis.js +76 -117
- package/core/async-producers.js +1729 -0
- package/core/bridge.js +1877 -268
- package/core/brief.js +6 -0
- package/core/build-worker.js +78 -14
- package/core/cache.js +522 -31
- package/core/callers.js +12053 -1047
- package/core/check.js +30 -38
- package/core/class-identity.js +1028 -0
- package/core/command-contracts.js +6 -6
- package/core/configuration-items.js +86 -0
- package/core/contract-membership.js +2262 -0
- package/core/cpp-scope.js +532 -0
- package/core/deadcode.js +171 -49
- package/core/decorator-binding.js +260 -0
- package/core/discovery.js +281 -164
- package/core/errors.js +52 -0
- package/core/execute.js +21 -9
- package/core/external-macros.js +326 -0
- package/core/graph-build.js +277 -8
- package/core/graph.js +35 -2
- package/core/ignore-rules.js +472 -0
- package/core/imports.js +504 -42
- package/core/index-ir.js +94 -11
- package/core/inprocess-clients.js +636 -0
- package/core/ir.js +87 -8
- package/core/macro-expansion.js +1416 -0
- package/core/output/analysis-ext.js +6 -6
- package/core/output/analysis.js +18 -2
- package/core/output/brief.js +3 -0
- package/core/output/doctor.js +4 -0
- package/core/output/endpoints.js +15 -6
- package/core/output/find.js +11 -2
- package/core/output/lines.js +8 -2
- package/core/output/refactoring.js +121 -14
- package/core/output/reporting.js +31 -3
- package/core/output/shared.js +14 -0
- package/core/output-budget.js +316 -122
- package/core/parallel-build.js +124 -47
- package/core/parser.js +3 -2
- package/core/project.js +457 -60
- package/core/provenance-facts.js +156 -12
- package/core/provenance-overload.js +30 -3
- package/core/provenance.js +104 -30
- package/core/pytest-fixtures.js +434 -0
- package/core/python-fixture-flow.js +17 -3
- package/core/python-modules.js +173 -0
- package/core/reflection.js +877 -0
- package/core/registry.js +1 -1
- package/core/reporting.js +234 -52
- package/core/request-helpers.js +561 -0
- package/core/route-graph.js +1922 -0
- package/core/route-lists.js +2414 -0
- package/core/rust-macro-expansion.js +2113 -0
- package/core/rust-modules.js +185 -0
- package/core/search.js +121 -29
- package/core/shared.js +19 -1
- package/core/test-name-bindings.js +256 -0
- package/core/type-denotation.js +1161 -0
- package/core/type-references.js +3229 -0
- package/core/verify.js +3360 -672
- package/languages/adapter.js +31 -5
- package/languages/c-family.js +4061 -212
- package/languages/c-macro-invocations.js +380 -0
- package/languages/c-preprocessor.js +254 -0
- package/languages/csharp.js +1406 -49
- package/languages/go.js +1243 -70
- package/languages/index.js +728 -6
- package/languages/java.js +473 -61
- package/languages/javascript.js +1270 -197
- package/languages/lexical-scope.js +1280 -0
- package/languages/python.js +1557 -72
- package/languages/rust-macro-rules.js +1020 -0
- package/languages/rust-value-flow.js +416 -0
- package/languages/rust.js +2237 -115
- package/languages/utils.js +298 -4
- package/mcp/server.js +25 -11
- package/package.json +1 -1
|
@@ -4,62 +4,59 @@ Use this reference when an agent, CI job, or script will make decisions from UCN
|
|
|
4
4
|
|
|
5
5
|
## Evidence classes
|
|
6
6
|
|
|
7
|
-
`confirmed` means UCN found positive identity evidence such as an exact binding, a same-class
|
|
7
|
+
`confirmed` means UCN found positive identity evidence such as an exact binding, a target on the same class definition (a same-name class in another module does not count), a validated receiver type, or an owned import/export chain. It does not mean the edge has a calibrated probability.
|
|
8
8
|
|
|
9
|
-
`unverified` means a syntactic candidate could reach the target but identity could not be established. Preserve and expose these edges. Do not silently treat them as negatives.
|
|
9
|
+
`unverified` means a syntactic candidate could reach the target but identity could not be established. Each carries the engine reason (`possible-dispatch`, `single-owner`, `method-ambiguous`, `overload-ambiguous`, `configuration-alternative`, ...; the per-language meanings are in [resolution-rules.md](resolution-rules.md)). Preserve and expose these edges. Do not silently treat them as negatives.
|
|
10
10
|
|
|
11
|
-
`excluded` means the engine found evidence for a different definition, an incompatible receiver, an external package, an unrelated runtime-language boundary, an arity mismatch, or another stated reason.
|
|
11
|
+
`excluded` means the engine found evidence for a different definition, an incompatible receiver, an external package, an unrelated runtime-language boundary, an arity mismatch, or another stated reason. Excluded sites are counted in `ACCOUNT` as other-target lines and exposed with their reasons under `account.excluded` in JSON. Inspect them when investigating an accuracy issue.
|
|
12
12
|
|
|
13
|
-
`non-call` means the literal name occurred as a definition, import, type reference, property, comment/string, or other text. Use `usages` for the underlying sites; comment/string occurrences appear under `OTHER TEXT` unless `
|
|
13
|
+
`non-call` means the literal name occurred as a definition, import, type reference, property, comment/string, or other text. Use `usages` for the underlying sites; comment/string occurrences appear under `OTHER TEXT` unless `--code-only` (`code_only=true`).
|
|
14
14
|
|
|
15
|
-
`beyond-text` means semantic binding or alias evidence produced an edge that a literal-name ground set could not observe.
|
|
15
|
+
`beyond-text` means semantic binding or alias evidence produced an edge that a literal-name ground set could not observe (Rust `use m::f as g; g()`, a call generated by a macro expansion). A reflective access whose member name is built at runtime from literal fragments (`getattr(self, "_get_%s_perms" % src)`) is also beyond text: it is listed as an unverified caller with reason `reflection-pattern` and the pattern, never confirmed. A Java single-element annotation (`@Marker("x")`) sets the element `value` without naming it: it is listed as an unverified caller of that element with reason `annotation-shorthand`, and `plan` names the element there when the annotation type resolves.
|
|
16
|
+
|
|
17
|
+
## Trust lines
|
|
18
|
+
|
|
19
|
+
Every caller-bearing answer ends with trust lines that truncation preserves:
|
|
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.
|
|
24
|
+
- `FILTERED` means query options hid evidence.
|
|
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`.
|
|
16
26
|
|
|
17
27
|
## Conservation scope
|
|
18
28
|
|
|
19
|
-
The caller account partitions a literal-name text-occurrence ground set. A conserved account answers:
|
|
29
|
+
The caller account partitions a literal-name text-occurrence ground set. A conserved account answers: "Did UCN explain every observed literal-name line?" It does not answer: "Did UCN discover every semantically possible runtime caller?"
|
|
20
30
|
|
|
21
31
|
Required automation checks:
|
|
22
32
|
|
|
23
33
|
1. `account.conserved` is true.
|
|
24
34
|
2. `account.contract.textComplete` is true.
|
|
25
|
-
3. No unreadable or unparsed files are reported.
|
|
35
|
+
3. No unreadable or unparsed files are reported, and no ground line sits in a syntax-error recovery region (`account.recovered`, WARNING line).
|
|
26
36
|
4. No contract metadata was lost to truncation.
|
|
27
37
|
5. Unverified and excluded reasons are retained for review.
|
|
28
|
-
6. Compiler/LSP evaluation reports keep configuration-unscored evidence below the release ceiling; a high-precision result from an undersized scored subset is not accepted.
|
|
29
|
-
7. The release review-burden board passes: true edges left unverified, zero-actionable-ambiguity target rate, actionable candidate p95, and effective unverified review items per oracle edge all remain within the published budget. Raw false candidates remain reported separately.
|
|
30
|
-
8. The deterministic cross-command board reports zero disagreements: overlapping `find`, `show`, `source`, `impact`, `tests`, `check`, and caller `trace` claims resolve the same stable handle to the same identity, projections, caller tiers, evidence reasons, totals, and caller account.
|
|
31
38
|
|
|
32
|
-
Even when all
|
|
39
|
+
Even when all five hold, `account.contract.semanticComplete` remains false. Use compiler/type-checker, test, runtime, and framework evidence for semantic decisions.
|
|
33
40
|
|
|
34
|
-
`account.contract.observedTextZero` is safe only for the claim
|
|
41
|
+
`account.contract.observedTextZero` is safe only for the claim "the complete observed literal-name ground set contained no caller candidates or beyond-text edges." It is explicitly not safe-delete proof.
|
|
35
42
|
|
|
36
43
|
## Numeric fields
|
|
37
44
|
|
|
38
|
-
`evidenceScore` (and the
|
|
39
|
-
|
|
40
|
-
## Unverified review burden
|
|
45
|
+
`evidenceScore` (and the `confidence` alias where present) is an ordinal weight used for ordering and thresholding evidence classes. `scoreKind` is `ordinal-evidence-not-probability`. Never display it as measured accuracy or use it as a probability in risk calculations.
|
|
41
46
|
|
|
42
|
-
|
|
47
|
+
## Provenance
|
|
43
48
|
|
|
44
|
-
|
|
45
|
-
- `zeroActionableUnverifiedTargetRate` is the share of reviewed targets with no actionable ambiguity.
|
|
46
|
-
- `actionableUnverifiedCandidatesP50`, `actionableUnverifiedCandidatesP95`, and `actionableUnverifiedCandidatesMax` measure actionable candidate-set size per target.
|
|
47
|
-
- `unverifiedReviewItemsPerOracleEdge` measures effective review work relative to the oracle workload. Actionable false candidates count individually; each named runtime-dispatch family counts once.
|
|
48
|
-
- `rawFalseUnverifiedPerOracleEdge` preserves the ungrouped false-candidate amplification for auditability.
|
|
49
|
-
- `runtimeDependentOracleEdges`, `compilerDependentOracleEdges`, and `oracleAbstentionEdges` disclose why all-oracle coverage is broader than exact target identity.
|
|
50
|
-
- `unverifiedReasons` groups candidates by the engine reason that kept them out of the confirmed tier.
|
|
49
|
+
Caller and callee JSON carries compact `provenance`: `rule`, contributing `rules` when there is more than one, `validation`, and the receiver's source and origin line when recorded. Incomplete proofs include a `diagnostic`, also shown in text. Callees retain compact `siteProvenance` for each distinct occurrence; their summary uses the weakest confirmed site's rule. Exclusions expose counts by rule and validation under `account.excluded.evidenceSummary`.
|
|
51
50
|
|
|
52
|
-
|
|
51
|
+
A lone project owner of a method name does not identify an untyped receiver. Such candidates stay unverified with `single-owner`; importing the class does not change that. `provenance-incomplete` means the available facts cannot establish this declaration. Inspect the site and diagnostic before editing it. Neither reason is permission to discard a possible caller. `validation: unsupported` means the witness collector does not cover that path (for example a wildcard re-export or an external inherited member); the existing classification is kept in report-only mode and is not a validated proof. Missing or inconsistent facts on a supported lookup still route unverified.
|
|
53
52
|
|
|
54
|
-
|
|
53
|
+
## Release evaluation
|
|
55
54
|
|
|
56
|
-
|
|
55
|
+
Releases are gated on pinned real repositories against independent oracles (ts-morph, Pyright, gopls, rust-analyzer, JDT LS, Roslyn, clangd): 100% in-scope caller and callee recall with zero unaccounted oracle edges, at least 98% confirmed-tier precision, full conservation, zero false-dead claims in the default `deadcode` audit, exactly agreeing cross-command claims for the same handle (`find`, `show`, `source`, `impact`, `tests`, `check`, caller `trace`), bounded unverified review burden (at most 10% of exact true edges left unverified, at least 80% of targets with no actionable ambiguity, at most five actionable candidates at p95), and throughput and memory budgets. Passing these sampled gates is release evidence for the measured static evidence classes; it does not turn dynamic dispatch, generated code, reflection, or external dependencies into complete runtime knowledge.
|
|
57
56
|
|
|
58
57
|
## Truncation
|
|
59
58
|
|
|
60
|
-
CLI and MCP text share the same 10K targeted / 3K broad default budgets and
|
|
61
|
-
100K ceiling. CLI truncation appends preserved contract lines directly. MCP
|
|
62
|
-
results may additionally include:
|
|
59
|
+
CLI and MCP text share the same 10K targeted / 3K broad default budgets and 100K ceiling. Room for every trust line (ACCOUNT, CONTRACT, WARNING, FILTERED, CALLEE ACCOUNT, TREE ACCOUNT) is reserved before any body text, and truncation appends the ones the kept body does not show. A limit that cannot hold them withholds the answer: the text states how many trust lines it shows and the budget that carries all of them, and never shows body text without them. MCP results may additionally include:
|
|
63
60
|
|
|
64
61
|
```json
|
|
65
62
|
{
|
|
@@ -83,7 +80,4 @@ Allow an automated change to proceed to compiler/tests only when:
|
|
|
83
80
|
- the repo health report lists no unsupported source handoff relevant to the change;
|
|
84
81
|
- the change is still validated by the language toolchain and relevant tests.
|
|
85
82
|
|
|
86
|
-
Never auto-delete from a UCN-only signal. Computed dispatch is reported as a
|
|
87
|
-
health blind spot, and modeled registry members are withheld from candidates,
|
|
88
|
-
but neither protection proves runtime reachability. Require usages, entry-point
|
|
89
|
-
review, public API review, and external validation.
|
|
83
|
+
Never auto-delete from a UCN-only signal. Computed dispatch is reported as a health blind spot, and modeled registry members are withheld from candidates, but neither protection proves runtime reachability. C/C++ token-pasting macro invocations and Rust `macro_rules!` invocations that cannot be expanded are reported the same way. Require usages, entry-point review, public API review, and external validation.
|
package/README.md
CHANGED
|
@@ -105,7 +105,8 @@ The ACCOUNT line reconciles the observed name occurrences: confirmed calls,
|
|
|
105
105
|
unverified candidates, non-call occurrences, and matches attributed to another
|
|
106
106
|
target. CONTRACT describes the scope of that accounting. Warnings identify
|
|
107
107
|
source the index could not cover. These details survive text truncation for
|
|
108
|
-
an agent's output budget
|
|
108
|
+
an agent's output budget; a budget too small to hold them withholds the answer
|
|
109
|
+
and names the budget that would.
|
|
109
110
|
|
|
110
111
|
An empty result therefore means something specific about the inspected code.
|
|
111
112
|
It cannot establish that reflection, generated code, runtime registration, or
|
|
@@ -117,7 +118,7 @@ needs the compiler and tests.
|
|
|
117
118
|
|
|
118
119
|
Release gates compare UCN's answers with independent compilers and language
|
|
119
120
|
servers on a ten-repository board of pinned production codebases. The local
|
|
120
|
-
|
|
121
|
+
October 4, 2026 evaluation recorded these sampled caller results:
|
|
121
122
|
|
|
122
123
|
| Repository | Pinned commit | Oracle | Symbols sampled | Confirmed precision | In-scope recall |
|
|
123
124
|
|---|---|---|---:|---:|---:|
|
|
@@ -134,10 +135,10 @@ September 6, 2026 evaluation recorded these sampled caller results:
|
|
|
134
135
|
|
|
135
136
|
That evaluation reported zero missing in-scope oracle edges in both caller
|
|
136
137
|
and callee answers, and 8,000 cross-command comparisons with zero disagreements.
|
|
137
|
-
The default dead-code audit found zero false-dead results among
|
|
138
|
-
claims;
|
|
138
|
+
The default dead-code audit found zero false-dead results among 9 scored
|
|
139
|
+
claims; 4 additional claims could not be pinned by the oracle and were unscored.
|
|
139
140
|
All ten repositories passed the performance budgets, with steady-state query
|
|
140
|
-
p95 from 4.
|
|
141
|
+
p95 from 4.7 to 101.1 ms. Those timings exclude process startup and indexing;
|
|
141
142
|
cold builds and cache loading are measured separately.
|
|
142
143
|
|
|
143
144
|
The samples are deterministic and stratified by reference activity. Confirmed
|
|
@@ -146,9 +147,9 @@ in either the confirmed or unverified band. Unverified candidates, oracle
|
|
|
146
147
|
abstentions, and unscored findings remain separate. These measurements do not
|
|
147
148
|
establish complete runtime knowledge or identical performance on every machine.
|
|
148
149
|
|
|
149
|
-
The scheduled board covers
|
|
150
|
+
The scheduled board covers 25 pinned repositories: the ten above plus zod,
|
|
150
151
|
express, hono, zustand, fastify, rich, click, attrs, grpc-go, chi, cursive,
|
|
151
|
-
itertools, gson, and jsoup. A rotating fresh-repository arm checks codebases
|
|
152
|
+
itertools, rayon, gson, and jsoup. A rotating fresh-repository arm checks codebases
|
|
152
153
|
outside that pinned board.
|
|
153
154
|
|
|
154
155
|
The [repository manifest](eval/lib/repos.js) records the full commits.
|
|
@@ -188,9 +189,10 @@ npm run trust:gate
|
|
|
188
189
|
|
|
189
190
|
`deps --cycles` groups circular dependencies and distinguishes eager imports
|
|
190
191
|
from deferred or type-only edges. Enumeration limits are disclosed. `repo`
|
|
191
|
-
reports source coverage as well as project structure; its
|
|
192
|
-
|
|
193
|
-
|
|
192
|
+
reports source coverage as well as project structure; its HOT ranking counts
|
|
193
|
+
confirmed production callers exactly (a refinement budget only binds on very
|
|
194
|
+
large repositories, and is disclosed), and `repo --sections=stats --hot` ranks
|
|
195
|
+
by all callers.
|
|
194
196
|
|
|
195
197
|
`endpoints --bridge` matches server routes and client requests recognized by
|
|
196
198
|
its framework extractors. `plan` handles code relationships
|
|
@@ -215,12 +217,16 @@ line limit is requested; any resulting truncation is reported on stderr.
|
|
|
215
217
|
|
|
216
218
|
Ordinary text-mode command errors also exit 2. JSON keeps exit 0 for successful
|
|
217
219
|
empty results and exit 1 for command errors (`meta.ok: false` plus `error`).
|
|
220
|
+
An exception inside UCN is reported as `Internal error: ...` (JSON adds
|
|
221
|
+
`meta.internalError: true`), never as an answer about the code.
|
|
218
222
|
Target-less `check` exits 1 when `TRUST` is `BLOCKED`, 0 for other completed
|
|
219
223
|
checks, and 2 if it could not run.
|
|
220
224
|
|
|
221
225
|
Outside `--lines`, `find`, text `search`, `deadcode`, `api`, and
|
|
222
|
-
`repo --sections=files` default to a maximum of 500 results.
|
|
223
|
-
|
|
226
|
+
`repo --sections=files` default to a maximum of 500 results. `find` text shows
|
|
227
|
+
the top 5 in detail and counts the rest (JSON returns up to 500); `--limit=N`
|
|
228
|
+
raises both. Use `--limit=N` to request more; `usages` lists every site,
|
|
229
|
+
test files included, unless a limit or `--exclude-tests` is given. Broad `find` queries select candidates by approximate usage
|
|
224
230
|
totals before calculating definition-pinned caller activity, and disclose
|
|
225
231
|
that selection when limited.
|
|
226
232
|
|
package/cli/index.js
CHANGED
|
@@ -7,6 +7,11 @@
|
|
|
7
7
|
* Auto-detects mode from target (file path → file mode, directory → project mode).
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
// Reuse V8 code caches across processes (Node >= 22.1; fix #365): compiling
|
|
11
|
+
// the engine's modules is a fixed cost of every command. Node validates each
|
|
12
|
+
// entry against the source, and NODE_DISABLE_COMPILE_CACHE turns it off.
|
|
13
|
+
try { require('module').enableCompileCache?.(); } catch (_) { /* optional */ }
|
|
14
|
+
|
|
10
15
|
const fs = require('fs');
|
|
11
16
|
const path = require('path');
|
|
12
17
|
|
|
@@ -28,6 +33,7 @@ const {
|
|
|
28
33
|
} = require('../core/registry');
|
|
29
34
|
const { buildPublicParams, isPublicCommand } = require('../core/public-command');
|
|
30
35
|
const { execute } = require('../core/execute');
|
|
36
|
+
const { describeError } = require('../core/errors');
|
|
31
37
|
const { applyOutputBudget, MAX_OUTPUT_CHARS } = require('../core/output-budget');
|
|
32
38
|
const { clearAllCaches } = require('../core/cache');
|
|
33
39
|
const { commentLines } = require('../core/output/lines');
|
|
@@ -188,7 +194,7 @@ function validateNumericFlags(flags) {
|
|
|
188
194
|
}
|
|
189
195
|
|
|
190
196
|
/** Emit the one CLI error contract, including failures before main dispatch. */
|
|
191
|
-
function emitCliError(msg, command = activeCanonicalCommand) {
|
|
197
|
+
function emitCliError(msg, command = activeCanonicalCommand, { internal = false } = {}) {
|
|
192
198
|
const wantsJson = process.argv.includes('--json');
|
|
193
199
|
const error = typeof msg === 'string' ? msg : String(msg);
|
|
194
200
|
if (wantsJson) {
|
|
@@ -201,6 +207,8 @@ function emitCliError(msg, command = activeCanonicalCommand) {
|
|
|
201
207
|
const env = {
|
|
202
208
|
meta: {
|
|
203
209
|
ok: false,
|
|
210
|
+
// An exception inside UCN, not a refusal (fix #394).
|
|
211
|
+
...(internal && { internalError: true }),
|
|
204
212
|
...(surfaceCommand && {
|
|
205
213
|
command: surfaceCommand,
|
|
206
214
|
...(canonical && surfaceCommand !== canonical && {
|
|
@@ -223,11 +231,24 @@ function emitCliError(msg, command = activeCanonicalCommand) {
|
|
|
223
231
|
* Print an error message and abort command execution. Throw instead of calling
|
|
224
232
|
* process.exit so index/cache finally blocks still run.
|
|
225
233
|
*/
|
|
226
|
-
function fail(msg, command = activeCanonicalCommand) {
|
|
227
|
-
emitCliError(msg, command);
|
|
234
|
+
function fail(msg, command = activeCanonicalCommand, options = {}) {
|
|
235
|
+
emitCliError(msg, command, options);
|
|
228
236
|
throw new CommandError();
|
|
229
237
|
}
|
|
230
238
|
|
|
239
|
+
/** Fail with an execute() error: refusals are rendered for the CLI, internal errors kept verbatim. */
|
|
240
|
+
function failExecution(execution) {
|
|
241
|
+
if (execution.internalError) fail(execution.error, activeCanonicalCommand, { internal: true });
|
|
242
|
+
fail(formatSurfaceMessage(execution.error, 'cli'));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** Report an exception caught outside execute(). */
|
|
246
|
+
function emitCaughtError(e) {
|
|
247
|
+
const described = describeError(e);
|
|
248
|
+
emitCliError(described.internalError ? described.error : `Error: ${described.error}`,
|
|
249
|
+
activeCanonicalCommand, { internal: !!described.internalError });
|
|
250
|
+
}
|
|
251
|
+
|
|
231
252
|
// ============================================================================
|
|
232
253
|
// ARGUMENT PARSING
|
|
233
254
|
// ============================================================================
|
|
@@ -607,7 +628,7 @@ function main() {
|
|
|
607
628
|
}
|
|
608
629
|
} catch (e) {
|
|
609
630
|
if (!(e instanceof CommandError)) {
|
|
610
|
-
|
|
631
|
+
emitCaughtError(e);
|
|
611
632
|
}
|
|
612
633
|
process.exitCode = flags.json ? 1 : 2;
|
|
613
634
|
}
|
|
@@ -717,8 +738,8 @@ function runFileCommand(filePath, command, arg) {
|
|
|
717
738
|
warnInapplicableFlags(canonical, scopedFlags, (message) => console.error(message));
|
|
718
739
|
const params = buildPublicParams(canonical, arg, scopedFlags);
|
|
719
740
|
const execution = execute(index, canonical, params);
|
|
720
|
-
const { ok, result
|
|
721
|
-
if (!ok)
|
|
741
|
+
const { ok, result } = execution;
|
|
742
|
+
if (!ok) failExecution(execution);
|
|
722
743
|
emitCliText(flags.json
|
|
723
744
|
? output.formatPublicJson(canonical, result, params, {
|
|
724
745
|
...execution, surface: 'cli',
|
|
@@ -799,7 +820,7 @@ function runProjectCommand(rootDir, command, arg) {
|
|
|
799
820
|
});
|
|
800
821
|
const publicExecution = execute(index, canonical, publicParams);
|
|
801
822
|
if (!publicExecution.ok) {
|
|
802
|
-
|
|
823
|
+
failExecution(publicExecution);
|
|
803
824
|
}
|
|
804
825
|
emitCliText(flags.json
|
|
805
826
|
? output.formatPublicJson(canonical, publicExecution.result, publicParams, {
|
|
@@ -813,7 +834,7 @@ function runProjectCommand(rootDir, command, arg) {
|
|
|
813
834
|
resultExitCode(canonical, publicExecution.result));
|
|
814
835
|
} catch (e) {
|
|
815
836
|
if (!(e instanceof CommandError)) {
|
|
816
|
-
|
|
837
|
+
emitCaughtError(e);
|
|
817
838
|
}
|
|
818
839
|
process.exitCode = flags.json ? 1 : 2;
|
|
819
840
|
} finally {
|
|
@@ -822,7 +843,7 @@ function runProjectCommand(rootDir, command, arg) {
|
|
|
822
843
|
// On cache-hit runs, only re-save if callsCache was mutated OR
|
|
823
844
|
// reachability was computed (MED-1: persists the BFS result so
|
|
824
845
|
// subsequent cold invocations don't repeat the 7-11s tax).
|
|
825
|
-
if (flags.cache && (needsCacheSave || index.callsCacheDirty || index.reachabilityDirty || index.computedDispatchDirty)) {
|
|
846
|
+
if (flags.cache && (needsCacheSave || index.callsCacheDirty || index.reachabilityDirty || index.computedDispatchDirty || index.macroExpansionDirty)) {
|
|
826
847
|
try { index.saveCache(); } catch (e) { /* best-effort */ }
|
|
827
848
|
}
|
|
828
849
|
if (flags.cache && index.usageCacheDirty) {
|
|
@@ -866,7 +887,7 @@ function runGlobCommand(pattern, command, arg) {
|
|
|
866
887
|
const publicParams = buildPublicParams(canonical, arg, flags);
|
|
867
888
|
const publicExecution = execute(index, canonical, publicParams);
|
|
868
889
|
if (!publicExecution.ok) {
|
|
869
|
-
|
|
890
|
+
failExecution(publicExecution);
|
|
870
891
|
}
|
|
871
892
|
emitCliText(flags.json
|
|
872
893
|
? output.formatPublicJson(canonical, publicExecution.result, publicParams, {
|
|
@@ -906,7 +927,10 @@ Commands:
|
|
|
906
927
|
show <symbol> Symbol summary and relationships
|
|
907
928
|
--sections=summary,callers,callees,source,dependencies,tests,types,example,related
|
|
908
929
|
find <name> Definitions; --type=type, --with-source
|
|
909
|
-
|
|
930
|
+
text: top 5 in detail + a count of the rest; --json: up to 500;
|
|
931
|
+
--limit=N raises both
|
|
932
|
+
usages <name> Calls, imports, definitions, references (test files
|
|
933
|
+
included; --exclude-tests hides them with a count)
|
|
910
934
|
search [term] Text or structural search
|
|
911
935
|
literal text by default; --regex enables regular-expression syntax
|
|
912
936
|
source <symbol|file:range> Exact function, class, or line extraction
|
|
@@ -933,9 +957,9 @@ Common flags:
|
|
|
933
957
|
--all --compact --no-compact --json --include-tests --class-name=X --line=N
|
|
934
958
|
--range=N-M (source with --file=PATH)
|
|
935
959
|
--base=REF --staged --no-cache --clear-cache [--all] --max-files=N --workers=N
|
|
936
|
-
--max-chars=N (text
|
|
960
|
+
--max-chars=N (text characters, not UTF-8 bytes; default 10K targeted / 3K broad, ceiling 100K)
|
|
937
961
|
--lines find/usages/search/show/impact: grep -n shape, one path:line:text
|
|
938
|
-
record per output line; usages
|
|
962
|
+
record per output line; usages combines occurrences on each source line
|
|
939
963
|
(tags after a tab: # unverified: <reason>, # import,
|
|
940
964
|
# callee); accounting and notes go to stderr as "# " lines; exit 1
|
|
941
965
|
when nothing matched; exit 2 on errors. No default result cap.
|
|
@@ -1045,7 +1069,7 @@ function runInteractive(rootDir) {
|
|
|
1045
1069
|
Commands:
|
|
1046
1070
|
repo Repository overview (--sections=files,stats,health)
|
|
1047
1071
|
show <name> Symbol summary + relationships (--sections=...)
|
|
1048
|
-
find <name> Find definitions (--type=type, --with-source)
|
|
1072
|
+
find <name> Find definitions (--type=type, --with-source; top 5 shown, --limit=N)
|
|
1049
1073
|
usages <name> All usages grouped by type
|
|
1050
1074
|
source <target> Extract a symbol or file:line-range
|
|
1051
1075
|
trace <name> Call tree (--direction=callees|callers, --to=entrypoints)
|
|
@@ -1155,7 +1179,8 @@ Flags can be added per-command: show myFunc --sections=source,callers
|
|
|
1155
1179
|
if (e instanceof FlagValidationError) {
|
|
1156
1180
|
console.log(e.message);
|
|
1157
1181
|
} else {
|
|
1158
|
-
|
|
1182
|
+
const described = describeError(e);
|
|
1183
|
+
console.error(described.internalError ? described.error : `Error: ${described.error}`);
|
|
1159
1184
|
}
|
|
1160
1185
|
}
|
|
1161
1186
|
|
|
@@ -1177,7 +1202,8 @@ function executeInteractiveCommand(index, command, arg, iflags = {}) {
|
|
|
1177
1202
|
const publicParams = buildPublicParams(command, arg, iflags);
|
|
1178
1203
|
const publicExecution = execute(index, command, publicParams);
|
|
1179
1204
|
if (!publicExecution.ok) {
|
|
1180
|
-
console.log(
|
|
1205
|
+
console.log(publicExecution.internalError ? publicExecution.error
|
|
1206
|
+
: formatSurfaceMessage(publicExecution.error, 'cli'));
|
|
1181
1207
|
return;
|
|
1182
1208
|
}
|
|
1183
1209
|
console.log(formatCliText(
|
package/core/accessors.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const path = require('path');
|
|
4
|
-
const { codeUnitCompare } = require('./shared');
|
|
4
|
+
const { codeUnitCompare, CALLABLE_SYMBOL_KINDS } = require('./shared');
|
|
5
5
|
const { getParser, safeParse } = require('../languages');
|
|
6
6
|
|
|
7
7
|
function inheritedAccessor(index, enclosing, name, definition) {
|
|
@@ -45,6 +45,85 @@ function accessKind(tree, usage) {
|
|
|
45
45
|
return 'read';
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
// How an occurrence without a simple receiver touches a property (fix #397):
|
|
49
|
+
// 'member' (the property of a longer member chain, `this.ctx.body`),
|
|
50
|
+
// { pattern: true, thisSource } (a destructuring key, `const { body } =
|
|
51
|
+
// this`, which reads the property from the source), or 'none' (a bare
|
|
52
|
+
// variable, an object-literal key: never a property access). null when the
|
|
53
|
+
// token cannot be located.
|
|
54
|
+
const MEMBER_ACCESS_TYPES = new Set(['attribute', 'member_expression', 'member_access_expression',
|
|
55
|
+
'field_expression', 'selector_expression', 'field_access']);
|
|
56
|
+
function receiverlessShape(tree, usage) {
|
|
57
|
+
if (!tree || !Number.isInteger(usage.column)) return null;
|
|
58
|
+
const node = tree.rootNode.descendantForPosition({ row: usage.line - 1, column: usage.column });
|
|
59
|
+
if (!node) return null;
|
|
60
|
+
const parent = node.parent;
|
|
61
|
+
if (MEMBER_ACCESS_TYPES.has(parent?.type) && node.startIndex > parent.startIndex) return 'member';
|
|
62
|
+
let key = null;
|
|
63
|
+
if (node.type === 'shorthand_property_identifier_pattern') key = node;
|
|
64
|
+
else if (parent?.type === 'pair_pattern' && parent.childForFieldName('key')?.startIndex === node.startIndex) key = node;
|
|
65
|
+
if (key) {
|
|
66
|
+
let pattern = key.parent;
|
|
67
|
+
if (pattern?.type === 'pair_pattern' || pattern?.type === 'object_assignment_pattern') pattern = pattern.parent;
|
|
68
|
+
const holder = pattern?.type === 'object_pattern' ? pattern.parent : null;
|
|
69
|
+
const source = holder?.type === 'variable_declarator' ? holder.childForFieldName('value') : null;
|
|
70
|
+
return { pattern: true, thisSource: source?.type === 'this',
|
|
71
|
+
shorthand: key.type === 'shorthand_property_identifier_pattern' };
|
|
72
|
+
}
|
|
73
|
+
return 'none';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Source offset of an occurrence (fix #398F): orders it against same-line
|
|
77
|
+
// assignments when its receiver is typed by return-type flow.
|
|
78
|
+
function flowSiteOf(tree, usage) {
|
|
79
|
+
if (!tree || !Number.isInteger(usage.column)) return null;
|
|
80
|
+
const node = tree.rootNode.descendantForPosition({ row: usage.line - 1, column: usage.column });
|
|
81
|
+
return node ? node.startIndex : null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// The enclosing callable scopes of a line, outermost first, as the parser's
|
|
85
|
+
// call records spell them (`enclosingFunction.scopeChain`): the innermost
|
|
86
|
+
// scope any record of the file reports around the line (anonymous callbacks
|
|
87
|
+
// included), else the indexed callables containing it.
|
|
88
|
+
function scopeChainAt(index, file, line, calls) {
|
|
89
|
+
let best = null;
|
|
90
|
+
for (const call of calls || []) {
|
|
91
|
+
const scope = call.enclosingFunction;
|
|
92
|
+
if (!scope || scope.startLine > line || scope.endLine < line) continue;
|
|
93
|
+
if (!best || scope.startLine > best.startLine ||
|
|
94
|
+
(scope.startLine === best.startLine && scope.endLine < best.endLine)) best = scope;
|
|
95
|
+
}
|
|
96
|
+
if (best && Array.isArray(best.scopeChain)) return best.scopeChain;
|
|
97
|
+
const scopes = (index.files.get(file)?.symbols || []).filter(symbol =>
|
|
98
|
+
CALLABLE_SYMBOL_KINDS.has(symbol.type) && symbol.startLine <= line && line <= symbol.endLine)
|
|
99
|
+
.sort((a, b) => a.startLine - b.startLine || b.endLine - a.endLine);
|
|
100
|
+
return scopes.map(symbol => symbol.startLine);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// A plain local receiver typed by return-type flow (`result =
|
|
104
|
+
// runner.invoke(..)` with `invoke(..) -> Result`), the evidence the caller
|
|
105
|
+
// engine uses for method calls on the same receiver (fix #398F).
|
|
106
|
+
function flowReceiverType(index, ref, flowMaps) {
|
|
107
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(ref.receiver || '') || ['self', 'cls', 'this'].includes(ref.receiver)) return null;
|
|
108
|
+
const callers = require('./callers');
|
|
109
|
+
let state = flowMaps.get(ref.file);
|
|
110
|
+
if (state === undefined) {
|
|
111
|
+
const calls = callers.getCachedCalls(index, ref.file);
|
|
112
|
+
state = calls ? { calls, map: callers._buildReturnTypeFlowMap(index, ref.file, calls) } : null;
|
|
113
|
+
flowMaps.set(ref.file, state);
|
|
114
|
+
}
|
|
115
|
+
const map = state?.map;
|
|
116
|
+
if (!map) return null;
|
|
117
|
+
const chain = scopeChainAt(index, ref.file, ref.line, state.calls);
|
|
118
|
+
const entry = callers._lookupReturnTypeFlow(map, {
|
|
119
|
+
receiver: ref.receiver, line: ref.line,
|
|
120
|
+
...(Number.isInteger(ref.flowSite) && { callStart: ref.flowSite }),
|
|
121
|
+
enclosingFunction: chain.length ? { startLine: chain[chain.length - 1], scopeChain: chain } : null,
|
|
122
|
+
});
|
|
123
|
+
if (!entry?.type || entry.invalidated || !entry.fromFile) return null;
|
|
124
|
+
return { type: entry.type, fromFile: entry.fromFile };
|
|
125
|
+
}
|
|
126
|
+
|
|
48
127
|
// Descriptors/properties are consumed through reads and writes, not only
|
|
49
128
|
// call syntax. Keep this vocabulary shared by impact and refactoring so the
|
|
50
129
|
// two commands cannot disagree about whether a selected symbol is an accessor.
|
|
@@ -61,6 +140,8 @@ function isAccessorDefinition(definition) {
|
|
|
61
140
|
|
|
62
141
|
function ownerName(definition) {
|
|
63
142
|
if (definition.className) return definition.className;
|
|
143
|
+
// An object-literal accessor's owner is the literal (fix #397).
|
|
144
|
+
if (definition.objectLiteralLine && definition.registryContainer) return definition.registryContainer;
|
|
64
145
|
if (!definition.receiver) return null;
|
|
65
146
|
return String(definition.receiver)
|
|
66
147
|
.replace(/^[*&]\s*/, '')
|
|
@@ -89,11 +170,19 @@ function typeMatchesOwner(index, typeName, contextFile, owner, ownerFile) {
|
|
|
89
170
|
path.resolve(ownerDefs[0].file) === path.resolve(ownerFile);
|
|
90
171
|
}
|
|
91
172
|
|
|
173
|
+
// Members of the same object literal (fix #397): `this` in one names the
|
|
174
|
+
// object the literal builds (or an object inheriting from it).
|
|
175
|
+
function sameObjectLiteral(a, b) {
|
|
176
|
+
return !!a && !!b && !!a.objectLiteralLine && a.objectLiteralLine === b.objectLiteralLine &&
|
|
177
|
+
a.file === b.file;
|
|
178
|
+
}
|
|
179
|
+
|
|
92
180
|
function insideSelectedAccessorBody(index, name, definition, file, line) {
|
|
93
181
|
return (index.symbols.get(name) || []).some(candidate =>
|
|
94
182
|
isAccessorDefinition(candidate) &&
|
|
95
183
|
candidate.file === definition.file &&
|
|
96
184
|
candidate.className === definition.className &&
|
|
185
|
+
(candidate.objectLiteralLine || null) === (definition.objectLiteralLine || null) &&
|
|
97
186
|
file === candidate.file &&
|
|
98
187
|
line >= candidate.startLine &&
|
|
99
188
|
line <= (candidate.endLine || candidate.startLine));
|
|
@@ -114,12 +203,16 @@ function findAccessorReferences(index, name, definition, options = {}) {
|
|
|
114
203
|
const confirmed = [];
|
|
115
204
|
const unverified = [];
|
|
116
205
|
const excluded = [];
|
|
206
|
+
const flowMaps = new Map();
|
|
117
207
|
// Use the parser's raw occurrence records rather than usages()' public
|
|
118
208
|
// line-oriented inventory. A line can contain two same-spelled member
|
|
119
209
|
// accesses with different receivers; collapsing them by file+line would
|
|
120
210
|
// make a rename edit one token while silently losing the other.
|
|
121
211
|
const refs = [];
|
|
122
212
|
for (const [file, entry] of index.files) {
|
|
213
|
+
// `files` (fix #398F): a caller that needs one file family (tests)
|
|
214
|
+
// never parses the rest of the project.
|
|
215
|
+
if (options.files && !options.files.has(file)) continue;
|
|
123
216
|
if (!index.matchesFilters(entry.relativePath, options)) continue;
|
|
124
217
|
let content;
|
|
125
218
|
let occurrences;
|
|
@@ -134,9 +227,16 @@ function findAccessorReferences(index, name, definition, options = {}) {
|
|
|
134
227
|
const lines = content.split('\n');
|
|
135
228
|
for (const usage of occurrences) {
|
|
136
229
|
if (usage.usageType !== 'reference') continue;
|
|
230
|
+
let shape = null;
|
|
231
|
+
if (!usage.receiver) {
|
|
232
|
+
shape = receiverlessShape(tree, usage);
|
|
233
|
+
if (shape === 'none') continue;
|
|
234
|
+
}
|
|
137
235
|
refs.push({
|
|
138
236
|
...usage,
|
|
139
|
-
|
|
237
|
+
...(shape?.pattern && { patternKey: shape, ...(shape.thisSource && { receiver: 'this' }) }),
|
|
238
|
+
accessKind: shape?.pattern ? 'read' : accessKind(tree, usage),
|
|
239
|
+
...(usage.receiver && { flowSite: flowSiteOf(tree, usage) }),
|
|
140
240
|
file,
|
|
141
241
|
relativePath: entry.relativePath,
|
|
142
242
|
content: lines[usage.line - 1] || '',
|
|
@@ -154,6 +254,7 @@ function findAccessorReferences(index, name, definition, options = {}) {
|
|
|
154
254
|
accessKind: ref.accessKind,
|
|
155
255
|
...(Number.isInteger(ref.column) && { column: ref.column }),
|
|
156
256
|
...(ref.receiver && { receiver: ref.receiver }),
|
|
257
|
+
...(ref.patternKey && { patternKey: { shorthand: !!ref.patternKey.shorthand } }),
|
|
157
258
|
};
|
|
158
259
|
|
|
159
260
|
// A backing-store attribute inside the selected getter/setter body
|
|
@@ -168,7 +269,9 @@ function findAccessorReferences(index, name, definition, options = {}) {
|
|
|
168
269
|
const enclosing = index.findEnclosingFunction(ref.file, ref.line, true);
|
|
169
270
|
const receiver = ref.receiver || null;
|
|
170
271
|
if (receiver && ['self', 'cls', 'this'].includes(receiver) &&
|
|
171
|
-
((enclosing?.className === owner &&
|
|
272
|
+
((definition.className && enclosing?.className === owner &&
|
|
273
|
+
enclosing.file === definition.file) ||
|
|
274
|
+
(!definition.className && sameObjectLiteral(enclosing, definition)) ||
|
|
172
275
|
inheritedAccessor(index, enclosing, name, definition))) {
|
|
173
276
|
confirmed.push({
|
|
174
277
|
...shaped,
|
|
@@ -195,6 +298,18 @@ function findAccessorReferences(index, name, definition, options = {}) {
|
|
|
195
298
|
});
|
|
196
299
|
continue;
|
|
197
300
|
}
|
|
301
|
+
const flowed = !receiverType && receiver ? flowReceiverType(index, ref, flowMaps) : null;
|
|
302
|
+
if (flowed && bareTypeName(flowed.type) === owner &&
|
|
303
|
+
path.resolve(flowed.fromFile) === path.resolve(definition.file)) {
|
|
304
|
+
confirmed.push({
|
|
305
|
+
...shaped,
|
|
306
|
+
callerName: enclosing?.name || null,
|
|
307
|
+
receiverType: flowed.type,
|
|
308
|
+
resolution: 'receiver-return-flow',
|
|
309
|
+
tier: 'confirmed',
|
|
310
|
+
});
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
198
313
|
if (receiver && typeMatchesOwner(index, receiver, ref.file, owner, definition.file)) {
|
|
199
314
|
confirmed.push({
|
|
200
315
|
...shaped,
|
package/core/account.js
CHANGED
|
@@ -69,6 +69,7 @@ const UNSUPPORTED_SITE_TEXT_MAX = 160;
|
|
|
69
69
|
* fileCount: number, // files (indexed + unparsed + unsupported) with >= 1 matching line
|
|
70
70
|
* perFile: Map<string, number[]>, // absPath -> sorted 1-indexed line numbers (indexed files only)
|
|
71
71
|
* unparsed: { fileCount: number, lines: number, files: string[] }, // relative paths
|
|
72
|
+
* recovered: { fileCount, lines, files, sites }, // matched lines inside parse-error recovery regions (subset of perFile)
|
|
72
73
|
* unsupported: { fileCount: number, lines: number, files: string[],
|
|
73
74
|
* languages: Object<string, number>,
|
|
74
75
|
* sites: Array<{file: string, line: number, text: string}>,
|
|
@@ -125,11 +126,39 @@ function computeGroundSet(index, name) {
|
|
|
125
126
|
? index.discoveryIssues.map(issue => ({ ...issue })) : [];
|
|
126
127
|
unreadableFiles.sort();
|
|
127
128
|
|
|
129
|
+
// fix #367e: ground lines inside a parser-recovery region of an indexed
|
|
130
|
+
// file. They are classified like any other line, but from a partially
|
|
131
|
+
// parsed tree - disclosed, never silently trusted.
|
|
132
|
+
const recovered = { fileCount: 0, lines: 0, files: [], sites: [] };
|
|
133
|
+
for (const [filePath, matchedLines] of perFile) {
|
|
134
|
+
const regions = index.files.get(filePath)?.parseErrorRegions;
|
|
135
|
+
if (!regions || regions.length === 0) continue;
|
|
136
|
+
let inFile = 0;
|
|
137
|
+
let r = 0;
|
|
138
|
+
for (const line of matchedLines) {
|
|
139
|
+
while (r < regions.length && regions[r][1] < line) r++;
|
|
140
|
+
if (r < regions.length && regions[r][0] <= line) {
|
|
141
|
+
inFile++;
|
|
142
|
+
if (recovered.sites.length < 10) {
|
|
143
|
+
recovered.sites.push({ file: path.relative(index.root, filePath), line });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (inFile > 0) {
|
|
148
|
+
recovered.fileCount++;
|
|
149
|
+
recovered.lines += inFile;
|
|
150
|
+
recovered.files.push(path.relative(index.root, filePath));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
recovered.files.sort(codeUnitCompare);
|
|
154
|
+
recovered.sites.sort((a, b) => codeUnitCompare(a.file, b.file) || a.line - b.line);
|
|
155
|
+
|
|
128
156
|
const result = {
|
|
129
157
|
total: total + unparsed.lines + unsupported.lines,
|
|
130
158
|
fileCount: fileCount + unparsed.fileCount + unsupported.fileCount,
|
|
131
159
|
perFile,
|
|
132
160
|
unparsed,
|
|
161
|
+
recovered,
|
|
133
162
|
unsupported,
|
|
134
163
|
unreadableFiles,
|
|
135
164
|
skippedSources,
|
|
@@ -473,6 +502,7 @@ function buildAccount(index, name, parts) {
|
|
|
473
502
|
line: e.line, reason: e.reason, provenance: e.provenance })) }),
|
|
474
503
|
},
|
|
475
504
|
unparsed: groundSet.unparsed,
|
|
505
|
+
...(groundSet.recovered?.lines > 0 && { recovered: groundSet.recovered }),
|
|
476
506
|
unsupported,
|
|
477
507
|
unreadableFiles: groundSet.unreadableFiles,
|
|
478
508
|
skippedSources: groundSet.skippedSources || [],
|