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.
- package/CHANGELOG.md +112 -3
- package/README.md +396 -281
- package/bin/ocskill.mjs +382 -156
- package/docs/DETERMINISTIC-TOOLS.md +25 -8
- package/docs/ENGINEERING-DESIGN.md +31 -13
- package/docs/EVALS.md +34 -12
- package/docs/NPM-PUBLISH.md +6 -6
- package/docs/OPENCODE-COMPAT.md +11 -8
- package/docs/TRACE-SCHEMA.md +15 -2
- package/docs/V8-INTELLIGENCE-RELIABILITY.md +206 -0
- package/docs/V9-SPEED-INTELLIGENCE.md +102 -0
- package/evals/live/tasks.json +6 -6
- package/evals/polyglot/fixtures/polyglot-bench/api/generated/client.ts +2 -0
- package/evals/polyglot/fixtures/polyglot-bench/api/openapi.json +25 -0
- package/evals/polyglot/fixtures/polyglot-bench/db/migrations/20260920_add_order_key.sql +1 -0
- package/evals/polyglot/fixtures/polyglot-bench/dotnet/OrderService.cs +8 -0
- package/evals/polyglot/fixtures/polyglot-bench/java/PriceService.java +5 -0
- package/evals/polyglot/fixtures/polyglot-bench/monorepo/package.json +6 -0
- package/evals/polyglot/fixtures/polyglot-bench/monorepo/packages/api/package.json +4 -0
- package/evals/polyglot/fixtures/polyglot-bench/monorepo/packages/web/package.json +7 -0
- package/evals/polyglot/fixtures/polyglot-bench/monorepo/pnpm-lock.yaml +5 -0
- package/evals/polyglot/fixtures/polyglot-bench/next/app/api/products/route.ts +7 -0
- package/evals/polyglot/fixtures/polyglot-bench/python/tenant_auth.py +4 -0
- package/evals/polyglot/fixtures/polyglot-bench/react-native/keyboard.ts +3 -0
- package/evals/polyglot/graders/polyglot-bench.mjs +101 -0
- package/evals/polyglot/tasks.json +54 -0
- package/global-config/AGENTS.md +78 -160
- package/global-config/agents/integration-verifier.md +1 -1
- package/global-config/agents/plan-checker.md +1 -1
- package/global-config/commands/run.md +9 -5
- package/global-config/plugins/ues-router/capabilities.js +4 -0
- package/global-config/plugins/ues-router/index.js +784 -37
- package/global-config/plugins/ues-router/router.js +175 -23
- package/global-config/plugins/ues-router/runtime-guard.js +265 -0
- package/global-config/skills/engineering-orchestrator/references/long-horizon.md +6 -4
- package/lib/aci.mjs +128 -0
- package/lib/benchmark-confidence.mjs +173 -0
- package/lib/cli-utils.mjs +41 -0
- package/lib/container-sandbox.mjs +102 -0
- package/lib/context-manifest.mjs +300 -22
- package/lib/control-center.mjs +36 -3
- package/lib/eval-ablation.mjs +104 -0
- package/lib/eval-order.mjs +9 -0
- package/lib/eval-report.mjs +11 -0
- package/lib/eval-telemetry.mjs +8 -2
- package/lib/gate-receipt.mjs +52 -0
- package/lib/installer.mjs +39 -25
- package/lib/learning-engine.mjs +236 -38
- package/lib/model-policy.mjs +6 -0
- package/lib/opencode-compat.mjs +25 -10
- package/lib/orchestrator-policy.mjs +195 -21
- package/lib/process-runner.mjs +30 -9
- package/lib/runtime-events.mjs +31 -0
- package/lib/semantic-index.mjs +318 -0
- package/lib/task-engine.mjs +557 -28
- package/lib/trajectory.mjs +89 -0
- package/lib/windows-shim.mjs +227 -0
- package/lib/worktree-sandbox.mjs +85 -3
- package/package.json +11 -4
- package/scripts/check-release-tag.mjs +22 -0
- package/scripts/control-center.mjs +25 -0
- package/scripts/eval-ablation.mjs +44 -0
- package/scripts/eval-live.mjs +39 -58
- package/scripts/eval-matrix.mjs +166 -0
- package/scripts/smoke-packed-install.mjs +138 -4
- package/scripts/smoke-plain-install.mjs +91 -0
- package/scripts/validate-live-suite.mjs +3 -3
- package/scripts/validate.mjs +27 -5
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { existsSync } from "node:fs"
|
|
2
|
+
import { mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises"
|
|
3
|
+
import path from "node:path"
|
|
4
|
+
|
|
5
|
+
const SCHEMA_VERSION = 1
|
|
6
|
+
const CACHE_DIR = ".ues-cache"
|
|
7
|
+
const CACHE_FILE = "semantic-index-v1.json"
|
|
8
|
+
const SOURCE_EXTENSIONS = new Set([
|
|
9
|
+
".js", ".mjs", ".cjs", ".jsx", ".ts", ".tsx", ".py", ".java", ".kt", ".kts",
|
|
10
|
+
".cs", ".go", ".rs", ".rb", ".php", ".vue", ".svelte", ".dart", ".swift", ".sql",
|
|
11
|
+
])
|
|
12
|
+
const SKIP_DIRS = new Set([
|
|
13
|
+
".git", "node_modules", ".next", "dist", "build", "coverage", ".venv", "venv",
|
|
14
|
+
"Pods", "DerivedData", ".gradle", ".idea", ".cache", ".turbo", "target", "bin", "obj",
|
|
15
|
+
".ues-work", ".ues-learning", ".ues-dashboard", ".ues-sandboxes", ".ues-cache", ".ues-traces",
|
|
16
|
+
])
|
|
17
|
+
const STOP_IDENTIFIERS = new Set([
|
|
18
|
+
"const","let","var","function","class","interface","type","export","import","from","return",
|
|
19
|
+
"async","await","if","else","for","while","switch","case","break","continue","true","false",
|
|
20
|
+
"null","undefined","this","new","public","private","protected","static","final","void","string",
|
|
21
|
+
"number","boolean","object","def","self","None","True","False","pass","with","yield","lambda",
|
|
22
|
+
"package","using","namespace","struct","enum","trait","impl","func","map","range","select",
|
|
23
|
+
])
|
|
24
|
+
|
|
25
|
+
function rel(root, file) {
|
|
26
|
+
return path.relative(root, file).replaceAll("\\", "/")
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function safeFull(root, relative) {
|
|
30
|
+
const base = path.resolve(root)
|
|
31
|
+
const full = path.resolve(base, relative)
|
|
32
|
+
if (full !== base && !full.startsWith(base + path.sep)) return null
|
|
33
|
+
return full
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function atomicJson(file, value) {
|
|
37
|
+
await mkdir(path.dirname(file), { recursive: true })
|
|
38
|
+
const temp = file + "." + process.pid + "." + Date.now() + ".tmp"
|
|
39
|
+
await writeFile(temp, JSON.stringify(value) + "\n", "utf8")
|
|
40
|
+
try {
|
|
41
|
+
await rename(temp, file)
|
|
42
|
+
} catch (error) {
|
|
43
|
+
await rm(temp, { force: true }).catch(() => {})
|
|
44
|
+
throw error
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function walk(root, options = {}) {
|
|
49
|
+
const maxFiles = Math.max(100, Number(options.maxFiles || 6000))
|
|
50
|
+
const maxDepth = Math.max(2, Number(options.maxDepth || 14))
|
|
51
|
+
const files = []
|
|
52
|
+
async function visit(dir, depth) {
|
|
53
|
+
if (depth > maxDepth || files.length >= maxFiles) return
|
|
54
|
+
const entries = await readdir(dir, { withFileTypes: true }).catch(() => [])
|
|
55
|
+
entries.sort((a, b) => a.name.localeCompare(b.name))
|
|
56
|
+
for (const entry of entries) {
|
|
57
|
+
if (files.length >= maxFiles) break
|
|
58
|
+
if (entry.isDirectory() && SKIP_DIRS.has(entry.name)) continue
|
|
59
|
+
const full = path.join(dir, entry.name)
|
|
60
|
+
if (entry.isDirectory()) await visit(full, depth + 1)
|
|
61
|
+
else if (entry.isFile() && SOURCE_EXTENSIONS.has(path.extname(entry.name).toLowerCase())) files.push(full)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
await visit(root, 0)
|
|
65
|
+
return { files, truncated: files.length >= maxFiles }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function symbolPatterns(ext) {
|
|
69
|
+
const common = [
|
|
70
|
+
{ kind: "function", re: /(?:export\s+)?(?:async\s+)?function\s+([A-Za-z_$][\w$]*)/ },
|
|
71
|
+
{ kind: "class", re: /(?:export\s+)?(?:abstract\s+)?class\s+([A-Za-z_$][\w$]*)/ },
|
|
72
|
+
{ kind: "interface", re: /(?:export\s+)?interface\s+([A-Za-z_$][\w$]*)/ },
|
|
73
|
+
{ kind: "type", re: /(?:export\s+)?type\s+([A-Za-z_$][\w$]*)\s*=/ },
|
|
74
|
+
{ kind: "binding", re: /(?:export\s+)?(?:const|let|var)\s+([A-Za-z_$][\w$]*)/ },
|
|
75
|
+
]
|
|
76
|
+
if ([".js",".mjs",".cjs",".jsx",".ts",".tsx",".vue",".svelte"].includes(ext)) return common
|
|
77
|
+
if (ext === ".py") return [
|
|
78
|
+
{ kind: "function", re: /^\s*(?:async\s+)?def\s+([A-Za-z_][\w]*)\s*\(/ },
|
|
79
|
+
{ kind: "class", re: /^\s*class\s+([A-Za-z_][\w]*)/ },
|
|
80
|
+
]
|
|
81
|
+
if ([".java",".kt",".kts",".cs"].includes(ext)) return [
|
|
82
|
+
{ kind: "type", re: /\b(?:class|interface|record|enum|object)\s+([A-Za-z_$][\w$]*)/ },
|
|
83
|
+
{ kind: "function", re: /\b(?:public|private|protected|internal|static|final|virtual|override|async|synchronized|abstract|sealed|partial|readonly|suspend|open|inline|\s)+[\w<>,?\[\].:]+\s+([A-Za-z_$][\w$]*)\s*\(/ },
|
|
84
|
+
]
|
|
85
|
+
if (ext === ".go") return [
|
|
86
|
+
{ kind: "function", re: /^\s*func\s+(?:\([^)]*\)\s*)?([A-Za-z_][\w]*)\s*\(/ },
|
|
87
|
+
{ kind: "type", re: /^\s*type\s+([A-Za-z_][\w]*)\s+/ },
|
|
88
|
+
]
|
|
89
|
+
if (ext === ".rs") return [
|
|
90
|
+
{ kind: "function", re: /^\s*(?:pub\s+)?(?:async\s+)?fn\s+([A-Za-z_][\w]*)/ },
|
|
91
|
+
{ kind: "type", re: /^\s*(?:pub\s+)?(?:struct|enum|trait|type)\s+([A-Za-z_][\w]*)/ },
|
|
92
|
+
]
|
|
93
|
+
if (ext === ".dart") return [
|
|
94
|
+
{ kind: "class", re: /^\s*(?:abstract\s+)?class\s+([A-Za-z_][\w]*)/ },
|
|
95
|
+
{ kind: "function", re: /^\s*(?:[\w<>,?\[\]]+\s+)+([A-Za-z_][\w]*)\s*\(/ },
|
|
96
|
+
]
|
|
97
|
+
if (ext === ".swift") return [
|
|
98
|
+
{ kind: "type", re: /^\s*(?:public\s+|internal\s+|private\s+)?(?:class|struct|enum|protocol)\s+([A-Za-z_][\w]*)/ },
|
|
99
|
+
{ kind: "function", re: /^\s*(?:public\s+|internal\s+|private\s+)?func\s+([A-Za-z_][\w]*)/ },
|
|
100
|
+
]
|
|
101
|
+
if (ext === ".sql") return [
|
|
102
|
+
{ kind: "table", re: /^\s*create\s+(?:or\s+replace\s+)?table\s+(?:if\s+not\s+exists\s+)?["`\[]?([A-Za-z_][\w$.-]*)/i },
|
|
103
|
+
{ kind: "view", re: /^\s*create\s+(?:or\s+replace\s+)?view\s+["`\[]?([A-Za-z_][\w$.-]*)/i },
|
|
104
|
+
{ kind: "function", re: /^\s*create\s+(?:or\s+replace\s+)?(?:function|procedure)\s+["`\[]?([A-Za-z_][\w$.-]*)/i },
|
|
105
|
+
]
|
|
106
|
+
return common
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function parseSource(source, ext) {
|
|
110
|
+
const lines = String(source || "").split(/\r?\n/)
|
|
111
|
+
const symbols = []
|
|
112
|
+
const patterns = symbolPatterns(ext)
|
|
113
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
114
|
+
const line = lines[index]
|
|
115
|
+
for (const pattern of patterns) {
|
|
116
|
+
const match = line.match(pattern.re)
|
|
117
|
+
if (match?.[1]) {
|
|
118
|
+
symbols.push({
|
|
119
|
+
name: match[1],
|
|
120
|
+
kind: pattern.kind,
|
|
121
|
+
line: index + 1,
|
|
122
|
+
preview: line.trim().slice(0, 240),
|
|
123
|
+
})
|
|
124
|
+
break
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const counts = new Map()
|
|
130
|
+
for (const match of String(source || "").matchAll(/\b[A-Za-z_$][A-Za-z0-9_$]{2,}\b/g)) {
|
|
131
|
+
const value = match[0]
|
|
132
|
+
if (STOP_IDENTIFIERS.has(value)) continue
|
|
133
|
+
counts.set(value, (counts.get(value) || 0) + 1)
|
|
134
|
+
}
|
|
135
|
+
const identifiers = Object.fromEntries(
|
|
136
|
+
[...counts.entries()]
|
|
137
|
+
.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
|
|
138
|
+
.slice(0, 320),
|
|
139
|
+
)
|
|
140
|
+
return { symbols: symbols.slice(0, 400), identifiers }
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function cachePath(root) {
|
|
144
|
+
return path.join(root, CACHE_DIR, CACHE_FILE)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function readCache(root) {
|
|
148
|
+
try {
|
|
149
|
+
const parsed = JSON.parse(await readFile(cachePath(root), "utf8"))
|
|
150
|
+
if (parsed?.schemaVersion !== SCHEMA_VERSION || typeof parsed.files !== "object") return null
|
|
151
|
+
return parsed
|
|
152
|
+
} catch {
|
|
153
|
+
return null
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export async function buildSemanticIndex(root = process.cwd(), options = {}) {
|
|
158
|
+
const started = Date.now()
|
|
159
|
+
root = path.resolve(root)
|
|
160
|
+
const previous = options.rebuild ? null : await readCache(root)
|
|
161
|
+
const discovered = await walk(root, options)
|
|
162
|
+
const nextFiles = {}
|
|
163
|
+
let reused = 0
|
|
164
|
+
let reparsed = 0
|
|
165
|
+
let skippedLarge = 0
|
|
166
|
+
const maxFileBytes = Math.max(64 * 1024, Number(options.maxFileBytes || 1024 * 1024))
|
|
167
|
+
|
|
168
|
+
for (const full of discovered.files) {
|
|
169
|
+
const info = await stat(full).catch(() => null)
|
|
170
|
+
if (!info?.isFile()) continue
|
|
171
|
+
const relative = rel(root, full)
|
|
172
|
+
const signature = info.size + ":" + Math.trunc(info.mtimeMs)
|
|
173
|
+
const old = previous?.files?.[relative]
|
|
174
|
+
if (old?.signature === signature) {
|
|
175
|
+
nextFiles[relative] = old
|
|
176
|
+
reused += 1
|
|
177
|
+
continue
|
|
178
|
+
}
|
|
179
|
+
if (info.size > maxFileBytes) {
|
|
180
|
+
nextFiles[relative] = { signature, bytes: info.size, skipped: "too-large", symbols: [], identifiers: {} }
|
|
181
|
+
skippedLarge += 1
|
|
182
|
+
continue
|
|
183
|
+
}
|
|
184
|
+
const source = await readFile(full, "utf8").catch(() => "")
|
|
185
|
+
const parsed = source ? parseSource(source, path.extname(relative).toLowerCase()) : { symbols: [], identifiers: {} }
|
|
186
|
+
nextFiles[relative] = {
|
|
187
|
+
signature,
|
|
188
|
+
bytes: info.size,
|
|
189
|
+
symbols: parsed.symbols,
|
|
190
|
+
identifiers: parsed.identifiers,
|
|
191
|
+
}
|
|
192
|
+
reparsed += 1
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const removed = previous
|
|
196
|
+
? Object.keys(previous.files || {}).filter((file) => !Object.hasOwn(nextFiles, file)).length
|
|
197
|
+
: 0
|
|
198
|
+
const index = {
|
|
199
|
+
schemaVersion: SCHEMA_VERSION,
|
|
200
|
+
kind: "ues-semantic-index",
|
|
201
|
+
root,
|
|
202
|
+
generatedAt: new Date().toISOString(),
|
|
203
|
+
truncated: discovered.truncated,
|
|
204
|
+
files: nextFiles,
|
|
205
|
+
}
|
|
206
|
+
const changed = !previous || reparsed > 0 || removed > 0 || skippedLarge > 0
|
|
207
|
+
if (changed || options.rebuild) await atomicJson(cachePath(root), index)
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
index,
|
|
211
|
+
stats: {
|
|
212
|
+
files: Object.keys(nextFiles).length,
|
|
213
|
+
reused,
|
|
214
|
+
reparsed,
|
|
215
|
+
removed,
|
|
216
|
+
skippedLarge,
|
|
217
|
+
truncated: discovered.truncated,
|
|
218
|
+
cacheFile: rel(root, cachePath(root)),
|
|
219
|
+
durationMs: Date.now() - started,
|
|
220
|
+
},
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function queryTerms(query) {
|
|
225
|
+
return [...new Set(
|
|
226
|
+
String(query || "")
|
|
227
|
+
.split(/[^\p{L}\p{N}_$.-]+/u)
|
|
228
|
+
.map((item) => item.trim())
|
|
229
|
+
.filter((item) => item.length >= 2),
|
|
230
|
+
)].slice(0, 16)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export async function querySemanticIndex(root, query, options = {}) {
|
|
234
|
+
const terms = queryTerms(query)
|
|
235
|
+
if (!terms.length) return { query: String(query || ""), terms: [], results: [], stats: null }
|
|
236
|
+
const built = await buildSemanticIndex(root, options)
|
|
237
|
+
const lowerTerms = terms.map((term) => term.toLowerCase())
|
|
238
|
+
const results = []
|
|
239
|
+
|
|
240
|
+
for (const [file, entry] of Object.entries(built.index.files)) {
|
|
241
|
+
let score = 0
|
|
242
|
+
const reasons = []
|
|
243
|
+
const definitions = []
|
|
244
|
+
const lowerPath = file.toLowerCase()
|
|
245
|
+
for (let i = 0; i < terms.length; i += 1) {
|
|
246
|
+
const term = terms[i]
|
|
247
|
+
const lower = lowerTerms[i]
|
|
248
|
+
if (lowerPath.includes(lower)) {
|
|
249
|
+
score += 4
|
|
250
|
+
reasons.push("path:" + term)
|
|
251
|
+
}
|
|
252
|
+
for (const symbol of entry.symbols || []) {
|
|
253
|
+
const name = String(symbol.name || "")
|
|
254
|
+
const symbolLower = name.toLowerCase()
|
|
255
|
+
if (symbolLower === lower) {
|
|
256
|
+
score += 24
|
|
257
|
+
definitions.push(symbol)
|
|
258
|
+
reasons.push("definition:" + name)
|
|
259
|
+
} else if (symbolLower.startsWith(lower) || symbolLower.includes(lower)) {
|
|
260
|
+
score += 10
|
|
261
|
+
definitions.push(symbol)
|
|
262
|
+
reasons.push("symbol:" + name)
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
const identifiers = entry.identifiers || {}
|
|
266
|
+
const direct = Number(identifiers[term] || 0)
|
|
267
|
+
let folded = direct
|
|
268
|
+
if (!direct) {
|
|
269
|
+
for (const [identifier, count] of Object.entries(identifiers)) {
|
|
270
|
+
if (identifier.toLowerCase() === lower) {
|
|
271
|
+
folded = Number(count || 0)
|
|
272
|
+
break
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if (folded > 0) {
|
|
277
|
+
score += Math.min(12, 2 + Math.log2(folded + 1) * 2)
|
|
278
|
+
reasons.push("reference:" + term + "x" + folded)
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if (score <= 0) continue
|
|
282
|
+
results.push({
|
|
283
|
+
path: file,
|
|
284
|
+
score: Number(score.toFixed(3)),
|
|
285
|
+
definitions: definitions
|
|
286
|
+
.filter((item, index, list) => list.findIndex((other) => other.name === item.name && other.line === item.line) === index)
|
|
287
|
+
.slice(0, 12),
|
|
288
|
+
reasons: [...new Set(reasons)].slice(0, 12),
|
|
289
|
+
})
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
results.sort((a, b) => b.score - a.score || a.path.localeCompare(b.path))
|
|
293
|
+
return {
|
|
294
|
+
schemaVersion: 1,
|
|
295
|
+
kind: "ues-semantic-query",
|
|
296
|
+
query: String(query || ""),
|
|
297
|
+
terms,
|
|
298
|
+
evidenceLevel: "syntax-aware-lexical",
|
|
299
|
+
results: results.slice(0, Math.max(1, Math.min(Number(options.limit || 20), 100))),
|
|
300
|
+
stats: built.stats,
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export async function semanticIndexStatus(root = process.cwd()) {
|
|
305
|
+
root = path.resolve(root)
|
|
306
|
+
const file = cachePath(root)
|
|
307
|
+
const exists = existsSync(file)
|
|
308
|
+
if (!exists) return { exists: false, cacheFile: rel(root, file) }
|
|
309
|
+
const cached = await readCache(root)
|
|
310
|
+
return {
|
|
311
|
+
exists: Boolean(cached),
|
|
312
|
+
cacheFile: rel(root, file),
|
|
313
|
+
schemaVersion: cached?.schemaVersion || null,
|
|
314
|
+
generatedAt: cached?.generatedAt || null,
|
|
315
|
+
files: cached ? Object.keys(cached.files || {}).length : 0,
|
|
316
|
+
truncated: Boolean(cached?.truncated),
|
|
317
|
+
}
|
|
318
|
+
}
|