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.
Files changed (53) hide show
  1. package/README.md +34 -16
  2. package/docs/PI-COMPAT.md +151 -2
  3. package/docs/V15-MANAGED-RUNTIME.md +128 -0
  4. package/global-config/agents/architect.md +63 -1
  5. package/global-config/agents/executor.md +7 -0
  6. package/global-config/agents/plan-checker.md +8 -1
  7. package/global-config/agents/verifier.md +3 -3
  8. package/global-config/skills/database-engineering/references/workflow.md +15 -0
  9. package/global-config/skills/engineering-orchestrator/references/long-horizon.md +21 -11
  10. package/lib/activity-deadline.mjs +70 -0
  11. package/lib/adaptive-context-budget.mjs +25 -5
  12. package/lib/affected-tests.mjs +38 -5
  13. package/lib/capability-fabric.mjs +17 -0
  14. package/lib/code-intelligence/index.mjs +3 -2
  15. package/lib/code-intelligence/lsp-provider.mjs +326 -52
  16. package/lib/compaction-resume-guard.mjs +255 -0
  17. package/lib/completion-auditor.mjs +25 -5
  18. package/lib/context-engine-v11.mjs +20 -7
  19. package/lib/evidence-store.mjs +92 -8
  20. package/lib/execution-contract.mjs +592 -0
  21. package/lib/leaf-runtime-optimizer.mjs +76 -0
  22. package/lib/memory-engine.mjs +189 -16
  23. package/lib/performance-fabric.mjs +262 -107
  24. package/lib/pi-child-invocation.mjs +65 -0
  25. package/lib/pi-rpc-pool.mjs +61 -9
  26. package/lib/plan-salvage.mjs +124 -0
  27. package/lib/planning-speed-policy.mjs +131 -0
  28. package/lib/repo-graph.mjs +1 -1
  29. package/lib/repo-inspect.mjs +1 -0
  30. package/lib/runtime-artifacts.mjs +42 -0
  31. package/lib/runtime-events.mjs +33 -1
  32. package/lib/safety.mjs +1 -0
  33. package/lib/semantic-index.mjs +1 -1
  34. package/lib/service-manager.mjs +526 -0
  35. package/lib/session-display.mjs +49 -0
  36. package/lib/subagent-artifacts.mjs +182 -0
  37. package/lib/task-graph.mjs +87 -0
  38. package/lib/task-policy.mjs +150 -4
  39. package/lib/trajectory.mjs +111 -1
  40. package/lib/turbo-fast-path.mjs +44 -0
  41. package/lib/verification-broker.mjs +32 -27
  42. package/lib/workspace-fingerprint.mjs +45 -19
  43. package/lib/workspace-hygiene.mjs +481 -0
  44. package/lib/workspace-root.mjs +49 -0
  45. package/lib/worktree-sandbox.mjs +234 -11
  46. package/package.json +8 -5
  47. package/pi/extensions/ues-child-runtime.ts +170 -4
  48. package/pi/extensions/ues.ts +2063 -188
  49. package/scripts/check-release-consistency.mjs +25 -2
  50. package/scripts/check-source-integrity.mjs +471 -4
  51. package/scripts/eval-pi.mjs +76 -36
  52. package/scripts/smoke-pi-extension.mjs +1 -1
  53. package/pi/prompts/ues-run.md +0 -17
@@ -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
- input.scope || "project",
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
- 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)
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 = rrf + row.confidence * 0.01 + row.graph * 0.01 + row.scope * 0.006 + row.taskClass * 0.008
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) => row.lexical > 0 || row.semantic > 0.05 || row.graph > 0)
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
  }