opencode-agent-skill 13.0.0-beta.2 → 14.4.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/README.md +1571 -607
- package/bin/ocskill.mjs +172 -24
- package/docs/OPENCODE-COMPAT.md +34 -97
- package/docs/PI-COMPAT.md +203 -0
- package/docs/V14-CONTEXT-MEMORY-FABRIC.md +70 -0
- package/docs/V14.1-QUALITY-PERFORMANCE-FABRIC.md +114 -0
- package/docs/V14.2-TURBO-WEAK-MODEL-RUNTIME.md +448 -0
- package/evals/v14/tasks.json +46 -0
- package/global-config/agents/executor.md +7 -0
- package/global-config/agents/visual-verifier.md +22 -3
- package/global-config/plugins/ues-router/index.js +13 -8
- package/global-config/plugins/ues-router/policy-runtime.js +7 -0
- package/global-config/plugins/ues-router/router.js +12 -2
- package/global-config/skills/ecommerce-engineering/SKILL.md +1 -1
- package/global-config/skills/file-upload-engineering/SKILL.md +1 -1
- package/global-config/skills/git-safety/SKILL.md +1 -1
- package/global-config/skills/nestjs-engineering/SKILL.md +1 -1
- package/global-config/skills/performance-engineering/SKILL.md +1 -1
- package/global-config/skills/react-native-engineering/SKILL.md +1 -1
- package/global-config/skills/rest-api-design/SKILL.md +1 -1
- package/global-config/skills/ui-ux-engineering/SKILL.md +1 -1
- package/lib/adaptive-context-budget.mjs +97 -0
- package/lib/affected-tests.mjs +260 -0
- package/lib/benchmark-confidence.mjs +41 -2
- package/lib/browser-mcp-routing.mjs +166 -0
- package/lib/capability-fabric.mjs +359 -0
- package/lib/capability-registry.mjs +9 -0
- package/lib/code-intelligence/edit-anchor.mjs +102 -0
- package/lib/code-intelligence/index.mjs +95 -0
- package/lib/code-intelligence/lsp-provider.mjs +189 -0
- package/lib/completion-auditor.mjs +82 -0
- package/lib/context-engine-v11.mjs +65 -1
- package/lib/context-graph-rank.mjs +118 -0
- package/lib/context-manifest.mjs +97 -18
- package/lib/control-center.mjs +19 -1
- package/lib/document-ingestion.mjs +60 -0
- package/lib/dynamic-workflow.mjs +3 -1
- package/lib/evidence-store.mjs +82 -1
- package/lib/fast-verification-gate.mjs +69 -0
- package/lib/hierarchical-context.mjs +215 -0
- package/lib/mcp-health.mjs +144 -0
- package/lib/mcp-tool-policy.mjs +19 -0
- package/lib/memory-engine.mjs +493 -0
- package/lib/model-performance.mjs +33 -8
- package/lib/model-policy.mjs +3 -3
- package/lib/orchestrator-policy.mjs +5 -209
- package/lib/performance-fabric.mjs +229 -0
- package/lib/pi-rpc-pool.mjs +433 -0
- package/lib/process-hang-detector.mjs +83 -0
- package/lib/process-supervisor.mjs +193 -0
- package/lib/prompt-cache.mjs +27 -11
- package/lib/repo-graph.mjs +53 -2
- package/lib/reversible-context.mjs +49 -0
- package/lib/runtime-config.mjs +31 -0
- package/lib/safety.mjs +132 -0
- package/lib/semantic-index.mjs +52 -3
- package/lib/skill-compiler.mjs +128 -0
- package/lib/skill-quality.mjs +48 -2
- package/lib/task-engine.mjs +62 -5
- package/lib/task-policy.mjs +291 -0
- package/lib/verification-broker.mjs +284 -0
- package/lib/verification-command.mjs +111 -0
- package/lib/windows-shim.mjs +35 -0
- package/lib/workspace-fingerprint.mjs +198 -0
- package/package.json +52 -42
- package/pi/extensions/ues-child-runtime.ts +393 -0
- package/pi/extensions/ues.ts +3327 -0
- package/pi/prompts/ues-audit.md +9 -0
- package/pi/prompts/ues-critique.md +9 -0
- package/pi/prompts/ues-debug.md +9 -0
- package/pi/prompts/ues-feature.md +9 -0
- package/pi/prompts/ues-fix.md +9 -0
- package/pi/prompts/ues-plan.md +9 -0
- package/pi/prompts/ues-research.md +9 -0
- package/pi/prompts/ues-resume.md +9 -0
- package/pi/prompts/ues-review.md +7 -0
- package/pi/prompts/ues-run.md +17 -0
- package/pi/prompts/ues-verify.md +9 -0
- package/scripts/check-release-consistency.mjs +119 -185
- package/scripts/check-runtime-exports.mjs +66 -0
- package/scripts/check-source-integrity.mjs +246 -0
- package/scripts/eval-pi.mjs +492 -0
- package/scripts/install.mjs +16 -0
- package/scripts/smoke-package-closure.mjs +110 -0
- package/scripts/smoke-packed-install.mjs +24 -11
- package/scripts/smoke-pi-extension.mjs +144 -0
- package/scripts/uninstall.mjs +44 -0
- package/CHANGELOG.md +0 -415
- package/docs/DETERMINISTIC-TOOLS.md +0 -105
- package/docs/ENGINEERING-DESIGN.md +0 -194
- package/docs/EVALS.md +0 -158
- package/docs/GITHUB-RULESET.md +0 -50
- package/docs/NPM-PUBLISH.md +0 -116
- package/docs/RESEARCH-SOURCES.md +0 -37
- package/docs/TRACE-SCHEMA.md +0 -122
- package/docs/V11-PERCEPTION-ADAPTIVE-EXECUTION.md +0 -75
- package/docs/V11-PERCEPTION-ADAPTIVE.md +0 -220
- package/docs/V12-WEAK-MODEL-INTELLIGENCE.md +0 -27
- package/docs/V13-PARALLEL-WEAK-MODEL-RUNTIME.md +0 -86
- package/docs/V7-INTELLIGENCE-RUNTIME.md +0 -166
- package/docs/V8-INTELLIGENCE-RELIABILITY.md +0 -206
- package/docs/V9-SPEED-INTELLIGENCE.md +0 -102
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
function firstObject(...values) { return values.find((value) => value && typeof value === "object" && !Array.isArray(value)) || {} }
|
|
2
|
+
function explicitBoolean(value) { return typeof value === "boolean" ? value : null }
|
|
3
|
+
export function normalizeMcpAnnotations(tool = {}) {
|
|
4
|
+
const raw = firstObject(tool.annotations, tool.metadata?.annotations, tool._meta?.annotations, tool._meta?.mcp?.annotations)
|
|
5
|
+
const annotations = { readOnlyHint: explicitBoolean(raw.readOnlyHint), destructiveHint: explicitBoolean(raw.destructiveHint), idempotentHint: explicitBoolean(raw.idempotentHint), openWorldHint: explicitBoolean(raw.openWorldHint) }
|
|
6
|
+
return { schemaVersion: 1, explicit: Object.values(annotations).some((value) => value !== null), ...annotations }
|
|
7
|
+
}
|
|
8
|
+
export function mcpExecutionPolicy(tool = {}) {
|
|
9
|
+
const annotations = normalizeMcpAnnotations(tool)
|
|
10
|
+
const destructive = annotations.destructiveHint === true
|
|
11
|
+
const readOnly = annotations.readOnlyHint === true && !destructive
|
|
12
|
+
const idempotent = annotations.idempotentHint === true && !destructive
|
|
13
|
+
return { schemaVersion: 1, tool: String(tool?.name || ""), annotations, fastAllowed: readOnly, confirmationRequired: destructive, retryAllowed: idempotent, externalEvidenceBoundary: annotations.openWorldHint === true, trust: "hint-only" }
|
|
14
|
+
}
|
|
15
|
+
export function mcpPolicyMap(tools = []) {
|
|
16
|
+
const map = new Map()
|
|
17
|
+
for (const tool of Array.isArray(tools) ? tools : []) { const name = String(tool?.name || "").trim(); if (name) map.set(name, mcpExecutionPolicy(tool)) }
|
|
18
|
+
return map
|
|
19
|
+
}
|
|
@@ -0,0 +1,493 @@
|
|
|
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 function buildMemorySnapshot(memories = []) {
|
|
307
|
+
const entries = (Array.isArray(memories) ? memories : [])
|
|
308
|
+
.map((item) => ({
|
|
309
|
+
id: item.id || null,
|
|
310
|
+
type: item.type || null,
|
|
311
|
+
scope: item.scope || null,
|
|
312
|
+
content: normalizedContent(item.content),
|
|
313
|
+
confidence: clamp(item.confidence),
|
|
314
|
+
files: uniq((item.files || []).map(normalizeFile)).sort(),
|
|
315
|
+
taskClass: item.taskClass || null,
|
|
316
|
+
verifiedAt: item.verifiedAt || null,
|
|
317
|
+
evidenceRefs: uniq(item.evidenceRefs || []).sort(),
|
|
318
|
+
}))
|
|
319
|
+
.filter((item) => item.id && item.content)
|
|
320
|
+
.sort((a, b) => String(a.id).localeCompare(String(b.id)))
|
|
321
|
+
const generation = createHash("sha256")
|
|
322
|
+
.update(JSON.stringify(entries))
|
|
323
|
+
.digest("hex")
|
|
324
|
+
.slice(0, 20)
|
|
325
|
+
return {
|
|
326
|
+
schemaVersion: 1,
|
|
327
|
+
generation,
|
|
328
|
+
entryCount: entries.length,
|
|
329
|
+
entries,
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
export async function retrieveMemories(root, query, options = {}) {
|
|
334
|
+
const state = await readMemoryState(root)
|
|
335
|
+
const memories = state.memories.filter((item) => item.status === "verified" && !item.supersededBy && !isExpired(item))
|
|
336
|
+
const queryTokens = uniq(tokens(query))
|
|
337
|
+
const queryVector = tokenVector(query)
|
|
338
|
+
const queryFiles = uniq((options.files || []).map(normalizeFile))
|
|
339
|
+
const lexical = bm25Scores(memories, queryTokens)
|
|
340
|
+
const rows = memories.map((item, index) => ({
|
|
341
|
+
item,
|
|
342
|
+
lexical: lexical[index] || 0,
|
|
343
|
+
semantic: Math.max(0, cosine(queryVector, tokenVector(item.content))),
|
|
344
|
+
graph: graphAffinity(queryFiles, item.files || []),
|
|
345
|
+
taskClass: taskClassAffinity(options.taskClass, item.taskClass),
|
|
346
|
+
recency: recencyScore(item.lastUsedAt || item.lastSeenAt || item.verifiedAt || item.createdAt),
|
|
347
|
+
confidence: clamp(item.confidence),
|
|
348
|
+
scope: 0,
|
|
349
|
+
}))
|
|
350
|
+
for (const row of rows) row.scope = scopeAffinity(row.item.scope, row.graph)
|
|
351
|
+
const ranks = {
|
|
352
|
+
lexical: rankMap(rows, "lexical"),
|
|
353
|
+
semantic: rankMap(rows, "semantic"),
|
|
354
|
+
graph: rankMap(rows, "graph"),
|
|
355
|
+
taskClass: rankMap(rows, "taskClass"),
|
|
356
|
+
recency: rankMap(rows, "recency"),
|
|
357
|
+
}
|
|
358
|
+
for (const row of rows) {
|
|
359
|
+
let rrf = 0
|
|
360
|
+
for (const key of ["lexical", "semantic", "graph", "taskClass", "recency"]) {
|
|
361
|
+
const rank = ranks[key].get(row.item.id)
|
|
362
|
+
if (rank) rrf += 1 / (60 + rank)
|
|
363
|
+
}
|
|
364
|
+
row.score = rrf + row.confidence * 0.01 + row.graph * 0.01 + row.scope * 0.006 + row.taskClass * 0.008
|
|
365
|
+
}
|
|
366
|
+
const limit = Math.max(1, Math.min(Number(options.limit || 6), 30))
|
|
367
|
+
const results = rows
|
|
368
|
+
.filter((row) => row.lexical > 0 || row.semantic > 0.05 || row.graph > 0)
|
|
369
|
+
.sort((a, b) => b.score - a.score || b.confidence - a.confidence || a.item.id.localeCompare(b.item.id))
|
|
370
|
+
.slice(0, limit)
|
|
371
|
+
.map((row) => ({
|
|
372
|
+
...row.item,
|
|
373
|
+
retrieval: {
|
|
374
|
+
score: Number(row.score.toFixed(8)),
|
|
375
|
+
lexical: Number(row.lexical.toFixed(6)),
|
|
376
|
+
semantic: Number(row.semantic.toFixed(6)),
|
|
377
|
+
graph: Number(row.graph.toFixed(6)),
|
|
378
|
+
taskClass: Number(row.taskClass.toFixed(6)),
|
|
379
|
+
scope: Number(row.scope.toFixed(6)),
|
|
380
|
+
recency: Number(row.recency.toFixed(6)),
|
|
381
|
+
},
|
|
382
|
+
}))
|
|
383
|
+
if (options.touch === true && results.length) {
|
|
384
|
+
const used = new Set(results.map((item) => item.id))
|
|
385
|
+
const timestamp = now()
|
|
386
|
+
await writeMemoryState(root, state.memories.map((item) => used.has(item.id)
|
|
387
|
+
? { ...item, lastUsedAt: timestamp, useCount: Number(item.useCount || 0) + 1 }
|
|
388
|
+
: item))
|
|
389
|
+
}
|
|
390
|
+
return {
|
|
391
|
+
schemaVersion: SCHEMA_VERSION,
|
|
392
|
+
query: String(query || ""),
|
|
393
|
+
eligible: memories.length,
|
|
394
|
+
results,
|
|
395
|
+
snapshot: buildMemorySnapshot(results),
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
export async function memoryStatus(root = process.cwd()) {
|
|
400
|
+
const state = await readMemoryState(root)
|
|
401
|
+
const byStatus = {}
|
|
402
|
+
const byType = {}
|
|
403
|
+
let expired = 0
|
|
404
|
+
let used = 0
|
|
405
|
+
for (const memory of state.memories) {
|
|
406
|
+
byStatus[memory.status] = (byStatus[memory.status] || 0) + 1
|
|
407
|
+
byType[memory.type] = (byType[memory.type] || 0) + 1
|
|
408
|
+
if (isExpired(memory)) expired += 1
|
|
409
|
+
if (Number(memory.useCount || 0) > 0) used += 1
|
|
410
|
+
}
|
|
411
|
+
return {
|
|
412
|
+
schemaVersion: SCHEMA_VERSION,
|
|
413
|
+
root: path.dirname(memoryPath(root)),
|
|
414
|
+
updatedAt: state.updatedAt,
|
|
415
|
+
entries: state.memories.length,
|
|
416
|
+
expired,
|
|
417
|
+
used,
|
|
418
|
+
byStatus,
|
|
419
|
+
byType,
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function changedFiles(root) {
|
|
424
|
+
const result = spawnSync("git", ["status", "--porcelain=v1", "--untracked-files=all"], {
|
|
425
|
+
cwd: path.resolve(root),
|
|
426
|
+
encoding: "utf8",
|
|
427
|
+
windowsHide: true,
|
|
428
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
429
|
+
})
|
|
430
|
+
if (result.status !== 0) return []
|
|
431
|
+
return uniq(
|
|
432
|
+
String(result.stdout || "")
|
|
433
|
+
.split(/\r?\n/)
|
|
434
|
+
.filter(Boolean)
|
|
435
|
+
.map((line) => line.slice(3).trim())
|
|
436
|
+
.map((value) => value.includes(" -> ") ? value.split(" -> ").at(-1) : value)
|
|
437
|
+
.map(normalizeFile),
|
|
438
|
+
).slice(0, 80)
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function compactVerification(value, limit = 1200) {
|
|
442
|
+
const text = String(value || "").replace(/\s+/g, " ").trim()
|
|
443
|
+
return text.length <= limit ? text : text.slice(0, limit) + "...[truncated]"
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
export async function recordVerifiedTaskMemory(root, input = {}) {
|
|
447
|
+
const task = normalizedContent(input.task)
|
|
448
|
+
if (!task) throw new Error("verified task memory requires task text")
|
|
449
|
+
const explicitFiles = uniq((input.files || []).map(normalizeFile))
|
|
450
|
+
const files = explicitFiles.length ? explicitFiles : changedFiles(root)
|
|
451
|
+
const verifier = input.verifier || "ues-verifier"
|
|
452
|
+
const verificationSummary = compactVerification(input.verifierOutput)
|
|
453
|
+
const integrationSummary = compactVerification(input.integrationOutput)
|
|
454
|
+
const evidence = await putEvidence(root, {
|
|
455
|
+
schemaVersion: 1,
|
|
456
|
+
kind: "verified-task-memory-receipt",
|
|
457
|
+
task,
|
|
458
|
+
verifier,
|
|
459
|
+
files,
|
|
460
|
+
verification: verificationSummary,
|
|
461
|
+
integration: integrationSummary || null,
|
|
462
|
+
recordedAt: now(),
|
|
463
|
+
}, {
|
|
464
|
+
kind: "verified-task-memory",
|
|
465
|
+
source: input.sourceTask || task.slice(0, 200),
|
|
466
|
+
summary: "Verified task outcome eligible for persistent memory",
|
|
467
|
+
})
|
|
468
|
+
const content = [
|
|
469
|
+
`Verified engineering task: ${task}`,
|
|
470
|
+
files.length ? `Files: ${files.join(", ")}` : "",
|
|
471
|
+
verificationSummary ? `Verification: ${verificationSummary}` : "",
|
|
472
|
+
].filter(Boolean).join("\n")
|
|
473
|
+
const candidate = await proposeMemory(root, {
|
|
474
|
+
key: input.key || `verified-task:${task}:${files.slice().sort().join(",")}`,
|
|
475
|
+
type: input.type || "episodic",
|
|
476
|
+
scope: input.scope || "project",
|
|
477
|
+
content,
|
|
478
|
+
confidence: input.confidence ?? (integrationSummary ? 0.92 : 0.84),
|
|
479
|
+
evidenceRefs: [evidence.ref],
|
|
480
|
+
files,
|
|
481
|
+
tags: uniq(["verified-task", ...(input.tags || [])]),
|
|
482
|
+
sourceTask: input.sourceTask || task,
|
|
483
|
+
sourceCommit: input.sourceCommit || null,
|
|
484
|
+
taskClass: input.taskClass || null,
|
|
485
|
+
expiresAt: input.expiresAt || null,
|
|
486
|
+
})
|
|
487
|
+
return verifyMemory(root, candidate.id, {
|
|
488
|
+
verdict: "PASS",
|
|
489
|
+
verifier,
|
|
490
|
+
evidenceRefs: [evidence.ref],
|
|
491
|
+
confidence: candidate.confidence,
|
|
492
|
+
})
|
|
493
|
+
}
|
|
@@ -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
|
-
|
|
88
|
-
|
|
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
|
|
91
|
-
|
|
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 ||
|
|
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
|
+
}
|
package/lib/model-policy.mjs
CHANGED
|
@@ -53,7 +53,7 @@ export function defaultModelPolicy() {
|
|
|
53
53
|
roleTiers: { ...ROLE_TIERS },
|
|
54
54
|
capabilities: {},
|
|
55
55
|
performance: {},
|
|
56
|
-
performanceMinSamples:
|
|
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 ||
|
|
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
|
+
}
|