opencode-agent-skill 13.0.0-beta.1 → 14.2.0-beta.1

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 (95) hide show
  1. package/README.md +1557 -606
  2. package/bin/ocskill.mjs +172 -24
  3. package/docs/OPENCODE-COMPAT.md +34 -95
  4. package/docs/PI-COMPAT.md +188 -0
  5. package/docs/V14-CONTEXT-MEMORY-FABRIC.md +70 -0
  6. package/docs/V14.1-QUALITY-PERFORMANCE-FABRIC.md +114 -0
  7. package/docs/V14.2-TURBO-WEAK-MODEL-RUNTIME.md +448 -0
  8. package/evals/v14/tasks.json +46 -0
  9. package/global-config/agents/executor.md +7 -0
  10. package/global-config/agents/visual-verifier.md +22 -3
  11. package/global-config/commands/run.md +27 -20
  12. package/global-config/plugins/ues-router/command-runtime.js +54 -0
  13. package/global-config/plugins/ues-router/index.js +30 -24
  14. package/global-config/plugins/ues-router/policy-runtime.js +7 -0
  15. package/global-config/plugins/ues-router/router.js +12 -2
  16. package/global-config/skills/ecommerce-engineering/SKILL.md +1 -1
  17. package/global-config/skills/file-upload-engineering/SKILL.md +1 -1
  18. package/global-config/skills/git-safety/SKILL.md +1 -1
  19. package/global-config/skills/nestjs-engineering/SKILL.md +1 -1
  20. package/global-config/skills/performance-engineering/SKILL.md +1 -1
  21. package/global-config/skills/react-native-engineering/SKILL.md +1 -1
  22. package/global-config/skills/rest-api-design/SKILL.md +1 -1
  23. package/global-config/skills/ui-ux-engineering/SKILL.md +1 -1
  24. package/lib/adaptive-context-budget.mjs +97 -0
  25. package/lib/affected-tests.mjs +260 -0
  26. package/lib/benchmark-confidence.mjs +41 -2
  27. package/lib/browser-mcp-routing.mjs +166 -0
  28. package/lib/capability-fabric.mjs +336 -0
  29. package/lib/capability-registry.mjs +9 -0
  30. package/lib/context-engine-v11.mjs +65 -1
  31. package/lib/context-graph-rank.mjs +118 -0
  32. package/lib/context-manifest.mjs +97 -18
  33. package/lib/control-center.mjs +19 -1
  34. package/lib/dynamic-workflow.mjs +3 -1
  35. package/lib/evidence-store.mjs +82 -1
  36. package/lib/hierarchical-context.mjs +215 -0
  37. package/lib/memory-engine.mjs +465 -0
  38. package/lib/model-performance.mjs +33 -8
  39. package/lib/model-policy.mjs +3 -3
  40. package/lib/orchestrator-policy.mjs +5 -209
  41. package/lib/performance-fabric.mjs +229 -0
  42. package/lib/pi-rpc-pool.mjs +433 -0
  43. package/lib/process-hang-detector.mjs +83 -0
  44. package/lib/process-supervisor.mjs +193 -0
  45. package/lib/prompt-cache.mjs +2 -0
  46. package/lib/repo-graph.mjs +53 -2
  47. package/lib/runtime-config.mjs +31 -0
  48. package/lib/safety.mjs +132 -0
  49. package/lib/semantic-index.mjs +52 -3
  50. package/lib/skill-compiler.mjs +128 -0
  51. package/lib/skill-quality.mjs +48 -2
  52. package/lib/task-engine.mjs +66 -5
  53. package/lib/task-policy.mjs +235 -0
  54. package/lib/verification-broker.mjs +284 -0
  55. package/lib/verification-command.mjs +111 -0
  56. package/lib/windows-shim.mjs +35 -0
  57. package/lib/workspace-fingerprint.mjs +198 -0
  58. package/package.json +52 -42
  59. package/pi/extensions/ues-child-runtime.ts +238 -0
  60. package/pi/extensions/ues.ts +3200 -0
  61. package/pi/prompts/ues-audit.md +9 -0
  62. package/pi/prompts/ues-critique.md +9 -0
  63. package/pi/prompts/ues-debug.md +9 -0
  64. package/pi/prompts/ues-feature.md +9 -0
  65. package/pi/prompts/ues-fix.md +9 -0
  66. package/pi/prompts/ues-plan.md +9 -0
  67. package/pi/prompts/ues-research.md +9 -0
  68. package/pi/prompts/ues-resume.md +9 -0
  69. package/pi/prompts/ues-review.md +7 -0
  70. package/pi/prompts/ues-run.md +17 -0
  71. package/pi/prompts/ues-verify.md +9 -0
  72. package/scripts/check-release-consistency.mjs +119 -185
  73. package/scripts/check-runtime-exports.mjs +66 -0
  74. package/scripts/check-source-integrity.mjs +184 -0
  75. package/scripts/eval-pi.mjs +492 -0
  76. package/scripts/install.mjs +16 -0
  77. package/scripts/smoke-package-closure.mjs +110 -0
  78. package/scripts/smoke-packed-install.mjs +24 -11
  79. package/scripts/smoke-pi-extension.mjs +144 -0
  80. package/scripts/uninstall.mjs +44 -0
  81. package/CHANGELOG.md +0 -405
  82. package/docs/DETERMINISTIC-TOOLS.md +0 -105
  83. package/docs/ENGINEERING-DESIGN.md +0 -194
  84. package/docs/EVALS.md +0 -158
  85. package/docs/GITHUB-RULESET.md +0 -50
  86. package/docs/NPM-PUBLISH.md +0 -116
  87. package/docs/RESEARCH-SOURCES.md +0 -37
  88. package/docs/TRACE-SCHEMA.md +0 -122
  89. package/docs/V11-PERCEPTION-ADAPTIVE-EXECUTION.md +0 -75
  90. package/docs/V11-PERCEPTION-ADAPTIVE.md +0 -220
  91. package/docs/V12-WEAK-MODEL-INTELLIGENCE.md +0 -27
  92. package/docs/V13-PARALLEL-WEAK-MODEL-RUNTIME.md +0 -75
  93. package/docs/V7-INTELLIGENCE-RUNTIME.md +0 -166
  94. package/docs/V8-INTELLIGENCE-RELIABILITY.md +0 -206
  95. package/docs/V9-SPEED-INTELLIGENCE.md +0 -102
