arkgate 4.8.23 → 4.8.25
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 +153 -0
- package/README.md +7 -5
- package/bin/ark-check-runtime.mjs +41 -91
- package/bin/ark-dashboard.mjs +56 -53
- package/bin/ark-layer-match.mjs +707 -121
- package/bin/ark-mcp-runtime.mjs +448 -169
- package/bin/ark-mcp.mjs +68 -4
- package/bin/ark-shared.mjs +42 -22
- package/bin/ark.mjs +65 -97
- package/bin/lib/adapter-contract-types.mjs +5 -0
- package/bin/lib/adapter-contract.mjs +15 -0
- package/bin/lib/adoption-stance.mjs +2 -2
- package/bin/lib/adr-path.mjs +3 -3
- package/bin/lib/agent-gates.mjs +0 -61
- package/bin/lib/agent-homes.mjs +12 -21
- package/bin/lib/agent-projection-command.mjs +9 -5
- package/bin/lib/ambient-state.mjs +9 -30
- package/bin/lib/analysis-engine.mjs +15 -11
- package/bin/lib/architecture-scan.mjs +61 -15
- package/bin/lib/ark-order-doctor.mjs +4 -29
- package/bin/lib/ark-order-facts.mjs +319 -17
- package/bin/lib/ark-order-sensors.mjs +28 -101
- package/bin/lib/ark-order-types.mjs +3 -2
- package/bin/lib/ark-run-doctor.mjs +17 -28
- package/bin/lib/ark-run-facts.mjs +461 -48
- package/bin/lib/ark-run-sensors.mjs +6 -4
- package/bin/lib/arkgate-pins.mjs +91 -0
- package/bin/lib/arkrule-file-hints.mjs +2 -47
- package/bin/lib/arkrules-contract.mjs +124 -133
- package/bin/lib/arkrules-sensors.mjs +55 -252
- package/bin/lib/check-args.mjs +85 -1
- package/bin/lib/ci-and-commands.mjs +24 -78
- package/bin/lib/ci-merge-boundary.mjs +1 -1
- package/bin/lib/class-source-scan.mjs +479 -0
- package/bin/lib/clone-detection.mjs +474 -0
- package/bin/lib/codex-home.mjs +5 -3
- package/bin/lib/config-contract-slices.mjs +739 -0
- package/bin/lib/config-contract.mjs +42 -607
- package/bin/lib/config-extras.mjs +16 -22
- package/bin/lib/config-warnings.mjs +32 -0
- package/bin/lib/contract-smells.mjs +33 -15
- package/bin/lib/core-ratchet.mjs +0 -2
- package/bin/lib/dashboard-cli.mjs +62 -0
- package/bin/lib/deep-module-coach.mjs +2 -4
- package/bin/lib/deploy-path.mjs +13 -38
- package/bin/lib/design-delta.mjs +1 -1
- package/bin/lib/design-smells.mjs +2 -2
- package/bin/lib/diagnostic-catalog.mjs +23 -9
- package/bin/lib/doctor-advisories.mjs +78 -8
- package/bin/lib/doctor-green-cite.mjs +1 -1
- package/bin/lib/doctor-human.mjs +1 -1
- package/bin/lib/doctor-next-actions.mjs +19 -4
- package/bin/lib/doctor-plan.mjs +9 -13
- package/bin/lib/duplication-io.mjs +572 -0
- package/bin/lib/effective-contract-load.mjs +282 -83
- package/bin/lib/enforcement-profiles.mjs +40 -4
- package/bin/lib/entry-points-io.mjs +424 -0
- package/bin/lib/extra-merge-teeth.mjs +36 -0
- package/bin/lib/field-install.mjs +38 -14
- package/bin/lib/first-run-help.mjs +10 -5
- package/bin/lib/flat-parent-importers.mjs +139 -0
- package/bin/lib/flat-parent-pilot.mjs +40 -100
- package/bin/lib/gate-files.mjs +58 -90
- package/bin/lib/git-change-scope.mjs +109 -0
- package/bin/lib/github-enforcement.mjs +166 -29
- package/bin/lib/golden-pattern.mjs +1 -1
- package/bin/lib/hook-templates.mjs +17 -15
- package/bin/lib/host-support-matrix.mjs +3 -1
- package/bin/lib/html-report-advisories.mjs +4 -0
- package/bin/lib/html-report-depth.mjs +2 -2
- package/bin/lib/html-report.mjs +2 -2
- package/bin/lib/import-graph-projection.mjs +112 -0
- package/bin/lib/import-resolve.mjs +13 -1
- package/bin/lib/improvement-compass-doctor.mjs +1 -24
- package/bin/lib/install-migrate.mjs +50 -73
- package/bin/lib/invariant-coverage-io.mjs +4 -3
- package/bin/lib/invariant-coverage.mjs +85 -11
- package/bin/lib/invariant-probe-cli.mjs +347 -0
- package/bin/lib/invariant-probe-io.mjs +165 -0
- package/bin/lib/invariant-probe-runner.mjs +268 -0
- package/bin/lib/invariant-probe-sites.mjs +174 -0
- package/bin/lib/invariant-probe-workspace.mjs +183 -0
- package/bin/lib/invariant-probe.mjs +461 -0
- package/bin/lib/invariant-tests-path.mjs +16 -22
- package/bin/lib/literal-path-drift.mjs +4 -2
- package/bin/lib/managed-upgrade.mjs +2 -8
- package/bin/lib/mcp-adoption.mjs +13 -6
- package/bin/lib/mcp-hook-payload.mjs +403 -63
- package/bin/lib/orphan-modules-io.mjs +458 -0
- package/bin/lib/orphan-modules.mjs +251 -0
- package/bin/lib/outside-importers.mjs +197 -0
- package/bin/lib/package-manager.mjs +60 -2
- package/bin/lib/parse-health.mjs +1 -1
- package/bin/lib/passthrough-commands.mjs +83 -0
- package/bin/lib/persistence-write-hint.mjs +106 -0
- package/bin/lib/pin-dependent-gates.mjs +117 -0
- package/bin/lib/policy-delta-io.mjs +219 -14
- package/bin/lib/post-green-path.mjs +1 -1
- package/bin/lib/prepare-change.mjs +115 -7
- package/bin/lib/prepare-write.mjs +2 -2
- package/bin/lib/presets.mjs +59 -73
- package/bin/lib/product-copy.mjs +1 -6
- package/bin/lib/project-root.mjs +31 -62
- package/bin/lib/remediation.mjs +23 -4
- package/bin/lib/resolved-candidate-facts.mjs +405 -117
- package/bin/lib/rules-inventory.mjs +22 -3
- package/bin/lib/rules-under-contract.mjs +247 -9
- package/bin/lib/scan-files.mjs +36 -4
- package/bin/lib/schema-validation.mjs +130 -0
- package/bin/lib/sensor-promote-cli.mjs +2 -11
- package/bin/lib/sensor-promote-io.mjs +33 -22
- package/bin/lib/sensor-promotion.mjs +1 -1
- package/bin/lib/skill-catalog-apply.mjs +1 -1
- package/bin/lib/skill-install.mjs +42 -38
- package/bin/lib/snippet-analysis.mjs +140 -6
- package/bin/lib/source-policy.mjs +34 -0
- package/bin/lib/start-install-recovery.mjs +72 -2
- package/bin/lib/start-preview.mjs +31 -1
- package/bin/lib/states-transitions-presence.mjs +3 -3
- package/bin/lib/status-command.mjs +35 -22
- package/bin/lib/status-manifest.mjs +50 -30
- package/bin/lib/status-transition-catalog.mjs +0 -6
- package/bin/lib/suggestions.mjs +3 -3
- package/bin/lib/team-parliament-io.mjs +76 -88
- package/bin/lib/team-parliament.mjs +21 -10
- package/bin/lib/upgrade-behavior-changes.mjs +2 -2
- package/bin/lib/upgrade-command.mjs +2 -24
- package/bin/lib/upgrade-outcome.mjs +1 -1
- package/bin/lib/upstream-report.mjs +21 -10
- package/bin/lib/violations.mjs +10 -9
- package/bin/lib/weakest-link.mjs +13 -0
- package/bin/lib/write-path-capabilities.mjs +6 -4
- package/dist/chunk-A5EIY3HI.cjs +5 -0
- package/dist/chunk-AHACEKSV.js +16 -0
- package/dist/chunk-SVSQVGFJ.js +5 -0
- package/dist/chunk-UN5EH4QV.cjs +16 -0
- package/dist/{configTypes-Bbf_MFGe.d.ts → configTypes-GPvvdleM.d.cts} +30 -4
- package/dist/configTypes-GPvvdleM.d.ts +257 -0
- package/dist/eslint/index.cjs +18 -5
- package/dist/eslint/index.d.cts +368 -0
- package/dist/eslint/index.d.ts +139 -156
- package/dist/eslint/index.js +11 -5
- package/dist/index.cjs +36 -35
- package/dist/index.d.cts +5547 -0
- package/dist/index.d.ts +3283 -198
- package/dist/index.js +36 -35
- package/dist/inspectorListen-6DQVJ4DC.js +1 -0
- package/dist/inspectorListen-KGMIQ2TF.cjs +1 -0
- package/dist/nestjs/index.cjs +1 -18
- package/dist/nestjs/index.d.cts +22 -0
- package/dist/nestjs/index.d.ts +1 -3
- package/dist/nestjs/index.js +1 -18
- package/dist/order/index.cjs +1 -1
- package/dist/order/index.d.cts +155 -0
- package/dist/order/index.d.ts +15 -3
- package/dist/order/index.js +1 -1
- package/dist/runtime/index.cjs +13 -30
- package/dist/{diagnosticCatalog-35mrwAHe.d.ts → runtime/index.d.cts} +828 -276
- package/dist/runtime/index.d.ts +2741 -133
- package/dist/runtime/index.js +13 -30
- package/dist/{types-Bdj32QwA.d.ts → types-CnauMQQL.d.cts} +607 -163
- package/dist/types-CnauMQQL.d.ts +1800 -0
- package/docs/README.md +3 -1
- package/docs/agent-guide.md +217 -16
- package/docs/ai-gates.md +174 -26
- package/docs/arkorder.md +32 -15
- package/docs/brownfield-adoption.md +5 -1
- package/docs/configuration.md +230 -31
- package/docs/develop.md +2 -1
- package/docs/diagnostics.md +212 -19
- package/docs/package-surface.md +35 -9
- package/docs/product-voice.md +7 -1
- package/package.json +69 -19
- package/schemas/ark.analysis-result.schema.json +15 -0
- package/schemas/ark.config.schema.json +77 -12
- package/schemas/ark.invariant-probe.schema.json +368 -0
- package/schemas/ark.resolved-candidate-facts.schema.json +1 -1
- package/schemas/ark.status-manifest.schema.json +22 -0
- package/server.json +3 -9
- package/templates/agent-skills/README.md +1 -1
- package/templates/agent-skills/ark-adopt/SKILL.md +5 -1
- package/templates/agent-skills/ark-contract/SKILL.md +17 -0
- package/templates/agent-skills/ark-coverage/SKILL.md +2 -0
- package/templates/agent-skills/ark-explore/SKILL.md +7 -0
- package/templates/agent-skills/ark-fix/SKILL.md +20 -0
- package/templates/agent-skills/ark-order/SKILL.md +4 -2
- package/templates/agent-skills/ark-place/SKILL.md +8 -0
- package/templates/agent-skills/ark-runtime/SKILL.md +6 -1
- package/templates/arkrules/PersistenceAdapters.json +1 -1
- package/templates/arkrules/PresentationAdapters.json +1 -1
- package/templates/skills/ark-adopt.md +5 -1
- package/templates/skills/ark-contract.md +17 -0
- package/templates/skills/ark-coverage.md +2 -0
- package/templates/skills/ark-explore.md +7 -0
- package/templates/skills/ark-fix.md +20 -0
- package/templates/skills/ark-order.md +4 -2
- package/templates/skills/ark-place.md +8 -0
- package/templates/skills/ark-runtime.md +6 -1
- package/bin/lib/ark-order-error.mjs +0 -18
- package/bin/lib/ark-order-invariants.mjs +0 -346
- package/dist/types-Ik_OakcI.d.ts +0 -183
|
@@ -36,16 +36,21 @@ import {
|
|
|
36
36
|
import { provePortProofInject } from './port-proof.mjs';
|
|
37
37
|
import { extractClassShapesFromSource } from './arkrules-sensors.mjs';
|
|
38
38
|
import {
|
|
39
|
+
arkRunEffectiveIntentPrefixes,
|
|
40
|
+
createArkRunKernelRootSpecifierMatcher,
|
|
39
41
|
extractArkRunDeclarationsFromSource,
|
|
42
|
+
reexportsArkRunKernelRoot,
|
|
40
43
|
extractArkRunKernelCallsFromSource,
|
|
41
44
|
extractArkRunManagedNewsFromSource,
|
|
42
45
|
} from './ark-run-facts.mjs';
|
|
43
46
|
import {
|
|
47
|
+
extractArkOrderBudgetLeaksFromSource,
|
|
44
48
|
extractArkOrderGenericUpdatesFromSource,
|
|
45
49
|
extractArkOrderIngestWritesXiFromSource,
|
|
46
50
|
extractArkOrderPlaneCallsFromSource,
|
|
47
51
|
extractArkOrderReleaseKeyCountsFromSource,
|
|
48
52
|
extractArkOrderXiFieldWritesFromSource,
|
|
53
|
+
extractArkOrderXiTtlKeysFromSource,
|
|
49
54
|
} from './ark-order-facts.mjs';
|
|
50
55
|
import {
|
|
51
56
|
collectGovernedFiles,
|
|
@@ -53,7 +58,7 @@ import {
|
|
|
53
58
|
normalize,
|
|
54
59
|
} from './scan-files.mjs';
|
|
55
60
|
|
|
56
|
-
|
|
61
|
+
const RESOLVED_FACTS_RESOLVER_IDENTITY = 'arkgate-typescript-resolver@1';
|
|
57
62
|
|
|
58
63
|
const IN_MEMORY_STORES = new Set([
|
|
59
64
|
'InMemoryAuditStore',
|
|
@@ -89,6 +94,17 @@ function canonicalProjectPath(value) {
|
|
|
89
94
|
return normalized;
|
|
90
95
|
}
|
|
91
96
|
|
|
97
|
+
/**
|
|
98
|
+
* `path.resolve` that reuses an equal string instead of allocating a copy, so
|
|
99
|
+
* one file's absolute/real path is one string across the resolver's maps.
|
|
100
|
+
* `sameAs` lets a real path that equals the lexical path share that instance.
|
|
101
|
+
*/
|
|
102
|
+
function resolvedPath(value, sameAs) {
|
|
103
|
+
const resolved = path.resolve(value);
|
|
104
|
+
if (sameAs !== undefined && resolved === sameAs) return sameAs;
|
|
105
|
+
return resolved === value ? value : resolved;
|
|
106
|
+
}
|
|
107
|
+
|
|
92
108
|
function isInsideRoot(root, target) {
|
|
93
109
|
const relative = path.relative(root, target);
|
|
94
110
|
return (
|
|
@@ -129,13 +145,13 @@ function readPackageName(root, observeInput) {
|
|
|
129
145
|
function rememberCanonicalAlias(aliases, real, relative, absolute) {
|
|
130
146
|
const current = aliases.get(real);
|
|
131
147
|
if (!current || relative < current.relative) {
|
|
132
|
-
aliases.set(real, { relative, absolute:
|
|
148
|
+
aliases.set(real, { relative, absolute: resolvedPath(absolute) });
|
|
133
149
|
}
|
|
134
150
|
}
|
|
135
151
|
|
|
136
152
|
function rememberDirectoryAlias(aliases, real, relative, absolute) {
|
|
137
153
|
const current = aliases.get(real) ?? new Map();
|
|
138
|
-
if (!current.has(relative)) current.set(relative, { relative, absolute:
|
|
154
|
+
if (!current.has(relative)) current.set(relative, { relative, absolute: resolvedPath(absolute) });
|
|
139
155
|
aliases.set(real, current);
|
|
140
156
|
}
|
|
141
157
|
|
|
@@ -244,10 +260,20 @@ function discoverScopedFiles(root, config, scopeRelatives, observeInput) {
|
|
|
244
260
|
);
|
|
245
261
|
}
|
|
246
262
|
|
|
247
|
-
function collectCandidateFiles(
|
|
263
|
+
function collectCandidateFiles(
|
|
264
|
+
root,
|
|
265
|
+
config,
|
|
266
|
+
changes,
|
|
267
|
+
observeInput,
|
|
268
|
+
scopeRelatives,
|
|
269
|
+
{ readContents = true } = {}
|
|
270
|
+
) {
|
|
248
271
|
const files = new Map();
|
|
249
272
|
const directoryAliases = new Map();
|
|
250
273
|
const expandedAliasDirectories = new Set();
|
|
274
|
+
// The walk already resolved each file's real path; reuse it instead of a
|
|
275
|
+
// second realpath per file.
|
|
276
|
+
const walkedRealpaths = new Map();
|
|
251
277
|
const discovered = Array.isArray(scopeRelatives)
|
|
252
278
|
? discoverScopedFiles(root, config, scopeRelatives, observeInput)
|
|
253
279
|
: (config.include ?? [])
|
|
@@ -259,13 +285,16 @@ function collectCandidateFiles(root, config, changes, observeInput, scopeRelativ
|
|
|
259
285
|
if (relative === '.') relative = '';
|
|
260
286
|
rememberDirectoryAlias(directoryAliases, real, relative, absolute);
|
|
261
287
|
},
|
|
288
|
+
onFile(absolute, real) {
|
|
289
|
+
walkedRealpaths.set(absolute, real);
|
|
290
|
+
},
|
|
262
291
|
})
|
|
263
292
|
)
|
|
264
293
|
.map((absolute) => {
|
|
265
294
|
observeResolvedInput(observeInput, absolute, 'realpath');
|
|
266
295
|
return {
|
|
267
296
|
absolute,
|
|
268
|
-
real: fs.realpathSync(absolute),
|
|
297
|
+
real: walkedRealpaths.get(absolute) ?? fs.realpathSync(absolute),
|
|
269
298
|
relative: canonicalProjectPath(normalize(path.relative(root, absolute))),
|
|
270
299
|
};
|
|
271
300
|
})
|
|
@@ -306,12 +335,14 @@ function collectCandidateFiles(root, config, changes, observeInput, scopeRelativ
|
|
|
306
335
|
}
|
|
307
336
|
}
|
|
308
337
|
for (const { absolute, real, relative } of canonicalByRealpath.values()) {
|
|
309
|
-
|
|
338
|
+
// Path-only callers (overlay canonicalization, tsconfig closure) never read
|
|
339
|
+
// source text: do not load every file's contents for them.
|
|
340
|
+
if (readContents) observeResolvedInput(observeInput, absolute, 'source');
|
|
310
341
|
files.set(relative, {
|
|
311
342
|
path: relative,
|
|
312
|
-
absolute:
|
|
313
|
-
real:
|
|
314
|
-
content: fs.readFileSync(absolute, 'utf8'),
|
|
343
|
+
absolute: resolvedPath(absolute),
|
|
344
|
+
real: resolvedPath(real, absolute),
|
|
345
|
+
...(readContents ? { content: fs.readFileSync(absolute, 'utf8') } : {}),
|
|
315
346
|
});
|
|
316
347
|
}
|
|
317
348
|
|
|
@@ -364,7 +395,9 @@ function collectCandidateFiles(root, config, changes, observeInput, scopeRelativ
|
|
|
364
395
|
/** Canonicalize a Tooling overlay without exposing filesystem identity to Kernel. */
|
|
365
396
|
export function canonicalizeCandidateChanges({ root, config, changes = [] }) {
|
|
366
397
|
const canonicalRoot = fs.realpathSync(root);
|
|
367
|
-
return collectCandidateFiles(canonicalRoot, config, changes
|
|
398
|
+
return collectCandidateFiles(canonicalRoot, config, changes, undefined, undefined, {
|
|
399
|
+
readContents: false,
|
|
400
|
+
}).changes.map((change) =>
|
|
368
401
|
change.delete === true
|
|
369
402
|
? { path: change.path, delete: true }
|
|
370
403
|
: { path: change.path, content: change.content }
|
|
@@ -390,13 +423,17 @@ function createOverlayModuleHost(ts, root, files, changes, observeInput) {
|
|
|
390
423
|
const rememberVirtualDirectories = (absolute) => {
|
|
391
424
|
let directory = path.dirname(absolute);
|
|
392
425
|
while (isInsideRoot(root, directory)) {
|
|
393
|
-
|
|
394
|
-
|
|
426
|
+
const resolved = path.resolve(directory);
|
|
427
|
+
// Every walk records the whole chain up to root, so a known directory
|
|
428
|
+
// already has all of its ancestors: stop instead of re-walking to root.
|
|
429
|
+
if (virtualDirectories.has(resolved) && resolved !== root) break;
|
|
430
|
+
virtualDirectories.add(resolved);
|
|
431
|
+
if (resolved === root) break;
|
|
395
432
|
directory = path.dirname(directory);
|
|
396
433
|
}
|
|
397
434
|
};
|
|
398
435
|
const remember = (absolute, file) => {
|
|
399
|
-
const key =
|
|
436
|
+
const key = resolvedPath(absolute);
|
|
400
437
|
byAbsolute.set(key, file);
|
|
401
438
|
pathByAbsolute.set(key, file.path);
|
|
402
439
|
rememberVirtualDirectories(key);
|
|
@@ -442,7 +479,9 @@ function createOverlayModuleHost(ts, root, files, changes, observeInput) {
|
|
|
442
479
|
observeResolvedInput(observeInput, absolute, 'module-read');
|
|
443
480
|
if (deleted.has(absolute)) return undefined;
|
|
444
481
|
const candidate = byAbsolute.get(absolute);
|
|
445
|
-
|
|
482
|
+
// Overlay candidates carry their proposed text. On-disk candidates are read
|
|
483
|
+
// lazily (their text is not held for the whole resolution).
|
|
484
|
+
if (typeof candidate?.content === 'string') return candidate.content;
|
|
446
485
|
if (sys?.readFile) return sys.readFile(fileName);
|
|
447
486
|
try {
|
|
448
487
|
return fs.readFileSync(fileName, 'utf8');
|
|
@@ -495,6 +534,13 @@ function createOverlayModuleHost(ts, root, files, changes, observeInput) {
|
|
|
495
534
|
};
|
|
496
535
|
|
|
497
536
|
return {
|
|
537
|
+
/** Drop the per-file lookup tables once module resolution is finished. */
|
|
538
|
+
dispose() {
|
|
539
|
+
byAbsolute.clear();
|
|
540
|
+
pathByAbsolute.clear();
|
|
541
|
+
deleted.clear();
|
|
542
|
+
virtualDirectories.clear();
|
|
543
|
+
},
|
|
498
544
|
fileExists,
|
|
499
545
|
readFile,
|
|
500
546
|
directoryExists,
|
|
@@ -597,7 +643,7 @@ function compilerContext(ts, root, tsconfig, candidateFiles, observeInput) {
|
|
|
597
643
|
const configByFile = new Map();
|
|
598
644
|
const nearestConfigByDirectory = new Map();
|
|
599
645
|
if (explicitPath) {
|
|
600
|
-
for (const file of candidateFiles) configByFile.set(
|
|
646
|
+
for (const file of candidateFiles) configByFile.set(resolvedPath(file.absolute), explicitPath);
|
|
601
647
|
} else if (!explicitPath) {
|
|
602
648
|
for (const file of candidateFiles) {
|
|
603
649
|
const nearest = nearestTsconfig(
|
|
@@ -606,7 +652,7 @@ function compilerContext(ts, root, tsconfig, candidateFiles, observeInput) {
|
|
|
606
652
|
nearestConfigByDirectory,
|
|
607
653
|
observeInput
|
|
608
654
|
);
|
|
609
|
-
if (nearest) configByFile.set(
|
|
655
|
+
if (nearest) configByFile.set(resolvedPath(file.absolute), nearest);
|
|
610
656
|
}
|
|
611
657
|
}
|
|
612
658
|
const configPaths = [...new Set(configByFile.values())].sort((left, right) => {
|
|
@@ -678,7 +724,7 @@ function compilerContext(ts, root, tsconfig, candidateFiles, observeInput) {
|
|
|
678
724
|
};
|
|
679
725
|
for (const configPath of configPaths) loadConfigOptions(configPath);
|
|
680
726
|
|
|
681
|
-
const
|
|
727
|
+
const configPathFor = (fileName) => {
|
|
682
728
|
const resolved = path.resolve(fileName);
|
|
683
729
|
let configPath = configByFile.get(resolved);
|
|
684
730
|
if (!configPath) {
|
|
@@ -686,6 +732,10 @@ function compilerContext(ts, root, tsconfig, candidateFiles, observeInput) {
|
|
|
686
732
|
?? nearestTsconfig(root, resolved, nearestConfigByDirectory, observeInput);
|
|
687
733
|
if (configPath) configByFile.set(resolved, configPath);
|
|
688
734
|
}
|
|
735
|
+
return configPath;
|
|
736
|
+
};
|
|
737
|
+
const optionsFor = (fileName) => {
|
|
738
|
+
const configPath = configPathFor(fileName);
|
|
689
739
|
return configPath ? loadConfigOptions(configPath) ?? {} : {};
|
|
690
740
|
};
|
|
691
741
|
const configs = configPaths.map((configPath) => ({
|
|
@@ -713,6 +763,8 @@ function compilerContext(ts, root, tsconfig, candidateFiles, observeInput) {
|
|
|
713
763
|
.sort();
|
|
714
764
|
return {
|
|
715
765
|
optionsFor,
|
|
766
|
+
/** Options are a function of the governing tsconfig path ('' = none). */
|
|
767
|
+
optionsKeyFor: (fileName) => configPathFor(fileName) ?? '',
|
|
716
768
|
reasons,
|
|
717
769
|
configInputPaths,
|
|
718
770
|
tsconfigHash: deterministicHash(stableSerialize(configClosure)),
|
|
@@ -734,7 +786,9 @@ export function resolvedCompilerInputPaths({
|
|
|
734
786
|
if (!ts?.readConfigFile || !ts?.parseJsonConfigFileContent) return [];
|
|
735
787
|
observeResolvedInput(observeInput, root, 'realpath');
|
|
736
788
|
const canonicalRoot = fs.realpathSync(root);
|
|
737
|
-
const candidate = collectCandidateFiles(canonicalRoot, config, changes, observeInput
|
|
789
|
+
const candidate = collectCandidateFiles(canonicalRoot, config, changes, observeInput, undefined, {
|
|
790
|
+
readContents: false,
|
|
791
|
+
});
|
|
738
792
|
return compilerContext(ts, canonicalRoot, tsconfig, candidate.files, observeInput)
|
|
739
793
|
.configInputPaths;
|
|
740
794
|
}
|
|
@@ -941,7 +995,8 @@ function objectLiteralHasProperty(ts, object, name) {
|
|
|
941
995
|
);
|
|
942
996
|
}
|
|
943
997
|
|
|
944
|
-
|
|
998
|
+
/** Start offsets of `@ts-ignore` directives and `@ts-nocheck` pragmas in a parsed file. */
|
|
999
|
+
export function tsSuppressionPositions(sourceFile, source) {
|
|
945
1000
|
const positions = new Set();
|
|
946
1001
|
for (const directive of sourceFile.commentDirectives ?? []) {
|
|
947
1002
|
const start = directive.range?.pos;
|
|
@@ -958,28 +1013,21 @@ function tsSuppressionPositions(sourceFile, source) {
|
|
|
958
1013
|
return [...positions];
|
|
959
1014
|
}
|
|
960
1015
|
|
|
961
|
-
|
|
962
|
-
const facts = tsSuppressionPositions(sourceFile, source).map((position) => ({
|
|
963
|
-
file: relativePath,
|
|
964
|
-
line: lineOf(sourceFile, position),
|
|
965
|
-
kind: 'ts-suppression',
|
|
966
|
-
}));
|
|
967
|
-
for (const dependency of dependencies) {
|
|
968
|
-
if (!dependency.unresolved) continue;
|
|
969
|
-
facts.push({
|
|
970
|
-
file: relativePath,
|
|
971
|
-
line: dependency.line,
|
|
972
|
-
kind: dependency.kind === 'require' ? 'dynamic-require' : 'dynamic-import',
|
|
973
|
-
});
|
|
974
|
-
}
|
|
1016
|
+
const ARKGATE_RUNTIME_SPECIFIER = /^arkgate(?:\/runtime)?$/;
|
|
975
1017
|
|
|
1018
|
+
/**
|
|
1019
|
+
* Local bindings of the arkgate kernel factories whose omitted options fall back
|
|
1020
|
+
* to in-memory stores: named imports (local name → factory) plus namespace
|
|
1021
|
+
* imports of `arkgate` / `arkgate/runtime`.
|
|
1022
|
+
*/
|
|
1023
|
+
export function arkInMemoryFactoryBindings(ts, sourceFile) {
|
|
976
1024
|
const importedFactories = new Map();
|
|
977
1025
|
const arkNamespaces = new Set();
|
|
978
1026
|
for (const statement of sourceFile.statements) {
|
|
979
1027
|
if (!ts.isImportDeclaration(statement) || !ts.isStringLiteralLike(statement.moduleSpecifier)) {
|
|
980
1028
|
continue;
|
|
981
1029
|
}
|
|
982
|
-
if (
|
|
1030
|
+
if (!ARKGATE_RUNTIME_SPECIFIER.test(statement.moduleSpecifier.text)) continue;
|
|
983
1031
|
const bindings = statement.importClause?.namedBindings;
|
|
984
1032
|
if (bindings && ts.isNamespaceImport(bindings)) arkNamespaces.add(bindings.name.text);
|
|
985
1033
|
if (!bindings || !ts.isNamedImports(bindings)) continue;
|
|
@@ -989,6 +1037,71 @@ function collectSafetyUses(ts, sourceFile, relativePath, source, dependencies) {
|
|
|
989
1037
|
if (requirements) importedFactories.set(element.name.text, { imported, requirements });
|
|
990
1038
|
}
|
|
991
1039
|
}
|
|
1040
|
+
return { importedFactories, arkNamespaces };
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
/** Named `InMemory*Store` imports of an arkgate import declaration. */
|
|
1044
|
+
export function arkInMemoryStoreImports(ts, node) {
|
|
1045
|
+
const specifier = node.moduleSpecifier;
|
|
1046
|
+
if (!ts.isStringLiteralLike(specifier) || !ARKGATE_RUNTIME_SPECIFIER.test(specifier.text)) {
|
|
1047
|
+
return [];
|
|
1048
|
+
}
|
|
1049
|
+
const elements =
|
|
1050
|
+
node.importClause?.namedBindings && ts.isNamedImports(node.importClause.namedBindings)
|
|
1051
|
+
? node.importClause.namedBindings.elements
|
|
1052
|
+
: [];
|
|
1053
|
+
const out = [];
|
|
1054
|
+
for (const element of elements) {
|
|
1055
|
+
const imported = element.propertyName?.text ?? element.name.text;
|
|
1056
|
+
if (IN_MEMORY_STORES.has(imported)) out.push({ element, imported });
|
|
1057
|
+
}
|
|
1058
|
+
return out;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
/**
|
|
1062
|
+
* The factory a call expression invokes when that call definitely takes the
|
|
1063
|
+
* in-memory defaults (no options, `undefined`, or an object literal missing a
|
|
1064
|
+
* required store); otherwise undefined.
|
|
1065
|
+
*/
|
|
1066
|
+
export function inMemoryDefaultsFactoryCall(ts, node, { importedFactories, arkNamespaces }) {
|
|
1067
|
+
let factory;
|
|
1068
|
+
if (ts.isIdentifier(node.expression)) {
|
|
1069
|
+
factory = importedFactories.get(node.expression.text);
|
|
1070
|
+
} else if (
|
|
1071
|
+
ts.isPropertyAccessExpression(node.expression) &&
|
|
1072
|
+
ts.isIdentifier(node.expression.expression) &&
|
|
1073
|
+
arkNamespaces.has(node.expression.expression.text)
|
|
1074
|
+
) {
|
|
1075
|
+
const imported = node.expression.name.text;
|
|
1076
|
+
const requirements = IN_MEMORY_DEFAULT_FACTORIES.get(imported);
|
|
1077
|
+
if (requirements) factory = { imported, requirements };
|
|
1078
|
+
}
|
|
1079
|
+
if (!factory) return undefined;
|
|
1080
|
+
const options = node.arguments[0];
|
|
1081
|
+
const definitelyDefaults =
|
|
1082
|
+
!options ||
|
|
1083
|
+
(ts.isIdentifier(options) && options.text === 'undefined') ||
|
|
1084
|
+
(ts.isObjectLiteralExpression(options) &&
|
|
1085
|
+
factory.requirements.some((name) => !objectLiteralHasProperty(ts, options, name)));
|
|
1086
|
+
return definitelyDefaults ? factory : undefined;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
function collectSafetyUses(ts, sourceFile, relativePath, source, dependencies) {
|
|
1090
|
+
const facts = tsSuppressionPositions(sourceFile, source).map((position) => ({
|
|
1091
|
+
file: relativePath,
|
|
1092
|
+
line: lineOf(sourceFile, position),
|
|
1093
|
+
kind: 'ts-suppression',
|
|
1094
|
+
}));
|
|
1095
|
+
for (const dependency of dependencies) {
|
|
1096
|
+
if (!dependency.unresolved) continue;
|
|
1097
|
+
facts.push({
|
|
1098
|
+
file: relativePath,
|
|
1099
|
+
line: dependency.line,
|
|
1100
|
+
kind: dependency.kind === 'require' ? 'dynamic-require' : 'dynamic-import',
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
const factoryBindings = arkInMemoryFactoryBindings(ts, sourceFile);
|
|
992
1105
|
|
|
993
1106
|
const visit = (node) => {
|
|
994
1107
|
if (
|
|
@@ -1003,56 +1116,25 @@ function collectSafetyUses(ts, sourceFile, relativePath, source, dependencies) {
|
|
|
1003
1116
|
}
|
|
1004
1117
|
|
|
1005
1118
|
if (ts.isImportDeclaration(node)) {
|
|
1006
|
-
const
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
: [];
|
|
1014
|
-
for (const element of elements) {
|
|
1015
|
-
const imported = element.propertyName?.text ?? element.name.text;
|
|
1016
|
-
if (IN_MEMORY_STORES.has(imported)) {
|
|
1017
|
-
facts.push({
|
|
1018
|
-
file: relativePath,
|
|
1019
|
-
line: lineOf(sourceFile, element.getStart(sourceFile)),
|
|
1020
|
-
kind: 'in-memory-store',
|
|
1021
|
-
symbol: imported,
|
|
1022
|
-
});
|
|
1023
|
-
}
|
|
1024
|
-
}
|
|
1119
|
+
for (const { element, imported } of arkInMemoryStoreImports(ts, node)) {
|
|
1120
|
+
facts.push({
|
|
1121
|
+
file: relativePath,
|
|
1122
|
+
line: lineOf(sourceFile, element.getStart(sourceFile)),
|
|
1123
|
+
kind: 'in-memory-store',
|
|
1124
|
+
symbol: imported,
|
|
1125
|
+
});
|
|
1025
1126
|
}
|
|
1026
1127
|
}
|
|
1027
1128
|
|
|
1028
1129
|
if (ts.isCallExpression(node)) {
|
|
1029
|
-
|
|
1030
|
-
if (ts.isIdentifier(node.expression)) {
|
|
1031
|
-
factory = importedFactories.get(node.expression.text);
|
|
1032
|
-
} else if (
|
|
1033
|
-
ts.isPropertyAccessExpression(node.expression) &&
|
|
1034
|
-
ts.isIdentifier(node.expression.expression) &&
|
|
1035
|
-
arkNamespaces.has(node.expression.expression.text)
|
|
1036
|
-
) {
|
|
1037
|
-
const imported = node.expression.name.text;
|
|
1038
|
-
const requirements = IN_MEMORY_DEFAULT_FACTORIES.get(imported);
|
|
1039
|
-
if (requirements) factory = { imported, requirements };
|
|
1040
|
-
}
|
|
1130
|
+
const factory = inMemoryDefaultsFactoryCall(ts, node, factoryBindings);
|
|
1041
1131
|
if (factory) {
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
if (definitelyDefaults) {
|
|
1049
|
-
facts.push({
|
|
1050
|
-
file: relativePath,
|
|
1051
|
-
line: lineOf(sourceFile, node.getStart(sourceFile)),
|
|
1052
|
-
kind: 'in-memory-store',
|
|
1053
|
-
symbol: `${factory.imported} defaults`,
|
|
1054
|
-
});
|
|
1055
|
-
}
|
|
1132
|
+
facts.push({
|
|
1133
|
+
file: relativePath,
|
|
1134
|
+
line: lineOf(sourceFile, node.getStart(sourceFile)),
|
|
1135
|
+
kind: 'in-memory-store',
|
|
1136
|
+
symbol: `${factory.imported} defaults`,
|
|
1137
|
+
});
|
|
1056
1138
|
}
|
|
1057
1139
|
}
|
|
1058
1140
|
ts.forEachChild(node, visit);
|
|
@@ -1081,6 +1163,30 @@ function unavailableFacts(config, ts, reason) {
|
|
|
1081
1163
|
});
|
|
1082
1164
|
}
|
|
1083
1165
|
|
|
1166
|
+
const SOURCE_READ_FAILURE = Symbol('ark.sourceReadFailure');
|
|
1167
|
+
|
|
1168
|
+
/**
|
|
1169
|
+
* Share per-file parse results between resolutions of one config (a base tree
|
|
1170
|
+
* and its overlay candidate). Pass the same object to both calls; it is only
|
|
1171
|
+
* used while `config` is the identical object.
|
|
1172
|
+
*/
|
|
1173
|
+
export function createResolverIngestCache(config) {
|
|
1174
|
+
return { config, records: new Map() };
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
/** Overlay text when present; otherwise read the on-disk source now (not held afterwards). */
|
|
1178
|
+
function readCandidateSource(candidate, observeInput) {
|
|
1179
|
+
if (typeof candidate.content === 'string') return candidate.content;
|
|
1180
|
+
observeResolvedInput(observeInput, candidate.absolute, 'source');
|
|
1181
|
+
try {
|
|
1182
|
+
return fs.readFileSync(candidate.absolute, 'utf8');
|
|
1183
|
+
} catch (error) {
|
|
1184
|
+
const failure = error instanceof Error ? error : new Error(String(error));
|
|
1185
|
+
failure[SOURCE_READ_FAILURE] = true;
|
|
1186
|
+
throw failure;
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1084
1190
|
function loadOneCandidateFile(root, config, relative, observeInput) {
|
|
1085
1191
|
const discovered = discoverScopedFiles(root, config, [relative], observeInput);
|
|
1086
1192
|
const found = discovered[0];
|
|
@@ -1088,8 +1194,8 @@ function loadOneCandidateFile(root, config, relative, observeInput) {
|
|
|
1088
1194
|
observeResolvedInput(observeInput, found.absolute, 'source');
|
|
1089
1195
|
return {
|
|
1090
1196
|
path: found.relative,
|
|
1091
|
-
absolute:
|
|
1092
|
-
real:
|
|
1197
|
+
absolute: resolvedPath(found.absolute),
|
|
1198
|
+
real: resolvedPath(found.real, found.absolute),
|
|
1093
1199
|
content: fs.readFileSync(found.absolute, 'utf8'),
|
|
1094
1200
|
};
|
|
1095
1201
|
}
|
|
@@ -1103,6 +1209,7 @@ export function resolveCandidateFacts({
|
|
|
1103
1209
|
changes = [],
|
|
1104
1210
|
observeInput,
|
|
1105
1211
|
scopeFiles,
|
|
1212
|
+
ingestCache,
|
|
1106
1213
|
}) {
|
|
1107
1214
|
if (!ts?.createSourceFile || !ts?.resolveModuleName) {
|
|
1108
1215
|
return unavailableFacts(config, ts, 'No API-compatible TypeScript resolver is available.');
|
|
@@ -1116,12 +1223,15 @@ export function resolveCandidateFacts({
|
|
|
1116
1223
|
try {
|
|
1117
1224
|
observeResolvedInput(observeInput, root, 'realpath');
|
|
1118
1225
|
canonicalRoot = fs.realpathSync(root);
|
|
1226
|
+
// Source text is read one file at a time in `ingest` and released after it,
|
|
1227
|
+
// instead of holding every file's text for the whole resolution.
|
|
1119
1228
|
const candidate = collectCandidateFiles(
|
|
1120
1229
|
canonicalRoot,
|
|
1121
1230
|
config,
|
|
1122
1231
|
changes,
|
|
1123
1232
|
observeInput,
|
|
1124
|
-
scoped ? scopeFiles : undefined
|
|
1233
|
+
scoped ? scopeFiles : undefined,
|
|
1234
|
+
{ readContents: false }
|
|
1125
1235
|
);
|
|
1126
1236
|
candidateFiles = candidate.files;
|
|
1127
1237
|
canonicalChanges = candidate.changes;
|
|
@@ -1141,6 +1251,28 @@ export function resolveCandidateFacts({
|
|
|
1141
1251
|
canonicalChanges,
|
|
1142
1252
|
observeInput
|
|
1143
1253
|
);
|
|
1254
|
+
// TypeScript resolves a specifier from its containing directory (its own
|
|
1255
|
+
// module cache is keyed the same way), and this run's host never changes.
|
|
1256
|
+
// Resolve each (tsconfig, directory, specifier) once instead of once per
|
|
1257
|
+
// importing file: a shared `react` or `@/lib/x` no longer re-walks every
|
|
1258
|
+
// ancestor node_modules for each file. Only the small verdict is kept.
|
|
1259
|
+
const resolutionMemo = new Map();
|
|
1260
|
+
const resolveOnce = (dependency, containingFile) => {
|
|
1261
|
+
if (!dependency.specifier) return resolveDependency(ts, dependency, containingFile, {}, host);
|
|
1262
|
+
const key = `${compiler.optionsKeyFor(containingFile)}\0${path.dirname(containingFile)}\0${dependency.specifier}`;
|
|
1263
|
+
let resolved = resolutionMemo.get(key);
|
|
1264
|
+
if (!resolved) {
|
|
1265
|
+
resolved = resolveDependency(
|
|
1266
|
+
ts,
|
|
1267
|
+
dependency,
|
|
1268
|
+
containingFile,
|
|
1269
|
+
compiler.optionsFor(containingFile),
|
|
1270
|
+
host
|
|
1271
|
+
);
|
|
1272
|
+
resolutionMemo.set(key, resolved);
|
|
1273
|
+
}
|
|
1274
|
+
return resolved;
|
|
1275
|
+
};
|
|
1144
1276
|
const forbiddenGlobals = [
|
|
1145
1277
|
...new Set([
|
|
1146
1278
|
...AMBIENT_CAPABILITY_ENTRIES,
|
|
@@ -1168,6 +1300,8 @@ export function resolveCandidateFacts({
|
|
|
1168
1300
|
const arkOrderXiFieldWrites = [];
|
|
1169
1301
|
const arkOrderIngestWritesXi = [];
|
|
1170
1302
|
const arkOrderReleaseKeyCounts = [];
|
|
1303
|
+
const arkOrderXiTtlKeys = [];
|
|
1304
|
+
const arkOrderBudgetLeaks = [];
|
|
1171
1305
|
const arkRunActive = config.arkRun != null && typeof config.arkRun === 'object';
|
|
1172
1306
|
const arkOrderActive = config.arkOrder != null && typeof config.arkOrder === 'object';
|
|
1173
1307
|
const arkRulesActive =
|
|
@@ -1178,13 +1312,21 @@ export function resolveCandidateFacts({
|
|
|
1178
1312
|
const compositionRootPatterns = arkRunActive
|
|
1179
1313
|
? [...(config.arkRun?.compositionRoots ?? [])]
|
|
1180
1314
|
: [];
|
|
1315
|
+
// Identifiers imported from a kernel root module are traced kernel receivers.
|
|
1316
|
+
const kernelRootPatterns = arkRunActive
|
|
1317
|
+
? [...(config.arkRun?.kernelRoots ?? config.arkRun?.compositionRoots ?? [])]
|
|
1318
|
+
: [];
|
|
1181
1319
|
const planeRootPatterns = arkOrderActive ? [...(config.arkOrder?.planeRoots ?? [])] : [];
|
|
1182
1320
|
|
|
1183
1321
|
const seedPathSet = new Set(candidateFiles.map((file) => file.path));
|
|
1184
|
-
const
|
|
1322
|
+
const ingestFresh = (candidate, fullExtract, knownContent) => {
|
|
1323
|
+
const content = knownContent ?? readCandidateSource(candidate, observeInput);
|
|
1324
|
+
// Keep the text only when a later pass re-reads it (ArkRun kernel calls, kernel-root
|
|
1325
|
+
// barrels and managed-new facts).
|
|
1326
|
+
if (arkRunActive && candidate.content === undefined) candidate.content = content;
|
|
1185
1327
|
const sourceFile = ts.createSourceFile(
|
|
1186
1328
|
candidate.absolute,
|
|
1187
|
-
|
|
1329
|
+
content,
|
|
1188
1330
|
ts.ScriptTarget.Latest,
|
|
1189
1331
|
true,
|
|
1190
1332
|
ts.getScriptKindFromFileName?.(candidate.absolute)
|
|
@@ -1199,10 +1341,10 @@ export function resolveCandidateFacts({
|
|
|
1199
1341
|
namedBindings: namedModuleBindings(ts, node),
|
|
1200
1342
|
}));
|
|
1201
1343
|
let portProofEligible = false;
|
|
1202
|
-
if (/\bimport\s*\{[^}]+\}\s*from\s*['"]\.\.?\//.test(
|
|
1344
|
+
if (/\bimport\s*\{[^}]+\}\s*from\s*['"]\.\.?\//.test(content)) {
|
|
1203
1345
|
try {
|
|
1204
1346
|
portProofEligible = Boolean(
|
|
1205
|
-
provePortProofInject(ts,
|
|
1347
|
+
provePortProofInject(ts, content, { filePath: candidate.absolute, sourceFile })
|
|
1206
1348
|
.eligible
|
|
1207
1349
|
);
|
|
1208
1350
|
} catch {
|
|
@@ -1219,7 +1361,7 @@ export function resolveCandidateFacts({
|
|
|
1219
1361
|
});
|
|
1220
1362
|
files.push({
|
|
1221
1363
|
path: candidate.path,
|
|
1222
|
-
contentHash: deterministicHash(
|
|
1364
|
+
contentHash: deterministicHash(content),
|
|
1223
1365
|
parseStatus: parseDiagnosticCount === 0 ? 'parsed' : 'invalid',
|
|
1224
1366
|
parseDiagnosticCount,
|
|
1225
1367
|
exportsOnlyTypes,
|
|
@@ -1260,7 +1402,7 @@ export function resolveCandidateFacts({
|
|
|
1260
1402
|
publishCalls.push(...policy.publishCalls);
|
|
1261
1403
|
intentReferences.push(...policy.intentReferences);
|
|
1262
1404
|
safetyUses.push(
|
|
1263
|
-
...collectSafetyUses(ts, sourceFile, candidate.path,
|
|
1405
|
+
...collectSafetyUses(ts, sourceFile, candidate.path, content, semanticDependencies)
|
|
1264
1406
|
);
|
|
1265
1407
|
// Class-shape extraction is text-conservative (false negatives over false positives).
|
|
1266
1408
|
// Only TS/TSX candidates; sensors consume the same shape via facts.classShapes.
|
|
@@ -1268,22 +1410,17 @@ export function resolveCandidateFacts({
|
|
|
1268
1410
|
if (/\.(tsx?|mts|cts)$/i.test(candidate.path)) {
|
|
1269
1411
|
if (arkRulesActive || arkRunActive) {
|
|
1270
1412
|
try {
|
|
1271
|
-
classShapes.push(...extractClassShapesFromSource(candidate.path,
|
|
1413
|
+
classShapes.push(...extractClassShapesFromSource(candidate.path, content));
|
|
1272
1414
|
} catch {
|
|
1273
1415
|
// Never fail the resolver for shape extraction; sensors stay silent on this file.
|
|
1274
1416
|
}
|
|
1275
1417
|
}
|
|
1276
1418
|
if (arkRunActive) {
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
...extractArkRunKernelCallsFromSource(candidate.path, candidate.content)
|
|
1280
|
-
);
|
|
1281
|
-
} catch {
|
|
1282
|
-
// Never fail the resolver for ArkRun call extraction.
|
|
1283
|
-
}
|
|
1419
|
+
// Kernel calls are extracted after every file is parsed, so imports of a
|
|
1420
|
+
// kernel root resolve through tsconfig paths and barrel re-exports.
|
|
1284
1421
|
try {
|
|
1285
1422
|
arkRunDeclarations.push(
|
|
1286
|
-
...extractArkRunDeclarationsFromSource(candidate.path,
|
|
1423
|
+
...extractArkRunDeclarationsFromSource(candidate.path, content)
|
|
1287
1424
|
);
|
|
1288
1425
|
} catch {
|
|
1289
1426
|
// Never fail the resolver for ArkRun declaration extraction.
|
|
@@ -1292,44 +1429,125 @@ export function resolveCandidateFacts({
|
|
|
1292
1429
|
if (arkOrderActive) {
|
|
1293
1430
|
try {
|
|
1294
1431
|
arkOrderPlaneCalls.push(
|
|
1295
|
-
...extractArkOrderPlaneCallsFromSource(candidate.path,
|
|
1432
|
+
...extractArkOrderPlaneCallsFromSource(candidate.path, content)
|
|
1296
1433
|
);
|
|
1297
1434
|
} catch {
|
|
1298
1435
|
// Never fail the resolver for ArkOrder factory extraction.
|
|
1299
1436
|
}
|
|
1300
1437
|
try {
|
|
1301
1438
|
arkOrderGenericUpdates.push(
|
|
1302
|
-
...extractArkOrderGenericUpdatesFromSource(candidate.path,
|
|
1439
|
+
...extractArkOrderGenericUpdatesFromSource(candidate.path, content, {
|
|
1440
|
+
planeRoots: planeRootPatterns,
|
|
1441
|
+
})
|
|
1303
1442
|
);
|
|
1304
1443
|
} catch {
|
|
1305
1444
|
// Never fail the resolver for ArkOrder generic-update extraction.
|
|
1306
1445
|
}
|
|
1307
1446
|
try {
|
|
1308
1447
|
arkOrderXiFieldWrites.push(
|
|
1309
|
-
...extractArkOrderXiFieldWritesFromSource(candidate.path,
|
|
1448
|
+
...extractArkOrderXiFieldWritesFromSource(candidate.path, content, xiKeys)
|
|
1310
1449
|
);
|
|
1311
1450
|
} catch {
|
|
1312
1451
|
// Never fail the resolver for ArkOrder xi-field-write extraction.
|
|
1313
1452
|
}
|
|
1314
1453
|
try {
|
|
1315
1454
|
arkOrderIngestWritesXi.push(
|
|
1316
|
-
...extractArkOrderIngestWritesXiFromSource(candidate.path,
|
|
1455
|
+
...extractArkOrderIngestWritesXiFromSource(candidate.path, content)
|
|
1317
1456
|
);
|
|
1318
1457
|
} catch {
|
|
1319
1458
|
// Never fail the resolver for ArkOrder ingest-writes-ξ extraction.
|
|
1320
1459
|
}
|
|
1321
1460
|
try {
|
|
1322
1461
|
arkOrderReleaseKeyCounts.push(
|
|
1323
|
-
...extractArkOrderReleaseKeyCountsFromSource(candidate.path,
|
|
1462
|
+
...extractArkOrderReleaseKeyCountsFromSource(candidate.path, content)
|
|
1324
1463
|
);
|
|
1325
1464
|
} catch {
|
|
1326
1465
|
// Never fail the resolver for ArkOrder release key-count extraction.
|
|
1327
1466
|
}
|
|
1467
|
+
try {
|
|
1468
|
+
arkOrderXiTtlKeys.push(
|
|
1469
|
+
...extractArkOrderXiTtlKeysFromSource(candidate.path, content, {
|
|
1470
|
+
planeRoots: planeRootPatterns,
|
|
1471
|
+
})
|
|
1472
|
+
);
|
|
1473
|
+
} catch {
|
|
1474
|
+
// Never fail the resolver for ArkOrder ξ-TTL extraction.
|
|
1475
|
+
}
|
|
1476
|
+
try {
|
|
1477
|
+
arkOrderBudgetLeaks.push(
|
|
1478
|
+
...extractArkOrderBudgetLeaksFromSource(candidate.path, content)
|
|
1479
|
+
);
|
|
1480
|
+
} catch {
|
|
1481
|
+
// Never fail the resolver for ArkOrder information-budget extraction.
|
|
1482
|
+
}
|
|
1328
1483
|
}
|
|
1329
1484
|
}
|
|
1330
1485
|
};
|
|
1331
1486
|
|
|
1332
|
-
|
|
1487
|
+
// Per-file facts depend only on (config, path, text). A base/candidate pair
|
|
1488
|
+
// (atomic preflight) shares one cache: every file the overlay does not touch
|
|
1489
|
+
// is parsed once, not twice. Overlay files are never cached or reused.
|
|
1490
|
+
const accumulators = {
|
|
1491
|
+
completenessReasons,
|
|
1492
|
+
files,
|
|
1493
|
+
capabilityUses,
|
|
1494
|
+
ambientUses,
|
|
1495
|
+
publishCalls,
|
|
1496
|
+
intentReferences,
|
|
1497
|
+
safetyUses,
|
|
1498
|
+
classShapes,
|
|
1499
|
+
arkRunDeclarations,
|
|
1500
|
+
arkOrderPlaneCalls,
|
|
1501
|
+
arkOrderGenericUpdates,
|
|
1502
|
+
arkOrderXiFieldWrites,
|
|
1503
|
+
arkOrderIngestWritesXi,
|
|
1504
|
+
arkOrderReleaseKeyCounts,
|
|
1505
|
+
arkOrderXiTtlKeys,
|
|
1506
|
+
arkOrderBudgetLeaks,
|
|
1507
|
+
};
|
|
1508
|
+
const accumulatorNames = Object.keys(accumulators);
|
|
1509
|
+
const reusable = ingestCache?.config === config ? ingestCache.records : undefined;
|
|
1510
|
+
const ingest = (candidate, fullExtract) => {
|
|
1511
|
+
if (!reusable || !fullExtract || typeof candidate.content === 'string') {
|
|
1512
|
+
ingestFresh(candidate, fullExtract);
|
|
1513
|
+
return;
|
|
1514
|
+
}
|
|
1515
|
+
const content = readCandidateSource(candidate, observeInput);
|
|
1516
|
+
const contentHash = deterministicHash(content);
|
|
1517
|
+
const cached = reusable.get(candidate.path);
|
|
1518
|
+
if (cached && cached.absolute === candidate.absolute && cached.contentHash === contentHash) {
|
|
1519
|
+
if (arkRunActive) candidate.content = content;
|
|
1520
|
+
parsed.set(candidate.path, { ...cached.parsed, candidate });
|
|
1521
|
+
for (const name of accumulatorNames) {
|
|
1522
|
+
const slice = cached.slices[name];
|
|
1523
|
+
if (slice) accumulators[name].push(...slice);
|
|
1524
|
+
}
|
|
1525
|
+
return;
|
|
1526
|
+
}
|
|
1527
|
+
const before = accumulatorNames.map((name) => accumulators[name].length);
|
|
1528
|
+
ingestFresh(candidate, fullExtract, content);
|
|
1529
|
+
const slices = {};
|
|
1530
|
+
accumulatorNames.forEach((name, index) => {
|
|
1531
|
+
if (accumulators[name].length > before[index]) {
|
|
1532
|
+
slices[name] = accumulators[name].slice(before[index]);
|
|
1533
|
+
}
|
|
1534
|
+
});
|
|
1535
|
+
const { candidate: _candidate, ...parsedFacts } = parsed.get(candidate.path);
|
|
1536
|
+
reusable.set(candidate.path, {
|
|
1537
|
+
absolute: candidate.absolute,
|
|
1538
|
+
contentHash,
|
|
1539
|
+
parsed: parsedFacts,
|
|
1540
|
+
slices,
|
|
1541
|
+
});
|
|
1542
|
+
};
|
|
1543
|
+
|
|
1544
|
+
try {
|
|
1545
|
+
for (const candidate of candidateFiles) ingest(candidate, true);
|
|
1546
|
+
} catch (error) {
|
|
1547
|
+
// Same outcome as the former eager read inside the discovery try-block.
|
|
1548
|
+
if (error?.[SOURCE_READ_FAILURE] !== true) throw error;
|
|
1549
|
+
return unavailableFacts(config, ts, error.message);
|
|
1550
|
+
}
|
|
1333
1551
|
|
|
1334
1552
|
if (scoped) {
|
|
1335
1553
|
const loaded = new Set(candidateFiles.map((file) => file.path));
|
|
@@ -1340,13 +1558,7 @@ export function resolveCandidateFacts({
|
|
|
1340
1558
|
const parsedFile = parsed.get(source.path);
|
|
1341
1559
|
if (!parsedFile) continue;
|
|
1342
1560
|
for (const dependency of parsedFile.dependencies) {
|
|
1343
|
-
const resolved =
|
|
1344
|
-
ts,
|
|
1345
|
-
dependency,
|
|
1346
|
-
parsedFile.candidate.absolute,
|
|
1347
|
-
compiler.optionsFor(parsedFile.candidate.absolute),
|
|
1348
|
-
host
|
|
1349
|
-
);
|
|
1561
|
+
const resolved = resolveOnce(dependency, parsedFile.candidate.absolute);
|
|
1350
1562
|
if (resolved.resolution !== 'resolved-project' || !resolved.target) continue;
|
|
1351
1563
|
if (loaded.has(resolved.target)) continue;
|
|
1352
1564
|
const extra = loadOneCandidateFile(
|
|
@@ -1369,6 +1581,27 @@ export function resolveCandidateFacts({
|
|
|
1369
1581
|
const extractCandidates = scoped
|
|
1370
1582
|
? candidateFiles.filter((file) => seedPathSet.has(file.path))
|
|
1371
1583
|
: candidateFiles;
|
|
1584
|
+
if (arkRunActive) {
|
|
1585
|
+
const rootMatcherFor = arkRunKernelRootMatchers({
|
|
1586
|
+
parsed,
|
|
1587
|
+
patterns: kernelRootPatterns,
|
|
1588
|
+
resolveTarget: (file, dependency) => resolveOnce(dependency, file.candidate.absolute),
|
|
1589
|
+
});
|
|
1590
|
+
const intentPrefixes = arkRunEffectiveIntentPrefixes(config);
|
|
1591
|
+
for (const candidate of extractCandidates) {
|
|
1592
|
+
if (!/\.(tsx?|mts|cts)$/i.test(candidate.path)) continue;
|
|
1593
|
+
try {
|
|
1594
|
+
arkRunKernelCalls.push(
|
|
1595
|
+
...extractArkRunKernelCallsFromSource(candidate.path, candidate.content, {
|
|
1596
|
+
isKernelRootSpecifier: rootMatcherFor(candidate.path),
|
|
1597
|
+
intentPrefixes,
|
|
1598
|
+
})
|
|
1599
|
+
);
|
|
1600
|
+
} catch {
|
|
1601
|
+
// Never fail the resolver for ArkRun call extraction.
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1372
1605
|
const admittedTypeNames = new Set(classShapes.map((shape) => shape.className));
|
|
1373
1606
|
if (arkRunActive) {
|
|
1374
1607
|
for (const candidate of extractCandidates) {
|
|
@@ -1428,13 +1661,7 @@ export function resolveCandidateFacts({
|
|
|
1428
1661
|
const dependencies = [];
|
|
1429
1662
|
for (const source of parsed.values()) {
|
|
1430
1663
|
for (const dependency of source.dependencies) {
|
|
1431
|
-
const resolved =
|
|
1432
|
-
ts,
|
|
1433
|
-
dependency,
|
|
1434
|
-
source.candidate.absolute,
|
|
1435
|
-
compiler.optionsFor(source.candidate.absolute),
|
|
1436
|
-
host
|
|
1437
|
-
);
|
|
1664
|
+
const resolved = resolveOnce(dependency, source.candidate.absolute);
|
|
1438
1665
|
if (resolved.resolverFailed) {
|
|
1439
1666
|
completenessReasons.push({
|
|
1440
1667
|
code: 'MODULE_RESOLUTION_FAILURE',
|
|
@@ -1486,6 +1713,11 @@ export function resolveCandidateFacts({
|
|
|
1486
1713
|
}
|
|
1487
1714
|
|
|
1488
1715
|
const projectPackageName = readPackageName(canonicalRoot, observeInput);
|
|
1716
|
+
// Canonicalization copies every fact. Release the per-file parse records and
|
|
1717
|
+
// the module host (closures keep them reachable) before that copy is built.
|
|
1718
|
+
parsed.clear();
|
|
1719
|
+
resolutionMemo.clear();
|
|
1720
|
+
host.dispose();
|
|
1489
1721
|
return createTrustedResolvedCandidateFacts({
|
|
1490
1722
|
schemaVersion: '1.2',
|
|
1491
1723
|
completeness: completenessReasons.length === 0 ? 'complete' : 'partial',
|
|
@@ -1514,5 +1746,61 @@ export function resolveCandidateFacts({
|
|
|
1514
1746
|
arkOrderXiFieldWrites,
|
|
1515
1747
|
arkOrderIngestWritesXi,
|
|
1516
1748
|
arkOrderReleaseKeyCounts,
|
|
1749
|
+
arkOrderXiTtlKeys,
|
|
1750
|
+
arkOrderBudgetLeaks,
|
|
1517
1751
|
});
|
|
1518
1752
|
}
|
|
1753
|
+
|
|
1754
|
+
/**
|
|
1755
|
+
* Per-file predicate: does an import specifier reach an ArkRun kernel root?
|
|
1756
|
+
* Uses real module resolution (tsconfig `paths`, package links) plus the
|
|
1757
|
+
* relative-glob fallback, and treats barrels that re-export a root as roots
|
|
1758
|
+
* (fixpoint), so `import { ark } from '@/main'` or a re-exporting index is traced.
|
|
1759
|
+
*/
|
|
1760
|
+
function arkRunKernelRootMatchers({ parsed, patterns, resolveTarget }) {
|
|
1761
|
+
const regexes = [];
|
|
1762
|
+
for (const pattern of patterns) {
|
|
1763
|
+
try {
|
|
1764
|
+
regexes.push(globToRegExp(pattern));
|
|
1765
|
+
} catch {
|
|
1766
|
+
// Invalid glob: the relative fallback still applies.
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
const roots = new Set([...parsed.keys()].filter((file) => regexes.some((re) => re.test(file))));
|
|
1770
|
+
const targets = new Map();
|
|
1771
|
+
if (patterns.length > 0) {
|
|
1772
|
+
for (const [file, info] of parsed) {
|
|
1773
|
+
const bySpecifier = new Map();
|
|
1774
|
+
for (const dependency of info.dependencies) {
|
|
1775
|
+
if (!dependency.specifier || (dependency.kind !== 'import' && dependency.kind !== 'export')) {
|
|
1776
|
+
continue;
|
|
1777
|
+
}
|
|
1778
|
+
if (bySpecifier.has(dependency.specifier)) continue;
|
|
1779
|
+
const resolved = resolveTarget(info, dependency);
|
|
1780
|
+
if (resolved.resolution === 'resolved-project' && resolved.target) {
|
|
1781
|
+
bySpecifier.set(dependency.specifier, resolved.target);
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
targets.set(file, bySpecifier);
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
const matcherFor = (file) => {
|
|
1788
|
+
const relative = createArkRunKernelRootSpecifierMatcher(file, patterns);
|
|
1789
|
+
const bySpecifier = targets.get(file);
|
|
1790
|
+
return (specifier) => {
|
|
1791
|
+
const target = bySpecifier?.get(specifier);
|
|
1792
|
+
return target !== undefined ? roots.has(target) : relative(specifier);
|
|
1793
|
+
};
|
|
1794
|
+
};
|
|
1795
|
+
for (let changed = patterns.length > 0; changed; ) {
|
|
1796
|
+
changed = false;
|
|
1797
|
+
for (const [file, info] of parsed) {
|
|
1798
|
+
if (roots.has(file)) continue;
|
|
1799
|
+
if (reexportsArkRunKernelRoot(info.candidate.content, matcherFor(file))) {
|
|
1800
|
+
roots.add(file);
|
|
1801
|
+
changed = true;
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
return matcherFor;
|
|
1806
|
+
}
|