opencode-agent-skill 15.0.0 → 15.1.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.
- package/README.md +5 -5
- package/docs/V15-MANAGED-RUNTIME.md +18 -0
- package/global-config/agents/verifier.md +6 -2
- package/lib/affected-tests.mjs +58 -8
- package/lib/capability-fabric.mjs +107 -48
- package/lib/code-intelligence/index.mjs +4 -15
- package/lib/code-intelligence/lsp-pool.mjs +694 -0
- package/lib/code-intelligence/lsp-provider.mjs +481 -67
- package/lib/compaction-resume-guard.mjs +327 -0
- package/lib/completion-auditor.mjs +20 -0
- package/lib/context-engine-v11.mjs +20 -7
- package/lib/context-manifest.mjs +38 -21
- package/lib/decision-policy.mjs +40 -2
- package/lib/evidence-store.mjs +121 -31
- package/lib/executable-probe.mjs +69 -0
- package/lib/execution-contract.mjs +29 -16
- package/lib/hierarchical-context.mjs +31 -3
- package/lib/learning-engine.mjs +164 -65
- package/lib/memory-engine.mjs +348 -98
- package/lib/model-config.mjs +86 -7
- package/lib/performance-fabric.mjs +354 -107
- package/lib/permission-policy.mjs +232 -0
- package/lib/pi-rpc-pool.mjs +170 -5
- package/lib/provider-recovery.mjs +51 -0
- package/lib/repo-graph.mjs +38 -5
- package/lib/runtime-events.mjs +31 -10
- package/lib/semantic-index.mjs +67 -20
- package/lib/skill-compiler.mjs +84 -18
- package/lib/subagent-artifacts.mjs +182 -0
- package/lib/task-policy.mjs +80 -7
- package/lib/trajectory.mjs +25 -1
- package/lib/untrusted-output.mjs +106 -0
- package/lib/verification-broker.mjs +32 -27
- package/lib/workspace-fingerprint.mjs +43 -8
- package/lib/workspace-hygiene.mjs +481 -0
- package/package.json +8 -6
- package/pi/extensions/ues-child-runtime.ts +103 -5
- package/pi/extensions/ues.ts +984 -95
- package/scripts/benchmark-lsp-pool.mjs +128 -0
- package/scripts/check-release-consistency.mjs +16 -4
- package/scripts/check-source-integrity.mjs +60 -1
- package/scripts/run-test-suite.mjs +143 -0
- package/scripts/smoke-package-closure.mjs +4 -0
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import { createHash } from "node:crypto"
|
|
2
|
+
import { existsSync } from "node:fs"
|
|
3
|
+
import { readdir, readFile } from "node:fs/promises"
|
|
4
|
+
import path from "node:path"
|
|
5
|
+
import { checkpointWork } from "./task-engine.mjs"
|
|
6
|
+
|
|
7
|
+
const WORK_DIR = ".ues-work"
|
|
8
|
+
const MAX_WORKSPACES = 3
|
|
9
|
+
const MAX_TASKS = 28
|
|
10
|
+
const MAX_RECEIPTS = 20
|
|
11
|
+
|
|
12
|
+
function digest(value) {
|
|
13
|
+
return createHash("sha256").update(String(value || "")).digest("hex").slice(0, 20)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function readJson(file, fallback = null) {
|
|
17
|
+
try {
|
|
18
|
+
return JSON.parse(await readFile(file, "utf8"))
|
|
19
|
+
} catch (error) {
|
|
20
|
+
if (error?.code === "ENOENT") return fallback
|
|
21
|
+
return fallback
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function inside(base, target) {
|
|
26
|
+
const root = path.resolve(base)
|
|
27
|
+
const resolved = path.resolve(target)
|
|
28
|
+
return resolved === root || resolved.startsWith(root + path.sep)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function normalizePhasePath(value) {
|
|
32
|
+
return String(value || "").replaceAll("\\", "/").replace(/^\.\/+/, "")
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function activeWorkspaces(root, max = MAX_WORKSPACES) {
|
|
36
|
+
const base = path.join(path.resolve(root), WORK_DIR)
|
|
37
|
+
if (!existsSync(base)) return []
|
|
38
|
+
const entries = await readdir(base, { withFileTypes: true }).catch(() => [])
|
|
39
|
+
const rows = []
|
|
40
|
+
for (const entry of entries) {
|
|
41
|
+
if (!entry.isDirectory()) continue
|
|
42
|
+
const dir = path.join(base, entry.name)
|
|
43
|
+
const state = await readJson(path.join(dir, "STATE.json"), null)
|
|
44
|
+
if (!state || String(state.status || "").toLowerCase() === "completed") continue
|
|
45
|
+
rows.push({
|
|
46
|
+
slug: entry.name,
|
|
47
|
+
dir,
|
|
48
|
+
state,
|
|
49
|
+
updatedAt: Date.parse(state.updatedAt || state.createdAt || "") || 0,
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
return rows
|
|
53
|
+
.sort((a, b) => b.updatedAt - a.updatedAt || a.slug.localeCompare(b.slug))
|
|
54
|
+
.slice(0, Math.max(1, Math.min(10, Number(max || MAX_WORKSPACES))))
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function taskRows(plan, state) {
|
|
58
|
+
const tasks = Array.isArray(plan?.tasks) ? plan.tasks : []
|
|
59
|
+
const rows = tasks.map((task) => {
|
|
60
|
+
const runtime = state?.tasks?.[task.id] || {}
|
|
61
|
+
return {
|
|
62
|
+
id: task.id,
|
|
63
|
+
title: String(task.title || "").slice(0, 240),
|
|
64
|
+
status: runtime.status || "unknown",
|
|
65
|
+
attempts: Number(runtime.attempts || 0),
|
|
66
|
+
runId: runtime.runId || null,
|
|
67
|
+
dependsOn: Array.isArray(task.dependsOn) ? task.dependsOn.slice(0, 24) : [],
|
|
68
|
+
evidenceStrength: runtime.evidenceStrength || null,
|
|
69
|
+
report: runtime.report || null,
|
|
70
|
+
lastError: runtime.lastError ? String(runtime.lastError).slice(0, 800) : null,
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
rows.sort((a, b) => {
|
|
74
|
+
const rank = (row) => row.status === "running" ? 0 : row.status === "completed" ? 2 : 1
|
|
75
|
+
return rank(a) - rank(b) || String(a.id).localeCompare(String(b.id))
|
|
76
|
+
})
|
|
77
|
+
return rows.slice(0, MAX_TASKS)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function receiptRows(evidence) {
|
|
81
|
+
return (Array.isArray(evidence?.receipts) ? evidence.receipts : [])
|
|
82
|
+
.slice(-MAX_RECEIPTS)
|
|
83
|
+
.map((item) => ({
|
|
84
|
+
id: item.id || null,
|
|
85
|
+
kind: item.kind || "verification-receipt",
|
|
86
|
+
task: item.task || null,
|
|
87
|
+
runId: item.runId || null,
|
|
88
|
+
passed: item.passed === true,
|
|
89
|
+
verdict: item.verdict || (item.passed === true ? "PASS" : null),
|
|
90
|
+
command: item.command || null,
|
|
91
|
+
workspaceAfter: item.workspaceAfter || null,
|
|
92
|
+
recordedAt: item.recordedAt || item.finishedAt || null,
|
|
93
|
+
}))
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function gateRows(evidence) {
|
|
97
|
+
return (Array.isArray(evidence?.gateReceipts) ? evidence.gateReceipts : [])
|
|
98
|
+
.slice(-12)
|
|
99
|
+
.map((item) => ({
|
|
100
|
+
id: item.id || null,
|
|
101
|
+
kind: item.kind || null,
|
|
102
|
+
verdict: item.verdict || null,
|
|
103
|
+
verifier: item.verifier || null,
|
|
104
|
+
planHash: item.planHash || null,
|
|
105
|
+
workspaceFingerprint: item.workspaceFingerprint || null,
|
|
106
|
+
recordedAt: item.recordedAt || null,
|
|
107
|
+
}))
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function phaseRows(dir, manifest) {
|
|
111
|
+
const artifacts = Array.isArray(manifest?.artifacts) ? manifest.artifacts : []
|
|
112
|
+
const rows = []
|
|
113
|
+
for (const relative of artifacts.slice(0, 32)) {
|
|
114
|
+
const normalized = normalizePhasePath(relative)
|
|
115
|
+
const target = path.resolve(dir, normalized)
|
|
116
|
+
if (!normalized || !inside(dir, target)) continue
|
|
117
|
+
const phase = await readJson(target, null)
|
|
118
|
+
if (!phase) continue
|
|
119
|
+
rows.push({
|
|
120
|
+
artifact: normalized,
|
|
121
|
+
phase: phase.phase ?? null,
|
|
122
|
+
title: String(phase.title || "").slice(0, 240),
|
|
123
|
+
status: phase.status || "UNKNOWN",
|
|
124
|
+
sourceBodyHash: phase.sourceBodyHash || null,
|
|
125
|
+
evidenceCount: Array.isArray(phase.evidence) ? phase.evidence.length : 0,
|
|
126
|
+
finalVerdicts: phase.finalVerdicts || null,
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
return rows
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function stateSummary(state) {
|
|
133
|
+
return {
|
|
134
|
+
status: state?.status || null,
|
|
135
|
+
goal: state?.goal ? String(state.goal).slice(0, 800) : null,
|
|
136
|
+
updatedAt: state?.updatedAt || null,
|
|
137
|
+
planHash: state?.planHash || null,
|
|
138
|
+
planApproved: state?.planApproved === true,
|
|
139
|
+
nextAction: state?.nextAction || null,
|
|
140
|
+
checkpoint: state?.checkpoint ? {
|
|
141
|
+
id: state.checkpoint.id || null,
|
|
142
|
+
reason: state.checkpoint.reason || null,
|
|
143
|
+
createdAt: state.checkpoint.createdAt || null,
|
|
144
|
+
currentTaskId: state.checkpoint.currentTaskId || null,
|
|
145
|
+
runId: state.checkpoint.runId || null,
|
|
146
|
+
planHash: state.checkpoint.planHash || null,
|
|
147
|
+
workspaceFingerprint: state.checkpoint.workspaceFingerprint || null,
|
|
148
|
+
nextAction: state.checkpoint.nextAction || null,
|
|
149
|
+
evidencePointers: Array.isArray(state.checkpoint.evidencePointers)
|
|
150
|
+
? state.checkpoint.evidencePointers.slice(-20)
|
|
151
|
+
: [],
|
|
152
|
+
} : null,
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function buildInstructionEpoch(state, tasks, receipts, gateReceipts) {
|
|
157
|
+
const rows = Array.isArray(tasks) ? tasks : []
|
|
158
|
+
const passedTaskReceipts = new Set(
|
|
159
|
+
(Array.isArray(receipts) ? receipts : [])
|
|
160
|
+
.filter((item) => item?.passed === true && item?.task)
|
|
161
|
+
.map((item) => String(item.task)),
|
|
162
|
+
)
|
|
163
|
+
const completedTaskIds = rows.filter((row) => row.status === "completed").map((row) => row.id)
|
|
164
|
+
const activeTaskIds = rows.filter((row) => row.status === "running").map((row) => row.id)
|
|
165
|
+
const failedTaskIds = rows.filter((row) => row.status === "failed").map((row) => row.id)
|
|
166
|
+
const pendingTaskIds = rows
|
|
167
|
+
.filter((row) => !["completed", "failed"].includes(String(row.status || "").toLowerCase()))
|
|
168
|
+
.map((row) => row.id)
|
|
169
|
+
const tasksWithoutPassingReceipt = completedTaskIds.filter((id) => !passedTaskReceipts.has(String(id)))
|
|
170
|
+
const decisions = (Array.isArray(state?.decisions) ? state.decisions : [])
|
|
171
|
+
.slice(-8)
|
|
172
|
+
.map((item) => ({
|
|
173
|
+
at: item?.at || null,
|
|
174
|
+
text: item?.text ? String(item.text).slice(0, 600) : null,
|
|
175
|
+
source: item?.source || null,
|
|
176
|
+
risk: item?.policy?.risk || null,
|
|
177
|
+
autoResolvable: item?.policy?.autoResolvable === true,
|
|
178
|
+
confidence: Number.isFinite(Number(item?.policy?.decision?.confidence))
|
|
179
|
+
? Number(item.policy.decision.confidence)
|
|
180
|
+
: null,
|
|
181
|
+
}))
|
|
182
|
+
|
|
183
|
+
const body = {
|
|
184
|
+
schemaVersion: 1,
|
|
185
|
+
source: "durable-state-fold",
|
|
186
|
+
objective: state?.goal ? String(state.goal).slice(0, 1200) : null,
|
|
187
|
+
status: state?.status || null,
|
|
188
|
+
planHash: state?.planHash || null,
|
|
189
|
+
planApproved: state?.planApproved === true,
|
|
190
|
+
activeTaskIds,
|
|
191
|
+
pendingTaskIds,
|
|
192
|
+
completedTaskIds,
|
|
193
|
+
failedTaskIds,
|
|
194
|
+
blockers: (Array.isArray(state?.blockers) ? state.blockers : []).slice(-12).map((item) => String(item).slice(0, 500)),
|
|
195
|
+
recentDecisions: decisions,
|
|
196
|
+
checkpoint: state?.checkpoint ? {
|
|
197
|
+
id: state.checkpoint.id || null,
|
|
198
|
+
currentTaskId: state.checkpoint.currentTaskId || null,
|
|
199
|
+
runId: state.checkpoint.runId || null,
|
|
200
|
+
workspaceFingerprint: state.checkpoint.workspaceFingerprint || null,
|
|
201
|
+
nextAction: state.checkpoint.nextAction || null,
|
|
202
|
+
} : null,
|
|
203
|
+
nextAction: state?.nextAction || null,
|
|
204
|
+
verification: {
|
|
205
|
+
tasksWithoutPassingReceipt,
|
|
206
|
+
passingReceiptIds: (Array.isArray(receipts) ? receipts : [])
|
|
207
|
+
.filter((item) => item?.passed === true && item?.id)
|
|
208
|
+
.slice(-20)
|
|
209
|
+
.map((item) => item.id),
|
|
210
|
+
passingGateReceiptIds: (Array.isArray(gateReceipts) ? gateReceipts : [])
|
|
211
|
+
.filter((item) => String(item?.verdict || "").toUpperCase() === "PASS" && item?.id)
|
|
212
|
+
.slice(-12)
|
|
213
|
+
.map((item) => item.id),
|
|
214
|
+
},
|
|
215
|
+
}
|
|
216
|
+
return {
|
|
217
|
+
...body,
|
|
218
|
+
epochHash: digest(JSON.stringify(body)),
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export async function checkpointDurableWorkBeforeCompaction(root, options = {}) {
|
|
223
|
+
const reason = String(options.reason || "unknown")
|
|
224
|
+
const rows = await activeWorkspaces(root, options.maxWorkspaces || MAX_WORKSPACES)
|
|
225
|
+
const results = []
|
|
226
|
+
for (const row of rows) {
|
|
227
|
+
try {
|
|
228
|
+
const checkpoint = await checkpointWork(root, row.slug, {
|
|
229
|
+
reason: `pi-compaction:${reason}`,
|
|
230
|
+
})
|
|
231
|
+
results.push({ slug: row.slug, checkpoint, ok: true })
|
|
232
|
+
} catch (error) {
|
|
233
|
+
results.push({
|
|
234
|
+
slug: row.slug,
|
|
235
|
+
checkpoint: null,
|
|
236
|
+
ok: false,
|
|
237
|
+
error: error instanceof Error ? error.message : String(error),
|
|
238
|
+
})
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return results
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export async function buildCompactionResumeGuard(root, options = {}) {
|
|
245
|
+
const rows = await activeWorkspaces(root, options.maxWorkspaces || MAX_WORKSPACES)
|
|
246
|
+
const workspaces = []
|
|
247
|
+
for (const row of rows) {
|
|
248
|
+
const [plan, evidence, contract, manifest] = await Promise.all([
|
|
249
|
+
readJson(path.join(row.dir, "PLAN.json"), null),
|
|
250
|
+
readJson(path.join(row.dir, "EVIDENCE.json"), {}),
|
|
251
|
+
readJson(path.join(row.dir, "EXECUTION_CONTRACT.json"), null),
|
|
252
|
+
readJson(path.join(row.dir, "phases", "MANIFEST.json"), null),
|
|
253
|
+
])
|
|
254
|
+
const phases = await phaseRows(row.dir, manifest)
|
|
255
|
+
const sources = {
|
|
256
|
+
state: path.relative(root, path.join(row.dir, "STATE.json")).replaceAll("\\", "/"),
|
|
257
|
+
plan: path.relative(root, path.join(row.dir, "PLAN.json")).replaceAll("\\", "/"),
|
|
258
|
+
evidence: path.relative(root, path.join(row.dir, "EVIDENCE.json")).replaceAll("\\", "/"),
|
|
259
|
+
executionContract: contract
|
|
260
|
+
? path.relative(root, path.join(row.dir, "EXECUTION_CONTRACT.json")).replaceAll("\\", "/")
|
|
261
|
+
: null,
|
|
262
|
+
phaseManifest: manifest
|
|
263
|
+
? path.relative(root, path.join(row.dir, "phases", "MANIFEST.json")).replaceAll("\\", "/")
|
|
264
|
+
: null,
|
|
265
|
+
}
|
|
266
|
+
const tasks = taskRows(plan, row.state)
|
|
267
|
+
const receipts = receiptRows(evidence)
|
|
268
|
+
const gateReceipts = gateRows(evidence)
|
|
269
|
+
const instructionEpoch = buildInstructionEpoch(row.state, tasks, receipts, gateReceipts)
|
|
270
|
+
workspaces.push({
|
|
271
|
+
slug: row.slug,
|
|
272
|
+
sources,
|
|
273
|
+
sourceHashes: {
|
|
274
|
+
state: digest(JSON.stringify(row.state)),
|
|
275
|
+
plan: digest(JSON.stringify(plan || {})),
|
|
276
|
+
evidence: digest(JSON.stringify(evidence || {})),
|
|
277
|
+
executionContract: contract ? digest(JSON.stringify(contract)) : null,
|
|
278
|
+
phaseManifest: manifest ? digest(JSON.stringify(manifest)) : null,
|
|
279
|
+
},
|
|
280
|
+
state: stateSummary(row.state),
|
|
281
|
+
instructionEpoch,
|
|
282
|
+
tasks,
|
|
283
|
+
receipts,
|
|
284
|
+
gateReceipts,
|
|
285
|
+
executionContract: contract ? {
|
|
286
|
+
schemaVersion: contract.schemaVersion || null,
|
|
287
|
+
taskHash: contract.taskHash || null,
|
|
288
|
+
gates: contract.gates || null,
|
|
289
|
+
phaseCount: Array.isArray(contract.phases) ? contract.phases.length : 0,
|
|
290
|
+
approvedInheritedDirtyPaths: Array.isArray(contract.approvedInheritedDirtyPaths)
|
|
291
|
+
? contract.approvedInheritedDirtyPaths.slice(0, 40)
|
|
292
|
+
: [],
|
|
293
|
+
} : null,
|
|
294
|
+
phases,
|
|
295
|
+
})
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return {
|
|
299
|
+
schemaVersion: 1,
|
|
300
|
+
kind: "ues-durable-compaction-resume-guard",
|
|
301
|
+
generatedAt: new Date().toISOString(),
|
|
302
|
+
compactionReason: options.reason || null,
|
|
303
|
+
source: "deterministic-filesystem-artifacts",
|
|
304
|
+
modelSummaryTrustedForDurableState: false,
|
|
305
|
+
workspaceCount: workspaces.length,
|
|
306
|
+
workspaces,
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export function renderCompactionResumeGuard(packet, options = {}) {
|
|
311
|
+
if (!packet?.workspaces?.length) return ""
|
|
312
|
+
const maxChars = Math.max(8000, Math.min(48000, Number(options.maxChars || 28000)))
|
|
313
|
+
const preface = [
|
|
314
|
+
"## UES Durable Compaction Resume Guard",
|
|
315
|
+
"Authoritative durable state was rebuilt from filesystem artifacts after Pi compaction.",
|
|
316
|
+
"The model-generated compaction summary is advisory only for durable execution state.",
|
|
317
|
+
"If the summary conflicts with STATE.json, PLAN.json, EXECUTION_CONTRACT.json, phase artifacts, instructionEpoch, or evidence receipts below, the deterministic artifacts win.",
|
|
318
|
+
"Treat instructionEpoch as the compact deterministic control-plane fold: objective, active work, blockers, decisions, receipt gaps and next action.",
|
|
319
|
+
"Do not mark a task/phase complete unless the rebuilt state and fresh receipts support it.",
|
|
320
|
+
"Follow checkpoint.nextAction/state.nextAction and re-read the referenced artifact when more detail is required.",
|
|
321
|
+
"",
|
|
322
|
+
].join("\n")
|
|
323
|
+
const encoded = JSON.stringify(packet, null, 2)
|
|
324
|
+
if (preface.length + encoded.length <= maxChars) return preface + encoded
|
|
325
|
+
return preface + encoded.slice(0, Math.max(0, maxChars - preface.length - 120)) +
|
|
326
|
+
"\n...[resume packet bounded; re-read referenced .ues-work artifacts for exact detail]"
|
|
327
|
+
}
|
|
@@ -42,6 +42,17 @@ function receiptPassed(row = {}) {
|
|
|
42
42
|
return Boolean(receipt?.passed === true && Number(receipt?.exitCode) === 0)
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
function claimEvidenceStatus(value) {
|
|
46
|
+
const lines = text(value).split(/\r?\n/).map((line) => line.trim()).filter(Boolean)
|
|
47
|
+
const counts = { verified: 0, inferred: 0, unknown: 0 }
|
|
48
|
+
for (const line of lines) {
|
|
49
|
+
if (/^(?:[-*]\s*)?VERIFIED\s*:/i.test(line)) counts.verified += 1
|
|
50
|
+
else if (/^(?:[-*]\s*)?INFERRED\s*:/i.test(line)) counts.inferred += 1
|
|
51
|
+
else if (/^(?:[-*]\s*)?UNKNOWN\s*:/i.test(line)) counts.unknown += 1
|
|
52
|
+
}
|
|
53
|
+
return counts
|
|
54
|
+
}
|
|
55
|
+
|
|
45
56
|
export function auditCompletion(input = {}) {
|
|
46
57
|
const failures = []
|
|
47
58
|
const warnings = []
|
|
@@ -67,6 +78,14 @@ export function auditCompletion(input = {}) {
|
|
|
67
78
|
}
|
|
68
79
|
if (Object.hasOwn(sections, "checks-not-run") && !benignEmptySection(sections["checks-not-run"])) warnings.push("verification-reports-checks-not-run")
|
|
69
80
|
|
|
81
|
+
const claimStatus = claimEvidenceStatus(sections["acceptance-criteria-proven"])
|
|
82
|
+
const claimStatusCount = claimStatus.verified + claimStatus.inferred + claimStatus.unknown
|
|
83
|
+
if (input.requireClaimEvidenceStatus === true && claimStatusCount === 0) {
|
|
84
|
+
failures.push("acceptance-criteria-evidence-status-missing")
|
|
85
|
+
}
|
|
86
|
+
if (claimStatus.inferred > 0) failures.push("acceptance-criteria-inferred-not-proven")
|
|
87
|
+
if (claimStatus.unknown > 0) failures.push("acceptance-criteria-unknown-not-proven")
|
|
88
|
+
|
|
70
89
|
const snapshot = input.workspaceSnapshot || null
|
|
71
90
|
if (snapshot) {
|
|
72
91
|
if (snapshot.cacheable === true && !text(snapshot.fingerprint)) failures.push("workspace-fingerprint-missing")
|
|
@@ -95,6 +114,7 @@ export function auditCompletion(input = {}) {
|
|
|
95
114
|
visual: requireVisual ? passRun(visual) : null,
|
|
96
115
|
reportSections: Object.keys(sections).sort(),
|
|
97
116
|
passingReceiptCount: passingReceipts.length,
|
|
117
|
+
claimEvidenceStatus: claimStatus,
|
|
98
118
|
workspaceFingerprint: snapshot?.fingerprint || null,
|
|
99
119
|
changedFiles: Array.isArray(snapshot?.changedFiles) ? snapshot.changedFiles.slice(0, 200) : [],
|
|
100
120
|
},
|
|
@@ -101,6 +101,16 @@ export async function buildAdaptiveTaskContext(root, task, options = {}) {
|
|
|
101
101
|
const policy = options.policy || {}
|
|
102
102
|
const capabilities = options.capabilities || inferTaskCapabilities(taskText(task), options.facts || {})
|
|
103
103
|
const evidenceBudget = options.evidenceBudget || planEvidenceBudget(policy, task, capabilities.required)
|
|
104
|
+
const contextBuildStartedAt = Date.now()
|
|
105
|
+
const memoryPromise = retrieveMemories(root, taskText(task), {
|
|
106
|
+
files: taskFilesForMemory(task),
|
|
107
|
+
limit: options.memoryLimit ?? 6,
|
|
108
|
+
taskClass: options.taskClass || task.type || policy.executionProfile || policy.profile?.name || null,
|
|
109
|
+
touch: options.touchMemories !== false,
|
|
110
|
+
}).catch(() => ({ schemaVersion: 1, query: taskText(task), eligible: 0, results: [] }))
|
|
111
|
+
const fabricPromise = capabilityFabricStatus(root).catch(() => null)
|
|
112
|
+
|
|
113
|
+
const manifestStartedAt = Date.now()
|
|
104
114
|
const manifest = await buildContextManifest(root, task, {
|
|
105
115
|
budget: evidenceBudget.total,
|
|
106
116
|
evidenceBudget,
|
|
@@ -110,21 +120,18 @@ export async function buildAdaptiveTaskContext(root, task, options = {}) {
|
|
|
110
120
|
changedFiles: options.changedFiles,
|
|
111
121
|
workspaceFingerprint: options.workspaceFingerprint,
|
|
112
122
|
})
|
|
123
|
+
const manifestMs = Date.now() - manifestStartedAt
|
|
113
124
|
|
|
125
|
+
const externalizeStartedAt = Date.now()
|
|
114
126
|
const externalized = await externalizeContextExcerpts(root, manifest, {
|
|
115
127
|
task,
|
|
116
128
|
threshold: options.externalizeThreshold,
|
|
117
129
|
inlineChars: options.inlineChars,
|
|
118
130
|
})
|
|
131
|
+
const externalizeMs = Date.now() - externalizeStartedAt
|
|
119
132
|
|
|
120
|
-
const memoryRetrieval = await
|
|
121
|
-
files: taskFilesForMemory(task),
|
|
122
|
-
limit: options.memoryLimit ?? 6,
|
|
123
|
-
taskClass: options.taskClass || task.type || policy.executionProfile || policy.profile?.name || null,
|
|
124
|
-
touch: options.touchMemories !== false,
|
|
125
|
-
}).catch(() => ({ schemaVersion: 1, query: taskText(task), eligible: 0, results: [] }))
|
|
133
|
+
const [memoryRetrieval, fabric] = await Promise.all([memoryPromise, fabricPromise])
|
|
126
134
|
const memories = memoryRetrieval.results || []
|
|
127
|
-
const fabric = await capabilityFabricStatus(root).catch(() => null)
|
|
128
135
|
const providerHints = (fabric?.rows || [])
|
|
129
136
|
.filter((row) => row.selected)
|
|
130
137
|
.slice(0, 12)
|
|
@@ -202,6 +209,12 @@ export async function buildAdaptiveTaskContext(root, task, options = {}) {
|
|
|
202
209
|
entries: externalized.externalized,
|
|
203
210
|
},
|
|
204
211
|
promptEnvelope,
|
|
212
|
+
performance: {
|
|
213
|
+
parallelContextPreparation: true,
|
|
214
|
+
manifestMs,
|
|
215
|
+
externalizeMs,
|
|
216
|
+
totalMs: Date.now() - contextBuildStartedAt,
|
|
217
|
+
},
|
|
205
218
|
promptCache: {
|
|
206
219
|
stablePrefixHash: promptEnvelope.stablePrefixHash,
|
|
207
220
|
dynamicHash: promptEnvelope.dynamicHash,
|
package/lib/context-manifest.mjs
CHANGED
|
@@ -7,6 +7,7 @@ import { taskFiles } from "./task-graph.mjs"
|
|
|
7
7
|
import { buildSemanticIndexCached, querySemanticIndex } from "./semantic-index.mjs"
|
|
8
8
|
import { queryContextHierarchy } from "./hierarchical-context.mjs"
|
|
9
9
|
import { rankContextGraph } from "./context-graph-rank.mjs"
|
|
10
|
+
import { runtimeWorkspaceSnapshot } from "./workspace-fingerprint.mjs"
|
|
10
11
|
|
|
11
12
|
const ROOT_INSTRUCTIONS = [
|
|
12
13
|
"AGENTS.md",
|
|
@@ -249,9 +250,20 @@ export async function buildContextManifest(root, task, options = {}) {
|
|
|
249
250
|
const evidenceBudget = options.evidenceBudget || null
|
|
250
251
|
const declared = taskFiles(task)
|
|
251
252
|
const terms = taskTerms(task)
|
|
253
|
+
let workspaceSnapshot = null
|
|
254
|
+
const suppliedFingerprint = String(options.workspaceFingerprint || "").trim()
|
|
255
|
+
if (!Array.isArray(options.changedFiles) || !suppliedFingerprint) {
|
|
256
|
+
try {
|
|
257
|
+
workspaceSnapshot = runtimeWorkspaceSnapshot(root, { workspaceState: options.workspaceState })
|
|
258
|
+
} catch {}
|
|
259
|
+
}
|
|
260
|
+
const reusableWorkspaceFingerprint = suppliedFingerprint ||
|
|
261
|
+
(workspaceSnapshot?.cacheable ? String(workspaceSnapshot.fingerprint || "") : "")
|
|
252
262
|
const changed = Array.isArray(options.changedFiles)
|
|
253
263
|
? options.changedFiles.map((file) => String(file || "").replaceAll("\\", "/")).filter(Boolean)
|
|
254
|
-
:
|
|
264
|
+
: Array.isArray(workspaceSnapshot?.changedFiles)
|
|
265
|
+
? workspaceSnapshot.changedFiles
|
|
266
|
+
: gitChangedFiles(root)
|
|
255
267
|
const strategy = String(options.strategy || "semantic+graph+git")
|
|
256
268
|
const queryText = [
|
|
257
269
|
task?.title,
|
|
@@ -263,22 +275,32 @@ export async function buildContextManifest(root, task, options = {}) {
|
|
|
263
275
|
const semanticSnapshot = await buildSemanticIndexCached(root, {
|
|
264
276
|
maxFiles: options.semanticMaxFiles ?? 6000,
|
|
265
277
|
maxDepth: options.semanticMaxDepth ?? 14,
|
|
266
|
-
workspaceFingerprint:
|
|
278
|
+
workspaceFingerprint: reusableWorkspaceFingerprint,
|
|
267
279
|
}).catch(() => null)
|
|
268
280
|
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
281
|
+
const useGraph = strategy.includes("graph")
|
|
282
|
+
const [hierarchy, semantic, graph, instructions] = await Promise.all([
|
|
283
|
+
queryContextHierarchy(root, queryText, {
|
|
284
|
+
declaredFiles: declared,
|
|
285
|
+
maxScopes: options.hierarchyMaxScopes ?? 6,
|
|
286
|
+
maxFiles: options.semanticMaxFiles ?? 6000,
|
|
287
|
+
builtIndex: semanticSnapshot || undefined,
|
|
288
|
+
}).catch(() => null),
|
|
289
|
+
querySemanticIndex(root, queryText, {
|
|
290
|
+
limit: 32,
|
|
291
|
+
maxFiles: options.semanticMaxFiles ?? 6000,
|
|
292
|
+
builtIndex: semanticSnapshot || undefined,
|
|
293
|
+
}).catch(() => null),
|
|
294
|
+
useGraph
|
|
295
|
+
? buildRepoGraphCached(root, {
|
|
296
|
+
maxFiles: options.maxFiles ?? 2500,
|
|
297
|
+
workspaceFingerprint: reusableWorkspaceFingerprint,
|
|
298
|
+
}).catch(() => ({ nodes: [], edges: [], hotspots: [], scannedFiles: 0, truncated: false }))
|
|
299
|
+
: Promise.resolve({ nodes: [], edges: [], hotspots: [], scannedFiles: 0, truncated: false }),
|
|
300
|
+
nearbyInstructions(root, declared).catch(() => []),
|
|
301
|
+
])
|
|
275
302
|
const hierarchyScopes = hierarchy?.scopes?.map((item) => item.path) || []
|
|
276
303
|
|
|
277
|
-
const semantic = await querySemanticIndex(root, queryText, {
|
|
278
|
-
limit: 32,
|
|
279
|
-
maxFiles: options.semanticMaxFiles ?? 6000,
|
|
280
|
-
builtIndex: semanticSnapshot || undefined,
|
|
281
|
-
}).catch(() => null)
|
|
282
304
|
const scopedSemanticResults = (semantic?.results || []).map((item) => {
|
|
283
305
|
const scopeRank = hierarchyScopes.findIndex((scope) =>
|
|
284
306
|
scope === "." || item.path === scope || item.path.startsWith(scope + "/"),
|
|
@@ -293,13 +315,6 @@ export async function buildContextManifest(root, task, options = {}) {
|
|
|
293
315
|
}).sort((a, b) => b.score - a.score || a.path.localeCompare(b.path))
|
|
294
316
|
const semanticPaths = scopedSemanticResults.map((item) => item.path)
|
|
295
317
|
|
|
296
|
-
const useGraph = strategy.includes("graph")
|
|
297
|
-
const graph = useGraph
|
|
298
|
-
? await buildRepoGraphCached(root, {
|
|
299
|
-
maxFiles: options.maxFiles ?? 2500,
|
|
300
|
-
workspaceFingerprint: options.workspaceFingerprint,
|
|
301
|
-
})
|
|
302
|
-
: { nodes: [], edges: [], hotspots: [], scannedFiles: 0, truncated: false }
|
|
303
318
|
const nodeMap = new Map(graph.nodes.map((node) => [node.path, node]))
|
|
304
319
|
const incoming = new Map()
|
|
305
320
|
|
|
@@ -330,7 +345,6 @@ export async function buildContextManifest(root, task, options = {}) {
|
|
|
330
345
|
.slice(0, 24)
|
|
331
346
|
.map((item) => item.file)
|
|
332
347
|
|
|
333
|
-
const instructions = await nearbyInstructions(root, declared)
|
|
334
348
|
const graphRanked = useGraph
|
|
335
349
|
? rankContextGraph(graph, {
|
|
336
350
|
semanticResults: scopedSemanticResults,
|
|
@@ -437,6 +451,7 @@ export async function buildContextManifest(root, task, options = {}) {
|
|
|
437
451
|
excerpts,
|
|
438
452
|
hierarchy: hierarchy ? {
|
|
439
453
|
schemaVersion: hierarchy.schemaVersion,
|
|
454
|
+
runtimeCacheHit: hierarchy.runtimeCacheHit === true,
|
|
440
455
|
levels: hierarchy.levels,
|
|
441
456
|
scopes: hierarchy.scopes.slice(0, 6).map((item) => ({
|
|
442
457
|
path: item.path,
|
|
@@ -450,6 +465,8 @@ export async function buildContextManifest(root, task, options = {}) {
|
|
|
450
465
|
semantic: semantic ? {
|
|
451
466
|
evidenceLevel: semantic.evidenceLevel,
|
|
452
467
|
resultCount: semantic.results.length,
|
|
468
|
+
runtimeCacheHit: semanticSnapshot?.runtimeCacheHit === true,
|
|
469
|
+
runtimeCacheCoalesced: semanticSnapshot?.runtimeCacheCoalesced === true,
|
|
453
470
|
stats: semantic.stats,
|
|
454
471
|
} : null,
|
|
455
472
|
graph: {
|
package/lib/decision-policy.mjs
CHANGED
|
@@ -2,6 +2,21 @@ const HIGH_RISK = /(npm publish|publish package|deploy|production|git push|force
|
|
|
2
2
|
const MEDIUM_RISK = /(migration|schema|dependency upgrade|lockfile|generated code|shared config|auth|permission|security)/i
|
|
3
3
|
const REVERSIBLE = /(local|test|rename|format|refactor|temporary|fixture|internal|reversible)/i
|
|
4
4
|
|
|
5
|
+
function confidenceForDecision(input = {}) {
|
|
6
|
+
if (input.requiresUser === true && (input.destructive === true || input.externalSideEffect === true || input.explicitlyIrreversible === true)) return 0.99
|
|
7
|
+
if (input.reversible === true && input.risk === "low") return 0.96
|
|
8
|
+
if (input.risk === "medium") return 0.82
|
|
9
|
+
if (input.requiresUser === true) return 0.90
|
|
10
|
+
return 0.74
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function confidenceBand(value) {
|
|
14
|
+
const score = Number(value || 0)
|
|
15
|
+
if (score >= 0.90) return "high"
|
|
16
|
+
if (score >= 0.72) return "medium"
|
|
17
|
+
return "low"
|
|
18
|
+
}
|
|
19
|
+
|
|
5
20
|
export function classifyDecisionPolicy(text = "", facts = {}) {
|
|
6
21
|
const value = String(text || "").trim()
|
|
7
22
|
const explicitlyIrreversible = facts.irreversible === true
|
|
@@ -11,10 +26,33 @@ export function classifyDecisionPolicy(text = "", facts = {}) {
|
|
|
11
26
|
const reversible = facts.reversible === true || (!destructive && REVERSIBLE.test(value))
|
|
12
27
|
const risk = destructive || explicitlyIrreversible || externalSideEffect ? "high" : medium ? "medium" : "low"
|
|
13
28
|
const requiresUser = risk === "high" || facts.productDecision === true
|
|
29
|
+
const autoResolvable = !requiresUser && (reversible || risk === "low")
|
|
30
|
+
const confidence = confidenceForDecision({
|
|
31
|
+
requiresUser,
|
|
32
|
+
destructive,
|
|
33
|
+
externalSideEffect,
|
|
34
|
+
explicitlyIrreversible,
|
|
35
|
+
reversible,
|
|
36
|
+
risk,
|
|
37
|
+
})
|
|
14
38
|
return {
|
|
15
|
-
schemaVersion: 1,
|
|
16
|
-
|
|
39
|
+
schemaVersion: 1,
|
|
40
|
+
risk,
|
|
41
|
+
reversible,
|
|
42
|
+
destructive,
|
|
43
|
+
externalSideEffect,
|
|
44
|
+
requiresUser,
|
|
45
|
+
autoResolvable,
|
|
17
46
|
reason: requiresUser ? "human-approval-required" : reversible ? "reversible-local-decision" : "bounded-engineering-decision",
|
|
47
|
+
decision: {
|
|
48
|
+
schemaVersion: 1,
|
|
49
|
+
kind: String(facts.decisionKind || "engineering-choice"),
|
|
50
|
+
source: "deterministic",
|
|
51
|
+
value: requiresUser ? "escalate-to-user" : autoResolvable ? "auto-resolve" : "bounded-review",
|
|
52
|
+
confidence,
|
|
53
|
+
confidenceBand: confidenceBand(confidence),
|
|
54
|
+
crossCheckRecommended: confidence < 0.72 || risk === "medium",
|
|
55
|
+
},
|
|
18
56
|
}
|
|
19
57
|
}
|
|
20
58
|
|