@@ -0,0 +1,465 @@
1
+ import { createHash } from "node:crypto"
2
+ import { existsSync } from "node:fs"
3
+ import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises"
4
+ import { spawnSync } from "node:child_process"
5
+ import path from "node:path"
6
+ import { evidenceExists, putEvidence } from "./evidence-store.mjs"
7
+
8
+ const MEMORY_DIR = ".ues-memory"
9
+ const MEMORY_FILE = "MEMORY.json"
10
+ const SCHEMA_VERSION = 1
11
+ const VALID_TYPES = new Set(["episodic", "semantic", "procedural", "failure", "decision"])
12
+ const VALID_SCOPES = new Set(["global", "project", "module", "file"])
13
+ const STOP = new Set([
14
+ "this","that","with","from","into","then","than","when","where","what","your","have","will","task","code",
15
+ "file","files","change","changes","được","các","cho","với","trong","này","một","những","không","theo",
16
+ ])
17
+
18
+ function now() {
19
+ return new Date().toISOString()
20
+ }
21
+
22
+ function clamp(value, fallback = 0.7) {
23
+ const number = Number(value)
24
+ return Number.isFinite(number) ? Math.max(0, Math.min(1, number)) : fallback
25
+ }
26
+
27
+ function uniq(values) {
28
+ return [...new Set((values || []).filter(Boolean))]
29
+ }
30
+
31
+ function normalizeFile(value) {
32
+ return String(value || "").replaceAll("\\", "/").replace(/^\.\/+/, "")
33
+ }
34
+
35
+ function tokens(value) {
36
+ return String(value || "")
37
+ .toLowerCase()
38
+ .split(/[^\p{L}\p{N}_$.-]+/u)
39
+ .map((item) => item.replace(/^[-.$]+|[-.$]+$/g, ""))
40
+ .filter((item) => item.length >= 2 && !STOP.has(item))
41
+ }
42
+
43
+ function normalizedContent(value) {
44
+ return String(value || "").trim().replace(/\s+/g, " ")
45
+ }
46
+
47
+ function idFor(input) {
48
+ const key = input.key || [
49
+ input.scope || "project",
50
+ input.type || "episodic",
51
+ normalizedContent(input.content),
52
+ ].join(":")
53
+ return createHash("sha256").update(String(key)).digest("hex").slice(0, 20)
54
+ }
55
+
56
+ function memoryPath(root) {
57
+ return path.join(path.resolve(root), MEMORY_DIR, MEMORY_FILE)
58
+ }
59
+
60
+ async function atomicJson(file, value) {
61
+ await mkdir(path.dirname(file), { recursive: true })
62
+ const temp = file + "." + process.pid + "." + Date.now() + ".tmp"
63
+ await writeFile(temp, JSON.stringify(value, null, 2) + "\n", "utf8")
64
+ try {
65
+ await rename(temp, file)
66
+ } catch (error) {
67
+ await rm(temp, { force: true }).catch(() => {})
68
+ throw error
69
+ }
70
+ }
71
+
72
+ export async function readMemoryState(root = process.cwd()) {
73
+ const file = memoryPath(root)
74
+ if (!existsSync(file)) {
75
+ return { schemaVersion: SCHEMA_VERSION, updatedAt: null, memories: [] }
76
+ }
77
+ try {
78
+ const parsed = JSON.parse(await readFile(file, "utf8"))
79
+ return {
80
+ schemaVersion: SCHEMA_VERSION,
81
+ updatedAt: parsed.updatedAt || null,
82
+ memories: Array.isArray(parsed.memories) ? parsed.memories : [],
83
+ }
84
+ } catch {
85
+ return { schemaVersion: SCHEMA_VERSION, updatedAt: null, memories: [], invalid: true }
86
+ }
87
+ }
88
+
89
+ async function writeMemoryState(root, memories) {
90
+ const state = { schemaVersion: SCHEMA_VERSION, updatedAt: now(), memories }
91
+ await atomicJson(memoryPath(root), state)
92
+ return state
93
+ }
94
+
95
+ export async function proposeMemory(root, input = {}) {
96
+ const content = normalizedContent(input.content)
97
+ if (!content) throw new Error("memory content is required")
98
+ const type = VALID_TYPES.has(input.type) ? input.type : "episodic"
99
+ const scope = VALID_SCOPES.has(input.scope) ? input.scope : "project"
100
+ const state = await readMemoryState(root)
101
+ const id = idFor({ ...input, type, scope, content })
102
+ const timestamp = now()
103
+ const existing = state.memories.find((item) => item.id === id)
104
+ if (existing) {
105
+ const reinforced = {
106
+ ...existing,
107
+ lastSeenAt: timestamp,
108
+ reinforcementCount: Number(existing.reinforcementCount || 1) + 1,
109
+ evidenceRefs: uniq([...(existing.evidenceRefs || []), ...(input.evidenceRefs || [])]),
110
+ files: uniq([...(existing.files || []), ...(input.files || []).map(normalizeFile)]),
111
+ tags: uniq([...(existing.tags || []), ...(input.tags || [])]),
112
+ confidence: Math.max(clamp(existing.confidence), clamp(input.confidence, existing.confidence ?? 0.7)),
113
+ taskClass: input.taskClass || existing.taskClass || null,
114
+ expiresAt: input.expiresAt || existing.expiresAt || null,
115
+ }
116
+ await writeMemoryState(root, state.memories.map((item) => item.id === id ? reinforced : item))
117
+ return reinforced
118
+ }
119
+
120
+ const memory = {
121
+ id,
122
+ key: input.key || null,
123
+ type,
124
+ scope,
125
+ content,
126
+ status: "candidate",
127
+ confidence: clamp(input.confidence),
128
+ evidenceRefs: uniq(input.evidenceRefs || []),
129
+ files: uniq((input.files || []).map(normalizeFile)),
130
+ tags: uniq(input.tags || []),
131
+ sourceTask: input.sourceTask || null,
132
+ sourceCommit: input.sourceCommit || null,
133
+ taskClass: input.taskClass || null,
134
+ expiresAt: input.expiresAt || null,
135
+ createdAt: timestamp,
136
+ lastSeenAt: timestamp,
137
+ lastUsedAt: null,
138
+ useCount: 0,
139
+ reinforcementCount: 1,
140
+ verifiedAt: null,
141
+ verifier: null,
142
+ supersededBy: null,
143
+ }
144
+ await writeMemoryState(root, [...state.memories, memory])
145
+ return memory
146
+ }
147
+
148
+ export async function verifyMemory(root, id, verification = {}) {
149
+ if (String(verification.verdict || "").toUpperCase() !== "PASS") {
150
+ throw new Error("memory verification requires verdict PASS")
151
+ }
152
+ if (!verification.verifier) throw new Error("memory verification requires an independent verifier identity")
153
+ const state = await readMemoryState(root)
154
+ const current = state.memories.find((item) => item.id === id)
155
+ if (!current) throw new Error("unknown memory: " + id)
156
+ if (current.status === "superseded") throw new Error("cannot verify a superseded memory")
157
+
158
+ const refs = uniq([...(current.evidenceRefs || []), ...(verification.evidenceRefs || [])])
159
+ if (!refs.length) throw new Error("memory verification requires at least one durable evidence reference")
160
+ for (const ref of refs) {
161
+ if (!(await evidenceExists(root, ref))) throw new Error("memory evidence reference is missing: " + ref)
162
+ }
163
+
164
+ const verified = {
165
+ ...current,
166
+ status: "verified",
167
+ evidenceRefs: refs,
168
+ verifier: String(verification.verifier),
169
+ verifiedAt: now(),
170
+ lastSeenAt: now(),
171
+ confidence: Math.max(clamp(current.confidence), clamp(verification.confidence, current.confidence ?? 0.7)),
172
+ }
173
+ await writeMemoryState(root, state.memories.map((item) => item.id === id ? verified : item))
174
+ return verified
175
+ }
176
+
177
+ export async function supersedeMemory(root, id, replacementId) {
178
+ if (!replacementId || replacementId === id) throw new Error("replacement memory id must be different")
179
+ const state = await readMemoryState(root)
180
+ const current = state.memories.find((item) => item.id === id)
181
+ const replacement = state.memories.find((item) => item.id === replacementId)
182
+ if (!current) throw new Error("unknown memory: " + id)
183
+ if (!replacement) throw new Error("unknown replacement memory: " + replacementId)
184
+ if (replacement.status !== "verified") throw new Error("replacement memory must be verified before supersession")
185
+ const timestamp = now()
186
+ const superseded = {
187
+ ...current,
188
+ status: "superseded",
189
+ supersededBy: replacementId,
190
+ lastSeenAt: timestamp,
191
+ }
192
+ await writeMemoryState(root, state.memories.map((item) => item.id === id ? superseded : item))
193
+ return superseded
194
+ }
195
+
196
+ function tokenVector(value, dims = 96) {
197
+ const vector = new Array(dims).fill(0)
198
+ for (const token of tokens(value)) {
199
+ let hash = 2166136261
200
+ for (let index = 0; index < token.length; index += 1) {
201
+ hash ^= token.charCodeAt(index)
202
+ hash = Math.imul(hash, 16777619) >>> 0
203
+ }
204
+ const slot = hash % dims
205
+ const sign = (hash & 0x100) === 0 ? 1 : -1
206
+ vector[slot] += sign
207
+ }
208
+ return vector
209
+ }
210
+
211
+ function cosine(a, b) {
212
+ let dot = 0
213
+ let left = 0
214
+ let right = 0
215
+ for (let index = 0; index < Math.min(a.length, b.length); index += 1) {
216
+ dot += a[index] * b[index]
217
+ left += a[index] * a[index]
218
+ right += b[index] * b[index]
219
+ }
220
+ return left && right ? dot / Math.sqrt(left * right) : 0
221
+ }
222
+
223
+ function bm25Scores(memories, queryTokens) {
224
+ const docs = memories.map((item) => tokens([item.content, ...(item.tags || []), ...(item.files || [])].join(" ")))
225
+ const avgLength = docs.length ? docs.reduce((sum, doc) => sum + doc.length, 0) / docs.length : 1
226
+ const df = new Map()
227
+ for (const term of queryTokens) {
228
+ df.set(term, docs.reduce((count, doc) => count + (doc.includes(term) ? 1 : 0), 0))
229
+ }
230
+ const k1 = 1.2
231
+ const b = 0.75
232
+ return docs.map((doc) => {
233
+ const counts = new Map()
234
+ for (const token of doc) counts.set(token, (counts.get(token) || 0) + 1)
235
+ let score = 0
236
+ for (const term of queryTokens) {
237
+ const tf = counts.get(term) || 0
238
+ if (!tf) continue
239
+ const freq = df.get(term) || 0
240
+ const idf = Math.log(1 + (memories.length - freq + 0.5) / (freq + 0.5))
241
+ const denom = tf + k1 * (1 - b + b * (doc.length / Math.max(1, avgLength)))
242
+ score += idf * ((tf * (k1 + 1)) / denom)
243
+ }
244
+ return score
245
+ })
246
+ }
247
+
248
+ function commonPrefixParts(left, right) {
249
+ const a = normalizeFile(left).split("/")
250
+ const b = normalizeFile(right).split("/")
251
+ let count = 0
252
+ while (count < a.length && count < b.length && a[count] === b[count]) count += 1
253
+ return count
254
+ }
255
+
256
+ function graphAffinity(queryFiles, memoryFiles) {
257
+ if (!queryFiles.length || !memoryFiles.length) return 0
258
+ let best = 0
259
+ for (const left of queryFiles) {
260
+ for (const right of memoryFiles) {
261
+ if (left === right) best = Math.max(best, 1)
262
+ else {
263
+ const common = commonPrefixParts(left, right)
264
+ if (common >= 2) best = Math.max(best, 0.8)
265
+ else if (common === 1) best = Math.max(best, 0.45)
266
+ }
267
+ }
268
+ }
269
+ return best
270
+ }
271
+
272
+ function taskClassAffinity(queryTaskClass, memoryTaskClass) {
273
+ if (!queryTaskClass || !memoryTaskClass) return 0
274
+ return String(queryTaskClass).toLowerCase() === String(memoryTaskClass).toLowerCase() ? 1 : 0
275
+ }
276
+
277
+ function scopeAffinity(scope, graph) {
278
+ if (scope === "file") return graph > 0 ? 1 : 0
279
+ if (scope === "module") return graph > 0 ? 0.8 : 0
280
+ if (scope === "project") return 0.3
281
+ if (scope === "global") return 0.15
282
+ return 0
283
+ }
284
+
285
+ function isExpired(item, at = Date.now()) {
286
+ const expires = Date.parse(item?.expiresAt || "")
287
+ return Boolean(expires && expires <= at)
288
+ }
289
+
290
+ function recencyScore(value) {
291
+ const timestamp = Date.parse(value || "")
292
+ if (!timestamp) return 0
293
+ const ageDays = Math.max(0, (Date.now() - timestamp) / 86_400_000)
294
+ return 1 / (1 + ageDays / 30)
295
+ }
296
+
297
+ function rankMap(rows, key) {
298
+ return new Map(
299
+ [...rows]
300
+ .filter((row) => Number(row[key]) > 0)
301
+ .sort((a, b) => b[key] - a[key] || a.item.id.localeCompare(b.item.id))
302
+ .map((row, index) => [row.item.id, index + 1]),
303
+ )
304
+ }
305
+
306
+ export async function retrieveMemories(root, query, options = {}) {
307
+ const state = await readMemoryState(root)
308
+ const memories = state.memories.filter((item) => item.status === "verified" && !item.supersededBy && !isExpired(item))
309
+ const queryTokens = uniq(tokens(query))
310
+ const queryVector = tokenVector(query)
311
+ const queryFiles = uniq((options.files || []).map(normalizeFile))
312
+ const lexical = bm25Scores(memories, queryTokens)
313
+ const rows = memories.map((item, index) => ({
314
+ item,
315
+ lexical: lexical[index] || 0,
316
+ semantic: Math.max(0, cosine(queryVector, tokenVector(item.content))),
317
+ graph: graphAffinity(queryFiles, item.files || []),
318
+ taskClass: taskClassAffinity(options.taskClass, item.taskClass),
319
+ recency: recencyScore(item.lastUsedAt || item.lastSeenAt || item.verifiedAt || item.createdAt),
320
+ confidence: clamp(item.confidence),
321
+ scope: 0,
322
+ }))
323
+ for (const row of rows) row.scope = scopeAffinity(row.item.scope, row.graph)
324
+ const ranks = {
325
+ lexical: rankMap(rows, "lexical"),
326
+ semantic: rankMap(rows, "semantic"),
327
+ graph: rankMap(rows, "graph"),
328
+ taskClass: rankMap(rows, "taskClass"),
329
+ recency: rankMap(rows, "recency"),
330
+ }
331
+ for (const row of rows) {
332
+ let rrf = 0
333
+ for (const key of ["lexical", "semantic", "graph", "taskClass", "recency"]) {
334
+ const rank = ranks[key].get(row.item.id)
335
+ if (rank) rrf += 1 / (60 + rank)
336
+ }
337
+ row.score = rrf + row.confidence * 0.01 + row.graph * 0.01 + row.scope * 0.006 + row.taskClass * 0.008
338
+ }
339
+ const limit = Math.max(1, Math.min(Number(options.limit || 6), 30))
340
+ const results = rows
341
+ .filter((row) => row.lexical > 0 || row.semantic > 0.05 || row.graph > 0)
342
+ .sort((a, b) => b.score - a.score || b.confidence - a.confidence || a.item.id.localeCompare(b.item.id))
343
+ .slice(0, limit)
344
+ .map((row) => ({
345
+ ...row.item,
346
+ retrieval: {
347
+ score: Number(row.score.toFixed(8)),
348
+ lexical: Number(row.lexical.toFixed(6)),
349
+ semantic: Number(row.semantic.toFixed(6)),
350
+ graph: Number(row.graph.toFixed(6)),
351
+ taskClass: Number(row.taskClass.toFixed(6)),
352
+ scope: Number(row.scope.toFixed(6)),
353
+ recency: Number(row.recency.toFixed(6)),
354
+ },
355
+ }))
356
+ if (options.touch === true && results.length) {
357
+ const used = new Set(results.map((item) => item.id))
358
+ const timestamp = now()
359
+ await writeMemoryState(root, state.memories.map((item) => used.has(item.id)
360
+ ? { ...item, lastUsedAt: timestamp, useCount: Number(item.useCount || 0) + 1 }
361
+ : item))
362
+ }
363
+ return {
364
+ schemaVersion: SCHEMA_VERSION,
365
+ query: String(query || ""),
366
+ eligible: memories.length,
367
+ results,
368
+ }
369
+ }
370
+
371
+ export async function memoryStatus(root = process.cwd()) {
372
+ const state = await readMemoryState(root)
373
+ const byStatus = {}
374
+ const byType = {}
375
+ let expired = 0
376
+ let used = 0
377
+ for (const memory of state.memories) {
378
+ byStatus[memory.status] = (byStatus[memory.status] || 0) + 1
379
+ byType[memory.type] = (byType[memory.type] || 0) + 1
380
+ if (isExpired(memory)) expired += 1
381
+ if (Number(memory.useCount || 0) > 0) used += 1
382
+ }
383
+ return {
384
+ schemaVersion: SCHEMA_VERSION,
385
+ root: path.dirname(memoryPath(root)),
386
+ updatedAt: state.updatedAt,
387
+ entries: state.memories.length,
388
+ expired,
389
+ used,
390
+ byStatus,
391
+ byType,
392
+ }
393
+ }
394
+
395
+ function changedFiles(root) {
396
+ const result = spawnSync("git", ["status", "--porcelain=v1", "--untracked-files=all"], {
397
+ cwd: path.resolve(root),
398
+ encoding: "utf8",
399
+ windowsHide: true,
400
+ maxBuffer: 4 * 1024 * 1024,
401
+ })
402
+ if (result.status !== 0) return []
403
+ return uniq(
404
+ String(result.stdout || "")
405
+ .split(/\r?\n/)
406
+ .filter(Boolean)
407
+ .map((line) => line.slice(3).trim())
408
+ .map((value) => value.includes(" -> ") ? value.split(" -> ").at(-1) : value)
409
+ .map(normalizeFile),
410
+ ).slice(0, 80)
411
+ }
412
+
413
+ function compactVerification(value, limit = 1200) {
414
+ const text = String(value || "").replace(/\s+/g, " ").trim()
415
+ return text.length <= limit ? text : text.slice(0, limit) + "...[truncated]"
416
+ }
417
+
418
+ export async function recordVerifiedTaskMemory(root, input = {}) {
419
+ const task = normalizedContent(input.task)
420
+ if (!task) throw new Error("verified task memory requires task text")
421
+ const explicitFiles = uniq((input.files || []).map(normalizeFile))
422
+ const files = explicitFiles.length ? explicitFiles : changedFiles(root)
423
+ const verifier = input.verifier || "ues-verifier"
424
+ const verificationSummary = compactVerification(input.verifierOutput)
425
+ const integrationSummary = compactVerification(input.integrationOutput)
426
+ const evidence = await putEvidence(root, {
427
+ schemaVersion: 1,
428
+ kind: "verified-task-memory-receipt",
429
+ task,
430
+ verifier,
431
+ files,
432
+ verification: verificationSummary,
433
+ integration: integrationSummary || null,
434
+ recordedAt: now(),
435
+ }, {
436
+ kind: "verified-task-memory",
437
+ source: input.sourceTask || task.slice(0, 200),
438
+ summary: "Verified task outcome eligible for persistent memory",
439
+ })
440
+ const content = [
441
+ `Verified engineering task: ${task}`,
442
+ files.length ? `Files: ${files.join(", ")}` : "",
443
+ verificationSummary ? `Verification: ${verificationSummary}` : "",
444
+ ].filter(Boolean).join("\n")
445
+ const candidate = await proposeMemory(root, {
446
+ key: input.key || `verified-task:${task}:${files.slice().sort().join(",")}`,
447
+ type: input.type || "episodic",
448
+ scope: input.scope || "project",
449
+ content,
450
+ confidence: input.confidence ?? (integrationSummary ? 0.92 : 0.84),
451
+ evidenceRefs: [evidence.ref],
452
+ files,
453
+ tags: uniq(["verified-task", ...(input.tags || [])]),
454
+ sourceTask: input.sourceTask || task,
455
+ sourceCommit: input.sourceCommit || null,
456
+ taskClass: input.taskClass || null,
457
+ expiresAt: input.expiresAt || null,
458
+ })
459
+ return verifyMemory(root, candidate.id, {
460
+ verdict: "PASS",
461
+ verifier,
462
+ evidenceRefs: [evidence.ref],
463
+ confidence: candidate.confidence,
464
+ })
465
+ }
@@ -80,20 +80,44 @@ export function recordPerformanceOutcome(history = {}, outcome = {}) {
80
80
  return { ...normalized, [model]: { ...(normalized[model] || {}), [taskClass]: next } }
81
81
  }
