opencode-agent-skill 14.2.0-beta.1 → 15.0.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 (55) hide show
  1. package/README.md +51 -18
  2. package/docs/PI-COMPAT.md +167 -3
  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 +1 -1
  13. package/lib/capability-fabric.mjs +40 -0
  14. package/lib/code-intelligence/edit-anchor.mjs +102 -0
  15. package/lib/code-intelligence/index.mjs +95 -0
  16. package/lib/code-intelligence/lsp-provider.mjs +189 -0
  17. package/lib/completion-auditor.mjs +102 -0
  18. package/lib/document-ingestion.mjs +60 -0
  19. package/lib/evidence-store.mjs +92 -8
  20. package/lib/execution-contract.mjs +579 -0
  21. package/lib/fast-verification-gate.mjs +69 -0
  22. package/lib/leaf-runtime-optimizer.mjs +76 -0
  23. package/lib/mcp-health.mjs +144 -0
  24. package/lib/mcp-tool-policy.mjs +19 -0
  25. package/lib/memory-engine.mjs +28 -0
  26. package/lib/pi-child-invocation.mjs +65 -0
  27. package/lib/pi-rpc-pool.mjs +61 -9
  28. package/lib/plan-salvage.mjs +124 -0
  29. package/lib/planning-speed-policy.mjs +131 -0
  30. package/lib/prompt-cache.mjs +26 -12
  31. package/lib/repo-graph.mjs +1 -1
  32. package/lib/repo-inspect.mjs +1 -0
  33. package/lib/reversible-context.mjs +49 -0
  34. package/lib/runtime-artifacts.mjs +42 -0
  35. package/lib/runtime-events.mjs +33 -1
  36. package/lib/safety.mjs +1 -0
  37. package/lib/semantic-index.mjs +1 -1
  38. package/lib/service-manager.mjs +526 -0
  39. package/lib/session-display.mjs +49 -0
  40. package/lib/task-engine.mjs +6 -10
  41. package/lib/task-graph.mjs +87 -0
  42. package/lib/task-policy.mjs +210 -8
  43. package/lib/trajectory.mjs +111 -1
  44. package/lib/turbo-fast-path.mjs +44 -0
  45. package/lib/workspace-fingerprint.mjs +2 -11
  46. package/lib/workspace-root.mjs +49 -0
  47. package/lib/worktree-sandbox.mjs +234 -11
  48. package/package.json +9 -6
  49. package/pi/extensions/ues-child-runtime.ts +300 -2
  50. package/pi/extensions/ues.ts +1887 -160
  51. package/scripts/check-release-consistency.mjs +25 -2
  52. package/scripts/check-source-integrity.mjs +532 -3
  53. package/scripts/eval-pi.mjs +76 -36
  54. package/scripts/smoke-pi-extension.mjs +1 -1
  55. package/pi/prompts/ues-run.md +0 -17
