opencode-agent-skill 7.7.0 → 10.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 (68) hide show
  1. package/CHANGELOG.md +112 -3
  2. package/README.md +396 -281
  3. package/bin/ocskill.mjs +382 -156
  4. package/docs/DETERMINISTIC-TOOLS.md +25 -8
  5. package/docs/ENGINEERING-DESIGN.md +31 -13
  6. package/docs/EVALS.md +34 -12
  7. package/docs/NPM-PUBLISH.md +6 -6
  8. package/docs/OPENCODE-COMPAT.md +11 -8
  9. package/docs/TRACE-SCHEMA.md +15 -2
  10. package/docs/V8-INTELLIGENCE-RELIABILITY.md +206 -0
  11. package/docs/V9-SPEED-INTELLIGENCE.md +102 -0
  12. package/evals/live/tasks.json +6 -6
  13. package/evals/polyglot/fixtures/polyglot-bench/api/generated/client.ts +2 -0
  14. package/evals/polyglot/fixtures/polyglot-bench/api/openapi.json +25 -0
  15. package/evals/polyglot/fixtures/polyglot-bench/db/migrations/20260920_add_order_key.sql +1 -0
  16. package/evals/polyglot/fixtures/polyglot-bench/dotnet/OrderService.cs +8 -0
  17. package/evals/polyglot/fixtures/polyglot-bench/java/PriceService.java +5 -0
  18. package/evals/polyglot/fixtures/polyglot-bench/monorepo/package.json +6 -0
  19. package/evals/polyglot/fixtures/polyglot-bench/monorepo/packages/api/package.json +4 -0
  20. package/evals/polyglot/fixtures/polyglot-bench/monorepo/packages/web/package.json +7 -0
  21. package/evals/polyglot/fixtures/polyglot-bench/monorepo/pnpm-lock.yaml +5 -0
  22. package/evals/polyglot/fixtures/polyglot-bench/next/app/api/products/route.ts +7 -0
  23. package/evals/polyglot/fixtures/polyglot-bench/python/tenant_auth.py +4 -0
  24. package/evals/polyglot/fixtures/polyglot-bench/react-native/keyboard.ts +3 -0
  25. package/evals/polyglot/graders/polyglot-bench.mjs +101 -0
  26. package/evals/polyglot/tasks.json +54 -0
  27. package/global-config/AGENTS.md +78 -160
  28. package/global-config/agents/integration-verifier.md +1 -1
  29. package/global-config/agents/plan-checker.md +1 -1
  30. package/global-config/commands/run.md +9 -5
  31. package/global-config/plugins/ues-router/capabilities.js +4 -0
  32. package/global-config/plugins/ues-router/index.js +784 -37
  33. package/global-config/plugins/ues-router/router.js +175 -23
  34. package/global-config/plugins/ues-router/runtime-guard.js +265 -0
  35. package/global-config/skills/engineering-orchestrator/references/long-horizon.md +6 -4
  36. package/lib/aci.mjs +128 -0
  37. package/lib/benchmark-confidence.mjs +173 -0
  38. package/lib/cli-utils.mjs +41 -0
  39. package/lib/container-sandbox.mjs +102 -0
  40. package/lib/context-manifest.mjs +300 -22
  41. package/lib/control-center.mjs +36 -3
  42. package/lib/eval-ablation.mjs +104 -0
  43. package/lib/eval-order.mjs +9 -0
  44. package/lib/eval-report.mjs +11 -0
  45. package/lib/eval-telemetry.mjs +8 -2
  46. package/lib/gate-receipt.mjs +52 -0
  47. package/lib/installer.mjs +39 -25
  48. package/lib/learning-engine.mjs +236 -38
  49. package/lib/model-policy.mjs +6 -0
  50. package/lib/opencode-compat.mjs +25 -10
  51. package/lib/orchestrator-policy.mjs +195 -21
  52. package/lib/process-runner.mjs +30 -9
  53. package/lib/runtime-events.mjs +31 -0
  54. package/lib/semantic-index.mjs +318 -0
  55. package/lib/task-engine.mjs +557 -28
  56. package/lib/trajectory.mjs +89 -0
  57. package/lib/windows-shim.mjs +227 -0
  58. package/lib/worktree-sandbox.mjs +85 -3
  59. package/package.json +11 -4
  60. package/scripts/check-release-tag.mjs +22 -0
  61. package/scripts/control-center.mjs +25 -0
  62. package/scripts/eval-ablation.mjs +44 -0
  63. package/scripts/eval-live.mjs +39 -58
  64. package/scripts/eval-matrix.mjs +166 -0
  65. package/scripts/smoke-packed-install.mjs +138 -4
  66. package/scripts/smoke-plain-install.mjs +91 -0
  67. package/scripts/validate-live-suite.mjs +3 -3
  68. package/scripts/validate.mjs +27 -5