82
82
 
83
+ function wilsonLowerBound(successes, samples, z = 1.96) {
84
+ if (samples <= 0) return 0
85
+ const p = successes / samples
86
+ const z2 = z * z
87
+ const denominator = 1 + z2 / samples
88
+ const center = p + z2 / (2 * samples)
89
+ const margin = z * Math.sqrt((p * (1 - p) + z2 / (4 * samples)) / samples)
90
+ return Math.max(0, (center - margin) / denominator)
91
+ }
92
+
83
93
  function performanceAdjustment(record, minSamples) {
84
94
  const normalized = normalizePerformanceRecord(record)
85
- if (normalized.samples <= 0) return { adjustment: 0, confidence: 0, record: normalized }
86
- const confidence = Math.min(1, normalized.samples / Math.max(1, minSamples))
87
- if (normalized.samples < minSamples) return { adjustment: 0, confidence, record: normalized }
88
- const correctness = (normalized.passRate - 0.5) * 80
95
+ if (normalized.samples <= 0) return { adjustment: 0, confidence: 0, record: normalized, lowerBound: 0 }
96
+ const confidence = Math.min(1, normalized.samples / Math.max(1, minSamples * 2))
97
+ const lowerBound = wilsonLowerBound(normalized.successes, normalized.samples)
98
+
99
+ // Do not let a tiny sample reorder weak models. Only empirical evidence with
100
+ // enough observations is allowed to move routing.
101
+ if (normalized.samples < minSamples) {
102
+ return { adjustment: 0, confidence, record: normalized, lowerBound }
103
+ }
104
+
105
+ const correctness = (lowerBound - 0.5) * 80
89
106
  const retryPenalty = Math.min(20, normalized.avgRetries * 5)
90
- const latencyPenalty = normalized.avgLatencyMs > 0 ? Math.min(10, Math.max(0, Math.log10(Math.max(1, normalized.avgLatencyMs / 1000)) * 3)) : 0
91
- return { adjustment: (correctness - retryPenalty - latencyPenalty) * confidence, confidence, record: normalized }
107
+ const latencyPenalty = normalized.avgLatencyMs > 0
108
+ ? Math.min(10, Math.max(0, Math.log10(Math.max(1, normalized.avgLatencyMs / 1000)) * 3))
109
+ : 0
110
+ return {
111
+ adjustment: (correctness - retryPenalty - latencyPenalty) * confidence,
112
+ confidence,
113
+ record: normalized,
114
+ lowerBound,
115
+ }
92
116
  }
