opencode-agent-skill 15.1.0 → 15.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/docs/V15-MANAGED-RUNTIME.md +18 -0
- package/global-config/agents/verifier.md +6 -2
- package/lib/affected-tests.mjs +28 -7
- package/lib/capability-fabric.mjs +107 -48
- package/lib/code-intelligence/index.mjs +50 -24
- package/lib/code-intelligence/lsp-pool.mjs +831 -0
- package/lib/code-intelligence/lsp-provider.mjs +197 -20
- package/lib/compaction-resume-guard.mjs +76 -4
- package/lib/completion-auditor.mjs +20 -0
- package/lib/context-manifest.mjs +88 -36
- package/lib/decision-policy.mjs +40 -2
- package/lib/evidence-store.mjs +121 -31
- package/lib/executable-probe.mjs +69 -0
- package/lib/execution-contract.mjs +2 -2
- package/lib/hierarchical-context.mjs +31 -3
- package/lib/learning-engine.mjs +164 -65
- package/lib/memory-engine.mjs +177 -95
- package/lib/model-config.mjs +86 -7
- package/lib/performance-fabric.mjs +92 -0
- package/lib/permission-policy.mjs +232 -0
- package/lib/pi-rpc-pool.mjs +220 -24
- package/lib/provider-recovery.mjs +51 -0
- package/lib/repo-graph.mjs +40 -7
- package/lib/repo-inspect.mjs +3 -2
- package/lib/runtime-events.mjs +31 -10
- package/lib/semantic-index.mjs +69 -21
- package/lib/skill-compiler.mjs +84 -18
- package/lib/task-engine.mjs +28 -9
- package/lib/task-policy.mjs +80 -7
- package/lib/trajectory.mjs +25 -1
- package/lib/untrusted-output.mjs +106 -0
- package/lib/workspace-fingerprint.mjs +17 -9
- package/lib/workspace-hygiene.mjs +2 -2
- package/lib/worktree-sandbox.mjs +14 -8
- package/package.json +8 -6
- package/pi/extensions/ues-child-runtime.ts +116 -5
- package/pi/extensions/ues.ts +780 -81
- package/scripts/benchmark-lsp-pool.mjs +128 -0
- package/scripts/check-release-consistency.mjs +16 -4
- package/scripts/check-source-integrity.mjs +118 -6
- package/scripts/run-test-suite.mjs +143 -0
- package/scripts/smoke-package-closure.mjs +4 -0
package/lib/decision-policy.mjs
CHANGED
|
@@ -2,6 +2,21 @@ const HIGH_RISK = /(npm publish|publish package|deploy|production|git push|force
|
|
|
2
2
|
const MEDIUM_RISK = /(migration|schema|dependency upgrade|lockfile|generated code|shared config|auth|permission|security)/i
|
|
3
3
|
const REVERSIBLE = /(local|test|rename|format|refactor|temporary|fixture|internal|reversible)/i
|
|
4
4
|
|
|
5
|
+
function confidenceForDecision(input = {}) {
|
|
6
|
+
if (input.requiresUser === true && (input.destructive === true || input.externalSideEffect === true || input.explicitlyIrreversible === true)) return 0.99
|
|
7
|
+
if (input.reversible === true && input.risk === "low") return 0.96
|
|
8
|
+
if (input.risk === "medium") return 0.82
|
|
9
|
+
if (input.requiresUser === true) return 0.90
|
|
10
|
+
return 0.74
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function confidenceBand(value) {
|
|
14
|
+
const score = Number(value || 0)
|
|
15
|
+
if (score >= 0.90) return "high"
|
|
16
|
+
if (score >= 0.72) return "medium"
|
|
17
|
+
return "low"
|
|
18
|
+
}
|
|
19
|
+
|
|
5
20
|
export function classifyDecisionPolicy(text = "", facts = {}) {
|
|
6
21
|
const value = String(text || "").trim()
|
|
7
22
|
const explicitlyIrreversible = facts.irreversible === true
|
|
@@ -11,10 +26,33 @@ export function classifyDecisionPolicy(text = "", facts = {}) {
|
|
|
11
26
|
const reversible = facts.reversible === true || (!destructive && REVERSIBLE.test(value))
|
|
12
27
|
const risk = destructive || explicitlyIrreversible || externalSideEffect ? "high" : medium ? "medium" : "low"
|
|
13
28
|
const requiresUser = risk === "high" || facts.productDecision === true
|
|
29
|
+
const autoResolvable = !requiresUser && (reversible || risk === "low")
|
|
30
|
+
const confidence = confidenceForDecision({
|
|
31
|
+
requiresUser,
|
|
32
|
+
destructive,
|
|
33
|
+
externalSideEffect,
|
|
34
|
+
explicitlyIrreversible,
|
|
35
|
+
reversible,
|
|
36
|
+
risk,
|
|
37
|
+
})
|
|
14
38
|
return {
|
|
15
|
-
schemaVersion: 1,
|
|
16
|
-
|
|
39
|
+
schemaVersion: 1,
|
|
40
|
+
risk,
|
|
41
|
+
reversible,
|
|
42
|
+
destructive,
|
|
43
|
+
externalSideEffect,
|
|
44
|
+
requiresUser,
|
|
45
|
+
autoResolvable,
|
|
17
46
|
reason: requiresUser ? "human-approval-required" : reversible ? "reversible-local-decision" : "bounded-engineering-decision",
|
|
47
|
+
decision: {
|
|
48
|
+
schemaVersion: 1,
|
|
49
|
+
kind: String(facts.decisionKind || "engineering-choice"),
|
|
50
|
+
source: "deterministic",
|
|
51
|
+
value: requiresUser ? "escalate-to-user" : autoResolvable ? "auto-resolve" : "bounded-review",
|
|
52
|
+
confidence,
|
|
53
|
+
confidenceBand: confidenceBand(confidence),
|
|
54
|
+
crossCheckRecommended: confidence < 0.72 || risk === "medium",
|
|
55
|
+
},
|
|
18
56
|
}
|
|
19
57
|
}
|
|
20
58
|
|
package/lib/evidence-store.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto"
|
|
2
2
|
import { existsSync } from "node:fs"
|
|
3
|
-
import { mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises"
|
|
3
|
+
import { mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises"
|
|
4
4
|
import path from "node:path"
|
|
5
5
|
|
|
6
6
|
const STORE_VERSION = 1
|
|
@@ -12,6 +12,82 @@ const AUTO_GC_EVERY_WRITES = 8
|
|
|
12
12
|
const AUTO_GC_LARGE_ITEM_BYTES = 16 * 1024 * 1024
|
|
13
13
|
const PUT_COUNTS = new Map()
|
|
14
14
|
|
|
15
|
+
const STORE_MUTATION_TAILS = new Map()
|
|
16
|
+
const STORE_LOCK_STALE_MS = 30_000
|
|
17
|
+
const STORE_LOCK_WAIT_MS = 40_000
|
|
18
|
+
|
|
19
|
+
function sleep(ms) {
|
|
20
|
+
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function storeLockPath(root) {
|
|
24
|
+
return path.join(evidenceStoreRoot(root), ".mutation.lock")
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function withCrossProcessStoreLock(root, fn) {
|
|
28
|
+
const lockDir = storeLockPath(root)
|
|
29
|
+
await mkdir(path.dirname(lockDir), { recursive: true })
|
|
30
|
+
const deadline = Date.now() + STORE_LOCK_WAIT_MS
|
|
31
|
+
let delayMs = 8
|
|
32
|
+
while (true) {
|
|
33
|
+
try {
|
|
34
|
+
await mkdir(lockDir)
|
|
35
|
+
break
|
|
36
|
+
} catch (error) {
|
|
37
|
+
if (error?.code !== "EEXIST") throw error
|
|
38
|
+
const info = await stat(lockDir).catch(() => null)
|
|
39
|
+
if (info && Date.now() - info.mtimeMs > STORE_LOCK_STALE_MS) {
|
|
40
|
+
const confirmed = await stat(lockDir).catch(() => null)
|
|
41
|
+
if (
|
|
42
|
+
confirmed &&
|
|
43
|
+
confirmed.ino === info.ino &&
|
|
44
|
+
confirmed.mtimeMs === info.mtimeMs
|
|
45
|
+
) {
|
|
46
|
+
await rm(lockDir, { recursive: true, force: true }).catch(() => {})
|
|
47
|
+
continue
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (Date.now() >= deadline) throw new Error("Timed out waiting for evidence-store mutation lock")
|
|
51
|
+
await sleep(delayMs)
|
|
52
|
+
delayMs = Math.min(100, delayMs * 2)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
return await fn()
|
|
57
|
+
} finally {
|
|
58
|
+
await rm(lockDir, { recursive: true, force: true }).catch(() => {})
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function withStoreMutationLock(root, fn) {
|
|
63
|
+
const key = path.resolve(root)
|
|
64
|
+
const previous = STORE_MUTATION_TAILS.get(key) || Promise.resolve()
|
|
65
|
+
let release
|
|
66
|
+
const barrier = new Promise((resolve) => { release = resolve })
|
|
67
|
+
const tail = previous.catch(() => {}).then(() => barrier)
|
|
68
|
+
STORE_MUTATION_TAILS.set(key, tail)
|
|
69
|
+
|
|
70
|
+
await previous.catch(() => {})
|
|
71
|
+
try {
|
|
72
|
+
return await withCrossProcessStoreLock(root, fn)
|
|
73
|
+
} finally {
|
|
74
|
+
release()
|
|
75
|
+
if (STORE_MUTATION_TAILS.get(key) === tail) STORE_MUTATION_TAILS.delete(key)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function atomicWrite(file, value, encoding) {
|
|
80
|
+
await mkdir(path.dirname(file), { recursive: true })
|
|
81
|
+
const temp = file + "." + process.pid + "." + Date.now() + ".tmp"
|
|
82
|
+
await writeFile(temp, value, encoding)
|
|
83
|
+
try {
|
|
84
|
+
await rename(temp, file)
|
|
85
|
+
} catch (error) {
|
|
86
|
+
await rm(temp, { force: true }).catch(() => {})
|
|
87
|
+
throw error
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
15
91
|
function boundedNumber(value, fallback, min, max) {
|
|
16
92
|
const parsed = Number(value)
|
|
17
93
|
if (!Number.isFinite(parsed)) return fallback
|
|
@@ -104,42 +180,50 @@ export async function putEvidence(root, value, options = {}) {
|
|
|
104
180
|
const encoded = toBytes(value, options)
|
|
105
181
|
const hash = createHash("sha256").update(encoded.bytes).digest("hex")
|
|
106
182
|
const target = evidencePaths(root, hash)
|
|
107
|
-
await mkdir(target.dir, { recursive: true })
|
|
108
183
|
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
184
|
+
const mutation = await withStoreMutationLock(root, async () => {
|
|
185
|
+
await mkdir(target.dir, { recursive: true })
|
|
186
|
+
const now = new Date().toISOString()
|
|
187
|
+
const existing = existsSync(target.meta)
|
|
188
|
+
? JSON.parse(await readFile(target.meta, "utf8").catch(() => "{}"))
|
|
189
|
+
: null
|
|
113
190
|
|
|
114
|
-
|
|
191
|
+
if (!existsSync(target.data)) {
|
|
192
|
+
await atomicWrite(target.data, encoded.bytes)
|
|
193
|
+
}
|
|
115
194
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
195
|
+
const preview = encoded.encoding === "utf8"
|
|
196
|
+
? encoded.bytes.toString("utf8", 0, Math.min(encoded.bytes.length, 600))
|
|
197
|
+
: null
|
|
119
198
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
199
|
+
const metadata = {
|
|
200
|
+
schemaVersion: STORE_VERSION,
|
|
201
|
+
ref: evidenceReference(hash),
|
|
202
|
+
sha256: hash,
|
|
203
|
+
bytes: encoded.bytes.length,
|
|
204
|
+
encoding: encoded.encoding,
|
|
205
|
+
mediaType: encoded.mediaType,
|
|
206
|
+
kind: options.kind || existing?.kind || "tool-output",
|
|
207
|
+
source: options.source || existing?.source || null,
|
|
208
|
+
summary: options.summary || existing?.summary || null,
|
|
209
|
+
createdAt: existing?.createdAt || now,
|
|
210
|
+
lastSeenAt: now,
|
|
211
|
+
preview,
|
|
212
|
+
}
|
|
213
|
+
await atomicWrite(target.meta, JSON.stringify(metadata, null, 2) + "\n", "utf8")
|
|
135
214
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
215
|
+
const writes = (PUT_COUNTS.get(root) || 0) + 1
|
|
216
|
+
PUT_COUNTS.set(root, writes)
|
|
217
|
+
return {
|
|
218
|
+
metadata,
|
|
219
|
+
shouldGc: writes % AUTO_GC_EVERY_WRITES === 0 || encoded.bytes.length >= AUTO_GC_LARGE_ITEM_BYTES,
|
|
220
|
+
}
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
if (mutation.shouldGc) {
|
|
139
224
|
await gcEvidenceStore(root, defaultStoreLimits()).catch(() => null)
|
|
140
225
|
}
|
|
141
|
-
|
|
142
|
-
return metadata
|
|
226
|
+
return mutation.metadata
|
|
143
227
|
}
|
|
144
228
|
|
|
145
229
|
export async function getEvidence(root, ref, options = {}) {
|
|
@@ -275,7 +359,7 @@ export async function evidenceStoreStatus(root = process.cwd()) {
|
|
|
275
359
|
}
|
|
276
360
|
}
|
|
277
361
|
|
|
278
|
-
|
|
362
|
+
async function gcEvidenceStoreUnlocked(root = process.cwd(), options = {}) {
|
|
279
363
|
root = path.resolve(root)
|
|
280
364
|
const files = await metadataFiles(root)
|
|
281
365
|
const defaults = defaultStoreLimits()
|
|
@@ -332,6 +416,12 @@ export async function gcEvidenceStore(root = process.cwd(), options = {}) {
|
|
|
332
416
|
}
|
|
333
417
|
}
|
|
334
418
|
|
|
419
|
+
|
|
420
|
+
export async function gcEvidenceStore(root = process.cwd(), options = {}) {
|
|
421
|
+
root = path.resolve(root)
|
|
422
|
+
return withStoreMutationLock(root, () => gcEvidenceStoreUnlocked(root, options))
|
|
423
|
+
}
|
|
424
|
+
|
|
335
425
|
export async function evidenceExists(root, ref) {
|
|
336
426
|
try {
|
|
337
427
|
const target = evidencePaths(root, normalizeHash(ref))
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { accessSync, constants as fsConstants } from "node:fs"
|
|
2
|
+
import path from "node:path"
|
|
3
|
+
|
|
4
|
+
const DEFAULT_TTL_MS = 60_000
|
|
5
|
+
const commandProbeCache = new Map()
|
|
6
|
+
|
|
7
|
+
function executableFile(target) {
|
|
8
|
+
try {
|
|
9
|
+
accessSync(target, process.platform === "win32" ? fsConstants.F_OK : fsConstants.X_OK)
|
|
10
|
+
return true
|
|
11
|
+
} catch {
|
|
12
|
+
return false
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function probeKey(name) {
|
|
17
|
+
return [
|
|
18
|
+
name,
|
|
19
|
+
String(process.env.PATH || ""),
|
|
20
|
+
process.platform === "win32" ? String(process.env.PATHEXT || ".COM;.EXE;.BAT;.CMD") : "",
|
|
21
|
+
].join("\u0000")
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function commandExists(command, options = {}) {
|
|
25
|
+
if (!command) return false
|
|
26
|
+
const name = String(command).trim()
|
|
27
|
+
if (!name) return false
|
|
28
|
+
|
|
29
|
+
const ttlMs = Math.max(0, Number(options.ttlMs ?? DEFAULT_TTL_MS))
|
|
30
|
+
const key = probeKey(name)
|
|
31
|
+
const now = Date.now()
|
|
32
|
+
const cached = commandProbeCache.get(key)
|
|
33
|
+
if (cached && now - cached.checkedAt < ttlMs) return cached.available
|
|
34
|
+
|
|
35
|
+
const hasPath = path.isAbsolute(name) || name.includes("/") || name.includes("\\")
|
|
36
|
+
let available = false
|
|
37
|
+
if (hasPath) {
|
|
38
|
+
available = executableFile(path.resolve(name))
|
|
39
|
+
} else {
|
|
40
|
+
const dirs = String(process.env.PATH || "")
|
|
41
|
+
.split(path.delimiter)
|
|
42
|
+
.map((item) => item.trim().replace(/^"|"$/g, ""))
|
|
43
|
+
.filter(Boolean)
|
|
44
|
+
const extension = path.extname(name)
|
|
45
|
+
const suffixes = process.platform === "win32" && !extension
|
|
46
|
+
? String(process.env.PATHEXT || ".COM;.EXE;.BAT;.CMD")
|
|
47
|
+
.split(";")
|
|
48
|
+
.map((item) => item.trim())
|
|
49
|
+
.filter(Boolean)
|
|
50
|
+
: [""]
|
|
51
|
+
|
|
52
|
+
outer:
|
|
53
|
+
for (const dir of dirs) {
|
|
54
|
+
for (const suffix of suffixes) {
|
|
55
|
+
if (executableFile(path.join(dir, name + suffix))) {
|
|
56
|
+
available = true
|
|
57
|
+
break outer
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
commandProbeCache.set(key, { available, checkedAt: now })
|
|
64
|
+
return available
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function clearExecutableProbeCache() {
|
|
68
|
+
commandProbeCache.clear()
|
|
69
|
+
}
|
|
@@ -2,7 +2,7 @@ import { createHash } from "node:crypto"
|
|
|
2
2
|
import { spawnSync } from "node:child_process"
|
|
3
3
|
import { lstatSync, readFileSync, readlinkSync } from "node:fs"
|
|
4
4
|
import path from "node:path"
|
|
5
|
-
import { sourceFacingPaths } from "./runtime-artifacts.mjs"
|
|
5
|
+
import { sourceFacingPaths, sourceGitPathspecs } from "./runtime-artifacts.mjs"
|
|
6
6
|
|
|
7
7
|
const PHASE_HEADING = /^\s*(?:=+\s*)?PHASE\s+(\d+)\s*(?:[—–:-]+)\s*(.+?)\s*$/i
|
|
8
8
|
const LOCAL_ENV_BASENAME = /^\.env(?:\.[^/\\]+)?$/i
|
|
@@ -187,7 +187,7 @@ export function captureInheritedDirtyState(root, options = {}) {
|
|
|
187
187
|
) {
|
|
188
188
|
statusOutput = workspaceState.statusOutput
|
|
189
189
|
} else {
|
|
190
|
-
const probe = spawnSync("git", ["status", "--porcelain=v1", "-z", "--untracked-files=all"], {
|
|
190
|
+
const probe = spawnSync("git", ["status", "--porcelain=v1", "-z", "--untracked-files=all", "--", ...sourceGitPathspecs()], {
|
|
191
191
|
cwd: resolved,
|
|
192
192
|
encoding: "utf8",
|
|
193
193
|
maxBuffer: 16 * 1024 * 1024,
|
|
@@ -3,6 +3,7 @@ import { buildSemanticIndex } from "./semantic-index.mjs"
|
|
|
3
3
|
|
|
4
4
|
const DEFAULT_L0 = 256
|
|
5
5
|
const DEFAULT_L1 = 4000
|
|
6
|
+
let HIERARCHY_RUNTIME_CACHE = new WeakMap()
|
|
6
7
|
const STOP = new Set([
|
|
7
8
|
"this","that","with","from","into","then","than","when","where","what","your","have","will",
|
|
8
9
|
"task","code","file","files","fix","update","change","changes","được","các","cho","với","trong","này","một","những","không","theo",
|
|
@@ -84,6 +85,20 @@ export async function buildContextHierarchy(root = process.cwd(), options = {})
|
|
|
84
85
|
maxFiles: options.maxFiles ?? 6000,
|
|
85
86
|
maxDepth: options.maxDepth ?? 14,
|
|
86
87
|
})
|
|
88
|
+
const l0Limit = Math.max(96, Number(options.l0Chars || DEFAULT_L0))
|
|
89
|
+
const l1Limit = Math.max(512, Number(options.l1Chars || DEFAULT_L1))
|
|
90
|
+
const indexObject = built?.index && typeof built.index === "object" ? built.index : null
|
|
91
|
+
const cacheKey = [root, l0Limit, l1Limit].join("\u0000")
|
|
92
|
+
const cachedBucket = indexObject ? HIERARCHY_RUNTIME_CACHE.get(indexObject) : null
|
|
93
|
+
const cached = cachedBucket?.get(cacheKey)
|
|
94
|
+
if (cached) {
|
|
95
|
+
return {
|
|
96
|
+
...cached,
|
|
97
|
+
stats: built.stats,
|
|
98
|
+
runtimeCacheHit: true,
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
87
102
|
const nodes = new Map()
|
|
88
103
|
const children = new Map()
|
|
89
104
|
const ensure = (dir) => {
|
|
@@ -122,19 +137,27 @@ export async function buildContextHierarchy(root = process.cwd(), options = {})
|
|
|
122
137
|
.map((node) => buildNodeSummary(node, children, options))
|
|
123
138
|
.sort((a, b) => a.path.localeCompare(b.path))
|
|
124
139
|
|
|
125
|
-
|
|
140
|
+
const result = {
|
|
126
141
|
schemaVersion: 1,
|
|
127
142
|
kind: "ues-context-hierarchy",
|
|
128
143
|
root,
|
|
129
144
|
generatedAt: new Date().toISOString(),
|
|
130
145
|
levels: {
|
|
131
|
-
L0: { maxChars:
|
|
132
|
-
L1: { maxChars:
|
|
146
|
+
L0: { maxChars: l0Limit, purpose: "routing abstract" },
|
|
147
|
+
L1: { maxChars: l1Limit, purpose: "scope overview" },
|
|
133
148
|
L2: { purpose: "source excerpts loaded on demand by the context manifest" },
|
|
134
149
|
},
|
|
135
150
|
nodes: summaries,
|
|
136
151
|
stats: built.stats,
|
|
152
|
+
runtimeCacheHit: false,
|
|
137
153
|
}
|
|
154
|
+
|
|
155
|
+
if (indexObject) {
|
|
156
|
+
const bucket = cachedBucket || new Map()
|
|
157
|
+
bucket.set(cacheKey, result)
|
|
158
|
+
if (!cachedBucket) HIERARCHY_RUNTIME_CACHE.set(indexObject, bucket)
|
|
159
|
+
}
|
|
160
|
+
return result
|
|
138
161
|
}
|
|
139
162
|
|
|
140
163
|
function containsFile(scope, file) {
|
|
@@ -211,5 +234,10 @@ export async function queryContextHierarchy(root, query, options = {}) {
|
|
|
211
234
|
levels: hierarchy.levels,
|
|
212
235
|
scopes,
|
|
213
236
|
stats: hierarchy.stats,
|
|
237
|
+
runtimeCacheHit: hierarchy.runtimeCacheHit === true,
|
|
214
238
|
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export function clearContextHierarchyRuntimeCache() {
|
|
242
|
+
HIERARCHY_RUNTIME_CACHE = new WeakMap()
|
|
215
243
|
}
|