@bybrawe/opencode-loop 0.5.31 → 0.5.33

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.
@@ -0,0 +1,119 @@
1
+ import assert from "node:assert/strict"
2
+ import { createRunFinalizationRuntime } from "../src/source/runtime/run-finalization.js"
3
+
4
+ const noop = async () => {}
5
+ assert.throws(() => createRunFinalizationRuntime({}), /runGoalChecks/)
6
+ assert.throws(() => createRunFinalizationRuntime({ runGoalChecks: noop }), /applyGoalNoProgressGuard/)
7
+ assert.throws(() => createRunFinalizationRuntime({ runGoalChecks: noop, applyGoalNoProgressGuard: noop }), /createCheckpoint/)
8
+ assert.throws(() => createRunFinalizationRuntime({ runGoalChecks: noop, applyGoalNoProgressGuard: noop, createCheckpoint: noop }), /scheduleDueWork/)
9
+
10
+ let clock = 1000
11
+ const shellCalls = []
12
+ const writes = []
13
+ const logs = []
14
+ const notifications = []
15
+ const toasts = []
16
+ const checkpoints = []
17
+ const schedules = []
18
+ const goalChecks = []
19
+ const progressGuards = []
20
+ const reports = []
21
+
22
+ const runtime = createRunFinalizationRuntime({
23
+ runGoalChecks: async (_directory, sessionID, job) => {
24
+ goalChecks.push([sessionID, job.id])
25
+ return { ...job, checksRan: true }
26
+ },
27
+ applyGoalNoProgressGuard: async (_directory, _client, sessionID, job, previousJob) => {
28
+ progressGuards.push([sessionID, job.id, previousJob?.id])
29
+ return { ...job, guardRan: true }
30
+ },
31
+ createCheckpoint: async (...args) => { checkpoints.push(args) },
32
+ scheduleDueWork: async (...args) => { schedules.push(args) },
33
+ now: () => ++clock,
34
+ writeState: async (...args) => { writes.push(args) },
35
+ appendLoopLog: async (...args) => { logs.push(args) },
36
+ runShellCommand: async (command) => {
37
+ shellCalls.push(command)
38
+ if (command.includes("verify-fail")) return { code: 1, stdout: "bad", stderr: "verify error" }
39
+ if (command.includes("post-fail")) return { code: 2, stdout: "post bad", stderr: "post error" }
40
+ return { code: 0, stdout: "ok", stderr: "" }
41
+ },
42
+ notifyJob: async (...args) => { notifications.push(args) },
43
+ toast: async (...args) => { toasts.push(args) },
44
+ writeGoalReport: async (...args) => { reports.push(args) },
45
+ dangerousShell: (command) => command.includes("danger"),
46
+ })
47
+
48
+ const passing = { id: "passing", enabled: true, verifyCommand: "verify-ok", failureCount: 2 }
49
+ const passingState = { jobs: [passing] }
50
+ const passingResult = await runtime.finalizeJob("/repo", {}, "session-pass", passingState, passing, { id: "before-pass" })
51
+ assert.equal(passingResult.lastVerifyCode, 0)
52
+ assert.equal(passingResult.failureCount, 0)
53
+ assert.equal(passingResult.lastVerifyFailure, "")
54
+ assert.equal(writes.at(-1)[2].jobs[0].id, "passing")
55
+ assert.equal(checkpoints.at(-1)[2].id, "passing")
56
+ assert.equal(schedules.at(-1)[2], "session-pass")
57
+ assert.match(toasts.at(-1)[1], /Loop verify passed/)
58
+
59
+ const failing = {
60
+ id: "failing",
61
+ enabled: true,
62
+ verifyCommand: "verify-fail",
63
+ pauseOnVerifyFail: true,
64
+ failureCount: 0,
65
+ }
66
+ const failingState = { jobs: [failing] }
67
+ await runtime.finalizeJob("/repo", {}, "session-fail", failingState, failing, { id: "before-fail" })
68
+ assert.equal(failing.lastVerifyCode, 1)
69
+ assert.equal(failing.failureCount, 1)
70
+ assert.equal(failing.paused, true)
71
+ assert.match(failing.lastVerifyFailure, /verify error/)
72
+ assert.equal(notifications.at(-1)[2], "verify_failed")
73
+ assert.equal(logs.at(-1)[1], "verify")
74
+
75
+ const blockedPostrun = {
76
+ id: "blocked-postrun",
77
+ enabled: true,
78
+ postrunCommand: "danger --all",
79
+ safe: true,
80
+ }
81
+ const blockedState = { jobs: [blockedPostrun] }
82
+ const shellCountBeforeBlocked = shellCalls.length
83
+ await runtime.finalizeJob("/repo", {}, "session-blocked", blockedState, blockedPostrun, { id: "before-blocked" })
84
+ assert.equal(shellCalls.length, shellCountBeforeBlocked)
85
+ assert.equal(logs.findLast((entry) => entry[1] === "postrun-blocked")?.[2].command, "danger --all")
86
+
87
+ const failedPostrun = {
88
+ id: "failed-postrun",
89
+ enabled: true,
90
+ postrunCommand: "post-fail",
91
+ maxFailures: 1,
92
+ failureCount: 0,
93
+ }
94
+ const failedPostrunState = { jobs: [failedPostrun] }
95
+ await runtime.finalizeJob("/repo", {}, "session-post-fail", failedPostrunState, failedPostrun, { id: "before-post-fail" })
96
+ assert.equal(failedPostrun.lastPostrunCode, 2)
97
+ assert.equal(failedPostrun.failureCount, 1)
98
+ assert.equal(failedPostrun.paused, true)
99
+ assert.equal(notifications.at(-1)[2], "postrun_failed")
100
+
101
+ const goal = {
102
+ id: "goal-job",
103
+ kind: "goal",
104
+ enabled: false,
105
+ paused: true,
106
+ goalStatus: "completed",
107
+ }
108
+ const otherDisabled = { id: "other-disabled", enabled: false }
109
+ const goalState = { jobs: [goal, otherDisabled] }
110
+ const goalResult = await runtime.finalizeJob("/repo", {}, "session-goal", goalState, goal, { id: "before-goal" })
111
+ assert.equal(goalResult.checksRan, true)
112
+ assert.equal(goalResult.guardRan, true)
113
+ assert.deepEqual(goalChecks.at(-1), ["session-goal", "goal-job"])
114
+ assert.deepEqual(progressGuards.at(-1), ["session-goal", "goal-job", "before-goal"])
115
+ assert.equal(goalState.jobs.length, 1)
116
+ assert.equal(goalState.jobs[0].id, "goal-job")
117
+ assert.equal(reports.at(-1)[2].id, "goal-job")
118
+
119
+ console.log("run finalization runtime tests passed")