hoi4-agent-tools 3.0.9 → 3.2.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/CHANGELOG.md +32 -1
- package/README.md +10 -2
- package/dist/bin/stdio.js +23 -12
- package/dist/hoi4_agent_tools/core/analysis-checkpoints.js +82 -0
- package/dist/hoi4_agent_tools/core/analysis-evidence.js +9 -0
- package/dist/hoi4_agent_tools/core/analysis-job-operations.js +38 -0
- package/dist/hoi4_agent_tools/core/artifact-resource.js +173 -0
- package/dist/hoi4_agent_tools/core/artifacts.js +84 -2
- package/dist/hoi4_agent_tools/core/condition-evaluator.js +16 -0
- package/dist/hoi4_agent_tools/core/dependency-pages.js +216 -0
- package/dist/hoi4_agent_tools/core/dependency-walk.js +266 -0
- package/dist/hoi4_agent_tools/core/domain-validation.js +188 -0
- package/dist/hoi4_agent_tools/core/engine.js +48 -5
- package/dist/hoi4_agent_tools/core/helper-expansion.js +186 -0
- package/dist/hoi4_agent_tools/core/impact-comparison.js +49 -0
- package/dist/hoi4_agent_tools/core/impact-graph.js +301 -0
- package/dist/hoi4_agent_tools/core/impact-semantic.js +256 -0
- package/dist/hoi4_agent_tools/core/index-segments.js +137 -0
- package/dist/hoi4_agent_tools/core/index.js +47 -4
- package/dist/hoi4_agent_tools/core/job-controls.js +109 -0
- package/dist/hoi4_agent_tools/core/job-executor.js +190 -0
- package/dist/hoi4_agent_tools/core/job-service.js +112 -0
- package/dist/hoi4_agent_tools/core/job-store.js +641 -0
- package/dist/hoi4_agent_tools/core/job-transactions.js +126 -0
- package/dist/hoi4_agent_tools/core/job-worker-host.js +202 -0
- package/dist/hoi4_agent_tools/core/job-worker.js +64 -0
- package/dist/hoi4_agent_tools/core/operation-context.js +31 -0
- package/dist/hoi4_agent_tools/core/operation-result.js +412 -0
- package/dist/hoi4_agent_tools/core/operation-tasks.js +387 -0
- package/dist/hoi4_agent_tools/core/persistent-analysis-cache.js +383 -0
- package/dist/hoi4_agent_tools/core/result.js +1 -0
- package/dist/hoi4_agent_tools/core/scanner.js +15 -75
- package/dist/hoi4_agent_tools/core/semantic-dependencies.js +233 -0
- package/dist/hoi4_agent_tools/core/shared-request-capacity.js +43 -11
- package/dist/hoi4_agent_tools/core/source/cache.js +107 -0
- package/dist/hoi4_agent_tools/core/source/parser.js +11 -1
- package/dist/hoi4_agent_tools/core/source-overlay.js +125 -0
- package/dist/hoi4_agent_tools/core/transaction-execution.js +134 -0
- package/dist/hoi4_agent_tools/core/transactions.js +80 -27
- package/dist/hoi4_agent_tools/decision/ai.js +104 -0
- package/dist/hoi4_agent_tools/decision/comparison.js +106 -0
- package/dist/hoi4_agent_tools/decision/cost.js +177 -0
- package/dist/hoi4_agent_tools/decision/evaluation.js +256 -0
- package/dist/hoi4_agent_tools/decision/lifecycle.js +114 -0
- package/dist/hoi4_agent_tools/decision/service.js +172 -0
- package/dist/hoi4_agent_tools/decision/source-inventory.js +182 -0
- package/dist/hoi4_agent_tools/event/graph.js +214 -290
- package/dist/hoi4_agent_tools/event/helper-expansion.js +33 -0
- package/dist/hoi4_agent_tools/event/job-operations.js +51 -0
- package/dist/hoi4_agent_tools/event/operations.js +179 -0
- package/dist/hoi4_agent_tools/event/service.js +67 -15
- package/dist/hoi4_agent_tools/event/source-analysis.js +5 -4
- package/dist/hoi4_agent_tools/focus/job-operations.js +77 -0
- package/dist/hoi4_agent_tools/focus/tool-operations.js +669 -0
- package/dist/hoi4_agent_tools/focus/tool-support.js +123 -0
- package/dist/hoi4_agent_tools/gui/job-operations.js +81 -0
- package/dist/hoi4_agent_tools/gui/tool-operations.js +270 -0
- package/dist/hoi4_agent_tools/impact/scenario-suites.js +74 -0
- package/dist/hoi4_agent_tools/impact/service.js +127 -0
- package/dist/hoi4_agent_tools/map/job-operations.js +76 -0
- package/dist/hoi4_agent_tools/map/tool-operations.js +493 -0
- package/dist/hoi4_agent_tools/mcp/prompts/probability.js +25 -0
- package/dist/hoi4_agent_tools/mcp/prompts/register-modern.js +29 -0
- package/dist/hoi4_agent_tools/mcp/resources/register-modern.js +34 -0
- package/dist/hoi4_agent_tools/mcp/resources/register.js +4 -150
- package/dist/hoi4_agent_tools/mcp/server/base-tools.js +3 -219
- package/dist/hoi4_agent_tools/mcp/server/create-modern-operations.js +188 -0
- package/dist/hoi4_agent_tools/mcp/server/create.js +18 -6
- package/dist/hoi4_agent_tools/mcp/server/instructions.js +2 -0
- package/dist/hoi4_agent_tools/mcp/server/job-task-store.js +67 -0
- package/dist/hoi4_agent_tools/mcp/server/legacy-task-tools.js +20 -0
- package/dist/hoi4_agent_tools/mcp/server/modern-tasks.js +168 -0
- package/dist/hoi4_agent_tools/mcp/server/progress.js +17 -15
- package/dist/hoi4_agent_tools/mcp/server/request-lifecycle.js +45 -0
- package/dist/hoi4_agent_tools/mcp/server/result.js +2 -412
- package/dist/hoi4_agent_tools/mcp/server/task-request-context.js +9 -0
- package/dist/hoi4_agent_tools/mcp/server/task-tool-catalog.js +16 -0
- package/dist/hoi4_agent_tools/mcp/server/task-tool-definition.js +1 -0
- package/dist/hoi4_agent_tools/mcp/server/transaction-execution.js +1 -123
- package/dist/hoi4_agent_tools/mcp/tools/analysis.js +65 -0
- package/dist/hoi4_agent_tools/mcp/tools/chaosx.js +38 -21
- package/dist/hoi4_agent_tools/mcp/tools/event.js +24 -315
- package/dist/hoi4_agent_tools/mcp/tools/focus.js +34 -1039
- package/dist/hoi4_agent_tools/mcp/tools/gui.js +19 -504
- package/dist/hoi4_agent_tools/mcp/tools/job.js +46 -0
- package/dist/hoi4_agent_tools/mcp/tools/map.js +21 -600
- package/dist/hoi4_agent_tools/mcp/tools/probability.js +39 -463
- package/dist/hoi4_agent_tools/mcp/tools/technology.js +40 -304
- package/dist/hoi4_agent_tools/mcp/transports/http.js +58 -1
- package/dist/hoi4_agent_tools/mcp/transports/modern-task-routing.js +109 -0
- package/dist/hoi4_agent_tools/mcp/transports/negotiated-stdio.js +121 -0
- package/dist/hoi4_agent_tools/probability/job-operations.js +58 -0
- package/dist/hoi4_agent_tools/probability/operations.js +183 -0
- package/dist/hoi4_agent_tools/probability/service.js +24 -3
- package/dist/hoi4_agent_tools/schemas/analysis.js +228 -0
- package/dist/hoi4_agent_tools/schemas/common.js +2 -0
- package/dist/hoi4_agent_tools/schemas/event.js +72 -1
- package/dist/hoi4_agent_tools/schemas/focus-requests.js +189 -0
- package/dist/hoi4_agent_tools/schemas/gui-requests.js +193 -0
- package/dist/hoi4_agent_tools/schemas/helper-expansion.js +58 -0
- package/dist/hoi4_agent_tools/schemas/map-requests.js +85 -0
- package/dist/hoi4_agent_tools/schemas/probability-requests.js +168 -0
- package/dist/hoi4_agent_tools/schemas/technology.js +90 -1
- package/dist/hoi4_agent_tools/schemas/transaction.js +1 -0
- package/dist/hoi4_agent_tools/technology/graph.js +83 -78
- package/dist/hoi4_agent_tools/technology/helper-expansion.js +33 -0
- package/dist/hoi4_agent_tools/technology/job-operations.js +60 -0
- package/dist/hoi4_agent_tools/technology/operations.js +125 -0
- package/dist/hoi4_agent_tools/technology/service.js +89 -15
- package/dist/hoi4_agent_tools/technology/source-analysis.js +2 -1
- package/dist/hoi4_agent_tools/version.js +1 -1
- package/docs/README.md +3 -0
- package/docs/analysis.md +100 -0
- package/docs/development.md +2 -0
- package/docs/events.md +7 -2
- package/docs/helper-expansion.md +78 -0
- package/docs/http.md +2 -0
- package/docs/jobs.md +86 -0
- package/docs/technology.md +6 -2
- package/package.json +15 -6
- package/schemas/configuration.schema.json +1 -1
- package/schemas/continuous-focus-palette.schema.json +1 -1
- package/schemas/custom-weighted-pool.schema.json +1 -1
- package/schemas/decision-inspect-input.schema.json +290 -0
- package/schemas/event-feature-manifest.schema.json +1 -1
- package/schemas/focus-plan.schema.json +1 -1
- package/schemas/focus-planning-sidecar.schema.json +1 -1
- package/schemas/gui-animation-source.schema.json +1 -1
- package/schemas/gui-helper.schema.json +1 -1
- package/schemas/gui-scenario.schema.json +1 -1
- package/schemas/helper-expansion-request.schema.json +44 -0
- package/schemas/helper-expansion-summary.schema.json +70 -0
- package/schemas/impact-inspect-input.schema.json +203 -0
- package/schemas/map-operation.schema.json +1 -1
- package/schemas/operation-result.schema.json +1 -1
- package/schemas/probability-analysis-result.schema.json +1 -1
- package/schemas/probability-compare-input.schema.json +1 -1
- package/schemas/probability-evaluate-input.schema.json +1 -1
- package/schemas/probability-inspect-input.schema.json +1 -1
- package/schemas/probability-render-input.schema.json +1 -1
- package/schemas/probability-scenario-set.schema.json +1 -1
- package/schemas/probability-sequence-input.schema.json +1 -1
- package/schemas/probability-simulate-input.schema.json +1 -1
- package/schemas/probability-sweep-input.schema.json +1 -1
- package/server.json +3 -3
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { compareCodeUnits, hashCanonical } from './canonical.js';
|
|
2
2
|
import { SymbolIndex } from './index.js';
|
|
3
|
+
import { IndexSegmentCache } from './index-segments.js';
|
|
4
|
+
import { PersistentAnalysisCache, } from './persistent-analysis-cache.js';
|
|
5
|
+
import { sourceDocuments } from './source/cache.js';
|
|
3
6
|
import { WorkspaceScanner } from './scanner.js';
|
|
4
7
|
import { ArtifactStore } from './artifacts.js';
|
|
5
|
-
import { TransactionManager } from './transactions.js';
|
|
8
|
+
import { TransactionManager, transactionRootFingerprint } from './transactions.js';
|
|
6
9
|
import { RequestScheduler } from './request-scheduler.js';
|
|
7
10
|
import { SharedRequestCapacity } from './shared-request-capacity.js';
|
|
11
|
+
import { JobStore } from './job-store.js';
|
|
8
12
|
const RECOVERY_TTL_SECONDS = 3_600;
|
|
9
13
|
const RECOVERY_MAX_BYTES = 536_870_912;
|
|
10
14
|
const RECOVERY_MAX_RECORDS = 128;
|
|
@@ -81,6 +85,8 @@ export class CoreEngine {
|
|
|
81
85
|
scanner;
|
|
82
86
|
artifacts;
|
|
83
87
|
transactions;
|
|
88
|
+
indexSegments;
|
|
89
|
+
persistentAnalysisCache;
|
|
84
90
|
#scanCache = new Map();
|
|
85
91
|
#scanFlights = new Map();
|
|
86
92
|
#scanGenerations = new Map();
|
|
@@ -88,14 +94,30 @@ export class CoreEngine {
|
|
|
88
94
|
#activeScanAdmissions = new Set();
|
|
89
95
|
constructor(resolver, services = {}) {
|
|
90
96
|
this.resolver = resolver;
|
|
97
|
+
this.indexSegments = services.indexSegments ?? new IndexSegmentCache();
|
|
98
|
+
const state = resolver.serverState();
|
|
99
|
+
this.persistentAnalysisCache =
|
|
100
|
+
services.persistentAnalysisCache === null || state === undefined
|
|
101
|
+
? undefined
|
|
102
|
+
: Promise.resolve(services.persistentAnalysisCache ?? PersistentAnalysisCache.create(state));
|
|
91
103
|
this.requests = new RequestScheduler(resolver.config().maxConcurrentTools);
|
|
92
104
|
this.sharedRequests = new SharedRequestCapacity(resolver.serverState()?.root, resolver.config().maxSharedTools);
|
|
93
105
|
this.scanner =
|
|
94
106
|
services.scanner ??
|
|
95
107
|
new WorkspaceScanner(resolver.config().scanMaxFiles, resolver.config().scanMaxBytes, resolver.config().scanMaxFileBytes);
|
|
108
|
+
let retentionJobs;
|
|
96
109
|
this.artifacts =
|
|
97
110
|
services.artifacts ??
|
|
98
|
-
new ArtifactStore(resolver.config().artifactMaxBytes, resolver.config().artifactMaxEntries, resolver.config().artifactMaxSingleBytes
|
|
111
|
+
new ArtifactStore(resolver.config().artifactMaxBytes, resolver.config().artifactMaxEntries, resolver.config().artifactMaxSingleBytes, state === undefined
|
|
112
|
+
? undefined
|
|
113
|
+
: async (workspace, signal) => {
|
|
114
|
+
retentionJobs ??= JobStore.create(state);
|
|
115
|
+
return (await retentionJobs).checkpointResources({
|
|
116
|
+
workspaceId: workspace.id,
|
|
117
|
+
workspaceIdentity: workspace.workspaceIdentity,
|
|
118
|
+
rootFingerprint: transactionRootFingerprint(workspace),
|
|
119
|
+
}, signal);
|
|
120
|
+
});
|
|
99
121
|
this.transactions =
|
|
100
122
|
services.transactions ??
|
|
101
123
|
new TransactionManager(resolver, this.artifacts, RECOVERY_TTL_SECONDS, RECOVERY_MAX_BYTES, RECOVERY_MAX_RECORDS, resolver.serverState());
|
|
@@ -160,12 +182,28 @@ export class CoreEngine {
|
|
|
160
182
|
signal: controller.signal,
|
|
161
183
|
});
|
|
162
184
|
controller.signal.throwIfAborted();
|
|
163
|
-
const
|
|
185
|
+
const persistentAnalysisCache = await this.persistentAnalysisCache;
|
|
186
|
+
const analysisScope = {
|
|
187
|
+
workspaceIdentity: workspace.workspaceIdentity,
|
|
188
|
+
rootFingerprint: transactionRootFingerprint(workspace),
|
|
189
|
+
principal: principal ?? null,
|
|
190
|
+
};
|
|
191
|
+
await persistentAnalysisCache?.hydrate(analysisScope, files, sourceDocuments, this.indexSegments, controller.signal);
|
|
192
|
+
const revision = hashCanonical(files.map(({ absolutePath, displayPath, relativePath, rootKind, loadOrder, shadowedBy, sha256, }) => ({
|
|
193
|
+
absolutePath,
|
|
194
|
+
displayPath,
|
|
195
|
+
relativePath,
|
|
196
|
+
rootKind,
|
|
197
|
+
loadOrder,
|
|
198
|
+
shadowedBy: shadowedBy ?? null,
|
|
199
|
+
sha256,
|
|
200
|
+
})));
|
|
164
201
|
const cacheKey = `${requestKey}:${revision}`;
|
|
165
202
|
const cached = this.#scanCache.get(cacheKey);
|
|
166
203
|
if (cached !== undefined)
|
|
167
204
|
return cached;
|
|
168
|
-
const index = await SymbolIndex.buildAsync(files, controller.signal);
|
|
205
|
+
const index = await SymbolIndex.buildAsync(files, controller.signal, this.indexSegments);
|
|
206
|
+
await persistentAnalysisCache?.persist(analysisScope, files, sourceDocuments, this.indexSegments, controller.signal);
|
|
169
207
|
const snapshot = {
|
|
170
208
|
workspaceId,
|
|
171
209
|
revision,
|
|
@@ -219,12 +257,17 @@ export class CoreEngine {
|
|
|
219
257
|
/** Drop completed scan buffers without invalidating revisions or interrupting active scans. */
|
|
220
258
|
releaseScanCaches() {
|
|
221
259
|
this.#scanCache.clear();
|
|
260
|
+
this.indexSegments.clear();
|
|
261
|
+
sourceDocuments.clear();
|
|
222
262
|
this.scanner.clearCaches();
|
|
223
263
|
}
|
|
224
264
|
/** Monotonic cache generation used by domain services to invalidate derived snapshots. */
|
|
225
265
|
generation(workspaceId) {
|
|
226
266
|
return this.#scanGenerations.get(workspaceId) ?? 0;
|
|
227
267
|
}
|
|
268
|
+
async persistentAnalysisCacheStatistics() {
|
|
269
|
+
return (await this.persistentAnalysisCache)?.statistics();
|
|
270
|
+
}
|
|
228
271
|
awaitScanFlight(requestKey, flight, signal) {
|
|
229
272
|
signal?.throwIfAborted();
|
|
230
273
|
flight.waiters += 1;
|
|
@@ -342,6 +385,6 @@ export class CoreEngine {
|
|
|
342
385
|
}
|
|
343
386
|
}
|
|
344
387
|
indexFiles(files) {
|
|
345
|
-
return SymbolIndex.build(files);
|
|
388
|
+
return SymbolIndex.build(files, this.indexSegments);
|
|
346
389
|
}
|
|
347
390
|
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { z } from 'zod/v4';
|
|
2
|
+
import { PACKAGE_VERSION } from '../version.js';
|
|
3
|
+
import { helperExpansionRequestSchema, } from '../schemas/helper-expansion.js';
|
|
4
|
+
import { canonicalJson, compareCodeUnits, hashCanonical } from './canonical.js';
|
|
5
|
+
import { boundedSourceHashEvidence } from './artifacts.js';
|
|
6
|
+
import { DependencyPages, dependencyPageCursorSchema } from './dependency-pages.js';
|
|
7
|
+
import { ServiceError } from './result.js';
|
|
8
|
+
import { transactionRootFingerprint } from './transactions.js';
|
|
9
|
+
const digest = z.string().regex(/^[a-f0-9]{64}$/u);
|
|
10
|
+
const querySchema = helperExpansionRequestSchema
|
|
11
|
+
.pick({ rootIds: true })
|
|
12
|
+
.extend({ maxDepth: z.number().int().min(1).max(256) });
|
|
13
|
+
const continuationSchema = z
|
|
14
|
+
.object({
|
|
15
|
+
version: z.literal(1),
|
|
16
|
+
toolVersion: z.string(),
|
|
17
|
+
domain: z.enum(['event', 'technology']),
|
|
18
|
+
scope: digest,
|
|
19
|
+
sourceRevision: digest,
|
|
20
|
+
query: querySchema,
|
|
21
|
+
cursor: dependencyPageCursorSchema,
|
|
22
|
+
})
|
|
23
|
+
.strict();
|
|
24
|
+
export function helperExpansionValidation(summary) {
|
|
25
|
+
return {
|
|
26
|
+
passed: summary.complete,
|
|
27
|
+
checks: [
|
|
28
|
+
{
|
|
29
|
+
id: 'helper-expansion-coverage',
|
|
30
|
+
passed: summary.complete,
|
|
31
|
+
message: `${summary.completedRoots}/${summary.totalRoots} roots finished; ${summary.depthStops} depth stops; source inventory ${summary.sourceComplete ? 'complete' : 'partial'}; static source coverage only, not runtime validity`,
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/** Read-only structural evidence, with authenticated opaque continuation resources shared by both domains. */
|
|
37
|
+
export async function inspectHelperExpansion(engine, workspaceId, inventory, request = {}, principal, signal) {
|
|
38
|
+
signal?.throwIfAborted();
|
|
39
|
+
const options = helperExpansionRequestSchema.parse(request);
|
|
40
|
+
const workspace = engine.resolver.get(workspaceId, principal);
|
|
41
|
+
const scope = hashCanonical({
|
|
42
|
+
workspaceId: workspace.id,
|
|
43
|
+
workspaceIdentity: workspace.workspaceIdentity,
|
|
44
|
+
ownerIdentity: workspace.ownerIdentity,
|
|
45
|
+
roots: transactionRootFingerprint(workspace),
|
|
46
|
+
principal: principal ?? null,
|
|
47
|
+
});
|
|
48
|
+
let restored;
|
|
49
|
+
if (options.continuationUri !== undefined) {
|
|
50
|
+
const resource = await engine.artifacts.readLogical(workspace, options.continuationUri, { mimeType: 'application/json', maxBytes: 8_388_608, maxChunks: 8_192 }, signal);
|
|
51
|
+
try {
|
|
52
|
+
restored = continuationSchema.parse(JSON.parse(resource.bytes.toString('utf8')));
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
throw new ServiceError('HELPER_CONTINUATION_INVALID', 'Helper continuation does not contain a valid bounded traversal envelope');
|
|
56
|
+
}
|
|
57
|
+
if (restored.domain !== inventory.domain || restored.scope !== scope)
|
|
58
|
+
throw new ServiceError('HELPER_CONTINUATION_SCOPE_MISMATCH', 'Helper continuation belongs to a different domain, workspace, root topology, or principal');
|
|
59
|
+
if (restored.toolVersion !== PACKAGE_VERSION ||
|
|
60
|
+
restored.sourceRevision !== inventory.sourceRevision)
|
|
61
|
+
throw new ServiceError('HELPER_CONTINUATION_STALE', 'Helper continuation requires the same tool version and exact source revision; start a new analysis');
|
|
62
|
+
}
|
|
63
|
+
const rootIds = options.rootIds === undefined
|
|
64
|
+
? restored?.query.rootIds
|
|
65
|
+
: [...new Set(options.rootIds)].sort(compareCodeUnits);
|
|
66
|
+
const query = querySchema.parse({
|
|
67
|
+
...(rootIds === undefined ? {} : { rootIds }),
|
|
68
|
+
maxDepth: options.maxDepth ?? restored?.query.maxDepth ?? 64,
|
|
69
|
+
});
|
|
70
|
+
if (restored !== undefined && hashCanonical(restored.query) !== hashCanonical(query))
|
|
71
|
+
throw new ServiceError('HELPER_CONTINUATION_QUERY_MISMATCH', 'Helper continuation cannot change root selection or depth; start a new analysis');
|
|
72
|
+
const knownRoots = new Set(inventory.roots);
|
|
73
|
+
if (query.rootIds?.some((id) => !knownRoots.has(id)) === true)
|
|
74
|
+
throw new ServiceError('HELPER_ROOT_UNKNOWN', 'Every selected root must be a structural helper-call root in the current source inventory');
|
|
75
|
+
const input = {
|
|
76
|
+
domain: `${inventory.domain}-helper-expansion.v1`,
|
|
77
|
+
sourceRevision: inventory.sourceRevision,
|
|
78
|
+
edges: inventory.edges.map(({ id, from, to }) => ({ id, from, to })),
|
|
79
|
+
roots: query.rootIds ?? [...inventory.roots].sort(compareCodeUnits),
|
|
80
|
+
branches: inventory.branches,
|
|
81
|
+
maximumDepth: query.maxDepth,
|
|
82
|
+
visitPolicy: 'edge_path',
|
|
83
|
+
reverseEdges: false,
|
|
84
|
+
};
|
|
85
|
+
const pager = new DependencyPages(input, restored?.cursor);
|
|
86
|
+
const page = pager.page(options.maxRecords ?? 250, options.maxWork ?? 5_000, signal);
|
|
87
|
+
const summary = {
|
|
88
|
+
complete: inventory.sourceComplete && page.finished && page.cursor.depthStops === 0,
|
|
89
|
+
finished: page.finished,
|
|
90
|
+
sourceComplete: inventory.sourceComplete,
|
|
91
|
+
maxDepth: query.maxDepth,
|
|
92
|
+
records: page.records.length,
|
|
93
|
+
totalRecords: page.cursor.emitted,
|
|
94
|
+
work: page.work,
|
|
95
|
+
totalWork: page.cursor.steps,
|
|
96
|
+
completedRoots: page.completedRoots,
|
|
97
|
+
totalRoots: page.totalRoots,
|
|
98
|
+
cycles: page.cursor.cycles,
|
|
99
|
+
depthStops: page.cursor.depthStops,
|
|
100
|
+
};
|
|
101
|
+
const used = new Set(page.records.flatMap(({ path }) => path));
|
|
102
|
+
const records = page.records.map(({ kind, root, path }) => ({
|
|
103
|
+
id: hashCanonical({ key: pager.key, kind, root, path }),
|
|
104
|
+
kind,
|
|
105
|
+
rootId: input.roots[root],
|
|
106
|
+
path: path.map((index) => inventory.edges[index].id),
|
|
107
|
+
}));
|
|
108
|
+
const continuationName = `${inventory.domain}-helper-continuation.json`;
|
|
109
|
+
const report = {
|
|
110
|
+
schemaVersion: 'helper-expansion.v1',
|
|
111
|
+
domain: inventory.domain,
|
|
112
|
+
workspaceId: workspace.id,
|
|
113
|
+
workspaceIdentity: workspace.workspaceIdentity,
|
|
114
|
+
sourceRevision: inventory.sourceRevision,
|
|
115
|
+
sourceEvidence: boundedSourceHashEvidence(inventory.sourceHashes),
|
|
116
|
+
unresolved: {
|
|
117
|
+
count: inventory.unresolved?.length ?? 0,
|
|
118
|
+
retainedCount: Math.min(100, inventory.unresolved?.length ?? 0),
|
|
119
|
+
sample: (inventory.unresolved ?? []).slice(0, 100),
|
|
120
|
+
},
|
|
121
|
+
query,
|
|
122
|
+
coverage: summary,
|
|
123
|
+
analysisBoundary: {
|
|
124
|
+
staticAnalysis: true,
|
|
125
|
+
visitPolicy: 'edge_path',
|
|
126
|
+
cyclePolicy: 'report_and_stop',
|
|
127
|
+
conditions: 'source evidence only; not evaluated',
|
|
128
|
+
directReferences: 'helper-owned leaves only; use the ordinary inspection modes for non-helper references',
|
|
129
|
+
completeness: 'recognized static call paths under report-and-stop cycle semantics; not runtime outcomes or resolution of dynamic source findings',
|
|
130
|
+
},
|
|
131
|
+
records,
|
|
132
|
+
edges: [...used].sort((left, right) => left - right).map((index) => inventory.edges[index]),
|
|
133
|
+
...(page.finished
|
|
134
|
+
? {}
|
|
135
|
+
: {
|
|
136
|
+
continuation: {
|
|
137
|
+
name: continuationName,
|
|
138
|
+
mimeType: 'application/json',
|
|
139
|
+
resolution: 'helperExpansion.continuationUri in the tool result; also linked as a sibling artifact',
|
|
140
|
+
},
|
|
141
|
+
}),
|
|
142
|
+
};
|
|
143
|
+
const reportJson = `${canonicalJson(report)}\n`;
|
|
144
|
+
const provenance = {
|
|
145
|
+
kind: `${inventory.domain}-helper-expansion`,
|
|
146
|
+
toolVersion: PACKAGE_VERSION,
|
|
147
|
+
schemaVersion: '1',
|
|
148
|
+
sourceHashes: {},
|
|
149
|
+
metadata: { scope, sourceRevision: inventory.sourceRevision, traversal: pager.key },
|
|
150
|
+
};
|
|
151
|
+
const writes = [
|
|
152
|
+
{
|
|
153
|
+
name: `${inventory.domain}-helper-expansion.json`,
|
|
154
|
+
mimeType: 'application/json',
|
|
155
|
+
content: reportJson,
|
|
156
|
+
provenance,
|
|
157
|
+
description: 'Bounded source-linked helper path evidence and explicit traversal coverage',
|
|
158
|
+
},
|
|
159
|
+
];
|
|
160
|
+
if (!page.finished)
|
|
161
|
+
writes.push({
|
|
162
|
+
name: continuationName,
|
|
163
|
+
mimeType: 'application/json',
|
|
164
|
+
content: canonicalJson(continuationSchema.parse({
|
|
165
|
+
version: 1,
|
|
166
|
+
toolVersion: PACKAGE_VERSION,
|
|
167
|
+
domain: inventory.domain,
|
|
168
|
+
scope,
|
|
169
|
+
sourceRevision: inventory.sourceRevision,
|
|
170
|
+
query,
|
|
171
|
+
cursor: page.cursor,
|
|
172
|
+
})),
|
|
173
|
+
provenance,
|
|
174
|
+
description: 'Opaque revision- and principal-bound helper continuation; pass as helperExpansion.continuationUri',
|
|
175
|
+
});
|
|
176
|
+
const artifacts = await engine.artifacts.withAtomicChunkedWrites(workspace, writes, (stored) => Promise.resolve([...stored]), signal);
|
|
177
|
+
return {
|
|
178
|
+
report,
|
|
179
|
+
reportJson,
|
|
180
|
+
summary: {
|
|
181
|
+
...summary,
|
|
182
|
+
...(artifacts[1] === undefined ? {} : { continuationUri: artifacts[1].uri }),
|
|
183
|
+
},
|
|
184
|
+
artifacts,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { compareCodeUnits, hashCanonical } from './canonical.js';
|
|
2
|
+
import { inspectImpactGraph, } from './impact-graph.js';
|
|
3
|
+
function edgeIdentity(edge) {
|
|
4
|
+
const targetDefinition = edge.targetDefinition;
|
|
5
|
+
return hashCanonical({
|
|
6
|
+
source: edge.source,
|
|
7
|
+
target: edge.target,
|
|
8
|
+
targetDefinition: targetDefinition === undefined
|
|
9
|
+
? null
|
|
10
|
+
: {
|
|
11
|
+
path: targetDefinition.path,
|
|
12
|
+
rootKind: targetDefinition.rootKind,
|
|
13
|
+
loadOrder: targetDefinition.loadOrder,
|
|
14
|
+
},
|
|
15
|
+
referenceKind: edge.referenceKind,
|
|
16
|
+
accessRole: edge.accessRole,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
function consumers(result) {
|
|
20
|
+
return [...result.directConsumers, ...result.transitiveConsumers];
|
|
21
|
+
}
|
|
22
|
+
/** Compare typed dependency consumers under the same selection and bounds. */
|
|
23
|
+
export function compareImpactGraphs(beforeSnapshot, afterSnapshot, query) {
|
|
24
|
+
if (beforeSnapshot.workspaceId !== afterSnapshot.workspaceId)
|
|
25
|
+
throw new Error('Impact comparison crosses workspace boundaries');
|
|
26
|
+
const before = inspectImpactGraph(beforeSnapshot, query);
|
|
27
|
+
const after = inspectImpactGraph(afterSnapshot, query);
|
|
28
|
+
const beforeEdges = consumers(before);
|
|
29
|
+
const afterEdges = consumers(after);
|
|
30
|
+
const beforeKeys = new Set(beforeEdges.map(edgeIdentity));
|
|
31
|
+
const afterKeys = new Set(afterEdges.map(edgeIdentity));
|
|
32
|
+
const beforeFiles = new Set(before.affectedFiles);
|
|
33
|
+
const afterFiles = new Set(after.affectedFiles);
|
|
34
|
+
return {
|
|
35
|
+
beforeRevision: before.sourceRevision,
|
|
36
|
+
afterRevision: after.sourceRevision,
|
|
37
|
+
complete: before.complete && after.complete,
|
|
38
|
+
before,
|
|
39
|
+
after,
|
|
40
|
+
addedConsumers: afterEdges.filter((edge) => !beforeKeys.has(edgeIdentity(edge))),
|
|
41
|
+
removedConsumers: beforeEdges.filter((edge) => !afterKeys.has(edgeIdentity(edge))),
|
|
42
|
+
newlyAffectedFiles: after.affectedFiles
|
|
43
|
+
.filter((file) => !beforeFiles.has(file))
|
|
44
|
+
.sort(compareCodeUnits),
|
|
45
|
+
noLongerAffectedFiles: before.affectedFiles
|
|
46
|
+
.filter((file) => !afterFiles.has(file))
|
|
47
|
+
.sort(compareCodeUnits),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import { compareCodeUnits } from './canonical.js';
|
|
2
|
+
import { scanImpactSemanticReferences } from './impact-semantic.js';
|
|
3
|
+
function symbolKey(kind, id) {
|
|
4
|
+
return `${kind}\0${id}`;
|
|
5
|
+
}
|
|
6
|
+
function sourceKey(path, id) {
|
|
7
|
+
return `${path}\0${id}`;
|
|
8
|
+
}
|
|
9
|
+
function edgeOrder(left, right) {
|
|
10
|
+
return (compareCodeUnits(left.target.kind, right.target.kind) ||
|
|
11
|
+
compareCodeUnits(left.target.id, right.target.id) ||
|
|
12
|
+
compareCodeUnits(left.source.kind, right.source.kind) ||
|
|
13
|
+
compareCodeUnits(left.source.id, right.source.id) ||
|
|
14
|
+
compareCodeUnits(left.path, right.path) ||
|
|
15
|
+
compareCodeUnits(left.referenceKind, right.referenceKind));
|
|
16
|
+
}
|
|
17
|
+
function bounded(value, fallback, maximum) {
|
|
18
|
+
if (value === undefined)
|
|
19
|
+
return fallback;
|
|
20
|
+
if (!Number.isSafeInteger(value) || value < 1 || value > maximum)
|
|
21
|
+
throw new RangeError(`Impact limit must be an integer between 1 and ${maximum}`);
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
/** Finds every reached edge inside a strongly connected component without recursive call stacks. */
|
|
25
|
+
function reachedCycleEdges(reached, edges) {
|
|
26
|
+
const outgoing = new Map();
|
|
27
|
+
const incoming = new Map();
|
|
28
|
+
for (const edge of edges) {
|
|
29
|
+
const from = symbolKey(edge.target.kind, edge.target.id);
|
|
30
|
+
const to = symbolKey(edge.source.kind, edge.source.id);
|
|
31
|
+
if (!reached.has(from) || !reached.has(to))
|
|
32
|
+
continue;
|
|
33
|
+
const targets = outgoing.get(from) ?? [];
|
|
34
|
+
targets.push(to);
|
|
35
|
+
outgoing.set(from, targets);
|
|
36
|
+
const sources = incoming.get(to) ?? [];
|
|
37
|
+
sources.push(from);
|
|
38
|
+
incoming.set(to, sources);
|
|
39
|
+
}
|
|
40
|
+
const visited = new Set();
|
|
41
|
+
const order = [];
|
|
42
|
+
for (const root of [...reached].sort(compareCodeUnits)) {
|
|
43
|
+
if (visited.has(root))
|
|
44
|
+
continue;
|
|
45
|
+
visited.add(root);
|
|
46
|
+
const stack = [{ key: root, cursor: 0 }];
|
|
47
|
+
while (stack.length > 0) {
|
|
48
|
+
const frame = stack.at(-1);
|
|
49
|
+
const next = outgoing.get(frame.key)?.[frame.cursor];
|
|
50
|
+
if (next === undefined) {
|
|
51
|
+
order.push(frame.key);
|
|
52
|
+
stack.pop();
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
frame.cursor += 1;
|
|
56
|
+
if (!visited.has(next)) {
|
|
57
|
+
visited.add(next);
|
|
58
|
+
stack.push({ key: next, cursor: 0 });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const componentByKey = new Map();
|
|
63
|
+
const componentSizes = [];
|
|
64
|
+
for (const root of order.reverse()) {
|
|
65
|
+
if (componentByKey.has(root))
|
|
66
|
+
continue;
|
|
67
|
+
const component = componentSizes.length;
|
|
68
|
+
let size = 0;
|
|
69
|
+
const stack = [root];
|
|
70
|
+
componentByKey.set(root, component);
|
|
71
|
+
while (stack.length > 0) {
|
|
72
|
+
const key = stack.pop();
|
|
73
|
+
size += 1;
|
|
74
|
+
for (const previous of incoming.get(key) ?? []) {
|
|
75
|
+
if (componentByKey.has(previous))
|
|
76
|
+
continue;
|
|
77
|
+
componentByKey.set(previous, component);
|
|
78
|
+
stack.push(previous);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
componentSizes.push(size);
|
|
82
|
+
}
|
|
83
|
+
const cycles = [];
|
|
84
|
+
const recorded = new Set();
|
|
85
|
+
let omittedCycles = 0;
|
|
86
|
+
for (const edge of [...edges].sort(edgeOrder)) {
|
|
87
|
+
const from = symbolKey(edge.target.kind, edge.target.id);
|
|
88
|
+
const to = symbolKey(edge.source.kind, edge.source.id);
|
|
89
|
+
const component = componentByKey.get(from);
|
|
90
|
+
if (component === undefined ||
|
|
91
|
+
component !== componentByKey.get(to) ||
|
|
92
|
+
(componentSizes[component] === 1 && from !== to))
|
|
93
|
+
continue;
|
|
94
|
+
const identity = `${from}\0${to}\0${edge.source.path}`;
|
|
95
|
+
if (recorded.has(identity))
|
|
96
|
+
continue;
|
|
97
|
+
recorded.add(identity);
|
|
98
|
+
if (cycles.length < 1_000)
|
|
99
|
+
cycles.push({
|
|
100
|
+
from: edge.target,
|
|
101
|
+
to: { kind: edge.source.kind, id: edge.source.id },
|
|
102
|
+
path: edge.source.path,
|
|
103
|
+
});
|
|
104
|
+
else
|
|
105
|
+
omittedCycles += 1;
|
|
106
|
+
}
|
|
107
|
+
return { cycles, omittedCycles };
|
|
108
|
+
}
|
|
109
|
+
/** Revision-pinned reverse consumer walk over the shared typed symbol index. */
|
|
110
|
+
export function inspectImpactGraph(snapshot, query) {
|
|
111
|
+
const maxNodes = bounded(query.maxNodes, 5_000, 50_000);
|
|
112
|
+
const maxEdges = bounded(query.maxEdges, 10_000, 100_000);
|
|
113
|
+
const maxDepth = bounded(query.maxDepth, 12, 64);
|
|
114
|
+
const semantic = scanImpactSemanticReferences(snapshot);
|
|
115
|
+
const activeBySource = new Map();
|
|
116
|
+
const activeByKey = new Map();
|
|
117
|
+
const activeByPath = new Map();
|
|
118
|
+
for (const symbol of snapshot.index.symbols) {
|
|
119
|
+
if (symbol.overridden)
|
|
120
|
+
continue;
|
|
121
|
+
const source = sourceKey(symbol.path, symbol.id);
|
|
122
|
+
const sourceGroup = activeBySource.get(source) ?? [];
|
|
123
|
+
sourceGroup.push(symbol);
|
|
124
|
+
activeBySource.set(source, sourceGroup);
|
|
125
|
+
activeByKey.set(symbolKey(symbol.kind, symbol.id), symbol);
|
|
126
|
+
const pathGroup = activeByPath.get(symbol.path) ?? [];
|
|
127
|
+
pathGroup.push(symbol);
|
|
128
|
+
activeByPath.set(symbol.path, pathGroup);
|
|
129
|
+
}
|
|
130
|
+
const reverse = new Map();
|
|
131
|
+
const unresolvedByTarget = new Map();
|
|
132
|
+
const assetReferences = snapshot.index.symbols.flatMap((symbol) => symbol.kind === 'sprite' && !symbol.overridden && typeof symbol.metadata.texture === 'string'
|
|
133
|
+
? [
|
|
134
|
+
{
|
|
135
|
+
kind: 'sprite_texture',
|
|
136
|
+
from: symbol.id,
|
|
137
|
+
toKind: 'texture',
|
|
138
|
+
to: symbol.metadata.texture,
|
|
139
|
+
path: symbol.path,
|
|
140
|
+
...(symbol.location === undefined ? {} : { location: symbol.location }),
|
|
141
|
+
},
|
|
142
|
+
]
|
|
143
|
+
: []);
|
|
144
|
+
for (const reference of [
|
|
145
|
+
...snapshot.index.references,
|
|
146
|
+
...semantic.references,
|
|
147
|
+
...assetReferences,
|
|
148
|
+
]) {
|
|
149
|
+
const sources = activeBySource.get(sourceKey(reference.path, reference.from)) ?? [];
|
|
150
|
+
const targetKey = symbolKey(reference.toKind, reference.to);
|
|
151
|
+
if (sources.length !== 1) {
|
|
152
|
+
const list = unresolvedByTarget.get(targetKey) ?? [];
|
|
153
|
+
list.push({
|
|
154
|
+
reference,
|
|
155
|
+
reason: sources.length === 0
|
|
156
|
+
? 'Reference has no active enclosing symbol in the shared index'
|
|
157
|
+
: 'Reference has more than one active enclosing symbol kind',
|
|
158
|
+
});
|
|
159
|
+
unresolvedByTarget.set(targetKey, list);
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
const target = activeByKey.get(symbolKey(reference.toKind, reference.to));
|
|
163
|
+
const edge = {
|
|
164
|
+
source: {
|
|
165
|
+
kind: sources[0].kind,
|
|
166
|
+
id: sources[0].id,
|
|
167
|
+
path: sources[0].path,
|
|
168
|
+
rootKind: sources[0].rootKind,
|
|
169
|
+
loadOrder: sources[0].loadOrder,
|
|
170
|
+
},
|
|
171
|
+
target: { kind: reference.toKind, id: reference.to },
|
|
172
|
+
...(target === undefined
|
|
173
|
+
? {}
|
|
174
|
+
: {
|
|
175
|
+
targetDefinition: {
|
|
176
|
+
path: target.path,
|
|
177
|
+
rootKind: target.rootKind,
|
|
178
|
+
loadOrder: target.loadOrder,
|
|
179
|
+
...(target.location === undefined ? {} : { location: target.location }),
|
|
180
|
+
},
|
|
181
|
+
}),
|
|
182
|
+
referenceKind: reference.kind,
|
|
183
|
+
accessRole: reference.kind.endsWith('_read')
|
|
184
|
+
? 'read'
|
|
185
|
+
: reference.kind.endsWith('_write')
|
|
186
|
+
? 'write'
|
|
187
|
+
: 'reference',
|
|
188
|
+
path: reference.path,
|
|
189
|
+
...(reference.location === undefined ? {} : { location: reference.location }),
|
|
190
|
+
targetStatus: ['variable', 'flag', 'event_target'].includes(reference.toKind)
|
|
191
|
+
? 'state_key'
|
|
192
|
+
: target === undefined
|
|
193
|
+
? snapshot.index.hasSkippedSourceForKind(reference.toKind)
|
|
194
|
+
? 'partial'
|
|
195
|
+
: 'missing'
|
|
196
|
+
: 'active',
|
|
197
|
+
};
|
|
198
|
+
const list = reverse.get(targetKey) ?? [];
|
|
199
|
+
list.push(edge);
|
|
200
|
+
reverse.set(targetKey, list);
|
|
201
|
+
}
|
|
202
|
+
for (const edges of reverse.values())
|
|
203
|
+
edges.sort(edgeOrder);
|
|
204
|
+
const selectors = query.symbols ?? [];
|
|
205
|
+
const selectorKeys = new Set(selectors.map(({ kind, id }) => symbolKey(kind, id)));
|
|
206
|
+
const changedFiles = [...new Set(query.changedFiles ?? [])].sort(compareCodeUnits);
|
|
207
|
+
const seeds = new Set(selectorKeys);
|
|
208
|
+
for (const changedFile of changedFiles)
|
|
209
|
+
for (const symbol of activeByPath.get(changedFile) ?? [])
|
|
210
|
+
seeds.add(symbolKey(symbol.kind, symbol.id));
|
|
211
|
+
const definitions = snapshot.index.symbols
|
|
212
|
+
.filter((symbol) => selectorKeys.has(symbolKey(symbol.kind, symbol.id)))
|
|
213
|
+
.sort((left, right) => compareCodeUnits(left.kind, right.kind) ||
|
|
214
|
+
compareCodeUnits(left.id, right.id) ||
|
|
215
|
+
right.loadOrder - left.loadOrder ||
|
|
216
|
+
compareCodeUnits(left.path, right.path));
|
|
217
|
+
const missingSymbols = selectors.filter(({ kind, id }) => !activeByKey.has(symbolKey(kind, id)) &&
|
|
218
|
+
!(['variable', 'flag', 'event_target'].includes(kind) && reverse.has(symbolKey(kind, id))));
|
|
219
|
+
const seedKeys = [...seeds].sort(compareCodeUnits);
|
|
220
|
+
const visited = new Set(seedKeys.slice(0, maxNodes));
|
|
221
|
+
const queue = [...visited].map((key) => ({ key, depth: 0 }));
|
|
222
|
+
const directConsumers = [];
|
|
223
|
+
const transitiveConsumers = [];
|
|
224
|
+
const affectedFiles = new Set(changedFiles);
|
|
225
|
+
for (const key of seeds) {
|
|
226
|
+
const source = activeByKey.get(key);
|
|
227
|
+
if (source !== undefined)
|
|
228
|
+
affectedFiles.add(source.path);
|
|
229
|
+
}
|
|
230
|
+
const unresolved = [];
|
|
231
|
+
let reachedEdges = 0;
|
|
232
|
+
let omittedEdges = 0;
|
|
233
|
+
let omittedNodes = seedKeys.length - visited.size;
|
|
234
|
+
let stoppedAtDepth = false;
|
|
235
|
+
for (const { key, depth } of queue) {
|
|
236
|
+
unresolved.push(...(unresolvedByTarget.get(key) ?? []));
|
|
237
|
+
const consumers = reverse.get(key) ?? [];
|
|
238
|
+
if (depth >= maxDepth) {
|
|
239
|
+
if (consumers.length > 0)
|
|
240
|
+
stoppedAtDepth = true;
|
|
241
|
+
omittedEdges += consumers.length;
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
for (const edge of consumers) {
|
|
245
|
+
if (reachedEdges >= maxEdges) {
|
|
246
|
+
omittedEdges += 1;
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
reachedEdges += 1;
|
|
250
|
+
affectedFiles.add(edge.source.path);
|
|
251
|
+
if (depth === 0)
|
|
252
|
+
directConsumers.push(edge);
|
|
253
|
+
else
|
|
254
|
+
transitiveConsumers.push(edge);
|
|
255
|
+
const consumerKey = symbolKey(edge.source.kind, edge.source.id);
|
|
256
|
+
if (visited.has(consumerKey))
|
|
257
|
+
continue;
|
|
258
|
+
if (visited.size >= maxNodes) {
|
|
259
|
+
omittedNodes += 1;
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
visited.add(consumerKey);
|
|
263
|
+
queue.push({ key: consumerKey, depth: depth + 1 });
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
const { cycles, omittedCycles } = reachedCycleEdges(visited, [
|
|
267
|
+
...directConsumers,
|
|
268
|
+
...transitiveConsumers,
|
|
269
|
+
]);
|
|
270
|
+
return {
|
|
271
|
+
sourceRevision: snapshot.revision,
|
|
272
|
+
complete: snapshot.complete &&
|
|
273
|
+
semantic.complete &&
|
|
274
|
+
omittedNodes === 0 &&
|
|
275
|
+
omittedEdges === 0 &&
|
|
276
|
+
omittedCycles === 0 &&
|
|
277
|
+
!stoppedAtDepth &&
|
|
278
|
+
unresolved.length === 0 &&
|
|
279
|
+
semantic.unresolved.length === 0,
|
|
280
|
+
definitions,
|
|
281
|
+
missingSymbols,
|
|
282
|
+
directConsumers: directConsumers.sort(edgeOrder),
|
|
283
|
+
transitiveConsumers: transitiveConsumers.sort(edgeOrder),
|
|
284
|
+
affectedFiles: [...affectedFiles].sort(compareCodeUnits),
|
|
285
|
+
unresolved,
|
|
286
|
+
dynamicReferences: semantic.unresolved,
|
|
287
|
+
cycles,
|
|
288
|
+
coverage: {
|
|
289
|
+
scannedSymbols: snapshot.index.symbols.length,
|
|
290
|
+
scannedReferences: snapshot.index.references.length + semantic.references.length + assetReferences.length,
|
|
291
|
+
reachedNodes: visited.size,
|
|
292
|
+
reachedEdges,
|
|
293
|
+
omittedNodes,
|
|
294
|
+
omittedEdges,
|
|
295
|
+
omittedCycles,
|
|
296
|
+
stoppedAtDepth,
|
|
297
|
+
sourceComplete: snapshot.complete,
|
|
298
|
+
skippedSourceCount: snapshot.skippedSourceCount,
|
|
299
|
+
},
|
|
300
|
+
};
|
|
301
|
+
}
|