@@ -0,0 +1,89 @@
1
+ import { createHash, randomUUID } from "node:crypto"
2
+ import { mkdir, readFile, writeFile } from "node:fs/promises"
3
+ import path from "node:path"
4
+
5
+ const TRACE_DIR = ".ues-traces"
6
+ const MAX_EVENT_BYTES = 64 * 1024
7
+
8
+ function cleanID(value) {
9
+ const text = String(value || "").trim()
10
+ if (/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,100}$/.test(text)) return text
11
+ return "trace-" + randomUUID()
12
+ }
13
+
14
+ function redactString(value) {
15
+ let text = String(value)
16
+ const patterns = [
17
+ [/(authorization\s*[:=]\s*bearer\s+)[^\s"'\\]+/gi, "$1[REDACTED]"],
18
+ [/\b(sk-[A-Za-z0-9_-]{12,})\b/g, "[REDACTED_OPENAI_KEY]"],
19
+ [/\b(gh[pousr]_[A-Za-z0-9]{20,})\b/g, "[REDACTED_GITHUB_TOKEN]"],
20
+ [/\b(npm_[A-Za-z0-9]{20,})\b/g, "[REDACTED_NPM_TOKEN]"],
21
+ [/((?:api[_-]?key|token|secret|password|passwd|credential)\s*[:=]\s*)("[^"]*"|'[^']*'|[^\s,;}]+)/gi, "$1[REDACTED]"],
22
+ ]
23
+ for (const [pattern, replacement] of patterns) text = text.replace(pattern, replacement)
24
+ return text
25
+ }
26
+
27
+ export function scrubTrajectoryValue(value, depth = 0) {
28
+ if (depth > 12) return "[TRUNCATED_DEPTH]"
29
+ if (typeof value === "string") return redactString(value)
30
+ if (Array.isArray(value)) return value.slice(0, 200).map((item) => scrubTrajectoryValue(item, depth + 1))
31
+ if (!value || typeof value !== "object") return value
32
+ const result = {}
33
+ for (const [key, child] of Object.entries(value)) {
34
+ if (/^(authorization|cookie|set-cookie|api[_-]?key|token|secret|password|passwd|credential)$/i.test(key)) {
35
+ result[key] = "[REDACTED]"
36
+ continue
37
+ }
38
+ result[key] = scrubTrajectoryValue(child, depth + 1)
39
+ }
40
+ return result
41
+ }
42
+
43
+ export function createTraceID(prefix = "run") {
44
+ return cleanID(prefix + "-" + new Date().toISOString().replace(/[:.]/g, "-") + "-" + randomUUID().slice(0, 8))
45
+ }
46
+
47
+ export function trajectoryFile(root, traceID) {
48
+ return path.join(path.resolve(root), TRACE_DIR, cleanID(traceID) + ".jsonl")
49
+ }
50
+
51
+ export async function appendTrajectoryEvent(root, traceID, type, payload = {}) {
52
+ const file = trajectoryFile(root, traceID)
53
+ await mkdir(path.dirname(file), { recursive: true })
54
+ const scrubbed = scrubTrajectoryValue(payload)
55
+ const rawPayload = JSON.stringify(scrubbed)
56
+ const bounded = Buffer.byteLength(rawPayload, "utf8") <= MAX_EVENT_BYTES
57
+ ? scrubbed
58
+ : {
59
+ truncated: true,
60
+ sha256: createHash("sha256").update(rawPayload).digest("hex"),
61
+ preview: redactString(rawPayload.slice(0, MAX_EVENT_BYTES - 1024)),
62
+ }
63
+ const event = {
64
+ schemaVersion: 1,
65
+ id: randomUUID(),
66
+ traceID: cleanID(traceID),
67
+ type: String(type || "event"),
68
+ at: new Date().toISOString(),
69
+ payload: bounded,
70
+ }
71
+ await writeFile(file, JSON.stringify(event) + "\n", { encoding: "utf8", flag: "a" })
72
+ return { file: path.relative(path.resolve(root), file).replaceAll("\\", "/"), event }
73
+ }
74
+
75
+ export async function readTrajectory(root, traceID, options = {}) {
76
+ const file = trajectoryFile(root, traceID)
77
+ const source = await readFile(file, "utf8").catch(() => "")
78
+ const limit = Math.max(1, Math.min(Number(options.limit || 200), 2000))
79
+ const events = source.split(/\r?\n/).filter(Boolean).map((line) => {
80
+ try { return JSON.parse(line) } catch { return null }
81
+ }).filter(Boolean)
82
+ return {
83
+ traceID: cleanID(traceID),
84
+ file: path.relative(path.resolve(root), file).replaceAll("\\", "/"),
85
+ events: events.slice(-limit),
86
+ total: events.length,
87
+ note: "Operational prompts/actions/observations only; hidden chain-of-thought is never recorded.",
88
+ }
89
+ }
@@ -0,0 +1,227 @@
1
+ import { existsSync, readFileSync, readdirSync } from "node:fs"
2
+ import path from "node:path"
3
+ import { spawnSync } from "node:child_process"
4
+
5
+ function within(base, candidate) {
6
+ const root = path.resolve(base)
7
+ const full = path.resolve(candidate)
8
+ return full === root || full.startsWith(root + path.sep)
9
+ }
10
+
11
+ function looksLikeNodeScript(file) {
12
+ try {
13
+ const head = readFileSync(file, "utf8").slice(0, 512)
14
+ const firstLine = head.split(/\r?\n/, 1)[0]
15
+ return /^#!.*(?:\/|\\|\b)node(?:\.exe)?(?:\s|$)/i.test(firstLine)
16
+ } catch {
17
+ return false
18
+ }
19
+ }
20
+
21
+
22
+ function looksLikeWindowsExecutable(file) {
23
+ try {
24
+ const head = readFileSync(file)
25
+ return head.length >= 2 && head[0] === 0x4d && head[1] === 0x5a
26
+ } catch {
27
+ return false
28
+ }
29
+ }
30
+
31
+ function executionForTarget(target) {
32
+ const ext = path.extname(target).toLowerCase()
33
+ if ([".exe", ".com"].includes(ext) && looksLikeWindowsExecutable(target)) {
34
+ return {
35
+ executable: target,
36
+ argsPrefix: [],
37
+ kind: "native",
38
+ entry: target,
39
+ }
40
+ }
41
+ if ([".js", ".mjs", ".cjs"].includes(ext) || (!ext && looksLikeNodeScript(target))) {
42
+ return {
43
+ executable: process.execPath,
44
+ argsPrefix: [target],
45
+ kind: "node-shim",
46
+ entry: target,
47
+ }
48
+ }
49
+ return null
50
+ }
51
+
52
+
53
+ function packageBinTarget(packageDir, commandName) {
54
+ let pkg
55
+ try {
56
+ pkg = JSON.parse(readFileSync(path.join(packageDir, "package.json"), "utf8"))
57
+ } catch {
58
+ return null
59
+ }
60
+
61
+ const bin = pkg?.bin
62
+ let relative = null
63
+ if (typeof bin === "string") {
64
+ const packageCommand = String(pkg?.name || "").split("/").at(-1)
65
+ if (packageCommand === commandName) relative = bin
66
+ } else if (bin && typeof bin === "object" && typeof bin[commandName] === "string") {
67
+ relative = bin[commandName]
68
+ }
69
+ if (!relative) return null
70
+
71
+ const target = path.resolve(packageDir, relative)
72
+ if (!within(packageDir, target) || !existsSync(target)) return null
73
+ return executionForTarget(target) ? target : null
74
+ }
75
+
76
+ function adjacentPackageBin(cmdPath) {
77
+ const fullCmd = path.resolve(String(cmdPath || ""))
78
+ const commandName = path.basename(fullCmd).replace(/\.(?:cmd|bat)$/i, "")
79
+ const nodeModulesRoot = path.join(path.dirname(fullCmd), "node_modules")
80
+ let entries = []
81
+ try {
82
+ entries = readdirSync(nodeModulesRoot, { withFileTypes: true })
83
+ } catch {
84
+ return null
85
+ }
86
+
87
+ let inspected = 0
88
+ for (const entry of entries) {
89
+ if (inspected >= 2000) break
90
+ if (!entry.isDirectory()) continue
91
+
92
+ if (entry.name.startsWith("@")) {
93
+ let scoped = []
94
+ try {
95
+ scoped = readdirSync(path.join(nodeModulesRoot, entry.name), { withFileTypes: true })
96
+ } catch {
97
+ continue
98
+ }
99
+ for (const child of scoped) {
100
+ if (inspected >= 2000) break
101
+ if (!child.isDirectory()) continue
102
+ inspected += 1
103
+ const target = packageBinTarget(
104
+ path.join(nodeModulesRoot, entry.name, child.name),
105
+ commandName,
106
+ )
107
+ if (target) return target
108
+ }
109
+ continue
110
+ }
111
+
112
+ inspected += 1
113
+ const target = packageBinTarget(path.join(nodeModulesRoot, entry.name), commandName)
114
+ if (target) return target
115
+ }
116
+ return null
117
+ }
118
+
119
+ function resolveShimTarget(cmdPath) {
120
+ const fullCmd = path.resolve(String(cmdPath || ""))
121
+ const dir = path.dirname(fullCmd)
122
+
123
+ if (/^npm(?:\.cmd|\.exe)?$/i.test(path.basename(fullCmd))) {
124
+ const npmEntry = path.join(dir, "node_modules", "npm", "bin", "npm-cli.js")
125
+ if (existsSync(npmEntry)) return npmEntry
126
+ }
127
+
128
+ let shim = ""
129
+ try {
130
+ shim = readFileSync(fullCmd, "utf8")
131
+ } catch {
132
+ return null
133
+ }
134
+
135
+ const nodeModulesRoot = path.join(dir, "node_modules")
136
+ const pattern = /node_modules[\\/](?:@[A-Za-z0-9._-]+[\\/])?[A-Za-z0-9._-]+(?:[\\/][A-Za-z0-9._+-]+)*/gi
137
+ const matches = [...shim.matchAll(pattern)].map((match) => match[0]).reverse()
138
+
139
+ for (const match of matches) {
140
+ const portableMatch = match.split(/[\\/]+/).join(path.sep)
141
+ const candidate = path.resolve(dir, portableMatch)
142
+ if (!within(nodeModulesRoot, candidate) || !existsSync(candidate)) continue
143
+ if (executionForTarget(candidate)) return candidate
144
+ }
145
+
146
+ // Some npm shims vary in formatting. Fall back to the adjacent global
147
+ // node_modules metadata and only trust an explicit package.json bin mapping
148
+ // for the command represented by this shim.
149
+ return adjacentPackageBin(fullCmd)
150
+ }
151
+
152
+ export function resolveNodeShimEntry(cmdPath) {
153
+ const target = resolveShimTarget(cmdPath)
154
+ if (!target) return null
155
+ const execution = executionForTarget(target)
156
+ return execution?.kind === "node-shim" ? target : null
157
+ }
158
+
159
+ function resolveShimExecution(cmdPath) {
160
+ const target = resolveShimTarget(cmdPath)
161
+ if (!target) return null
162
+ return executionForTarget(target)
163
+ }
164
+
165
+ export function resolveWindowsCommandCandidates(candidates = []) {
166
+ const values = [...new Set(
167
+ candidates.map((item) => String(item || "").trim()).filter(Boolean),
168
+ )]
169
+
170
+ // Respect PATH ordering, but only return a target that can be launched without
171
+ // invoking cmd.exe. npm commonly emits both an extensionless POSIX shim and a
172
+ // .cmd shim; either may point at a native .exe or a Node launcher.
173
+ for (const candidate of values) {
174
+ if (!existsSync(candidate)) continue
175
+ const ext = path.extname(candidate).toLowerCase()
176
+
177
+ if ([".exe", ".com"].includes(ext)) {
178
+ const direct = executionForTarget(candidate)
179
+ if (direct) return { ...direct, source: candidate }
180
+ continue
181
+ }
182
+
183
+ if ([".cmd", ".bat"].includes(ext)) {
184
+ const resolved = resolveShimExecution(candidate)
185
+ if (resolved) return { ...resolved, source: candidate }
186
+ continue
187
+ }
188
+
189
+ if (!ext) {
190
+ if (looksLikeNodeScript(candidate)) {
191
+ return {
192
+ executable: process.execPath,
193
+ argsPrefix: [candidate],
194
+ kind: "node-script",
195
+ source: candidate,
196
+ entry: candidate,
197
+ }
198
+ }
199
+ // npm's extensionless shim is normally a POSIX shell wrapper even on
200
+ // Windows. Parse its package target instead of trying to execute the shell.
201
+ const resolved = resolveShimExecution(candidate)
202
+ if (resolved) return { ...resolved, source: candidate }
203
+ }
204
+ }
205
+ return null
206
+ }
207
+
208
+ export function resolveWindowsCommand(name) {
209
+ const value = String(name || "").trim()
210
+ if (!value) return null
211
+
212
+ if (path.isAbsolute(value)) {
213
+ return resolveWindowsCommandCandidates([value])
214
+ }
215
+
216
+ const result = spawnWhere(value)
217
+ if (result.status !== 0 || !result.stdout) return null
218
+ const candidates = result.stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean)
219
+ return resolveWindowsCommandCandidates(candidates)
220
+ }
221
+
222
+ function spawnWhere(name) {
223
+ // Lazy import avoidance is unnecessary here; child_process is builtin and this
224
+ // module is Node-only. Kept as one helper so tests can cover candidate selection
225
+ // without invoking the host PATH.
226
+ return spawnSync("where", [name], { encoding: "utf8" })
227
+ }
@@ -3,8 +3,24 @@ import { mkdir, rm } from "node:fs/promises"
3
3
  import { spawnSync } from "node:child_process"
4
4
  import path from "node:path"
5
5
 
6
- function git(root, args) {
7
- return spawnSync("git", args, { cwd: root, encoding: "utf8", maxBuffer: 4 * 1024 * 1024 })
6
+ function git(root, args, options = {}) {
7
+ return spawnSync("git", args, {
8
+ cwd: root,
9
+ encoding: "utf8",
10
+ maxBuffer: 16 * 1024 * 1024,
11
+ input: options.input,
12
+ })
13
+ }
14
+
15
+ function statusFiles(root) {
16
+ const result = git(root, ["status", "--porcelain=v1", "--untracked-files=all"])
17
+ if (result.status !== 0) throw new Error((result.stderr || result.stdout || "git status failed").trim())
18
+ return result.stdout
19
+ .split(/\r?\n/)
20
+ .filter(Boolean)
21
+ .map((line) => line.slice(3).trim())
22
+ .map((value) => value.includes(" -> ") ? value.split(" -> ").at(-1) : value)
23
+ .map((value) => value.replaceAll("\\", "/"))
8
24
  }
9
25
 
10
26
  function safeName(value) {
@@ -13,6 +29,11 @@ function safeName(value) {
13
29
 
14
30
  export async function createTaskSandbox(root, slug, taskID, options = {}) {
15
31
  root = path.resolve(root)
32
+ const status = git(root, ["status", "--porcelain=v1", "--untracked-files=all"])
33
+ if (status.status !== 0) throw new Error((status.stderr || status.stdout || "git status failed").trim())
34
+ if (status.stdout.trim() && options.allowDirtyRoot !== true) {
35
+ throw new Error("sandbox creation requires a clean root working tree so the isolated executor cannot miss existing uncommitted changes")
36
+ }
16
37
  const base = options.baseDir
17
38
  ? path.resolve(options.baseDir)
18
39
  : path.join(path.dirname(root), "." + path.basename(root) + ".ues-sandboxes")
@@ -30,16 +51,77 @@ export async function createTaskSandbox(root, slug, taskID, options = {}) {
30
51
  return { schemaVersion: 1, root, dir, branch, slug, taskID, startPoint }
31
52
  }
32
53
 
54
+ export async function integrateTaskSandbox(root, dir, options = {}) {
55
+ root = path.resolve(root)
56
+ dir = path.resolve(dir)
57
+ if (!existsSync(dir)) throw new Error("sandbox does not exist: " + dir)
58
+
59
+ const rootTop = git(root, ["rev-parse", "--show-toplevel"])
60
+ const sandboxTop = git(dir, ["rev-parse", "--show-toplevel"])
61
+ if (rootTop.status !== 0 || sandboxTop.status !== 0) throw new Error("root and sandbox must be Git worktrees")
62
+ if (path.resolve(sandboxTop.stdout.trim()) !== dir) throw new Error("sandbox path is not the worktree root")
63
+
64
+ // Resolve the merge base against the canonical root HEAD explicitly.
65
+ const rootHead = git(root, ["rev-parse", "HEAD"])
66
+ const sandboxHead = git(dir, ["rev-parse", "HEAD"])
67
+ if (rootHead.status !== 0 || sandboxHead.status !== 0) throw new Error("could not resolve worktree HEADs")
68
+ const base = git(dir, ["merge-base", rootHead.stdout.trim(), sandboxHead.stdout.trim()])
69
+ if (base.status !== 0) throw new Error((base.stderr || base.stdout || "git merge-base failed").trim())
70
+ const baseSha = base.stdout.trim()
71
+
72
+ // Intent-to-add exposes untracked files in a binary diff without committing them.
73
+ const addIntent = git(dir, ["add", "-N", "."])
74
+ if (addIntent.status !== 0) throw new Error((addIntent.stderr || addIntent.stdout || "git add -N failed").trim())
75
+
76
+ const changedRun = git(dir, ["diff", "--name-only", baseSha, "--"])
77
+ if (changedRun.status !== 0) throw new Error((changedRun.stderr || changedRun.stdout || "git diff --name-only failed").trim())
78
+ const changed = changedRun.stdout.split(/\r?\n/).filter(Boolean).map((value) => value.replaceAll("\\", "/"))
79
+ if (changed.length === 0) {
80
+ if (!options.keep) await removeTaskSandbox(root, dir, { force: true, deleteBranch: true })
81
+ return { integrated: true, changed: [], base: baseSha, empty: true }
82
+ }
83
+
84
+ const rootDirty = new Set(statusFiles(root))
85
+ const overlap = changed.filter((file) => rootDirty.has(file))
86
+ if (overlap.length) {
87
+ throw new Error("sandbox integration conflicts with existing root changes: " + overlap.join(", "))
88
+ }
89
+
90
+ const patch = git(dir, ["diff", "--binary", "--no-ext-diff", baseSha, "--"])
91
+ if (patch.status !== 0) throw new Error((patch.stderr || patch.stdout || "git diff failed").trim())
92
+ const applied = git(root, ["apply", "--whitespace=nowarn", "-"], { input: patch.stdout })
93
+ if (applied.status !== 0) {
94
+ throw new Error((applied.stderr || applied.stdout || "git apply failed").trim())
95
+ }
96
+
97
+ if (!options.keep) await removeTaskSandbox(root, dir, { force: true, deleteBranch: true })
98
+ return { integrated: true, changed, base: baseSha, empty: false }
99
+ }
100
+
33
101
  export async function removeTaskSandbox(root, dir, options = {}) {
34
102
  root = path.resolve(root)
35
103
  dir = path.resolve(dir)
104
+ let branch = options.branch || null
105
+ if (!branch && options.deleteBranch && existsSync(dir)) {
106
+ const currentBranch = git(dir, ["branch", "--show-current"])
107
+ if (currentBranch.status === 0) branch = currentBranch.stdout.trim() || null
108
+ }
36
109
  const result = git(root, ["worktree", "remove", ...(options.force ? ["--force"] : []), dir])
37
110
  if (result.status !== 0 && existsSync(dir)) {
38
111
  if (options.force) await rm(dir, { recursive: true, force: true })
39
112
  else throw new Error((result.stderr || result.stdout || "git worktree remove failed").trim())
40
113
  }
41
114
  git(root, ["worktree", "prune"])
42
- return { removed: !existsSync(dir), dir }
115
+ if (options.deleteBranch && branch) {
116
+ if (!branch.startsWith("ues/")) {
117
+ throw new Error("refusing to delete non-UES sandbox branch: " + branch)
118
+ }
119
+ const deleted = git(root, ["branch", "-D", branch])
120
+ if (deleted.status !== 0) {
121
+ throw new Error((deleted.stderr || deleted.stdout || "git branch delete failed").trim())
122
+ }
123
+ }
124
+ return { removed: !existsSync(dir), dir, branch: branch || null }
43
125
  }
44
126
 
45
127
  export function listTaskSandboxes(root) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "opencode-agent-skill",
3
- "version": "7.7.0",
4
- "description": "Adaptive crash-safe engineering runtime, durable orchestration, structured evidence, learning loop and control center for OpenCode",
3
+ "version": "10.0.0",
4
+ "description": "Evidence-first engineering runtime with adaptive context, selective routing, weak-model recovery, bounded ACI and benchmark-gated verification for OpenCode",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "ocskill": "bin/ocskill.mjs"
@@ -23,7 +23,7 @@
23
23
  "evals": "node scripts/eval-skills.mjs",
24
24
  "evals:live:validate": "node scripts/validate-live-suite.mjs",
25
25
  "test": "node --test test/*.test.mjs",
26
- "ci": "npm run syntax && npm run validate && npm run evals && npm run evals:router && npm run evals:live:validate && npm run evals:long:validate && npm test && npm pack --dry-run && npm run smoke:pack",
26
+ "ci": "npm run syntax && npm run validate && npm run evals && npm run evals:router && npm run evals:live:validate && npm run evals:long:validate && npm run evals:polyglot:validate && npm test && npm pack --dry-run && npm run smoke:pack && npm run smoke:plain-install",
27
27
  "postinstall": "node scripts/install.mjs",
28
28
  "preuninstall": "node scripts/uninstall.mjs",
29
29
  "prepublishOnly": "npm run ci",
@@ -39,7 +39,14 @@
39
39
  "evals:router": "node scripts/eval-router.mjs",
40
40
  "evals:long:validate": "node scripts/validate-live-suite.mjs --suite long",
41
41
  "evals:long": "node scripts/eval-live.mjs --suite long",
42
- "dashboard": "node scripts/control-center.mjs"
42
+ "dashboard": "node scripts/control-center.mjs",
43
+ "evals:matrix": "node scripts/eval-matrix.mjs",
44
+ "smoke:plain-install": "node scripts/smoke-plain-install.mjs",
45
+ "release:check-tag": "node scripts/check-release-tag.mjs",
46
+ "evals:polyglot:validate": "node scripts/validate-live-suite.mjs --suite polyglot",
47
+ "evals:polyglot": "node scripts/eval-live.mjs --suite polyglot",
48
+ "evals:matrix:gate": "node scripts/eval-matrix.mjs --require-confidence",
49
+ "evals:ablation": "node scripts/eval-ablation.mjs"
43
50
  },
44
51
  "keywords": [
45
52
  "opencode",
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+ import { readFile } from "node:fs/promises"
3
+ import path from "node:path"
4
+ import { fileURLToPath } from "node:url"
5
+
6
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..")
7
+ const pkg = JSON.parse(await readFile(path.join(root, "package.json"), "utf8"))
8
+ const explicit = process.argv[2] || process.env.GITHUB_REF_NAME || ""
9
+ const tag = String(explicit).trim()
10
+
11
+ if (!tag || !tag.startsWith("v")) {
12
+ console.error("[release] Refusing publish without an explicit v* release tag; package version is " + pkg.version + ".")
13
+ process.exit(1)
14
+ }
15
+
16
+ const expected = "v" + pkg.version
17
+ if (tag !== expected) {
18
+ console.error("[release] Tag/package mismatch: tag=" + tag + ", expected=" + expected)
19
+ process.exit(1)
20
+ }
21
+
22
+ console.log("[release] Tag matches package version: " + tag)
@@ -3,6 +3,7 @@ import { createServer } from "node:http"
3
3
  import { readFile } from "node:fs/promises"
4
4
  import path from "node:path"
5
5
  import { collectControlCenterData, writeControlCenter } from "../lib/control-center.mjs"
6
+ import { recoverStaleTasks } from "../lib/task-engine.mjs"
6
7
 
7
8
  const args = process.argv.slice(2)
8
9
  const root = path.resolve(args[0] && !args[0].startsWith("--") ? args[0] : process.cwd())
@@ -17,6 +18,30 @@ console.log("[ues] Control Center: " + result.file)
17
18
 
18
19
  if (serve) {
19
20
  const server = createServer(async (req, res) => {
21
+ if (req.method === "POST" && req.url === "/api/recover") {
22
+ let body = ""
23
+ for await (const chunk of req) {
24
+ body += chunk
25
+ if (body.length > 16 * 1024) {
26
+ res.writeHead(413)
27
+ res.end("request too large")
28
+ return
29
+ }
30
+ }
31
+ try {
32
+ const input = JSON.parse(body || "{}")
33
+ const result = await recoverStaleTasks(root, String(input.slug || ""))
34
+ res.writeHead(200, {
35
+ "content-type": "application/json; charset=utf-8",
36
+ "cache-control": "no-store",
37
+ })
38
+ res.end(JSON.stringify(result, null, 2) + "\n")
39
+ } catch (error) {
40
+ res.writeHead(400, { "content-type": "text/plain; charset=utf-8" })
41
+ res.end(String(error?.message || error))
42
+ }
43
+ return
44
+ }
20
45
  if (req.url === "/data.json") {
21
46
  const current = await collectControlCenterData(root)
22
47
  const body = JSON.stringify(current, null, 2) + "\n"
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env node
2
+ import { readFile } from "node:fs/promises"
3
+ import path from "node:path"
4
+ import { compareEvalSummaries } from "../lib/eval-ablation.mjs"
5
+
6
+ const args = process.argv.slice(2)
7
+
8
+ function option(name, fallback) {
9
+ const index = args.indexOf(name)
10
+ return index >= 0 && index + 1 < args.length ? args[index + 1] : fallback
11
+ }
12
+
13
+ function positional() {
14
+ const values = []
15
+ for (let index = 0; index < args.length; index += 1) {
16
+ if (args[index].startsWith("--")) {
17
+ if (["--pass-rate-tolerance", "--min-initial-reduction", "--max-token-ratio", "--max-duration-ratio"].includes(args[index])) index += 1
18
+ continue
19
+ }
20
+ values.push(args[index])
21
+ }
22
+ return values
23
+ }
24
+
25
+ const [referenceFile, candidateFile] = positional()
26
+ if (!referenceFile || !candidateFile) {
27
+ console.error("Usage: node scripts/eval-ablation.mjs <reference-summary.json> <candidate-summary.json> [--require-gate] [--min-initial-reduction 0.10]")
28
+ process.exit(2)
29
+ }
30
+
31
+ const [reference, candidate] = await Promise.all([
32
+ readFile(path.resolve(referenceFile), "utf8").then(JSON.parse),
33
+ readFile(path.resolve(candidateFile), "utf8").then(JSON.parse),
34
+ ])
35
+
36
+ const report = compareEvalSummaries(reference, candidate, {
37
+ passRateTolerance: Number(option("--pass-rate-tolerance", "0")),
38
+ minInitialInputReduction: Number(option("--min-initial-reduction", "0.10")),
39
+ maxTotalTokenRatio: Number(option("--max-token-ratio", "1.05")),
40
+ maxDurationRatio: Number(option("--max-duration-ratio", "1.10")),
41
+ })
42
+
43
+ console.log(JSON.stringify(report, null, 2))
44
+ if (args.includes("--require-gate") && !report.gateEligible) process.exitCode = 1