opencode-agent-skill 15.1.0 → 15.1.1
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 +3 -3
- package/docs/V15-MANAGED-RUNTIME.md +18 -0
- package/global-config/agents/verifier.md +6 -2
- package/lib/affected-tests.mjs +21 -4
- package/lib/capability-fabric.mjs +107 -48
- package/lib/code-intelligence/index.mjs +3 -15
- package/lib/code-intelligence/lsp-pool.mjs +694 -0
- package/lib/code-intelligence/lsp-provider.mjs +160 -20
- package/lib/compaction-resume-guard.mjs +76 -4
- package/lib/completion-auditor.mjs +20 -0
- package/lib/context-manifest.mjs +38 -21
- package/lib/decision-policy.mjs +40 -2
- package/lib/evidence-store.mjs +121 -31
- package/lib/executable-probe.mjs +69 -0
- package/lib/hierarchical-context.mjs +31 -3
- package/lib/learning-engine.mjs +164 -65
- package/lib/memory-engine.mjs +163 -86
- 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 +170 -5
- package/lib/provider-recovery.mjs +51 -0
- package/lib/repo-graph.mjs +38 -5
- package/lib/runtime-events.mjs +31 -10
- package/lib/semantic-index.mjs +67 -20
- package/lib/skill-compiler.mjs +84 -18
- package/lib/task-policy.mjs +80 -7
- package/lib/trajectory.mjs +25 -1
- package/lib/untrusted-output.mjs +106 -0
- package/package.json +8 -6
- package/pi/extensions/ues-child-runtime.ts +78 -3
- package/pi/extensions/ues.ts +695 -81
- package/scripts/benchmark-lsp-pool.mjs +128 -0
- package/scripts/check-release-consistency.mjs +16 -4
- package/scripts/check-source-integrity.mjs +60 -1
- package/scripts/run-test-suite.mjs +143 -0
- package/scripts/smoke-package-closure.mjs +4 -0
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
**Package:** <code>opencode-agent-skill</code>
|
|
6
6
|
**Host chính:** Pi Agent
|
|
7
|
-
**Phiên bản package hiện tại:** <code>15.1.
|
|
7
|
+
**Phiên bản package hiện tại:** <code>15.1.1</code>
|
|
8
8
|
**Nhánh phát triển hiện tại:** V15.19 Finalization Hardening
|
|
9
9
|
**Stable npm hiện tại:** <code>15.1.0</code>
|
|
10
10
|
**Runtime:** Node.js 22.19+
|
|
@@ -1431,7 +1431,7 @@ npm pack
|
|
|
1431
1431
|
Package version hiện tại trên nhánh <code>main</code> là:
|
|
1432
1432
|
|
|
1433
1433
|
~~~text
|
|
1434
|
-
15.1.
|
|
1434
|
+
15.1.1
|
|
1435
1435
|
~~~
|
|
1436
1436
|
|
|
1437
1437
|
Stable npm public hiện vẫn là <code>15.1.0</code>. V15.3 DEEP Speed giảm exploration trùng lặp ở long-horizon bằng diagnosis deduplication và bounded specialist exploration, nhưng giữ nguyên plan/verifier/integration gates. V15.4 ACP-safe child runtime ngăn UES spawn lại ACP/Zed entrypoint; child specialist luôn được chạy bằng Pi CLI thật. V15.5 Per-Leaf Turbo cho phép leaf task nhỏ trong DEEP plan tự xuống FAST, giữ failure delta task-local, và tái sử dụng context theo root namespace + workspace fingerprint để giảm retry/rebuild latency. V15.6 Fast Planning đặt soft-steer/hard-idle budget riêng cho architect/plan-checker, recovery từ warm context thay vì scan lại, và lease/cleanup sandbox orphan an toàn sau crash/Stop. V15.7 Lightweight Sandbox Cleanup thu hồi worktree theo trace ngay khi /ues-run kết thúc, dọn metadata mồ côi, giảm legacy grace xuống 30 phút và cung cấp /ues-clean để dọn stale artifacts an toàn mà không đụng sandbox đang active. V15.8 Plan Gate Recovery bổ sung soft-steer + bounded recovery cho plan-checker và quét cả detached physical sandbox folders mà Git worktree registry đã quên. V15.9 Runtime Artifact Isolation loại .ues-traces/.ues-cache/.ues-services/.ues-work và runtime state khác khỏi task delta, write-scope conflict và sandbox integration, nhưng vẫn giữ safety gate cho source/config thật như apps/mobile/package.json. V15.10 Adaptive Stability Runtime thay hard-timeout tuyệt đối bằng activity-aware bounded deadlines, giữ absolute cap chống treo, salvage plan JSON đã validate từ partial output, phát graph trước prose, và role-bound context cho read-only planner trong task high-risk mà không giảm evidence budget của executor/verifier. V15.1 bắt đầu bằng deterministic <code>/ues-run</code> admission và Managed Background Services để model yếu không thể bỏ qua controller và không bị treo bởi dev server foreground.
|
|
@@ -1439,7 +1439,7 @@ Stable npm public hiện vẫn là <code>15.1.0</code>. V15.3 DEEP Speed giảm
|
|
|
1439
1439
|
Test file packed với Pi:
|
|
1440
1440
|
|
|
1441
1441
|
~~~cmd
|
|
1442
|
-
pi install .\opencode-agent-skill-15.1.
|
|
1442
|
+
pi install .\opencode-agent-skill-15.1.1.tgz
|
|
1443
1443
|
pi list
|
|
1444
1444
|
~~~
|
|
1445
1445
|
|
|
@@ -126,3 +126,21 @@ The planning watchdog is progress-aware rather than a fixed wall-clock kill swit
|
|
|
126
126
|
Architect structured planning is JSON-first. A complete graph emitted before a late transport timeout can be recovered only through deterministic normalization/validation and must still pass the independent plan-checker.
|
|
127
127
|
|
|
128
128
|
High-risk execution and verification retain the original evidence ceiling. Only read-only planning context is role-bounded to reduce first-token latency and duplicate repository ingestion.
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
## Managed LSP V2
|
|
132
|
+
|
|
133
|
+
Code Intelligence now treats language servers as a bounded accelerator rather than a mandatory dependency.
|
|
134
|
+
|
|
135
|
+
- Persistent subprocesses are lazy-started and isolated by workspace, provider, and project-configuration fingerprint.
|
|
136
|
+
- Warm requests reuse the initialized server; document content is hash-tracked and synchronized with full-text `didChange` versions after edits.
|
|
137
|
+
- Diagnostics are fenced by document version so a delayed notification cannot silently prove an older source revision.
|
|
138
|
+
- The pool is bounded by global/per-workspace limits, idle TTL, and LRU eviction. Transient transport/startup failures receive at most the configured bounded restart budget.
|
|
139
|
+
- Non-transient request rejection does not destroy an otherwise healthy warm session.
|
|
140
|
+
- Windows npm shims are resolved through the existing safe Windows command resolver instead of attempting to execute `.cmd` wrappers directly.
|
|
141
|
+
- Pi child shutdown stops pooled language servers alongside managed services.
|
|
142
|
+
- If the persistent path is disabled, saturated, or unhealthy, Code Intelligence falls back to the existing ephemeral LSP path; semantic index/search remains independent of LSP availability.
|
|
143
|
+
|
|
144
|
+
Runtime tuning is optional through `UES_LSP_PERSISTENT`, `UES_LSP_IDLE_TTL_MS`, `UES_LSP_MAX_SERVERS`, `UES_LSP_MAX_PER_WORKSPACE`, `UES_LSP_REQUEST_TIMEOUT_MS`, `UES_LSP_STARTUP_TIMEOUT_MS`, and `UES_LSP_MAX_RESTARTS`.
|
|
145
|
+
|
|
146
|
+
Use `npm run bench:lsp` for a deterministic cold/warm protocol benchmark. The benchmark uses a mock language server so it measures pool overhead and reuse, not the indexing cost of a particular external language server.
|
|
@@ -16,7 +16,11 @@ Return exactly these sections:
|
|
|
16
16
|
Command/check, exit/result, and what claim it proves.
|
|
17
17
|
|
|
18
18
|
## Acceptance criteria proven
|
|
19
|
-
Criterion-by-criterion evidence.
|
|
19
|
+
Criterion-by-criterion evidence. Prefix each criterion with exactly one evidence status:
|
|
20
|
+
- `VERIFIED:` only when fresh direct evidence proves it.
|
|
21
|
+
- `INFERRED:` when it is only supported by reasoning or indirect evidence.
|
|
22
|
+
- `UNKNOWN:` when it was not checked or evidence is insufficient.
|
|
23
|
+
Any requested criterion marked `INFERRED:` or `UNKNOWN:` must also appear under **Unresolved gaps** and cannot support PASS.
|
|
20
24
|
|
|
21
25
|
## Failures
|
|
22
26
|
Actual failed checks or unmet criteria. If none, write exactly `None`.
|
|
@@ -30,4 +34,4 @@ What was skipped and why. Put optional or out-of-scope checks here rather than t
|
|
|
30
34
|
## Completion evidence
|
|
31
35
|
A concise statement limited to what the fresh evidence supports.
|
|
32
36
|
|
|
33
|
-
Do not infer success from another agent's report or from compilation alone.
|
|
37
|
+
Do not infer success from another agent's report or from compilation alone. A semantic claim about code, behavior, or an interface must be backed by an inspected path/symbol or fresh executable evidence before it can be marked VERIFIED.
|
package/lib/affected-tests.mjs
CHANGED
|
@@ -11,6 +11,22 @@ const AFFECTED_TEST_CACHE = new Map()
|
|
|
11
11
|
const AFFECTED_TEST_INFLIGHT = new Map()
|
|
12
12
|
const TEST_CONTENT_CACHE = new Map()
|
|
13
13
|
|
|
14
|
+
async function mapLimit(items, limit, worker) {
|
|
15
|
+
if (!items.length) return []
|
|
16
|
+
const output = new Array(items.length)
|
|
17
|
+
let cursor = 0
|
|
18
|
+
const width = Math.min(items.length, Math.max(1, Number(limit || 1)))
|
|
19
|
+
const runners = Array.from({ length: width }, async () => {
|
|
20
|
+
while (true) {
|
|
21
|
+
const index = cursor++
|
|
22
|
+
if (index >= items.length) return
|
|
23
|
+
output[index] = await worker(items[index], index)
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
await Promise.all(runners)
|
|
27
|
+
return output
|
|
28
|
+
}
|
|
29
|
+
|
|
14
30
|
function git(root, args) {
|
|
15
31
|
return spawnSync("git", args, { cwd: root, encoding: "utf8", maxBuffer: 8 * 1024 * 1024 })
|
|
16
32
|
}
|
|
@@ -228,8 +244,8 @@ export async function resolveAffectedTests(root = process.cwd(), options = {}) {
|
|
|
228
244
|
.map((file) => normalize(path.relative(root, file)))
|
|
229
245
|
.filter((file) => TEST_RE.test(file))
|
|
230
246
|
|
|
231
|
-
const
|
|
232
|
-
|
|
247
|
+
const ioConcurrency = Math.max(1, Math.min(32, Number(options.ioConcurrency ?? 12)))
|
|
248
|
+
const scoredTests = await mapLimit(tests, ioConcurrency, async (testFile) => {
|
|
233
249
|
const info = await stat(path.join(root, testFile)).catch(() => null)
|
|
234
250
|
const content = await cachedTestContent(path.join(root, testFile), info)
|
|
235
251
|
let best = { score: 0, reasons: [], changedFile: null }
|
|
@@ -237,8 +253,9 @@ export async function resolveAffectedTests(root = process.cwd(), options = {}) {
|
|
|
237
253
|
const current = scoreTest(testFile, changedFile, content)
|
|
238
254
|
if (current.score > best.score) best = { ...current, changedFile }
|
|
239
255
|
}
|
|
240
|
-
|
|
241
|
-
}
|
|
256
|
+
return best.score > 0 ? { path: testFile, ...best } : null
|
|
257
|
+
})
|
|
258
|
+
const ranked = scoredTests.filter(Boolean)
|
|
242
259
|
|
|
243
260
|
ranked.sort((a, b) => b.score - a.score || a.path.localeCompare(b.path))
|
|
244
261
|
const selected = ranked.slice(0, limit)
|
|
@@ -1,12 +1,77 @@
|
|
|
1
1
|
import { existsSync } from "node:fs"
|
|
2
|
-
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises"
|
|
3
|
-
import { spawnSync } from "node:child_process"
|
|
2
|
+
import { mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises"
|
|
4
3
|
import path from "node:path"
|
|
4
|
+
import { commandExists } from "./executable-probe.mjs"
|
|
5
5
|
|
|
6
6
|
const HEALTH_WEIGHT = { healthy: 40, degraded: 15, unknown: 0, unavailable: -1000 }
|
|
7
7
|
const COST_PENALTY = { low: 0, medium: 5, high: 12 }
|
|
8
8
|
const LATENCY_PENALTY = { fast: 0, medium: 3, slow: 8 }
|
|
9
9
|
const OBSERVATION_FILE = "CAPABILITY-OBSERVATIONS.json"
|
|
10
|
+
const OBSERVATION_WRITE_TAILS = new Map()
|
|
11
|
+
const OBSERVATION_LOCK_STALE_MS = 15_000
|
|
12
|
+
const OBSERVATION_LOCK_WAIT_MS = 20_000
|
|
13
|
+
|
|
14
|
+
function sleep(ms) {
|
|
15
|
+
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function observationLockPath(root) {
|
|
19
|
+
return path.join(path.resolve(root), ".ues-learning", ".capability-observations.lock")
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function withCrossProcessObservationLock(root, fn) {
|
|
23
|
+
const lockDir = observationLockPath(root)
|
|
24
|
+
await mkdir(path.dirname(lockDir), { recursive: true })
|
|
25
|
+
const deadline = Date.now() + OBSERVATION_LOCK_WAIT_MS
|
|
26
|
+
let delayMs = 8
|
|
27
|
+
|
|
28
|
+
while (true) {
|
|
29
|
+
try {
|
|
30
|
+
await mkdir(lockDir)
|
|
31
|
+
break
|
|
32
|
+
} catch (error) {
|
|
33
|
+
if (error?.code !== "EEXIST") throw error
|
|
34
|
+
const info = await stat(lockDir).catch(() => null)
|
|
35
|
+
if (info && Date.now() - info.mtimeMs > OBSERVATION_LOCK_STALE_MS) {
|
|
36
|
+
const confirmed = await stat(lockDir).catch(() => null)
|
|
37
|
+
if (
|
|
38
|
+
confirmed &&
|
|
39
|
+
confirmed.ino === info.ino &&
|
|
40
|
+
confirmed.mtimeMs === info.mtimeMs
|
|
41
|
+
) {
|
|
42
|
+
await rm(lockDir, { recursive: true, force: true }).catch(() => {})
|
|
43
|
+
continue
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (Date.now() >= deadline) throw new Error("Timed out waiting for capability observation lock")
|
|
47
|
+
await sleep(delayMs)
|
|
48
|
+
delayMs = Math.min(100, delayMs * 2)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
return await fn()
|
|
54
|
+
} finally {
|
|
55
|
+
await rm(lockDir, { recursive: true, force: true }).catch(() => {})
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function withObservationWriteLock(root, fn) {
|
|
60
|
+
const key = path.resolve(root)
|
|
61
|
+
const previous = OBSERVATION_WRITE_TAILS.get(key) || Promise.resolve()
|
|
62
|
+
let release
|
|
63
|
+
const barrier = new Promise((resolve) => { release = resolve })
|
|
64
|
+
const tail = previous.catch(() => {}).then(() => barrier)
|
|
65
|
+
OBSERVATION_WRITE_TAILS.set(key, tail)
|
|
66
|
+
|
|
67
|
+
await previous.catch(() => {})
|
|
68
|
+
try {
|
|
69
|
+
return await withCrossProcessObservationLock(root, fn)
|
|
70
|
+
} finally {
|
|
71
|
+
release()
|
|
72
|
+
if (OBSERVATION_WRITE_TAILS.get(key) === tail) OBSERVATION_WRITE_TAILS.delete(key)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
10
75
|
|
|
11
76
|
function observationPath(root) {
|
|
12
77
|
return path.join(path.resolve(root), ".ues-learning", OBSERVATION_FILE)
|
|
@@ -25,27 +90,29 @@ export async function readCapabilityObservations(root = process.cwd()) {
|
|
|
25
90
|
}
|
|
26
91
|
export async function recordCapabilityObservation(root, capability, providerId, input = {}) {
|
|
27
92
|
if (!capability || !providerId) throw new Error("capability observation requires capability and provider id")
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
93
|
+
return withObservationWriteLock(root, async () => {
|
|
94
|
+
const state = await readCapabilityObservations(root)
|
|
95
|
+
const previous = state.capabilities?.[capability]?.[providerId] || {}
|
|
96
|
+
const success = input.success === true, failure = input.success === false
|
|
97
|
+
const samples = Number(previous.samples || 0) + (success || failure ? 1 : 0)
|
|
98
|
+
const successes = Number(previous.successes || 0) + (success ? 1 : 0)
|
|
99
|
+
const failures = Number(previous.failures || 0) + (failure ? 1 : 0)
|
|
100
|
+
const latencyMs = Number.isFinite(Number(input.latencyMs)) ? Math.max(0, Number(input.latencyMs)) : null
|
|
101
|
+
const previousLatencySamples = Number(previous.latencySamples || 0)
|
|
102
|
+
const latencySamples = previousLatencySamples + (latencyMs == null ? 0 : 1)
|
|
103
|
+
const avgLatencyMs = latencyMs == null ? (previous.avgLatencyMs ?? null) : ((Number(previous.avgLatencyMs || 0) * previousLatencySamples) + latencyMs) / Math.max(1, latencySamples)
|
|
104
|
+
const next = {
|
|
105
|
+
samples, successes, failures,
|
|
106
|
+
successRate: samples ? successes / samples : 0,
|
|
107
|
+
failureRate: samples ? failures / samples : 0,
|
|
108
|
+
latencySamples, avgLatencyMs: avgLatencyMs == null ? null : Number(avgLatencyMs.toFixed(2)),
|
|
109
|
+
lastLatencyMs: latencyMs, lastObservedAt: new Date().toISOString(),
|
|
110
|
+
lastError: input.error ? String(input.error).slice(0, 500) : null,
|
|
111
|
+
}
|
|
112
|
+
const capabilities = { ...state.capabilities, [capability]: { ...(state.capabilities?.[capability] || {}), [providerId]: next } }
|
|
113
|
+
await atomicJson(observationPath(root), { schemaVersion: 1, updatedAt: next.lastObservedAt, capabilities })
|
|
114
|
+
return next
|
|
115
|
+
})
|
|
49
116
|
}
|
|
50
117
|
|
|
51
118
|
function clamp(value, min = 0, max = 1, fallback = 0) {
|
|
@@ -53,21 +120,6 @@ function clamp(value, min = 0, max = 1, fallback = 0) {
|
|
|
53
120
|
return Number.isFinite(number) ? Math.max(min, Math.min(max, number)) : fallback
|
|
54
121
|
}
|
|
55
122
|
|
|
56
|
-
const COMMAND_PROBE_TTL_MS = 60_000
|
|
57
|
-
const commandProbeCache = new Map()
|
|
58
|
-
|
|
59
|
-
function commandExists(command) {
|
|
60
|
-
if (!command) return false
|
|
61
|
-
const now = Date.now()
|
|
62
|
-
const cached = commandProbeCache.get(command)
|
|
63
|
-
if (cached && now - cached.checkedAt < COMMAND_PROBE_TTL_MS) return cached.available
|
|
64
|
-
const finder = process.platform === "win32" ? "where" : "which"
|
|
65
|
-
const result = spawnSync(finder, [command], { stdio: "ignore", windowsHide: true })
|
|
66
|
-
const available = result.status === 0
|
|
67
|
-
commandProbeCache.set(command, { available, checkedAt: now })
|
|
68
|
-
return available
|
|
69
|
-
}
|
|
70
|
-
|
|
71
123
|
async function packageDeclared(root, name) {
|
|
72
124
|
try {
|
|
73
125
|
const pkg = JSON.parse(await readFile(path.join(root, "package.json"), "utf8"))
|
|
@@ -348,20 +400,27 @@ export async function capabilityFabricStatus(root = process.cwd(), options = {})
|
|
|
348
400
|
const persistedObservations = options.observations ? null : await readCapabilityObservations(root)
|
|
349
401
|
const capabilities = {}
|
|
350
402
|
const rows = []
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
403
|
+
const entries = Object.entries(registry.capabilities || {})
|
|
404
|
+
const evaluated = await Promise.all(entries.map(async ([name, providers]) => {
|
|
405
|
+
const probed = await Promise.all((providers || []).map((provider) => probeCapabilityProvider(provider, root)))
|
|
354
406
|
const selected = selectCapabilityProvider(name, probed, {
|
|
355
407
|
observations: options.observations?.[name] || persistedObservations?.capabilities?.[name] || {},
|
|
356
408
|
allowUnknown: options.allowUnknown,
|
|
357
409
|
})
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
410
|
+
return {
|
|
411
|
+
name,
|
|
412
|
+
selected,
|
|
413
|
+
row: {
|
|
414
|
+
capability: name,
|
|
415
|
+
selected: selected.selected?.id || null,
|
|
416
|
+
status: selected.selected?.status || "unavailable",
|
|
417
|
+
fallbacks: selected.fallbacks.map((item) => item.id),
|
|
418
|
+
},
|
|
419
|
+
}
|
|
420
|
+
}))
|
|
421
|
+
for (const item of evaluated) {
|
|
422
|
+
capabilities[item.name] = item.selected
|
|
423
|
+
rows.push(item.row)
|
|
365
424
|
}
|
|
366
425
|
return {
|
|
367
426
|
schemaVersion: 1,
|
|
@@ -1,24 +1,12 @@
|
|
|
1
1
|
import { lstat, readFile, realpath, rename, rm, writeFile } from "node:fs/promises"
|
|
2
2
|
import { spawnSync } from "node:child_process"
|
|
3
3
|
import path from "node:path"
|
|
4
|
+
import { commandExists } from "../executable-probe.mjs"
|
|
4
5
|
import { querySemanticIndex } from "../semantic-index.mjs"
|
|
5
6
|
import { anchoredLines, applyAnchoredEdits } from "./edit-anchor.mjs"
|
|
6
|
-
import { diagnoseCode, lspOperation, lspProviderStatus, LSP_OPERATIONS } from "./lsp-provider.mjs"
|
|
7
|
+
import { diagnoseCode, lspOperation, lspPoolStatus, lspProviderStatus, shutdownLspPool, LSP_OPERATIONS } from "./lsp-provider.mjs"
|
|
7
8
|
|
|
8
9
|
const AST_COMMANDS = ["ast-grep", "sg"]
|
|
9
|
-
const commandCache = new Map()
|
|
10
|
-
const COMMAND_TTL_MS = 60_000
|
|
11
|
-
|
|
12
|
-
function commandExists(command) {
|
|
13
|
-
const now = Date.now()
|
|
14
|
-
const cached = commandCache.get(command)
|
|
15
|
-
if (cached && now - cached.at < COMMAND_TTL_MS) return cached.available
|
|
16
|
-
const finder = process.platform === "win32" ? "where" : "which"
|
|
17
|
-
const available = spawnSync(finder, [command], { stdio: "ignore", windowsHide: true }).status === 0
|
|
18
|
-
commandCache.set(command, { available, at: now })
|
|
19
|
-
return available
|
|
20
|
-
}
|
|
21
|
-
|
|
22
10
|
function inside(root, target) { return target === root || target.startsWith(root + path.sep) }
|
|
23
11
|
|
|
24
12
|
async function safeFile(root, relative) {
|
|
@@ -93,4 +81,4 @@ export async function searchCodeIntelligence(root, query, options = {}) {
|
|
|
93
81
|
}
|
|
94
82
|
}
|
|
95
83
|
|
|
96
|
-
export { diagnoseCode, lspOperation, lspProviderStatus, LSP_OPERATIONS } from "./lsp-provider.mjs"
|
|
84
|
+
export { diagnoseCode, lspOperation, lspPoolStatus, lspProviderStatus, shutdownLspPool, LSP_OPERATIONS } from "./lsp-provider.mjs"
|