93
117
 
94
118
  export function rerankCapabilitySelection(selection = {}, history = {}, options = {}) {
95
119
  const taskClass = inferTaskClass(options.text || "", { taskClass: options.taskClass })
96
- const minSamples = Math.max(1, Number(options.minSamples || 3))
120
+ const minSamples = Math.max(1, Number(options.minSamples || 8))
97
121
  const normalized = normalizePerformanceHistory(history)
98
122
  const candidates = (selection.candidates || []).map((candidate) => {
99
123
  const record = normalized[candidate.id]?.[taskClass] || normalized[candidate.id]?.overall || null
@@ -104,10 +128,11 @@ export function rerankCapabilitySelection(selection = {}, history = {}, options
104
128
  empiricalTaskClass: taskClass,
105
129
  empiricalEvidence: evidence.record,
106
130
  empiricalConfidence: Number(evidence.confidence.toFixed(4)),
131
+ empiricalPassLowerBound: Number(evidence.lowerBound.toFixed(4)),
107
132
  adjustedScore: Number((Number(candidate.score || 0) + evidence.adjustment).toFixed(6)),
108
133
  }
109
134
  })
110
135
  const eligible = candidates.filter((candidate) => candidate.eligible)
111
136
  .sort((a, b) => b.adjustedScore - a.adjustedScore || b.baseScore - a.baseScore)
112
137
  return { ...selection, selected: eligible[0] || null, candidates, taskClass, empirical: true }
113
- }
138
+ }
@@ -53,7 +53,7 @@ export function defaultModelPolicy() {
53
53
  roleTiers: { ...ROLE_TIERS },
54
54
  capabilities: {},
55
55
  performance: {},
56
- performanceMinSamples: 3,
56
+ performanceMinSamples: 8,
57
57
  }
58
58
  }
59
59
 
@@ -99,7 +99,7 @@ export function resolveCapabilityModel(role, attempt, taskText = "", taskPolicy
99
99
  })
100
100
  const taskClass = inferTaskClass(taskText, facts)
101
101
  const selection = rerankCapabilitySelection(staticSelection, config.performance || {}, {
102
- taskClass, text: taskText, minSamples: config.performanceMinSamples || 3,
102
+ taskClass, text: taskText, minSamples: config.performanceMinSamples || 8,
103
103
  })
104
104
  const capabilityEnforced = config.enabled === true && candidates.length > 0
105
105
  if (capabilityEnforced && !selection.selected) {
@@ -132,4 +132,4 @@ export function resolveCapabilityModel(role, attempt, taskText = "", taskPolicy
132
132
  capabilityFallback: false,
133
133
  capabilityBlocked: false,
134
134
  }
135
- }
135
+ }