@@ -0,0 +1,579 @@
1
+ import { createHash } from "node:crypto"
2
+ import { spawnSync } from "node:child_process"
3
+ import { lstatSync, readFileSync, readlinkSync } from "node:fs"
4
+ import path from "node:path"
5
+ import { sourceFacingPaths } from "./runtime-artifacts.mjs"
6
+
7
+ const PHASE_HEADING = /^\s*(?:=+\s*)?PHASE\s+(\d+)\s*(?:[—–:-]+)\s*(.+?)\s*$/i
8
+ const LOCAL_ENV_BASENAME = /^\.env(?:\.[^/\\]+)?$/i
9
+ const ENV_TEMPLATE_BASENAME = /^\.env\.(?:example|sample|template)$/i
10
+
11
+ function normalizePath(value) {
12
+ return String(value || "").trim().replaceAll("\\", "/").replace(/^\.\//, "")
13
+ }
14
+
15
+ function slugify(value) {
16
+ return String(value || "phase")
17
+ .normalize("NFKD")
18
+ .replace(/[\u0300-\u036f]/g, "")
19
+ .toLowerCase()
20
+ .replace(/[^a-z0-9]+/g, "-")
21
+ .replace(/^-+|-+$/g, "")
22
+ .slice(0, 48) || "phase"
23
+ }
24
+
25
+ function sha256(value) {
26
+ return createHash("sha256").update(String(value || "")).digest("hex")
27
+ }
28
+
29
+ export function isLocalEnvPath(value) {
30
+ const normalized = normalizePath(value)
31
+ const base = normalized.split("/").pop() || ""
32
+ if (ENV_TEMPLATE_BASENAME.test(base)) return false
33
+ return LOCAL_ENV_BASENAME.test(base)
34
+ }
35
+
36
+ export function crossToolTempPathRisk(value, platform = process.platform) {
37
+ const raw = String(value || "").trim().replaceAll("\\", "/")
38
+ if (!raw || String(platform || "").toLowerCase() !== "win32") {
39
+ return { risky: false, id: null, path: raw || null }
40
+ }
41
+ const risky = /^\/(?:tmp|var\/tmp)(?:\/|$)/i.test(raw)
42
+ return {
43
+ risky,
44
+ id: risky ? "cross-tool-posix-temp-path" : null,
45
+ path: raw || null,
46
+ }
47
+ }
48
+
49
+ export function taskExplicitlyAllowsLocalEnvWrite(task) {
50
+ const value = String(task || "")
51
+ const mentionsEnv = /(?:^|[\s"'\x60/])\.env(?:\.[\w.-]+)?(?=$|[\s"'\x60,;:.)/])/i.test(value)
52
+ if (!mentionsEnv) return false
53
+ const denied =
54
+ /(?:do\s+not|don't|never|must\s+not|không|khong|cấm|cam).{0,80}(?:modify|edit|update|write|create|set|change|sửa|chỉnh sửa|cập nhật|ghi|tạo|thay đổi)?[^\n.]*\.env|\.env[^\n.]*.{0,40}(?:do\s+not|must\s+not|không|khong|cấm|cam)/i.test(value)
55
+ if (denied) return false
56
+ return /(?:modify|edit|update|write|create|set|change|sửa|chỉnh sửa|cập nhật|ghi|tạo|thay đổi).{0,80}\.env|\.env.{0,80}(?:modify|edit|update|write|create|set|change|sửa|chỉnh sửa|cập nhật|ghi|tạo|thay đổi)/i.test(value)
57
+ }
58
+
59
+ export function localEnvWriteRisk(command) {
60
+ const value = String(command || "")
61
+ if (!value.trim()) return { risky: false, id: null }
62
+ const localEnv = String.raw`\.env(?!\.(?:example|sample|template)\b)(?:\.[\w.-]+)?\b`
63
+ const patterns = [
64
+ new RegExp(String.raw`(?:>|>>)[^\n;&|]*${localEnv}`, "i"),
65
+ new RegExp(String.raw`\b(?:sed|perl)\b[^\n;&|]*\s-i\b[^\n;&|]*${localEnv}`, "i"),
66
+ new RegExp(String.raw`\b(?:set-content|add-content|out-file|copy-item|move-item|rename-item)\b[^\n;&|]*${localEnv}`, "i"),
67
+ new RegExp(String.raw`\b(?:cp|mv|install)\b[^\n;&|]*\s${localEnv}`, "i"),
68
+ new RegExp(String.raw`\b(?:writeFileSync|writeFile|appendFileSync|appendFile)\s*\([^\n)]*["']${localEnv}["']`, "i"),
69
+ new RegExp(String.raw`\bopen\s*\([^\n)]*["']${localEnv}["'][^\n)]*["'](?:w|a|x)[+b]?["']`, "i"),
70
+ ]
71
+ return patterns.some((pattern) => pattern.test(value))
72
+ ? { risky: true, id: "local-env-write" }
73
+ : { risky: false, id: null }
74
+ }
75
+
76
+ function inheritedPathState(root, relative) {
77
+ const normalized = normalizePath(relative)
78
+ const full = path.resolve(root, normalized)
79
+ const rel = normalizePath(path.relative(path.resolve(root), full))
80
+ if (!normalized || rel === ".." || rel.startsWith("../") || path.isAbsolute(rel)) {
81
+ return { exists: false, kind: "outside", hash: null, size: 0 }
82
+ }
83
+ try {
84
+ const info = lstatSync(full)
85
+ if (info.isSymbolicLink()) {
86
+ const target = readlinkSync(full)
87
+ return {
88
+ exists: true,
89
+ kind: "symlink",
90
+ hash: sha256("symlink\0" + target),
91
+ size: Buffer.byteLength(target),
92
+ }
93
+ }
94
+ if (!info.isFile()) {
95
+ return {
96
+ exists: true,
97
+ kind: info.isDirectory() ? "directory" : "other",
98
+ hash: sha256(info.isDirectory() ? "directory" : "other"),
99
+ size: Number(info.size || 0),
100
+ }
101
+ }
102
+ const bytes = readFileSync(full)
103
+ return {
104
+ exists: true,
105
+ kind: "file",
106
+ hash: createHash("sha256").update(bytes).digest("hex"),
107
+ size: bytes.length,
108
+ }
109
+ } catch (error) {
110
+ if (error?.code === "ENOENT") return { exists: false, kind: "missing", hash: null, size: 0 }
111
+ return {
112
+ exists: false,
113
+ kind: "unreadable",
114
+ hash: sha256(String(error?.code || error?.message || error)),
115
+ size: 0,
116
+ }
117
+ }
118
+ }
119
+
120
+ function pathMentionedWithMutationIntent(task, relative) {
121
+ const text = String(task || "").replaceAll("\\", "/")
122
+ const needle = normalizePath(relative)
123
+ if (!needle) return false
124
+ const lower = text.toLowerCase()
125
+ const target = needle.toLowerCase()
126
+ let index = lower.indexOf(target)
127
+ while (index >= 0) {
128
+ const start = Math.max(0, index - 140)
129
+ const end = Math.min(text.length, index + target.length + 140)
130
+ const window = text.slice(start, end)
131
+ const denied =
132
+ /(?:do\s+not|don't|never|must\s+not|without|không|khong|cấm|cam).{0,100}(?:edit|modify|change|write|create|delete|remove|update|fix|sửa|chỉnh sửa|thay đổi|ghi|tạo|xóa|xoá|cập nhật)[^\n.]{0,100}/i.test(window) ||
133
+ /(?:edit|modify|change|write|create|delete|remove|update|fix|sửa|chỉnh sửa|thay đổi|ghi|tạo|xóa|xoá|cập nhật)[^\n.]{0,100}(?:do\s+not|don't|never|must\s+not|without|không|khong|cấm|cam)/i.test(window)
134
+ const mutation =
135
+ /(?:fix|repair|implement|edit|modify|change|write|create|delete|remove|update|refactor|migrate|rename|sửa|chỉnh sửa|thay đổi|ghi|tạo|xóa|xoá|cập nhật|đổi tên)/i.test(window)
136
+ if (mutation && !denied) return true
137
+ index = lower.indexOf(target, index + target.length)
138
+ }
139
+ return false
140
+ }
141
+
142
+ export function explicitlyAuthorizedInheritedDirtyPaths(task, dirtyPaths = []) {
143
+ return [...new Set((dirtyPaths || [])
144
+ .map(normalizePath)
145
+ .filter(Boolean)
146
+ .filter((item) => pathMentionedWithMutationIntent(task, item)))]
147
+ }
148
+
149
+ export function detectInheritedDirtyViolations(root, snapshot, approvedPaths = []) {
150
+ if (!snapshot?.available || !Array.isArray(snapshot.entries)) {
151
+ return { safe: false, violations: [], reason: "inherited-dirty-snapshot-unavailable" }
152
+ }
153
+ const approved = new Set((approvedPaths || []).map(normalizePath).filter(Boolean))
154
+ const violations = []
155
+ for (const entry of snapshot.entries) {
156
+ const relative = normalizePath(entry.path)
157
+ if (!relative || approved.has(relative)) continue
158
+ const before = entry.state || null
159
+ const after = inheritedPathState(root, relative)
160
+ if (
161
+ !before ||
162
+ before.exists !== after.exists ||
163
+ before.kind !== after.kind ||
164
+ before.hash !== after.hash ||
165
+ Number(before.size || 0) !== Number(after.size || 0)
166
+ ) {
167
+ violations.push({ path: relative, before, after })
168
+ }
169
+ }
170
+ return {
171
+ safe: violations.length === 0,
172
+ violations,
173
+ reason: violations.length ? "inherited-dirty-work-modified" : null,
174
+ }
175
+ }
176
+
177
+ export function captureInheritedDirtyState(root) {
178
+ const resolved = path.resolve(root)
179
+ const probe = spawnSync("git", ["status", "--porcelain=v1", "-z", "--untracked-files=all"], {
180
+ cwd: resolved,
181
+ encoding: "utf8",
182
+ maxBuffer: 16 * 1024 * 1024,
183
+ })
184
+ if (probe.status !== 0) {
185
+ return {
186
+ schemaVersion: 1,
187
+ available: false,
188
+ clean: false,
189
+ root: resolved,
190
+ entries: [],
191
+ paths: [],
192
+ error: String(probe.stderr || probe.stdout || "git status failed").trim(),
193
+ }
194
+ }
195
+
196
+ const tokens = String(probe.stdout || "").split("\0").filter(Boolean)
197
+ const entries = []
198
+ for (let index = 0; index < tokens.length; index += 1) {
199
+ const token = tokens[index]
200
+ if (token.length < 3) continue
201
+ const status = token.slice(0, 2)
202
+ const separator = token[2]
203
+ if (separator !== " ") continue
204
+ const currentPath = normalizePath(token.slice(3))
205
+ let originalPath = null
206
+ if (/[RC]/.test(status) && index + 1 < tokens.length) {
207
+ originalPath = normalizePath(tokens[index + 1])
208
+ index += 1
209
+ }
210
+ if (!currentPath) continue
211
+ entries.push({ status, path: currentPath, originalPath })
212
+ }
213
+
214
+ const generatedArtifact = /(?:^|\/)(?:node_modules|\.next(?:-desktop)?|dist|build|coverage|\.turbo|\.cache)(?:\/|$)/i
215
+ const paths = sourceFacingPaths(
216
+ entries.flatMap((entry) => [entry.path, entry.originalPath].filter(Boolean)),
217
+ ).filter((item) => !generatedArtifact.test(item)).sort()
218
+ const pathSet = new Set(paths)
219
+ const sourceEntries = entries
220
+ .filter((entry) =>
221
+ pathSet.has(entry.path) || (entry.originalPath && pathSet.has(entry.originalPath))
222
+ )
223
+ .map((entry) => ({
224
+ ...entry,
225
+ state: inheritedPathState(resolved, entry.path),
226
+ ...(entry.originalPath ? { originalState: inheritedPathState(resolved, entry.originalPath) } : {}),
227
+ }))
228
+ return {
229
+ schemaVersion: 1,
230
+ available: true,
231
+ clean: paths.length === 0,
232
+ root: resolved,
233
+ entries: sourceEntries,
234
+ paths,
235
+ }
236
+ }
237
+
238
+ export function extractExplicitPhases(task) {
239
+ const lines = String(task || "").split(/\r?\n/)
240
+ const phases = []
241
+ let current = null
242
+
243
+ const flush = () => {
244
+ if (!current) return
245
+ const body = current.lines.join("\n").trim()
246
+ const number = Number(current.number)
247
+ const title = current.title.trim()
248
+ const slug = slugify(title)
249
+ const constraintOnly =
250
+ /(?:do not|không được làm|tuyệt đối không|guardrail|constraint|prohibited)/i.test(title) ||
251
+ /(?:TUYỆT ĐỐI KHÔNG|MUST NOT|DO NOT)\s*:/i.test(body)
252
+ phases.push({
253
+ number,
254
+ title,
255
+ slug,
256
+ kind: constraintOnly ? "constraint" : "execution",
257
+ body,
258
+ bodyHash: sha256(body),
259
+ artifact: `phases/phase-${String(number).padStart(2, "0")}-${slug}.json`,
260
+ })
261
+ current = null
262
+ }
263
+
264
+ for (const line of lines) {
265
+ const match = line.match(PHASE_HEADING)
266
+ if (match) {
267
+ flush()
268
+ current = { number: match[1], title: match[2], lines: [] }
269
+ continue
270
+ }
271
+ if (current) current.lines.push(line)
272
+ }
273
+ flush()
274
+
275
+ phases.sort((a, b) => a.number - b.number)
276
+ return phases
277
+ }
278
+
279
+ function taskPhaseNumber(task) {
280
+ const explicit = Number(task?.phase)
281
+ if (Number.isInteger(explicit) && explicit >= 0) return explicit
282
+ const text = [task?.id, task?.title, task?.summary].filter(Boolean).join(" ")
283
+ const match = text.match(/\bphase\s*[-_:]?\s*(\d+)\b/i)
284
+ return match ? Number(match[1]) : null
285
+ }
286
+
287
+ export function enforcePhaseGates(plan, contract) {
288
+ if (!contract?.phases?.length) {
289
+ return { valid: true, plan, errors: [], assignments: {} }
290
+ }
291
+ if (!plan || !Array.isArray(plan.tasks)) {
292
+ return { valid: false, plan, errors: ["phase-gated plan must contain tasks"], assignments: {} }
293
+ }
294
+
295
+ const executionPhases = contract.phases.filter((phase) => phase.kind !== "constraint")
296
+ const allowed = new Set(executionPhases.map((phase) => Number(phase.number)))
297
+ const assignments = {}
298
+ const errors = []
299
+ const byPhase = new Map()
300
+ const taskById = new Map(plan.tasks.map((task) => [String(task.id), task]))
301
+
302
+ for (const task of plan.tasks) {
303
+ const phase = taskPhaseNumber(task)
304
+ if (phase == null) {
305
+ errors.push(`task ${task.id} is missing an explicit phase number`)
306
+ continue
307
+ }
308
+ if (!allowed.has(phase)) {
309
+ errors.push(`task ${task.id} declares unknown phase ${phase}`)
310
+ continue
311
+ }
312
+ assignments[String(task.id)] = phase
313
+ if (!byPhase.has(phase)) byPhase.set(phase, [])
314
+ byPhase.get(phase).push(String(task.id))
315
+ }
316
+
317
+ for (const phase of executionPhases) {
318
+ if (!(byPhase.get(Number(phase.number)) || []).length) {
319
+ errors.push(`explicit PHASE ${phase.number} has no planned task`)
320
+ }
321
+ }
322
+
323
+ for (const task of plan.tasks) {
324
+ const taskPhase = assignments[String(task.id)]
325
+ if (taskPhase == null) continue
326
+ for (const dep of Array.isArray(task.dependsOn) ? task.dependsOn : []) {
327
+ const depPhase = assignments[String(dep)]
328
+ if (depPhase != null && depPhase > taskPhase) {
329
+ errors.push(`task ${task.id} in phase ${taskPhase} depends on later phase ${depPhase} task ${dep}`)
330
+ }
331
+ if (!taskById.has(String(dep))) {
332
+ errors.push(`task ${task.id} depends on missing task ${dep}`)
333
+ }
334
+ }
335
+ }
336
+
337
+ if (errors.length) return { valid: false, plan, errors, assignments }
338
+
339
+ const orderedPhases = executionPhases.map((phase) => Number(phase.number)).sort((a, b) => a - b)
340
+ const tasks = plan.tasks.map((task) => ({
341
+ ...task,
342
+ phase: assignments[String(task.id)],
343
+ dependsOn: [...new Set(Array.isArray(task.dependsOn) ? task.dependsOn.map(String) : [])],
344
+ }))
345
+
346
+ for (let index = 1; index < orderedPhases.length; index += 1) {
347
+ const previous = byPhase.get(orderedPhases[index - 1]) || []
348
+ const current = new Set(byPhase.get(orderedPhases[index]) || [])
349
+ for (const task of tasks) {
350
+ if (!current.has(String(task.id))) continue
351
+ task.dependsOn = [...new Set([...task.dependsOn, ...previous])].filter((id) => id !== String(task.id))
352
+ }
353
+ }
354
+
355
+ return {
356
+ valid: true,
357
+ plan: {
358
+ ...plan,
359
+ tasks,
360
+ phaseGate: {
361
+ schemaVersion: 1,
362
+ orderedPhases,
363
+ barrierStrategy: "previous-phase-all-tasks",
364
+ },
365
+ },
366
+ errors: [],
367
+ assignments,
368
+ }
369
+ }
370
+
371
+ export function dataCleanupSafetyRequired(task) {
372
+ const value = String(task || "")
373
+ return /(?:cleanup|clean up|dọn|don dep|dọn dẹp|xóa fixture|xoá fixture)/i.test(value) &&
374
+ /(?:database|\bdb\b|fixture|seed|catalog|dữ liệu|du lieu|prisma|sql)/i.test(value)
375
+ }
376
+
377
+ export function deviceVerificationRequired(task) {
378
+ return /(?:expo go|real device|physical device|manual mobile acceptance|test thiết bị|thiết bị thật|device verification)/i.test(String(task || ""))
379
+ }
380
+
381
+ export function runtimeVerificationRequired(task) {
382
+ return /(?:runtime|smoke|e2e|integration|api thật|api real|real api|endpoint|build|typecheck|lint|tests?|jest|vitest|pytest|playwright|gradle|maven|cargo test|go test)/i.test(String(task || ""))
383
+ }
384
+
385
+ export function buildExecutionContract(task, inheritedDirty = null) {
386
+ const phases = extractExplicitPhases(task)
387
+ const dirty = inheritedDirty || {
388
+ schemaVersion: 1,
389
+ available: false,
390
+ clean: false,
391
+ entries: [],
392
+ paths: [],
393
+ }
394
+ const dbCleanup = dataCleanupSafetyRequired(task)
395
+ const device = deviceVerificationRequired(task)
396
+ const runtime = runtimeVerificationRequired(task)
397
+
398
+ const approvedInheritedDirtyPaths = explicitlyAuthorizedInheritedDirtyPaths(
399
+ task,
400
+ dirty.paths || [],
401
+ )
402
+
403
+ return {
404
+ schemaVersion: 1,
405
+ taskHash: sha256(task),
406
+ inheritedDirty: dirty,
407
+ approvedInheritedDirtyPaths,
408
+ localEnvWriteExplicitlyAllowed: taskExplicitlyAllowsLocalEnvWrite(task),
409
+ phases,
410
+ gates: {
411
+ source: true,
412
+ runtime,
413
+ dbClean: dbCleanup,
414
+ device,
415
+ },
416
+ invariants: [
417
+ "Preserve pre-existing dirty work. Never restore, checkout-away, clean, stash, or overwrite inherited changes unless the file is explicitly in the approved task write scope.",
418
+ "Treat .env and .env.* as local runtime inputs. Do not modify them unless the user explicitly requested that exact local env mutation. Prefer .env.example/sample/template and docs for repository changes.",
419
+ "Never claim cleanup success from frontend filtering or hidden presentation logic; prove cleanup at the data/source layer.",
420
+ "Do not cross an explicit PHASE boundary without verified evidence for prerequisite work represented in the approved plan.",
421
+ ],
422
+ databaseCleanupGate: dbCleanup
423
+ ? {
424
+ requireProductionRefusal: true,
425
+ requireDryRun: true,
426
+ requireExactIds: true,
427
+ requirePrePostCounts: true,
428
+ requireDeterministicMarkers: true,
429
+ requireCanonicalPreservation: true,
430
+ requireIdempotencyProof: true,
431
+ preferTransactionRollback: true,
432
+ }
433
+ : null,
434
+ }
435
+ }
436
+
437
+ export function executionContractPrompt(contract) {
438
+ if (!contract) return ""
439
+ const dirtyPaths = Array.isArray(contract.inheritedDirty?.paths)
440
+ ? contract.inheritedDirty.paths.slice(0, 120)
441
+ : []
442
+ const phaseSummary = Array.isArray(contract.phases)
443
+ ? contract.phases.map((phase) => `PHASE ${phase.number} [${phase.kind}]: ${phase.title} -> ${phase.artifact}`)
444
+ : []
445
+ const approvedDirty = Array.isArray(contract.approvedInheritedDirtyPaths)
446
+ ? contract.approvedInheritedDirtyPaths.slice(0, 120)
447
+ : []
448
+ const lines = [
449
+ "## UES deterministic execution contract",
450
+ "These constraints are controller-owned and must not be weakened by the model.",
451
+ ...contract.invariants.map((item) => "- " + item),
452
+ dirtyPaths.length
453
+ ? "\nInherited dirty paths present before this UES run (preserve unless explicitly approved in task write scope):\n" +
454
+ dirtyPaths.map((item) => "- " + item).join("\n") +
455
+ (approvedDirty.length
456
+ ? "\nExplicitly approved inherited dirty mutation scope:\n" + approvedDirty.map((item) => "- " + item).join("\n")
457
+ : "\nExplicitly approved inherited dirty mutation scope: none.")
458
+ : "\nInherited dirty paths: none detected.",
459
+ contract.localEnvWriteExplicitlyAllowed
460
+ ? "\nLocal .env mutation: explicitly requested by the user for this task."
461
+ : "\nLocal .env mutation: NOT authorized. Do not edit .env/.env.local/.env.development/etc.; use templates/docs or report NEEDS_USER_ENV.",
462
+ phaseSummary.length
463
+ ? "\nExplicit phase contract:\n" + phaseSummary.map((item) => "- " + item).join("\n")
464
+ : "",
465
+ ]
466
+
467
+ if (contract.databaseCleanupGate) {
468
+ lines.push(
469
+ "",
470
+ "Database cleanup gate:",
471
+ "- refuse production targets;",
472
+ "- perform a dry-run before deletion;",
473
+ "- record exact candidate IDs and pre/post entity counts;",
474
+ "- delete only fixtures linked to deterministic audited markers/source;",
475
+ "- preserve canonical seed and user history;",
476
+ "- prove idempotency with a second cleanup pass;",
477
+ "- use transaction/rollback protection when the stack supports it.",
478
+ )
479
+ }
480
+
481
+ lines.push(
482
+ "",
483
+ "Final verdict markers:",
484
+ "- SOURCE_PASS only after source/task verification and integration gates pass.",
485
+ contract.gates.runtime
486
+ ? "- RUNTIME_PASS only with fresh executable/runtime evidence; otherwise RUNTIME_NOT_VERIFIED."
487
+ : "- RUNTIME_NOT_REQUIRED.",
488
+ contract.gates.dbClean
489
+ ? "- DB_CLEAN_PASS only with data-layer cleanup/public-data evidence; otherwise DB_CLEAN_NOT_VERIFIED."
490
+ : "- DB_CLEAN_NOT_REQUIRED.",
491
+ contract.gates.device
492
+ ? "- DEVICE_PASS only after a real requested device check; otherwise DEVICE_NOT_VERIFIED."
493
+ : "- DEVICE_NOT_REQUIRED.",
494
+ )
495
+
496
+ return lines.filter(Boolean).join("\n")
497
+ }
498
+
499
+ export function buildFinalVerdictMatrix(task, evidence = {}) {
500
+ const contract = evidence.contract || buildExecutionContract(task)
501
+ const text = [
502
+ evidence.primaryOutput,
503
+ evidence.integrationOutput,
504
+ evidence.visualOutput,
505
+ ].filter(Boolean).join("\n")
506
+
507
+ const sourcePass = evidence.primaryPass === true && evidence.integrationPass !== false
508
+ const runtimeCheckText = [
509
+ evidence.primaryChecks,
510
+ evidence.integrationChecks,
511
+ evidence.visualChecks,
512
+ ].filter(Boolean).join("\n")
513
+ const executableRuntimeEvidence =
514
+ evidence.primaryPass === true &&
515
+ evidence.integrationPass !== false &&
516
+ /(?:\b(?:npm|pnpm|yarn|node|pytest|jest|vitest|gradle|mvn|dotnet|cargo|go)\b|runtime:smoke|e2e|typecheck|lint|curl\s|http:\/\/|https:\/\/)/i.test(runtimeCheckText)
517
+ const runtime = !contract.gates.runtime
518
+ ? "RUNTIME_NOT_REQUIRED"
519
+ : executableRuntimeEvidence
520
+ ? "RUNTIME_PASS"
521
+ : "RUNTIME_NOT_VERIFIED"
522
+ const dbCleanupEvidence =
523
+ evidence.primaryPass === true &&
524
+ evidence.integrationPass !== false &&
525
+ /\bDB_CLEAN_PASS\b/i.test(text) &&
526
+ /(?:dry[- ]?run|cleanup|fixture|seed|catalog|prisma|sql|dữ liệu|du lieu)/i.test(text + "\n" + runtimeCheckText) &&
527
+ /(?:idempotent|second\s+(?:cleanup|run|pass)|lần\s+(?:2|hai)|repeat(?:ed)?\s+run)/i.test(text) &&
528
+ /(?:count|removed|deleted|records?|xóa|xoá|đã dọn|pre[- ]?count|post[- ]?count|before|after|trước|sau)/i.test(text)
529
+ const dbClean = !contract.gates.dbClean
530
+ ? "DB_CLEAN_NOT_REQUIRED"
531
+ : dbCleanupEvidence
532
+ ? "DB_CLEAN_PASS"
533
+ : "DB_CLEAN_NOT_VERIFIED"
534
+ const deviceEvidence =
535
+ /\bDEVICE_PASS\b/i.test(text) &&
536
+ /(?:adb\s+(?:devices|shell)|expo\s+go|physical\s+device|thiết\s+bị\s+thật)/i.test(runtimeCheckText)
537
+ const device = !contract.gates.device
538
+ ? "DEVICE_NOT_REQUIRED"
539
+ : deviceEvidence
540
+ ? "DEVICE_PASS"
541
+ : "DEVICE_NOT_VERIFIED"
542
+
543
+ const source = sourcePass ? "SOURCE_PASS" : "SOURCE_NOT_VERIFIED"
544
+ const sourceRuntimeDataPass =
545
+ sourcePass &&
546
+ (!contract.gates.runtime || runtime === "RUNTIME_PASS") &&
547
+ (!contract.gates.dbClean || dbClean === "DB_CLEAN_PASS")
548
+ const requiredPass =
549
+ sourceRuntimeDataPass &&
550
+ (!contract.gates.device || device === "DEVICE_PASS")
551
+
552
+ return {
553
+ schemaVersion: 1,
554
+ source,
555
+ runtime,
556
+ dbClean,
557
+ device,
558
+ final: requiredPass
559
+ ? "PASS"
560
+ : sourceRuntimeDataPass && device === "DEVICE_NOT_VERIFIED"
561
+ ? "SOURCE_RUNTIME_PASS_DEVICE_NOT_VERIFIED"
562
+ : "PARTIAL_OR_NOT_VERIFIED",
563
+ }
564
+ }
565
+
566
+ export function phaseArtifactPayloads(contract) {
567
+ return (contract?.phases || []).map((phase) => ({
568
+ file: phase.artifact,
569
+ value: {
570
+ schemaVersion: 1,
571
+ phase: phase.number,
572
+ title: phase.title,
573
+ sourceBodyHash: phase.bodyHash,
574
+ sourceBody: phase.body,
575
+ status: "PENDING",
576
+ evidence: [],
577
+ },
578
+ }))
579
+ }
@@ -0,0 +1,69 @@
1
+ const BEHAVIORAL_TEST_COMMAND = [
2
+ /(?:^|\s)(?:node|bun)\s+--test(?:\s|$)/i,
3
+ /(?:^|\s)(?:npm|pnpm|yarn|bun)\s+(?:run\s+)?test(?:\s|$|:)/i,
4
+ /(?:^|\s)(?:npx\s+)?(?:jest|vitest|pytest)(?:\s|$)/i,
5
+ /(?:^|\s)go\s+test(?:\s|$)/i,
6
+ /(?:^|\s)cargo\s+test(?:\s|$)/i,
7
+ /(?:^|\s)dotnet\s+test(?:\s|$)/i,
8
+ /(?:^|\s)(?:mvn|mvnw|gradle|gradlew|\.\/gradlew|gradlew\.bat)\b[^\n]*\btest\b/i,
9
+ ]
10
+
11
+ function commandText(row = {}) {
12
+ const receipt = row?.receipt || {}
13
+ return [receipt.command, ...(Array.isArray(receipt.args) ? receipt.args : [])].filter(Boolean).join(" ").trim()
14
+ }
15
+
16
+ function finishedAtMs(row = {}) {
17
+ const value = Date.parse(row?.finishedAt || row?.receipt?.finishedAt || "")
18
+ return Number.isFinite(value) ? value : null
19
+ }
20
+
21
+ export function isBehavioralVerificationReceipt(row = {}) {
22
+ const receipt = row?.receipt || {}
23
+ if (receipt.passed !== true || Number(receipt.exitCode) !== 0) return false
24
+ const command = commandText(row)
25
+ return Boolean(command) && BEHAVIORAL_TEST_COMMAND.some((pattern) => pattern.test(command))
26
+ }
27
+
28
+ export function evaluateFastVerificationGate(input = {}) {
29
+ const policy = input.policy || {}
30
+ const implementation = input.implementation || {}
31
+ const receipts = Array.isArray(input.receipts) ? input.receipts : []
32
+ const attemptStartedAtMs = Number(input.attemptStartedAtMs || 0)
33
+ const eligible =
34
+ policy.executionProfile === "fast" &&
35
+ policy.singleFileBounded === true &&
36
+ policy.risk === "low" &&
37
+ input.visualRequired !== true
38
+ const implementationOk =
39
+ Number(implementation.exitCode) === 0 &&
40
+ String(implementation.stopReason || "").toLowerCase() !== "error"
41
+ const verificationText = String(implementation?.report?.sections?.verification || "").trim()
42
+ const implementationReportedVerification =
43
+ Boolean(verificationText) &&
44
+ !/(?:not\s+run|not\s+available|skipped|unverified|no\s+(?:tests?|checks?)\s+(?:run|available))/i.test(verificationText)
45
+ const behavioralReceipts = receipts
46
+ .filter((row) => {
47
+ const finished = finishedAtMs(row)
48
+ return isBehavioralVerificationReceipt(row) &&
49
+ (attemptStartedAtMs <= 0 || (finished != null && finished >= attemptStartedAtMs))
50
+ })
51
+ .map((row) => ({
52
+ command: commandText(row),
53
+ finishedAt: row?.finishedAt || row?.receipt?.finishedAt || null,
54
+ receiptId: row?.receipt?.id || null,
55
+ }))
56
+ let reason = "fresh-behavioral-evidence"
57
+ if (!eligible) reason = "not-fast-bounded"
58
+ else if (!implementationOk) reason = "implementation-not-clean"
59
+ else if (!implementationReportedVerification) reason = "implementation-did-not-report-verification"
60
+ else if (!behavioralReceipts.length) reason = "no-fresh-behavioral-receipt"
61
+ return {
62
+ schemaVersion: 1,
63
+ eligible,
64
+ passed: eligible && implementationOk && implementationReportedVerification && behavioralReceipts.length > 0,
65
+ reason,
66
+ behavioralReceiptCount: behavioralReceipts.length,
67
+ behavioralReceipts,
68
+ }
69
+ }