opencode-agent-skill 14.2.0-beta.1 → 15.0.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.
Files changed (55) hide show
  1. package/README.md +51 -18
  2. package/docs/PI-COMPAT.md +167 -3
  3. package/docs/V15-MANAGED-RUNTIME.md +128 -0
  4. package/global-config/agents/architect.md +63 -1
  5. package/global-config/agents/executor.md +7 -0
  6. package/global-config/agents/plan-checker.md +8 -1
  7. package/global-config/agents/verifier.md +3 -3
  8. package/global-config/skills/database-engineering/references/workflow.md +15 -0
  9. package/global-config/skills/engineering-orchestrator/references/long-horizon.md +21 -11
  10. package/lib/activity-deadline.mjs +70 -0
  11. package/lib/adaptive-context-budget.mjs +25 -5
  12. package/lib/affected-tests.mjs +1 -1
  13. package/lib/capability-fabric.mjs +40 -0
  14. package/lib/code-intelligence/edit-anchor.mjs +102 -0
  15. package/lib/code-intelligence/index.mjs +95 -0
  16. package/lib/code-intelligence/lsp-provider.mjs +189 -0
  17. package/lib/completion-auditor.mjs +102 -0
  18. package/lib/document-ingestion.mjs +60 -0
  19. package/lib/evidence-store.mjs +92 -8
  20. package/lib/execution-contract.mjs +579 -0
  21. package/lib/fast-verification-gate.mjs +69 -0
  22. package/lib/leaf-runtime-optimizer.mjs +76 -0
  23. package/lib/mcp-health.mjs +144 -0
  24. package/lib/mcp-tool-policy.mjs +19 -0
  25. package/lib/memory-engine.mjs +28 -0
  26. package/lib/pi-child-invocation.mjs +65 -0
  27. package/lib/pi-rpc-pool.mjs +61 -9
  28. package/lib/plan-salvage.mjs +124 -0
  29. package/lib/planning-speed-policy.mjs +131 -0
  30. package/lib/prompt-cache.mjs +26 -12
  31. package/lib/repo-graph.mjs +1 -1
  32. package/lib/repo-inspect.mjs +1 -0
  33. package/lib/reversible-context.mjs +49 -0
  34. package/lib/runtime-artifacts.mjs +42 -0
  35. package/lib/runtime-events.mjs +33 -1
  36. package/lib/safety.mjs +1 -0
  37. package/lib/semantic-index.mjs +1 -1
  38. package/lib/service-manager.mjs +526 -0
  39. package/lib/session-display.mjs +49 -0
  40. package/lib/task-engine.mjs +6 -10
  41. package/lib/task-graph.mjs +87 -0
  42. package/lib/task-policy.mjs +210 -8
  43. package/lib/trajectory.mjs +111 -1
  44. package/lib/turbo-fast-path.mjs +44 -0
  45. package/lib/workspace-fingerprint.mjs +2 -11
  46. package/lib/workspace-root.mjs +49 -0
  47. package/lib/worktree-sandbox.mjs +234 -11
  48. package/package.json +9 -6
  49. package/pi/extensions/ues-child-runtime.ts +300 -2
  50. package/pi/extensions/ues.ts +1887 -160
  51. package/scripts/check-release-consistency.mjs +25 -2
  52. package/scripts/check-source-integrity.mjs +532 -3
  53. package/scripts/eval-pi.mjs +76 -36
  54. package/scripts/smoke-pi-extension.mjs +1 -1
  55. package/pi/prompts/ues-run.md +0 -17
@@ -1,8 +1,9 @@
1
1
  import { existsSync } from "node:fs"
2
2
  import { createHash } from "node:crypto"
3
- import { copyFile, mkdir, readFile, rm, unlink, writeFile } from "node:fs/promises"
3
+ import { copyFile, mkdir, readFile, readdir, rm, unlink, writeFile } from "node:fs/promises"
4
4
  import { spawnSync } from "node:child_process"
5
5
  import path from "node:path"
