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,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
+ }