opencode-agent-skill 14.4.0 → 15.1.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 +34 -16
- package/docs/PI-COMPAT.md +151 -2
- package/docs/V15-MANAGED-RUNTIME.md +128 -0
- package/global-config/agents/architect.md +63 -1
- package/global-config/agents/executor.md +7 -0
- package/global-config/agents/plan-checker.md +8 -1
- package/global-config/agents/verifier.md +3 -3
- package/global-config/skills/database-engineering/references/workflow.md +15 -0
- package/global-config/skills/engineering-orchestrator/references/long-horizon.md +21 -11
- package/lib/activity-deadline.mjs +70 -0
- package/lib/adaptive-context-budget.mjs +25 -5
- package/lib/affected-tests.mjs +38 -5
- package/lib/capability-fabric.mjs +17 -0
- package/lib/code-intelligence/index.mjs +3 -2
- package/lib/code-intelligence/lsp-provider.mjs +326 -52
- package/lib/compaction-resume-guard.mjs +255 -0
- package/lib/completion-auditor.mjs +25 -5
- package/lib/context-engine-v11.mjs +20 -7
- package/lib/evidence-store.mjs +92 -8
- package/lib/execution-contract.mjs +592 -0
- package/lib/leaf-runtime-optimizer.mjs +76 -0
- package/lib/memory-engine.mjs +189 -16
- package/lib/performance-fabric.mjs +262 -107
- package/lib/pi-child-invocation.mjs +65 -0
- package/lib/pi-rpc-pool.mjs +61 -9
- package/lib/plan-salvage.mjs +124 -0
- package/lib/planning-speed-policy.mjs +131 -0
- package/lib/repo-graph.mjs +1 -1
- package/lib/repo-inspect.mjs +1 -0
- package/lib/runtime-artifacts.mjs +42 -0
- package/lib/runtime-events.mjs +33 -1
- package/lib/safety.mjs +1 -0
- package/lib/semantic-index.mjs +1 -1
- package/lib/service-manager.mjs +526 -0
- package/lib/session-display.mjs +49 -0
- package/lib/subagent-artifacts.mjs +182 -0
- package/lib/task-graph.mjs +87 -0
- package/lib/task-policy.mjs +150 -4
- package/lib/trajectory.mjs +111 -1
- package/lib/turbo-fast-path.mjs +44 -0
- package/lib/verification-broker.mjs +32 -27
- package/lib/workspace-fingerprint.mjs +45 -19
- package/lib/workspace-hygiene.mjs +481 -0
- package/lib/workspace-root.mjs +49 -0
- package/lib/worktree-sandbox.mjs +234 -11
- package/package.json +8 -5
- package/pi/extensions/ues-child-runtime.ts +170 -4
- package/pi/extensions/ues.ts +2063 -188
- package/scripts/check-release-consistency.mjs +25 -2
- package/scripts/check-source-integrity.mjs +471 -4
- package/scripts/eval-pi.mjs +76 -36
- package/scripts/smoke-pi-extension.mjs +1 -1
- package/pi/prompts/ues-run.md +0 -17
package/lib/memory-engine.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises"
|
|
|
4
4
|
import { spawnSync } from "node:child_process"
|
|
5
5
|
import path from "node:path"
|
|
6
6
|
import { evidenceExists, putEvidence } from "./evidence-store.mjs"
|
|
7
|
+
import { buildRepoGraphCached } from "./repo-graph.mjs"
|
|
7
8
|
|
|
8
9
|
const MEMORY_DIR = ".ues-memory"
|
|
9
10
|
const MEMORY_FILE = "MEMORY.json"
|
|
@@ -44,15 +45,35 @@ function normalizedContent(value) {
|
|
|
44
45
|
return String(value || "").trim().replace(/\s+/g, " ")
|
|
45
46
|
}
|
|
46
47
|
|
|
48
|
+
function scopedIdentityFiles(scope, files = []) {
|
|
49
|
+
if (!["file", "module"].includes(String(scope || ""))) return []
|
|
50
|
+
return uniq((files || []).map(normalizeFile)).sort()
|
|
51
|
+
}
|
|
52
|
+
|
|
47
53
|
function idFor(input) {
|
|
54
|
+
const scope = input.scope || "project"
|
|
55
|
+
const scopedFiles = scopedIdentityFiles(scope, input.files)
|
|
48
56
|
const key = input.key || [
|
|
49
|
-
|
|
57
|
+
scope,
|
|
50
58
|
input.type || "episodic",
|
|
59
|
+
scopedFiles.length ? scopedFiles.join(",") : "",
|
|
51
60
|
normalizedContent(input.content),
|
|
52
61
|
].join(":")
|
|
53
62
|
return createHash("sha256").update(String(key)).digest("hex").slice(0, 20)
|
|
54
63
|
}
|
|
55
64
|
|
|
65
|
+
function sameMemoryIdentity(item, input) {
|
|
66
|
+
if (!item) return false
|
|
67
|
+
if (String(item.scope || "project") !== String(input.scope || "project")) return false
|
|
68
|
+
if (String(item.type || "episodic") !== String(input.type || "episodic")) return false
|
|
69
|
+
if (normalizedContent(item.content) !== normalizedContent(input.content)) return false
|
|
70
|
+
const expectedFiles = scopedIdentityFiles(input.scope, input.files)
|
|
71
|
+
if (!expectedFiles.length) return true
|
|
72
|
+
const actualFiles = scopedIdentityFiles(item.scope, item.files)
|
|
73
|
+
return actualFiles.length === expectedFiles.length &&
|
|
74
|
+
actualFiles.every((file, index) => file === expectedFiles[index])
|
|
75
|
+
}
|
|
76
|
+
|
|
56
77
|
function memoryPath(root) {
|
|
57
78
|
return path.join(path.resolve(root), MEMORY_DIR, MEMORY_FILE)
|
|
58
79
|
}
|
|
@@ -100,7 +121,8 @@ export async function proposeMemory(root, input = {}) {
|
|
|
100
121
|
const state = await readMemoryState(root)
|
|
101
122
|
const id = idFor({ ...input, type, scope, content })
|
|
102
123
|
const timestamp = now()
|
|
103
|
-
const existing = state.memories.find((item) => item.id === id)
|
|
124
|
+
const existing = state.memories.find((item) => item.id === id) ||
|
|
125
|
+
state.memories.find((item) => sameMemoryIdentity(item, { ...input, type, scope, content }))
|
|
104
126
|
if (existing) {
|
|
105
127
|
const reinforced = {
|
|
106
128
|
...existing,
|
|
@@ -108,6 +130,7 @@ export async function proposeMemory(root, input = {}) {
|
|
|
108
130
|
reinforcementCount: Number(existing.reinforcementCount || 1) + 1,
|
|
109
131
|
evidenceRefs: uniq([...(existing.evidenceRefs || []), ...(input.evidenceRefs || [])]),
|
|
110
132
|
files: uniq([...(existing.files || []), ...(input.files || []).map(normalizeFile)]),
|
|
133
|
+
dependencies: uniq([...(existing.dependencies || []), ...(input.dependencies || []).map(normalizeFile)]),
|
|
111
134
|
tags: uniq([...(existing.tags || []), ...(input.tags || [])]),
|
|
112
135
|
confidence: Math.max(clamp(existing.confidence), clamp(input.confidence, existing.confidence ?? 0.7)),
|
|
113
136
|
taskClass: input.taskClass || existing.taskClass || null,
|
|
@@ -127,6 +150,7 @@ export async function proposeMemory(root, input = {}) {
|
|
|
127
150
|
confidence: clamp(input.confidence),
|
|
128
151
|
evidenceRefs: uniq(input.evidenceRefs || []),
|
|
129
152
|
files: uniq((input.files || []).map(normalizeFile)),
|
|
153
|
+
dependencies: uniq((input.dependencies || []).map(normalizeFile)),
|
|
130
154
|
tags: uniq(input.tags || []),
|
|
131
155
|
sourceTask: input.sourceTask || null,
|
|
132
156
|
sourceCommit: input.sourceCommit || null,
|
|
@@ -312,6 +336,7 @@ export function buildMemorySnapshot(memories = []) {
|
|
|
312
336
|
content: normalizedContent(item.content),
|
|
313
337
|
confidence: clamp(item.confidence),
|
|
314
338
|
files: uniq((item.files || []).map(normalizeFile)).sort(),
|
|
339
|
+
dependencies: uniq((item.dependencies || []).map(normalizeFile)).sort(),
|
|
315
340
|
taskClass: item.taskClass || null,
|
|
316
341
|
verifiedAt: item.verifiedAt || null,
|
|
317
342
|
evidenceRefs: uniq(item.evidenceRefs || []).sort(),
|
|
@@ -330,6 +355,96 @@ export function buildMemorySnapshot(memories = []) {
|
|
|
330
355
|
}
|
|
331
356
|
}
|
|
332
357
|
|
|
358
|
+
function dependencyAdjacency(edges = []) {
|
|
359
|
+
const graph = new Map()
|
|
360
|
+
const connect = (left, right) => {
|
|
361
|
+
if (!left || !right) return
|
|
362
|
+
if (!graph.has(left)) graph.set(left, new Set())
|
|
363
|
+
graph.get(left).add(right)
|
|
364
|
+
}
|
|
365
|
+
for (const edge of edges || []) {
|
|
366
|
+
const from = normalizeFile(edge?.from)
|
|
367
|
+
const to = normalizeFile(edge?.to)
|
|
368
|
+
if (!from || !to) continue
|
|
369
|
+
// Retrieval uses dependency proximity, not ownership direction, so both
|
|
370
|
+
// importers and imported modules are useful neighbors.
|
|
371
|
+
connect(from, to)
|
|
372
|
+
connect(to, from)
|
|
373
|
+
}
|
|
374
|
+
return graph
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function dependencyGraphAffinity(queryFiles, memoryFiles, adjacency, maxDepth = 3) {
|
|
378
|
+
if (!queryFiles.length || !memoryFiles.length || !adjacency?.size) return 0
|
|
379
|
+
const targets = new Set(memoryFiles.map(normalizeFile))
|
|
380
|
+
let best = 0
|
|
381
|
+
for (const source of queryFiles.map(normalizeFile)) {
|
|
382
|
+
if (targets.has(source)) return 1
|
|
383
|
+
let frontier = new Set([source])
|
|
384
|
+
const seen = new Set(frontier)
|
|
385
|
+
for (let depth = 1; depth <= maxDepth && frontier.size; depth += 1) {
|
|
386
|
+
const next = new Set()
|
|
387
|
+
for (const current of frontier) {
|
|
388
|
+
for (const neighbor of adjacency.get(current) || []) {
|
|
389
|
+
if (targets.has(neighbor)) {
|
|
390
|
+
const score = depth === 1 ? 0.95 : depth === 2 ? 0.7 : 0.45
|
|
391
|
+
best = Math.max(best, score)
|
|
392
|
+
}
|
|
393
|
+
if (!seen.has(neighbor)) {
|
|
394
|
+
seen.add(neighbor)
|
|
395
|
+
next.add(neighbor)
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
frontier = next
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return best
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function exactFileAffinity(queryFiles, memoryFiles) {
|
|
406
|
+
const wanted = new Set(queryFiles.map(normalizeFile))
|
|
407
|
+
return memoryFiles.some((file) => wanted.has(normalizeFile(file))) ? 1 : 0
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function scopeEligible(item, queryFiles, pathGraph) {
|
|
411
|
+
if (!queryFiles.length) return true
|
|
412
|
+
const memoryFiles = uniq((item.files || []).map(normalizeFile))
|
|
413
|
+
if (item.scope === "file") return exactFileAffinity(queryFiles, memoryFiles) === 1
|
|
414
|
+
if (item.scope === "module") return pathGraph >= 0.45
|
|
415
|
+
return true
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function memoryAgeDays(item) {
|
|
419
|
+
const timestamp = Date.parse(item.lastSeenAt || item.verifiedAt || item.createdAt || "")
|
|
420
|
+
if (!timestamp) return 0
|
|
421
|
+
return Math.max(0, (Date.now() - timestamp) / 86_400_000)
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function memoryHalfLifeDays(item) {
|
|
425
|
+
const base = {
|
|
426
|
+
failure: 45,
|
|
427
|
+
episodic: 75,
|
|
428
|
+
decision: 120,
|
|
429
|
+
procedural: 180,
|
|
430
|
+
semantic: 240,
|
|
431
|
+
}[item.type] || 120
|
|
432
|
+
const reinforcement = Math.max(1, Number(item.reinforcementCount || 1))
|
|
433
|
+
return base * (1 + Math.log2(reinforcement) * 0.2)
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function agingScore(item) {
|
|
437
|
+
const age = memoryAgeDays(item)
|
|
438
|
+
const halfLife = memoryHalfLifeDays(item)
|
|
439
|
+
return Math.pow(0.5, age / Math.max(1, halfLife))
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function reinforcementScore(item) {
|
|
443
|
+
const reinforcement = Math.max(0, Number(item.reinforcementCount || 0))
|
|
444
|
+
const use = Math.max(0, Number(item.useCount || 0))
|
|
445
|
+
return Math.min(1, (Math.log1p(reinforcement) + 0.55 * Math.log1p(use)) / 4)
|
|
446
|
+
}
|
|
447
|
+
|
|
333
448
|
export async function retrieveMemories(root, query, options = {}) {
|
|
334
449
|
const state = await readMemoryState(root)
|
|
335
450
|
const memories = state.memories.filter((item) => item.status === "verified" && !item.supersededBy && !isExpired(item))
|
|
@@ -337,47 +452,99 @@ export async function retrieveMemories(root, query, options = {}) {
|
|
|
337
452
|
const queryVector = tokenVector(query)
|
|
338
453
|
const queryFiles = uniq((options.files || []).map(normalizeFile))
|
|
339
454
|
const lexical = bm25Scores(memories, queryTokens)
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
455
|
+
|
|
456
|
+
let repoGraph = null
|
|
457
|
+
if (queryFiles.length && memories.length && options.dependencyGraph !== false) {
|
|
458
|
+
repoGraph = await buildRepoGraphCached(root, {
|
|
459
|
+
maxFiles: Math.max(100, Math.min(6000, Number(options.maxGraphFiles || 2500))),
|
|
460
|
+
...(options.workspaceFingerprint ? { workspaceFingerprint: options.workspaceFingerprint } : {}),
|
|
461
|
+
}).catch(() => null)
|
|
462
|
+
}
|
|
463
|
+
const adjacency = dependencyAdjacency(repoGraph?.edges || [])
|
|
464
|
+
|
|
465
|
+
const rows = memories.map((item, index) => {
|
|
466
|
+
const memoryFiles = uniq((item.files || []).map(normalizeFile))
|
|
467
|
+
const pathGraph = graphAffinity(queryFiles, memoryFiles)
|
|
468
|
+
const dependency = dependencyGraphAffinity(
|
|
469
|
+
queryFiles,
|
|
470
|
+
uniq([...memoryFiles, ...(item.dependencies || []).map(normalizeFile)]),
|
|
471
|
+
adjacency,
|
|
472
|
+
)
|
|
473
|
+
const graph = Math.max(pathGraph, dependency)
|
|
474
|
+
const aging = agingScore(item)
|
|
475
|
+
const reinforcement = reinforcementScore(item)
|
|
476
|
+
return {
|
|
477
|
+
item,
|
|
478
|
+
lexical: lexical[index] || 0,
|
|
479
|
+
semantic: Math.max(0, cosine(queryVector, tokenVector([item.content, ...(item.tags || []), ...memoryFiles].join(" ")))),
|
|
480
|
+
graph,
|
|
481
|
+
pathGraph,
|
|
482
|
+
dependency,
|
|
483
|
+
taskClass: taskClassAffinity(options.taskClass, item.taskClass),
|
|
484
|
+
recency: recencyScore(item.lastUsedAt || item.lastSeenAt || item.verifiedAt || item.createdAt),
|
|
485
|
+
aging,
|
|
486
|
+
reinforcement,
|
|
487
|
+
confidence: clamp(item.confidence) * (0.65 + 0.35 * aging),
|
|
488
|
+
scope: 0,
|
|
489
|
+
scopeEligible: true,
|
|
490
|
+
}
|
|
491
|
+
})
|
|
492
|
+
for (const row of rows) {
|
|
493
|
+
row.scope = scopeAffinity(row.item.scope, row.pathGraph)
|
|
494
|
+
row.scopeEligible = options.strictScope === false
|
|
495
|
+
? true
|
|
496
|
+
: scopeEligible(row.item, queryFiles, row.pathGraph)
|
|
497
|
+
}
|
|
351
498
|
const ranks = {
|
|
352
499
|
lexical: rankMap(rows, "lexical"),
|
|
353
500
|
semantic: rankMap(rows, "semantic"),
|
|
354
501
|
graph: rankMap(rows, "graph"),
|
|
502
|
+
dependency: rankMap(rows, "dependency"),
|
|
355
503
|
taskClass: rankMap(rows, "taskClass"),
|
|
356
504
|
recency: rankMap(rows, "recency"),
|
|
505
|
+
aging: rankMap(rows, "aging"),
|
|
506
|
+
reinforcement: rankMap(rows, "reinforcement"),
|
|
357
507
|
}
|
|
358
508
|
for (const row of rows) {
|
|
359
509
|
let rrf = 0
|
|
360
|
-
for (const key of ["lexical", "semantic", "graph", "taskClass", "recency"]) {
|
|
510
|
+
for (const key of ["lexical", "semantic", "graph", "dependency", "taskClass", "recency", "aging", "reinforcement"]) {
|
|
361
511
|
const rank = ranks[key].get(row.item.id)
|
|
362
512
|
if (rank) rrf += 1 / (60 + rank)
|
|
363
513
|
}
|
|
364
|
-
row.score =
|
|
514
|
+
row.score =
|
|
515
|
+
rrf +
|
|
516
|
+
row.confidence * 0.01 +
|
|
517
|
+
row.graph * 0.01 +
|
|
518
|
+
row.dependency * 0.012 +
|
|
519
|
+
row.scope * 0.006 +
|
|
520
|
+
row.taskClass * 0.008 +
|
|
521
|
+
row.reinforcement * 0.006 +
|
|
522
|
+
row.aging * 0.004
|
|
365
523
|
}
|
|
366
524
|
const limit = Math.max(1, Math.min(Number(options.limit || 6), 30))
|
|
367
525
|
const results = rows
|
|
368
|
-
.filter((row) =>
|
|
526
|
+
.filter((row) =>
|
|
527
|
+
row.scopeEligible &&
|
|
528
|
+
(row.lexical > 0 || row.semantic > 0.05 || row.graph > 0 || row.dependency > 0)
|
|
529
|
+
)
|
|
369
530
|
.sort((a, b) => b.score - a.score || b.confidence - a.confidence || a.item.id.localeCompare(b.item.id))
|
|
370
531
|
.slice(0, limit)
|
|
371
532
|
.map((row) => ({
|
|
372
533
|
...row.item,
|
|
373
534
|
retrieval: {
|
|
535
|
+
version: 2,
|
|
374
536
|
score: Number(row.score.toFixed(8)),
|
|
375
537
|
lexical: Number(row.lexical.toFixed(6)),
|
|
376
538
|
semantic: Number(row.semantic.toFixed(6)),
|
|
377
539
|
graph: Number(row.graph.toFixed(6)),
|
|
540
|
+
pathGraph: Number(row.pathGraph.toFixed(6)),
|
|
541
|
+
dependency: Number(row.dependency.toFixed(6)),
|
|
378
542
|
taskClass: Number(row.taskClass.toFixed(6)),
|
|
379
543
|
scope: Number(row.scope.toFixed(6)),
|
|
380
544
|
recency: Number(row.recency.toFixed(6)),
|
|
545
|
+
aging: Number(row.aging.toFixed(6)),
|
|
546
|
+
reinforcement: Number(row.reinforcement.toFixed(6)),
|
|
547
|
+
effectiveConfidence: Number(row.confidence.toFixed(6)),
|
|
381
548
|
},
|
|
382
549
|
}))
|
|
383
550
|
if (options.touch === true && results.length) {
|
|
@@ -389,8 +556,14 @@ export async function retrieveMemories(root, query, options = {}) {
|
|
|
389
556
|
}
|
|
390
557
|
return {
|
|
391
558
|
schemaVersion: SCHEMA_VERSION,
|
|
559
|
+
retrievalVersion: 2,
|
|
392
560
|
query: String(query || ""),
|
|
393
561
|
eligible: memories.length,
|
|
562
|
+
dependencyGraph: repoGraph ? {
|
|
563
|
+
scannedFiles: repoGraph.scannedFiles || 0,
|
|
564
|
+
edgeCount: Array.isArray(repoGraph.edges) ? repoGraph.edges.length : 0,
|
|
565
|
+
truncated: repoGraph.truncated === true,
|
|
566
|
+
} : null,
|
|
394
567
|
results,
|
|
395
568
|
snapshot: buildMemorySnapshot(results),
|
|
396
569
|
}
|