6
+ import { isUesRuntimeArtifactPath, sourceFacingPaths, sourceGitPathspecs } from "./runtime-artifacts.mjs"
6
7
 
7
8
  function git(root, args, options = {}) {
8
9
  return spawnSync("git", args, {
@@ -22,6 +23,51 @@ function statusFiles(root) {
22
23
  .map((line) => line.slice(3).trim())
23
24
  .map((value) => value.includes(" -> ") ? value.split(" -> ").at(-1) : value)
24
25
  .map((value) => value.replaceAll("\\", "/"))
26
+ .filter((value) => !isUesRuntimeArtifactPath(value))
27
+ }
28
+
29
+ function sourceIntentBatches(values, options = {}) {
30
+ const maxItems = Math.max(1, Math.min(256, Number(options.maxItems || 64)))
31
+ const maxChars = Math.max(1024, Math.min(24_000, Number(options.maxChars || 12_000)))
32
+ const batches = []
33
+ let batch = []
34
+ let chars = 0
35
+
36
+ for (const value of sourceFacingPaths(values)) {
37
+ const cost = value.length + 3
38
+ if (batch.length && (batch.length >= maxItems || chars + cost > maxChars)) {
39
+ batches.push(batch)
40
+ batch = []
41
+ chars = 0
42
+ }
43
+ batch.push(value)
44
+ chars += cost
45
+ }
46
+ if (batch.length) batches.push(batch)
47
+ return batches
48
+ }
49
+
50
+ function intentToAddSourceFiles(root) {
51
+ const untracked = git(root, [
52
+ "ls-files",
53
+ "--others",
54
+ "--exclude-standard",
55
+ "-z",
56
+ "--",
57
+ ...sourceGitPathspecs(),
58
+ ])
59
+ if (untracked.status !== 0) {
60
+ throw new Error((untracked.stderr || untracked.stdout || "git ls-files for intent-to-add failed").trim())
61
+ }
62
+
63
+ const candidates = sourceFacingPaths(untracked.stdout.split("\0").filter(Boolean))
64
+ for (const batch of sourceIntentBatches(candidates)) {
65
+ const addIntent = git(root, ["add", "-N", "--", ...batch])
66
+ if (addIntent.status !== 0) {
67
+ throw new Error((addIntent.stderr || addIntent.stdout || "git add -N failed").trim())
68
+ }
69
+ }
70
+ return candidates
25
71
  }
26
72
 
27
73
  function safeName(value) {
@@ -40,13 +86,19 @@ async function readSandboxMetadata(dir) {
40
86
  }
41
87
  }
42
88
 
89
+ export async function taskSandboxOwnerRoot(dir) {
90
+ const metadata = await readSandboxMetadata(path.resolve(dir)).catch(() => null)
91
+ const root = metadata?.root ? path.resolve(String(metadata.root)) : null
92
+ return root || null
93
+ }
94
+
43
95
  async function writeSandboxMetadata(dir, value) {
44
96
  await writeFile(metadataFile(dir), JSON.stringify(value, null, 2) + "\n", "utf8")
45
97
  }
46
98
 
47
99
  async function inheritDirtySnapshot(root, dir) {
48
100
  const inheritedUntrackedHashes = {}
49
- const diff = git(root, ["diff", "--binary", "--no-ext-diff", "HEAD", "--"])
101
+ const diff = git(root, ["diff", "--binary", "--no-ext-diff", "HEAD", "--", ...sourceGitPathspecs()])
50
102
  if (diff.status !== 0) throw new Error((diff.stderr || diff.stdout || "git diff HEAD failed").trim())
51
103
  if (diff.stdout) {
52
104
  const applied = git(dir, ["apply", "--whitespace=nowarn", "-"], { input: diff.stdout })
@@ -56,10 +108,12 @@ async function inheritDirtySnapshot(root, dir) {
56
108
  const untracked = git(root, ["ls-files", "--others", "--exclude-standard", "-z"])
57
109
  if (untracked.status !== 0) throw new Error((untracked.stderr || untracked.stdout || "git ls-files failed").trim())
58
110
  for (const relative of untracked.stdout.split("\0").filter(Boolean)) {
111
+ const normalizedRelative = relative.replaceAll("\\", "/")
112
+ if (isUesRuntimeArtifactPath(normalizedRelative)) continue
59
113
  const source = path.join(root, relative)
60
114
  const target = path.join(dir, relative)
61
115
  const bytes = await readFile(source)
62
- inheritedUntrackedHashes[relative.replaceAll("\\", "/")] = createHash("sha256").update(bytes).digest("hex")
116
+ inheritedUntrackedHashes[normalizedRelative] = createHash("sha256").update(bytes).digest("hex")
63
117
  await mkdir(path.dirname(target), { recursive: true })
64
118
  await copyFile(source, target)
65
119
  }
@@ -87,9 +141,7 @@ async function inheritDirtySnapshot(root, dir) {
87
141
 
88
142
  export async function createTaskSandbox(root, slug, taskID, options = {}) {
89
143
  root = path.resolve(root)
90
- const status = git(root, ["status", "--porcelain=v1", "--untracked-files=all"])
91
- if (status.status !== 0) throw new Error((status.stderr || status.stdout || "git status failed").trim())
92
- const rootDirty = Boolean(status.stdout.trim())
144
+ const rootDirty = statusFiles(root).length > 0
93
145
  if (rootDirty && options.allowDirtyRoot !== true && options.inheritDirtyRoot !== true) {
94
146
  throw new Error("sandbox creation requires a clean root working tree so the isolated executor cannot miss existing uncommitted changes")
95
147
  }
@@ -129,6 +181,7 @@ export async function createTaskSandbox(root, slug, taskID, options = {}) {
129
181
  inheritedDirtyRoot,
130
182
  inheritedUntrackedHashes,
131
183
  createdAt: new Date().toISOString(),
184
+ ownerPid: process.pid,
132
185
  }
133
186
  await writeSandboxMetadata(dir, metadata)
134
187
  return metadata
@@ -155,11 +208,12 @@ export async function integrateTaskSandbox(root, dir, options = {}) {
155
208
  baseSha = base.stdout.trim()
156
209
  }
157
210
 
158
- // Intent-to-add exposes untracked files in a binary diff without committing them.
159
- const addIntent = git(dir, ["add", "-N", "."])
160
- if (addIntent.status !== 0) throw new Error((addIntent.stderr || addIntent.stdout || "git add -N failed").trim())
211
+ // Intent-to-add only the non-ignored untracked source files. A root-wide
212
+ // `git add -N -- .` fails when an ignored UES runtime directory such as
213
+ // .ues-cache exists, even when an exclude pathspec is also present.
214
+ intentToAddSourceFiles(dir)
161
215
 
162
- const changedRun = git(dir, ["diff", "--name-only", baseSha, "--"])
216
+ const changedRun = git(dir, ["diff", "--name-only", baseSha, "--", ...sourceGitPathspecs()])
163
217
  if (changedRun.status !== 0) throw new Error((changedRun.stderr || changedRun.stdout || "git diff --name-only failed").trim())
164
218
  const changed = changedRun.stdout.split(/\r?\n/).filter(Boolean).map((value) => value.replaceAll("\\", "/"))
165
219
  if (changed.length === 0) {
@@ -195,7 +249,7 @@ export async function integrateTaskSandbox(root, dir, options = {}) {
195
249
  throw new Error("sandbox integration conflicts with existing root changes: " + unsafeOverlap.join(", "))
196
250
  }
197
251
 
198
- const patch = git(dir, ["diff", "--binary", "--no-ext-diff", baseSha, "--"])
252
+ const patch = git(dir, ["diff", "--binary", "--no-ext-diff", baseSha, "--", ...sourceGitPathspecs()])
199
253
  if (patch.status !== 0) throw new Error((patch.stderr || patch.stdout || "git diff failed").trim())
200
254
  const applied = git(root, ["apply", "--whitespace=nowarn", "-"], { input: patch.stdout })
201
255
  if (applied.status !== 0) {
@@ -206,6 +260,175 @@ export async function integrateTaskSandbox(root, dir, options = {}) {
206
260
  return { integrated: true, changed, base: baseSha, empty: false }
207
261
  }
208
262
 
263
+ function processAlive(pid) {
264
+ const value = Number(pid)
265
+ if (!Number.isInteger(value) || value <= 0) return false
266
+ try {
267
+ process.kill(value, 0)
268
+ return true
269
+ } catch {
270
+ return false
271
+ }
272
+ }
273
+
274
+ export async function pruneOrphanTaskSandboxes(root, options = {}) {
275
+ root = path.resolve(root)
276
+ const now = Date.now()
277
+ const minAgeMs = Math.max(60_000, Number(options.minAgeMs || 5 * 60_000))
278
+ const legacyMinAgeMs = Math.max(minAgeMs, Number(options.legacyMinAgeMs || 30 * 60_000))
279
+ const ownedMinAgeMs = Math.max(0, Number(options.ownedMinAgeMs ?? 60_000))
280
+ const reclaimOwnerPid = Number(options.reclaimOwnerPid || 0)
281
+ const protectedDirs = new Set(
282
+ (options.protectedDirs || []).map((value) => path.resolve(String(value))),
283
+ )
284
+ const expectedBase = path.resolve(
285
+ options.baseDir || path.join(path.dirname(root), "." + path.basename(root) + ".ues-sandboxes"),
286
+ )
287
+ const removed = []
288
+ const skipped = []
289
+ const sidecarsRemoved = []
290
+
291
+ for (const item of listTaskSandboxes(root)) {
292
+ const branch = String(item.branch || "").replace(/^refs\/heads\//, "")
293
+ const dir = path.resolve(String(item.path || ""))
294
+ if (!branch.startsWith("ues/")) continue
295
+ if (path.dirname(dir) !== expectedBase) continue
296
+ if (protectedDirs.has(dir)) {
297
+ skipped.push({ dir, reason: "protected-active" })
298
+ continue
299
+ }
300
+
301
+ const metadata = await readSandboxMetadata(dir)
302
+ if (!metadata || path.resolve(String(metadata.root || "")) !== root) {
303
+ skipped.push({ dir, reason: "metadata-missing-or-root-mismatch" })
304
+ continue
305
+ }
306
+
307
+ const createdAt = Date.parse(String(metadata.createdAt || ""))
308
+ const ageMs = Number.isFinite(createdAt) ? Math.max(0, now - createdAt) : Infinity
309
+ const ownerPid = Number(metadata.ownerPid || 0)
310
+ const reclaimOwned = reclaimOwnerPid > 0 && ownerPid === reclaimOwnerPid
311
+
312
+ if (ownerPid > 0 && processAlive(ownerPid) && !reclaimOwned) {
313
+ skipped.push({ dir, reason: "owner-alive", ownerPid })
314
+ continue
315
+ }
316
+
317
+ const requiredAge = reclaimOwned
318
+ ? ownedMinAgeMs
319
+ : ownerPid > 0
320
+ ? minAgeMs
321
+ : legacyMinAgeMs
322
+ if (ageMs < requiredAge) {
323
+ skipped.push({ dir, reason: "too-recent", ageMs, ownerPid: ownerPid || null })
324
+ continue
325
+ }
326
+
327
+ try {
328
+ const result = await removeTaskSandbox(root, dir, {
329
+ force: true,
330
+ deleteBranch: true,
331
+ branch,
332
+ })
333
+ removed.push({ ...result, ageMs, ownerPid: ownerPid || null, reclaimOwned })
334
+ } catch (error) {
335
+ skipped.push({
336
+ dir,
337
+ reason: "remove-failed",
338
+ error: error instanceof Error ? error.message : String(error),
339
+ })
340
+ }
341
+ }
342
+
343
+ // Clean detached physical sandbox directories left after Git forgot the
344
+ // worktree registration (for example after a crash/manual prune). Only remove
345
+ // directories with UES metadata bound to this exact root repository.
346
+ try {
347
+ const registered = new Set(
348
+ listTaskSandboxes(root).map((item) => path.resolve(String(item.path || ""))),
349
+ )
350
+ const entries = await readdir(expectedBase, { withFileTypes: true })
351
+ for (const entry of entries) {
352
+ if (!entry.isDirectory()) continue
353
+ const dir = path.resolve(expectedBase, entry.name)
354
+ if (registered.has(dir) || protectedDirs.has(dir)) continue
355
+
356
+ const metadata = await readSandboxMetadata(dir)
357
+ if (!metadata || path.resolve(String(metadata.root || "")) !== root) continue
358
+
359
+ const createdAt = Date.parse(String(metadata.createdAt || ""))
360
+ const ageMs = Number.isFinite(createdAt) ? Math.max(0, now - createdAt) : Infinity
361
+ const ownerPid = Number(metadata.ownerPid || 0)
362
+ const reclaimOwned = reclaimOwnerPid > 0 && ownerPid === reclaimOwnerPid
363
+
364
+ if (ownerPid > 0 && processAlive(ownerPid) && !reclaimOwned) {
365
+ skipped.push({ dir, reason: "detached-owner-alive", ownerPid })
366
+ continue
367
+ }
368
+
369
+ const requiredAge = reclaimOwned
370
+ ? ownedMinAgeMs
371
+ : ownerPid > 0
372
+ ? minAgeMs
373
+ : legacyMinAgeMs
374
+ if (ageMs < requiredAge) {
375
+ skipped.push({ dir, reason: "detached-too-recent", ageMs, ownerPid: ownerPid || null })
376
+ continue
377
+ }
378
+
379
+ try {
380
+ await rm(dir, { recursive: true, force: true })
381
+ await unlink(metadataFile(dir)).catch(() => {})
382
+ const branch = String(metadata.branch || "")
383
+ if (branch.startsWith("ues/")) git(root, ["branch", "-D", branch])
384
+ removed.push({
385
+ removed: !existsSync(dir),
386
+ dir,
387
+ branch: branch || null,
388
+ ageMs,
389
+ ownerPid: ownerPid || null,
390
+ reclaimOwned,
391
+ detached: true,
392
+ })
393
+ } catch (error) {
394
+ skipped.push({
395
+ dir,
396
+ reason: "detached-remove-failed",
397
+ error: error instanceof Error ? error.message : String(error),
398
+ })
399
+ }
400
+ }
401
+ } catch {}
402
+
403
+ // Clean metadata sidecars left after a crash or manual worktree removal.
404
+ try {
405
+ const entries = await readdir(expectedBase, { withFileTypes: true })
406
+ for (const entry of entries) {
407
+ if (!entry.isFile() || !entry.name.endsWith(".ues-meta.json")) continue
408
+ const sidecar = path.join(expectedBase, entry.name)
409
+ const dir = sidecar.slice(0, -".ues-meta.json".length)
410
+ if (existsSync(dir)) continue
411
+ try {
412
+ const metadata = JSON.parse(await readFile(sidecar, "utf8"))
413
+ if (path.resolve(String(metadata.root || "")) !== root) continue
414
+ const createdAt = Date.parse(String(metadata.createdAt || ""))
415
+ const ageMs = Number.isFinite(createdAt) ? Math.max(0, now - createdAt) : Infinity
416
+ if (ageMs < minAgeMs) continue
417
+ await unlink(sidecar)
418
+ sidecarsRemoved.push(sidecar)
419
+ } catch {}
420
+ }
421
+
422
+ const remaining = await readdir(expectedBase)
423
+ if (remaining.length === 0) {
424
+ await rm(expectedBase, { recursive: false, force: true })
425
+ }
426
+ } catch {}
427
+
428
+ git(root, ["worktree", "prune"])
429
+ return { removed, skipped, sidecarsRemoved, baseRemoved: !existsSync(expectedBase) }
430
+ }
431
+
209
432
  export async function removeTaskSandbox(root, dir, options = {}) {
210
433
  root = path.resolve(root)
211
434
  dir = path.resolve(dir)
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "opencode-agent-skill",
3
- "version": "14.2.0-beta.1",
4
- "description": "Pi Agent engineering runtime with hierarchical context, verified persistent memory, learned capability failover, durable evidence, and evidence-gated verification",
3
+ "version": "15.0.0",
4
+ "description": "Pi-native UES coding-agent runtime with adaptive stability, Turbo Fast Path, durable verification, managed services, and weak-model orchestration",
5
5
  "type": "module",
6
6
  "bin": {
7
- "ues": "bin/ocskill.mjs",
8
- "ocskill": "bin/ocskill.mjs"
7
+ "ocskill": "bin/ocskill.mjs",
8
+ "ues": "bin/ocskill.mjs"
9
9
  },
10
10
  "files": [
11
11
  "bin/ocskill.mjs",
@@ -21,6 +21,7 @@
21
21
  "docs/V14-CONTEXT-MEMORY-FABRIC.md",
22
22
  "docs/V14.1-QUALITY-PERFORMANCE-FABRIC.md",
23
23
  "docs/V14.2-TURBO-WEAK-MODEL-RUNTIME.md",
24
+ "docs/V15-MANAGED-RUNTIME.md",
24
25
  "docs/PI-COMPAT.md",
25
26
  "docs/OPENCODE-COMPAT.md",
26
27
  "README.md",
@@ -38,9 +39,11 @@
38
39
  "ci": "npm run integrity && npm run runtime:exports && npm run syntax && npm test && npm run docs:check && npm pack --dry-run && npm run smoke:pi && npm run smoke:package && npm run smoke:packed",
39
40
  "prepublishOnly": "npm run ci",
40
41
  "smoke:packed": "node scripts/smoke-packed-install.mjs",
41
- "eval:v14": "node --test test/capability-fabric-v14.test.mjs test/hierarchical-context-v14.test.mjs test/memory-engine-v14.test.mjs test/context-engine-v14.test.mjs test/task-engine-v14-context.test.mjs test/cli-v14.test.mjs test/v14-contract.test.mjs test/performance-fabric-v14.test.mjs test/browser-mcp-routing-v14.test.mjs test/process-hang-detector-v14.test.mjs test/v14.2-runtime.test.mjs test/benchmark-confidence.test.mjs",
42
+ "eval:v14": "node --test test/capability-fabric-v14.test.mjs test/hierarchical-context-v14.test.mjs test/memory-engine-v14.test.mjs test/context-engine-v14.test.mjs test/task-engine-v14-context.test.mjs test/cli-v14.test.mjs test/v14-contract.test.mjs test/performance-fabric-v14.test.mjs test/browser-mcp-routing-v14.test.mjs test/process-hang-detector-v14.test.mjs test/v14.2-runtime.test.mjs test/benchmark-confidence.test.mjs test/v14.3-intelligence.test.mjs",
42
43
  "integrity": "node scripts/check-source-integrity.mjs",
43
- "runtime:exports": "node scripts/check-runtime-exports.mjs"
44
+ "runtime:exports": "node scripts/check-runtime-exports.mjs",
45
+ "eval:v15": "node --test test/v15-runtime.test.mjs test/trajectory.test.mjs test/evidence-store-v11.test.mjs test/runtime-events.test.mjs test/v14.3-intelligence.test.mjs test/v14.2-runtime.test.mjs test/pi-package.test.mjs test/execution-contract.test.mjs",
46
+ "release:verify": "npm run ci && npm run eval:v15"
44
47
  },
45
48
  "keywords": [
46
49
  "pi-package",