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,173 @@
1
+ function asBool(value) {
2
+ return value === true
3
+ }
4
+
5
+ function pairKey(item) {
6
+ return [item.suite || "", item.task || "", item.trial || 1].join("::")
7
+ }
8
+
9
+ function exactSignPValue(a, b) {
10
+ const n = a + b
11
+ if (n <= 0) return 1
12
+ const k = Math.min(a, b)
13
+ let probability = 2 ** (-n)
14
+ let cumulative = probability
15
+ for (let i = 1; i <= k; i += 1) {
16
+ probability *= (n - i + 1) / i
17
+ cumulative += probability
18
+ }
19
+ return Math.min(1, cumulative * 2)
20
+ }
21
+
22
+ function mean(values) {
23
+ const usable = values.filter((value) => Number.isFinite(value))
24
+ return usable.length ? usable.reduce((sum, value) => sum + value, 0) / usable.length : null
25
+ }
26
+
27
+ function positiveMean(values) {
28
+ return mean(values.map(Number).filter((value) => Number.isFinite(value) && value > 0))
29
+ }
30
+
31
+ function ratio(candidate, reference) {
32
+ return reference && candidate ? candidate / reference : null
33
+ }
34
+
35
+ export function pairedBenchmarkConfidence(results, options = {}) {
36
+ const byKey = new Map()
37
+ for (const item of results || []) {
38
+ if (!["baseline", "ues"].includes(item?.mode)) continue
39
+ const key = pairKey(item)
40
+ const pair = byKey.get(key) || {}
41
+ pair[item.mode] = item
42
+ byKey.set(key, pair)
43
+ }
44
+ const pairs = [...byKey.entries()]
45
+ .filter(([, pair]) => pair.baseline && pair.ues)
46
+ .map(([key, pair]) => ({ key, ...pair }))
47
+
48
+ let bothPass = 0
49
+ let bothFail = 0
50
+ let baselineOnly = 0
51
+ let uesOnly = 0
52
+ for (const pair of pairs) {
53
+ const b = asBool(pair.baseline.passed)
54
+ const u = asBool(pair.ues.passed)
55
+ if (b && u) bothPass += 1
56
+ else if (!b && !u) bothFail += 1
57
+ else if (b) baselineOnly += 1
58
+ else uesOnly += 1
59
+ }
60
+ const total = pairs.length
61
+ const baselinePassed = bothPass + baselineOnly
62
+ const uesPassed = bothPass + uesOnly
63
+ const baselinePassRate = total ? baselinePassed / total : 0
64
+ const uesPassRate = total ? uesPassed / total : 0
65
+ const delta = uesPassRate - baselinePassRate
66
+ const pValue = exactSignPValue(uesOnly, baselineOnly)
67
+
68
+ const suites = {}
69
+ for (const pair of pairs) {
70
+ const suite = pair.baseline.suite || pair.ues.suite || "unknown"
71
+ suites[suite] ??= { total: 0, baselinePassed: 0, uesPassed: 0 }
72
+ suites[suite].total += 1
73
+ if (pair.baseline.passed) suites[suite].baselinePassed += 1
74
+ if (pair.ues.passed) suites[suite].uesPassed += 1
75
+ }
76
+ for (const item of Object.values(suites)) {
77
+ item.baselinePassRate = item.total ? item.baselinePassed / item.total : 0
78
+ item.uesPassRate = item.total ? item.uesPassed / item.total : 0
79
+ item.delta = item.uesPassRate - item.baselinePassRate
80
+ }
81
+
82
+ const baselineDuration = mean(pairs.map((pair) => Number(pair.baseline.durationMs)))
83
+ const uesDuration = mean(pairs.map((pair) => Number(pair.ues.durationMs)))
84
+ const durationRatio = ratio(uesDuration, baselineDuration)
85
+
86
+ const baselineCost = positiveMean(pairs.map((pair) => pair.baseline.telemetry?.cost))
87
+ const uesCost = positiveMean(pairs.map((pair) => pair.ues.telemetry?.cost))
88
+ const costRatio = ratio(uesCost, baselineCost)
89
+
90
+ const baselineInitialInput = positiveMean(
91
+ pairs.map((pair) => pair.baseline.telemetry?.firstUsage?.input),
92
+ )
93
+ const uesInitialInput = positiveMean(
94
+ pairs.map((pair) => pair.ues.telemetry?.firstUsage?.input),
95
+ )
96
+ const initialInputRatio = ratio(uesInitialInput, baselineInitialInput)
97
+
98
+ const baselineTokens = positiveMean(
99
+ pairs.map((pair) => pair.baseline.telemetry?.tokens?.total),
100
+ )
101
+ const uesTokens = positiveMean(
102
+ pairs.map((pair) => pair.ues.telemetry?.tokens?.total),
103
+ )
104
+ const tokenRatio = ratio(uesTokens, baselineTokens)
105
+
106
+ const minPairs = Math.max(1, Number(options.minPairs || 20))
107
+ const alpha = Math.min(0.5, Math.max(0.0001, Number(options.alpha || 0.05)))
108
+ const minDelta = Math.max(0, Number(options.minDelta || 0))
109
+ const suiteRegressionTolerance = Math.max(0, Number(options.suiteRegressionTolerance || 0))
110
+ const maxDurationRatio = Math.max(1, Number(options.maxDurationRatio || 1.75))
111
+ const maxInitialInputRatio = Math.max(1, Number(options.maxInitialInputRatio || 1.5))
112
+ const maxTokenRatio = Math.max(1, Number(options.maxTokenRatio || 1.75))
113
+ const noSuiteRegression = Object.values(suites).every((item) => item.delta >= -suiteRegressionTolerance)
114
+ const speedAcceptable = durationRatio == null || durationRatio <= maxDurationRatio
115
+ const initialInputAcceptable = initialInputRatio == null || initialInputRatio <= maxInitialInputRatio
116
+ const tokensAcceptable = tokenRatio == null || tokenRatio <= maxTokenRatio
117
+
118
+ const checks = {
119
+ pairedCoverage: total >= minPairs,
120
+ positiveDelta: delta > minDelta,
121
+ moreWinsThanLosses: uesOnly > baselineOnly,
122
+ statisticallySupported: pValue <= alpha,
123
+ noSuiteRegression,
124
+ speedAcceptable,
125
+ initialInputAcceptable,
126
+ tokensAcceptable,
127
+ }
128
+ return {
129
+ schemaVersion: 2,
130
+ kind: "ues-paired-benchmark-confidence",
131
+ pairs: total,
132
+ bothPass,
133
+ bothFail,
134
+ baselineOnly,
135
+ uesOnly,
136
+ baselinePassed,
137
+ uesPassed,
138
+ baselinePassRate,
139
+ uesPassRate,
140
+ delta,
141
+ pValue,
142
+ alpha,
143
+ minPairs,
144
+ minDelta,
145
+ suiteRegressionTolerance,
146
+ duration: {
147
+ baselineMeanMs: baselineDuration,
148
+ uesMeanMs: uesDuration,
149
+ ratio: durationRatio,
150
+ maxRatio: maxDurationRatio,
151
+ },
152
+ initialInput: {
153
+ baselineMeanTokens: baselineInitialInput,
154
+ uesMeanTokens: uesInitialInput,
155
+ ratio: initialInputRatio,
156
+ maxRatio: maxInitialInputRatio,
157
+ },
158
+ tokens: {
159
+ baselineMean: baselineTokens,
160
+ uesMean: uesTokens,
161
+ ratio: tokenRatio,
162
+ maxRatio: maxTokenRatio,
163
+ },
164
+ cost: {
165
+ baselineMean: baselineCost,
166
+ uesMean: uesCost,
167
+ ratio: costRatio,
168
+ },
169
+ suites,
170
+ checks,
171
+ promotionEligible: Object.values(checks).every(Boolean),
172
+ }
173
+ }
@@ -0,0 +1,41 @@
1
+ import { readFileSync } from "node:fs"
2
+ import path from "node:path"
3
+
4
+ export function clipOutput(value, { head = 8000, tail = 8000 } = {}) {
5
+ const text = String(value ?? "")
6
+ if (text.length <= head + tail) return text
7
+ return text.slice(0, head) + "\n...[truncated]\n" + text.slice(-tail)
8
+ }
9
+
10
+ export function positionalArg(args, index) {
11
+ const value = args[index]
12
+ if (value == null || value === "" || value === "--" || value.startsWith("--")) return null
13
+ return value
14
+ }
15
+
16
+ export function readJsonFile(file) {
17
+ return JSON.parse(readFileSync(path.resolve(file), "utf8"))
18
+ }
19
+
20
+ export function readTextFile(file) {
21
+ return readFileSync(path.resolve(file), "utf8")
22
+ }
23
+
24
+ export function optionInt(args, name, fallback) {
25
+ const index = args.indexOf(name)
26
+ const raw = index >= 0 ? args[index + 1] : null
27
+ return Number.parseInt(raw || String(fallback), 10)
28
+ }
29
+
30
+ export function optionIntOrUndefined(args, name) {
31
+ return optionInt(args, name, 0) || undefined
32
+ }
33
+
34
+ export function optionValue(args, name) {
35
+ const index = args.indexOf(name)
36
+ return index >= 0 ? args[index + 1] : null
37
+ }
38
+
39
+ export function errorMessage(error) {
40
+ return error instanceof Error ? error.message : error
41
+ }
@@ -0,0 +1,102 @@
1
+ import { spawnSync } from "node:child_process"
2
+ import path from "node:path"
3
+
4
+ function probe(engine) {
5
+ const result = spawnSync(engine, ["--version"], { encoding: "utf8", timeout: 5000 })
6
+ return {
7
+ engine,
8
+ available: result.status === 0,
9
+ version: result.status === 0 ? String(result.stdout || result.stderr || "").trim() : null,
10
+ error: result.status === 0 ? null : String(result.stderr || result.stdout || "").trim() || "unavailable",
11
+ }
12
+ }
13
+
14
+ export function containerSandboxCapability(preferred = null) {
15
+ const names = preferred ? [preferred] : ["docker", "podman"]
16
+ const probes = names.map(probe)
17
+ const selected = probes.find((item) => item.available) || null
18
+ return {
19
+ schemaVersion: 1,
20
+ available: Boolean(selected),
21
+ selected,
22
+ probes,
23
+ guarantees: selected ? [
24
+ "network disabled by default",
25
+ "Linux capabilities dropped",
26
+ "no-new-privileges",
27
+ "read-only container root",
28
+ "bounded pids/memory/cpu",
29
+ "only requested workspace bind-mounted read-write",
30
+ "no host environment secrets forwarded by default",
31
+ ] : [],
32
+ limitation: "This isolates deterministic verification commands, not the OpenCode model session itself.",
33
+ }
34
+ }
35
+
36
+ function validImage(image) {
37
+ return /^[A-Za-z0-9][A-Za-z0-9._/:@-]{0,240}$/.test(String(image || ""))
38
+ }
39
+
40
+ export function buildContainerSandboxArgs(root, image, command, commandArgs = [], options = {}) {
41
+ if (!validImage(image)) throw new Error("container image is required and must be a concrete image reference")
42
+ if (!command) throw new Error("sandbox command is required")
43
+ root = path.resolve(root)
44
+ const memory = String(options.memory || "2g")
45
+ const cpus = String(options.cpus || "2")
46
+ const pids = String(options.pids || "256")
47
+ const args = [
48
+ "run", "--rm", "--init",
49
+ "--network", options.network === "bridge" ? "bridge" : "none",
50
+ "--cap-drop", "ALL",
51
+ "--security-opt", "no-new-privileges",
52
+ "--read-only",
53
+ "--pids-limit", pids,
54
+ "--memory", memory,
55
+ "--cpus", cpus,
56
+ "--tmpfs", "/tmp:rw,noexec,nosuid,size=256m",
57
+ "--mount", "type=bind,source=" + root + ",target=/workspace",
58
+ "--workdir", "/workspace",
59
+ "--env", "CI=true",
60
+ ]
61
+ if (process.platform !== "win32" && typeof process.getuid === "function" && typeof process.getgid === "function") {
62
+ args.push("--user", process.getuid() + ":" + process.getgid())
63
+ }
64
+ args.push(image, command, ...commandArgs)
65
+ return args
66
+ }
67
+
68
+ export function runContainerSandbox(root, input = {}) {
69
+ const capability = containerSandboxCapability(input.engine || null)
70
+ if (!capability.available) {
71
+ const error = new Error("container sandbox requested but Docker/Podman is unavailable")
72
+ error.capability = capability
73
+ throw error
74
+ }
75
+ const args = buildContainerSandboxArgs(root, input.image, input.command, input.args || [], input)
76
+ const started = Date.now()
77
+ const result = spawnSync(capability.selected.engine, args, {
78
+ cwd: path.resolve(root),
79
+ encoding: "utf8",
80
+ maxBuffer: Math.max(1024 * 1024, Number(input.maxBuffer || 8 * 1024 * 1024)),
81
+ timeout: Math.max(1000, Number(input.timeoutMs || 10 * 60_000)),
82
+ env: {
83
+ SystemRoot: process.env.SystemRoot,
84
+ WINDIR: process.env.WINDIR,
85
+ PATH: process.env.PATH,
86
+ PATHEXT: process.env.PATHEXT,
87
+ HOME: process.env.HOME,
88
+ USERPROFILE: process.env.USERPROFILE,
89
+ },
90
+ })
91
+ return {
92
+ schemaVersion: 1,
93
+ engine: capability.selected,
94
+ args,
95
+ status: result.status ?? 1,
96
+ signal: result.signal || null,
97
+ stdout: String(result.stdout || ""),
98
+ stderr: String(result.stderr || ""),
99
+ durationMs: Date.now() - started,
100
+ timedOut: Boolean(result.error?.code === "ETIMEDOUT"),
101
+ }
102
+ }