@oisincoveney/pipeline 1.29.3 → 2.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.
- package/README.md +1 -1
- package/docs/config-architecture.md +2 -1
- package/package.json +1 -9
- package/dist/runtime/goal-loop/continuation-prompt.js +0 -125
- package/dist/runtime/goal-loop.d.ts +0 -37
- package/dist/runtime/goal-loop.js +0 -138
- package/dist/runtime/goal-state/goal-requirement.js +0 -11
- package/dist/runtime/goal-state.d.ts +0 -146
- package/dist/runtime/goal-state.js +0 -339
package/README.md
CHANGED
|
@@ -189,7 +189,7 @@ Package-owned defaults declare:
|
|
|
189
189
|
- the singleton `pipeline-gateway` MCP connection
|
|
190
190
|
- workflows, schedules, hooks, gates, artifacts, retries, and timeouts
|
|
191
191
|
- generated OpenCode host resources
|
|
192
|
-
- goal-
|
|
192
|
+
- internal goal-state artifacts and goal-loop continuation context
|
|
193
193
|
|
|
194
194
|
Current default entrypoints:
|
|
195
195
|
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
The v1 runtime pipeline is package-owned config. Package-owned defaults declare
|
|
4
4
|
runner adapters, profiles, MCP gateway backends, the orchestrator profile,
|
|
5
5
|
entrypoints, schedules, hooks, workflows, gates, artifacts, OpenCode host
|
|
6
|
-
resources, and goal-loop
|
|
6
|
+
resources, and internal goal-loop continuation behavior. OpenCode is the package
|
|
7
|
+
runtime.
|
|
7
8
|
|
|
8
9
|
Runtime code does not read `.pipeline/config.toml`, phase profiles, or hardcoded
|
|
9
10
|
prompt constants.
|
package/package.json
CHANGED
|
@@ -86,14 +86,6 @@
|
|
|
86
86
|
"types": "./dist/runner-command-contract.d.ts",
|
|
87
87
|
"import": "./dist/runner-command-contract.js"
|
|
88
88
|
},
|
|
89
|
-
"./runtime/goal-loop": {
|
|
90
|
-
"types": "./dist/runtime/goal-loop.d.ts",
|
|
91
|
-
"import": "./dist/runtime/goal-loop.js"
|
|
92
|
-
},
|
|
93
|
-
"./runtime/goal-state": {
|
|
94
|
-
"types": "./dist/runtime/goal-state.d.ts",
|
|
95
|
-
"import": "./dist/runtime/goal-state.js"
|
|
96
|
-
},
|
|
97
89
|
"./runtime": {
|
|
98
90
|
"types": "./dist/pipeline-runtime.d.ts",
|
|
99
91
|
"import": "./dist/pipeline-runtime.js"
|
|
@@ -123,7 +115,7 @@
|
|
|
123
115
|
"prepack": "bun run build:cli"
|
|
124
116
|
},
|
|
125
117
|
"type": "module",
|
|
126
|
-
"version": "
|
|
118
|
+
"version": "2.0.0",
|
|
127
119
|
"description": "Config-driven multi-agent pipeline runner for repository work",
|
|
128
120
|
"main": "./dist/index.js",
|
|
129
121
|
"types": "./dist/index.d.ts",
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import { goalStateNextRequirement } from "../goal-state/goal-requirement.js";
|
|
2
|
-
//#region src/runtime/goal-loop/continuation-prompt.ts
|
|
3
|
-
function renderContinuationPrompt(input) {
|
|
4
|
-
const currentNode = currentScheduleNode(input.state, input.currentNodeId);
|
|
5
|
-
const failedGates = input.state.gateFailures.filter((gate) => !gate.passed);
|
|
6
|
-
return compactLines([
|
|
7
|
-
"# Pipeline Continuation",
|
|
8
|
-
"",
|
|
9
|
-
"## Original Task",
|
|
10
|
-
input.state.task.original,
|
|
11
|
-
"",
|
|
12
|
-
"## Task Refs",
|
|
13
|
-
...taskRefLines(input.state.task.context),
|
|
14
|
-
"",
|
|
15
|
-
"## Schedule",
|
|
16
|
-
...scheduleLines(input.state),
|
|
17
|
-
"",
|
|
18
|
-
"## Current Schedule Node Context",
|
|
19
|
-
...nodeContextLines(currentNode),
|
|
20
|
-
"",
|
|
21
|
-
"## Failed Gates",
|
|
22
|
-
...failedGateLines(failedGates),
|
|
23
|
-
"",
|
|
24
|
-
"## Verifier Evidence",
|
|
25
|
-
...verifierLines(input.state),
|
|
26
|
-
"",
|
|
27
|
-
"## Acceptance Evidence",
|
|
28
|
-
...acceptanceLines(input.state),
|
|
29
|
-
"",
|
|
30
|
-
"## Changed Files Summary",
|
|
31
|
-
...changedFileLines(input.state),
|
|
32
|
-
"",
|
|
33
|
-
"## Prior Attempts",
|
|
34
|
-
...priorAttemptLines(input.state),
|
|
35
|
-
"",
|
|
36
|
-
"## Exact Next Requirement",
|
|
37
|
-
input.exactNextRequirement ?? exactNextRequirement(input.state),
|
|
38
|
-
"",
|
|
39
|
-
"## Discipline",
|
|
40
|
-
"- Continue from the persisted goal state; do not restart the task.",
|
|
41
|
-
"- Address the failed evidence directly and preserve completed work.",
|
|
42
|
-
"- Run the real verification path needed to prove the fix.",
|
|
43
|
-
"- Stop and report blocked if the same failure repeats without new files or evidence."
|
|
44
|
-
]);
|
|
45
|
-
}
|
|
46
|
-
function exactNextRequirement(state) {
|
|
47
|
-
return goalStateNextRequirement(state);
|
|
48
|
-
}
|
|
49
|
-
function currentScheduleNode(state, requestedNodeId) {
|
|
50
|
-
if (requestedNodeId && state.nodes[requestedNodeId]) return state.nodes[requestedNodeId];
|
|
51
|
-
return [...Object.values(state.nodes)].reverse().find((node) => node.status === "failed") ?? Object.values(state.nodes).at(-1);
|
|
52
|
-
}
|
|
53
|
-
function taskRefLines(context) {
|
|
54
|
-
if (!isRecord(context)) return ["- No task context recorded."];
|
|
55
|
-
return [
|
|
56
|
-
lineFor("id", context.id),
|
|
57
|
-
lineFor("title", context.title),
|
|
58
|
-
lineFor("description", context.description),
|
|
59
|
-
...acceptanceCriteriaRefLines(context.acceptanceCriteria)
|
|
60
|
-
].filter(Boolean);
|
|
61
|
-
}
|
|
62
|
-
function acceptanceCriteriaRefLines(value) {
|
|
63
|
-
if (!Array.isArray(value) || value.length === 0) return [];
|
|
64
|
-
return ["- acceptance_criteria:", ...value.flatMap((item) => {
|
|
65
|
-
if (!isRecord(item)) return [];
|
|
66
|
-
return [` - ${String(item.id ?? "?")}: ${String(item.text ?? "")}`];
|
|
67
|
-
})];
|
|
68
|
-
}
|
|
69
|
-
function scheduleLines(state) {
|
|
70
|
-
return [
|
|
71
|
-
`- workflow_id: ${state.workflowId}`,
|
|
72
|
-
state.runId ? `- run_id: ${state.runId}` : "",
|
|
73
|
-
state.schedule?.id ? `- schedule_id: ${state.schedule.id}` : "",
|
|
74
|
-
state.schedule?.path ? `- schedule_path: ${state.schedule.path}` : ""
|
|
75
|
-
].filter(Boolean);
|
|
76
|
-
}
|
|
77
|
-
function nodeContextLines(node) {
|
|
78
|
-
if (!node) return ["- No schedule node has run yet."];
|
|
79
|
-
return [
|
|
80
|
-
`- node_id: ${node.nodeId}`,
|
|
81
|
-
`- status: ${node.status}`,
|
|
82
|
-
`- attempts: ${node.attempts}`,
|
|
83
|
-
node.profile ? `- profile: ${node.profile}` : "",
|
|
84
|
-
node.runnerId ? `- runner: ${node.runnerId}` : "",
|
|
85
|
-
node.changedFiles.length ? `- node_changed_files: ${node.changedFiles.join(", ")}` : "",
|
|
86
|
-
...node.gates.map((gate) => `- gate ${gate.gateId}: ${gate.passed ? "PASS" : "FAIL"}${gate.reason ? ` (${gate.reason})` : ""}`)
|
|
87
|
-
].filter(Boolean);
|
|
88
|
-
}
|
|
89
|
-
function failedGateLines(gates) {
|
|
90
|
-
if (gates.length === 0) return ["- No failed gates recorded."];
|
|
91
|
-
return gates.flatMap((gate) => [`- ${gate.nodeId}/${gate.gateId}: ${gate.reason ?? "failed"}`, ...gate.evidence.map((item) => ` evidence: ${item}`)]);
|
|
92
|
-
}
|
|
93
|
-
function verifierLines(state) {
|
|
94
|
-
const verifier = state.verifier;
|
|
95
|
-
if (!verifier.verdict && verifier.evidence.length === 0) return ["- No verifier evidence recorded."];
|
|
96
|
-
return [
|
|
97
|
-
verifier.nodeId ? `- node_id: ${verifier.nodeId}` : "",
|
|
98
|
-
verifier.verdict ? `- verdict: ${verifier.verdict}` : "",
|
|
99
|
-
verifier.reason ? `- reason: ${verifier.reason}` : "",
|
|
100
|
-
...verifier.evidence.map((item) => `- evidence: ${item}`)
|
|
101
|
-
].filter(Boolean);
|
|
102
|
-
}
|
|
103
|
-
function acceptanceLines(state) {
|
|
104
|
-
if (state.acceptance.length === 0) return ["- No acceptance evidence recorded."];
|
|
105
|
-
return state.acceptance.flatMap((item) => [`- ${item.id}: ${item.verdict}`, ...item.evidence.map((evidence) => ` evidence: ${evidence}`)]);
|
|
106
|
-
}
|
|
107
|
-
function changedFileLines(state) {
|
|
108
|
-
if (state.changedFiles.length === 0) return ["- No changed files recorded."];
|
|
109
|
-
return state.changedFiles.map((file) => `- ${file}`);
|
|
110
|
-
}
|
|
111
|
-
function priorAttemptLines(state) {
|
|
112
|
-
if (state.continuationAttempts.length === 0) return ["- No prior continuation attempts."];
|
|
113
|
-
return state.continuationAttempts.map((attempt) => `- #${attempt.attempt}: ${attempt.reason}${attempt.promptPath ? ` (${attempt.promptPath})` : ""}`);
|
|
114
|
-
}
|
|
115
|
-
function lineFor(label, value) {
|
|
116
|
-
return typeof value === "string" && value.length > 0 ? `- ${label}: ${value}` : "";
|
|
117
|
-
}
|
|
118
|
-
function compactLines(lines) {
|
|
119
|
-
return `${lines.join("\n").replace(/\n{3,}/g, "\n\n").trim()}\n`;
|
|
120
|
-
}
|
|
121
|
-
function isRecord(value) {
|
|
122
|
-
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
123
|
-
}
|
|
124
|
-
//#endregion
|
|
125
|
-
export { exactNextRequirement, renderContinuationPrompt };
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { PipelineConfig } from "../config/schemas.js";
|
|
2
|
-
import { RunnerLaunchPlan } from "../runner.js";
|
|
3
|
-
import { PipelineGoalState } from "./goal-state.js";
|
|
4
|
-
|
|
5
|
-
//#region src/runtime/goal-loop/goal-loop.d.ts
|
|
6
|
-
type GoalLoopTerminalState = "blocked" | "cancelled" | "max_continuations_reached" | "no_progress_detected" | "passed";
|
|
7
|
-
interface GoalLoopContinuationInput {
|
|
8
|
-
attempt: number;
|
|
9
|
-
prompt: string;
|
|
10
|
-
promptPath?: string;
|
|
11
|
-
state: PipelineGoalState;
|
|
12
|
-
}
|
|
13
|
-
interface GoalLoopOptions {
|
|
14
|
-
initialState: PipelineGoalState;
|
|
15
|
-
maxContinuations: number;
|
|
16
|
-
now?: () => Date;
|
|
17
|
-
runContinuation: (input: GoalLoopContinuationInput) => PipelineGoalState | Promise<PipelineGoalState>;
|
|
18
|
-
shouldCancel?: () => boolean;
|
|
19
|
-
writePrompt?: (attempt: number, prompt: string, state: PipelineGoalState) => string | Promise<string>;
|
|
20
|
-
}
|
|
21
|
-
interface GoalLoopResult {
|
|
22
|
-
attempts: number;
|
|
23
|
-
prompts: string[];
|
|
24
|
-
reason: string;
|
|
25
|
-
state: PipelineGoalState;
|
|
26
|
-
terminalState: GoalLoopTerminalState;
|
|
27
|
-
}
|
|
28
|
-
declare function runBoundedGoalLoop(options: GoalLoopOptions): Promise<GoalLoopResult>;
|
|
29
|
-
declare function createGoalContinuationLaunchPlan(input: {
|
|
30
|
-
config: PipelineConfig;
|
|
31
|
-
nodeId?: string;
|
|
32
|
-
profileId?: string;
|
|
33
|
-
prompt: string;
|
|
34
|
-
worktreePath: string;
|
|
35
|
-
}): RunnerLaunchPlan;
|
|
36
|
-
//#endregion
|
|
37
|
-
export { GoalLoopContinuationInput, GoalLoopOptions, GoalLoopResult, GoalLoopTerminalState, createGoalContinuationLaunchPlan, runBoundedGoalLoop };
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import { createRunnerLaunchPlan } from "../runner.js";
|
|
2
|
-
import { goalStateCompletionEvidence, goalStateFailureSignature, markGoalStateBlocked, recordGoalStateContinuationAttempt } from "./goal-state.js";
|
|
3
|
-
import { exactNextRequirement, renderContinuationPrompt } from "./goal-loop/continuation-prompt.js";
|
|
4
|
-
//#region src/runtime/goal-loop/goal-loop.ts
|
|
5
|
-
async function runBoundedGoalLoop(options) {
|
|
6
|
-
if (!Number.isInteger(options.maxContinuations) || options.maxContinuations < 0) throw new Error("maxContinuations must be a non-negative integer");
|
|
7
|
-
let state = options.initialState;
|
|
8
|
-
const prompts = [];
|
|
9
|
-
for (;;) {
|
|
10
|
-
const terminal = terminalResult(state, prompts, options.shouldCancel);
|
|
11
|
-
if (terminal) return terminal;
|
|
12
|
-
const maxContinuations = maxContinuationsResult(state, prompts, options);
|
|
13
|
-
if (maxContinuations) return maxContinuations;
|
|
14
|
-
const beforeProgress = progressSignature(state);
|
|
15
|
-
const nextState = await runGoalContinuation(options, state, prompts);
|
|
16
|
-
const postContinuation = postContinuationResult(nextState, beforeProgress, prompts, options.shouldCancel);
|
|
17
|
-
if (postContinuation) return postContinuation;
|
|
18
|
-
state = nextState;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
function maxContinuationsResult(state, prompts, options) {
|
|
22
|
-
return state.continuationAttempts.length >= options.maxContinuations ? {
|
|
23
|
-
attempts: state.continuationAttempts.length,
|
|
24
|
-
prompts,
|
|
25
|
-
reason: `maximum continuations reached: ${options.maxContinuations}`,
|
|
26
|
-
state,
|
|
27
|
-
terminalState: "max_continuations_reached"
|
|
28
|
-
} : null;
|
|
29
|
-
}
|
|
30
|
-
async function runGoalContinuation(options, state, prompts) {
|
|
31
|
-
const attempt = state.continuationAttempts.length + 1;
|
|
32
|
-
const prompt = renderContinuationPrompt({
|
|
33
|
-
exactNextRequirement: exactNextRequirement(state),
|
|
34
|
-
state
|
|
35
|
-
});
|
|
36
|
-
const promptPath = await options.writePrompt?.(attempt, prompt, state);
|
|
37
|
-
prompts.push(prompt);
|
|
38
|
-
return options.runContinuation({
|
|
39
|
-
attempt,
|
|
40
|
-
prompt,
|
|
41
|
-
...promptPath ? { promptPath } : {},
|
|
42
|
-
state: stateWithContinuationAttempt(state, promptPath)
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
function stateWithContinuationAttempt(state, promptPath) {
|
|
46
|
-
return recordGoalStateContinuationAttempt(state, {
|
|
47
|
-
...promptPath ? { promptPath } : {},
|
|
48
|
-
reason: continuationReason(state),
|
|
49
|
-
...state.verifier.nodeId ? { verifierNodeId: state.verifier.nodeId } : {}
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
function postContinuationResult(nextState, beforeProgress, prompts, shouldCancel) {
|
|
53
|
-
const terminal = terminalResult(nextState, prompts, shouldCancel);
|
|
54
|
-
if (terminal) return terminal;
|
|
55
|
-
return isNoProgress(beforeProgress, progressSignature(nextState)) ? noProgressResult(nextState, prompts) : null;
|
|
56
|
-
}
|
|
57
|
-
function noProgressResult(state, prompts) {
|
|
58
|
-
const reason = "same failure repeated without new changed files or evidence";
|
|
59
|
-
return {
|
|
60
|
-
attempts: state.continuationAttempts.length,
|
|
61
|
-
prompts,
|
|
62
|
-
reason,
|
|
63
|
-
state: markGoalStateBlocked(state, reason),
|
|
64
|
-
terminalState: "no_progress_detected"
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
function createGoalContinuationLaunchPlan(input) {
|
|
68
|
-
return createRunnerLaunchPlan(input.config, {
|
|
69
|
-
nodeId: input.nodeId ?? "goal-continuation",
|
|
70
|
-
profileId: input.profileId ?? "moka-code-writer",
|
|
71
|
-
prompt: input.prompt,
|
|
72
|
-
worktreePath: input.worktreePath
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
function terminalResult(state, prompts, shouldCancel) {
|
|
76
|
-
if (shouldCancel?.()) return {
|
|
77
|
-
attempts: state.continuationAttempts.length,
|
|
78
|
-
prompts,
|
|
79
|
-
reason: "goal loop cancelled",
|
|
80
|
-
state,
|
|
81
|
-
terminalState: "cancelled"
|
|
82
|
-
};
|
|
83
|
-
switch (state.terminalOutcome) {
|
|
84
|
-
case "PASS":
|
|
85
|
-
if (!goalStateCompletionEvidence(state).passed) return {
|
|
86
|
-
attempts: state.continuationAttempts.length,
|
|
87
|
-
prompts,
|
|
88
|
-
reason: "missing deterministic verifier or acceptance evidence",
|
|
89
|
-
state: markGoalStateBlocked(state, "missing deterministic verifier or acceptance evidence"),
|
|
90
|
-
terminalState: "blocked"
|
|
91
|
-
};
|
|
92
|
-
return {
|
|
93
|
-
attempts: state.continuationAttempts.length,
|
|
94
|
-
prompts,
|
|
95
|
-
reason: "goal passed",
|
|
96
|
-
state,
|
|
97
|
-
terminalState: "passed"
|
|
98
|
-
};
|
|
99
|
-
case "BLOCKED": return {
|
|
100
|
-
attempts: state.continuationAttempts.length,
|
|
101
|
-
prompts,
|
|
102
|
-
reason: state.blockedReasons.at(-1) ?? "goal blocked",
|
|
103
|
-
state,
|
|
104
|
-
terminalState: "blocked"
|
|
105
|
-
};
|
|
106
|
-
case "CANCELLED": return {
|
|
107
|
-
attempts: state.continuationAttempts.length,
|
|
108
|
-
prompts,
|
|
109
|
-
reason: "goal cancelled",
|
|
110
|
-
state,
|
|
111
|
-
terminalState: "cancelled"
|
|
112
|
-
};
|
|
113
|
-
default: return null;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
function continuationReason(state) {
|
|
117
|
-
const latestGate = state.gateFailures.at(-1);
|
|
118
|
-
if (latestGate) return latestGate.reason ?? `failed gate ${latestGate.gateId}`;
|
|
119
|
-
if (state.verifier.verdict === "FAIL") return state.verifier.reason ?? "verifier requested remediation";
|
|
120
|
-
return "goal incomplete";
|
|
121
|
-
}
|
|
122
|
-
function progressSignature(state) {
|
|
123
|
-
const failedAcceptance = state.acceptance.filter((item) => item.verdict === "FAIL");
|
|
124
|
-
return {
|
|
125
|
-
changedFiles: state.changedFiles.join("\0"),
|
|
126
|
-
evidence: [
|
|
127
|
-
...state.gateFailures.flatMap((gate) => gate.evidence),
|
|
128
|
-
...state.verifier.evidence,
|
|
129
|
-
...failedAcceptance.flatMap((item) => item.evidence)
|
|
130
|
-
].join("\0"),
|
|
131
|
-
failure: goalStateFailureSignature(state)
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
function isNoProgress(before, after) {
|
|
135
|
-
return before.failure.length > 0 && before.failure === after.failure && before.changedFiles === after.changedFiles && before.evidence === after.evidence;
|
|
136
|
-
}
|
|
137
|
-
//#endregion
|
|
138
|
-
export { createGoalContinuationLaunchPlan, runBoundedGoalLoop };
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
//#region src/runtime/goal-state/goal-requirement.ts
|
|
2
|
-
function goalStateNextRequirement(state) {
|
|
3
|
-
const failedAcceptance = state.acceptance.filter((item) => item.verdict === "FAIL");
|
|
4
|
-
if (failedAcceptance.length > 0) return `Satisfy failed acceptance criteria: ${failedAcceptance.map((item) => item.id).join(", ")}.`;
|
|
5
|
-
if (state.verifier.verdict === "FAIL") return `Satisfy verifier node '${state.verifier.nodeId ?? "verify"}' and return passing evidence.`;
|
|
6
|
-
const latestGate = state.gateFailures.at(-1);
|
|
7
|
-
if (latestGate) return `Resolve failed gate '${latestGate.gateId}' on node '${latestGate.nodeId}' and rerun the required verification.`;
|
|
8
|
-
return "Complete the remaining schedule work and provide passing evidence.";
|
|
9
|
-
}
|
|
10
|
-
//#endregion
|
|
11
|
-
export { goalStateNextRequirement };
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
import { PipelineRuntimeEvent, PipelineTaskContext } from "./contracts/contracts.js";
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
|
|
4
|
-
//#region src/runtime/goal-state/goal-state.d.ts
|
|
5
|
-
declare const pipelineGoalStateSchema: z.ZodObject<{
|
|
6
|
-
acceptance: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
7
|
-
evidence: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
8
|
-
id: z.ZodString;
|
|
9
|
-
violations: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
10
|
-
verdict: z.ZodEnum<{
|
|
11
|
-
FAIL: "FAIL";
|
|
12
|
-
PASS: "PASS";
|
|
13
|
-
}>;
|
|
14
|
-
}, z.core.$strict>>>;
|
|
15
|
-
blockedReasons: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
16
|
-
changedFiles: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
17
|
-
continuationAttempts: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
18
|
-
attempt: z.ZodNumber;
|
|
19
|
-
promptPath: z.ZodOptional<z.ZodString>;
|
|
20
|
-
reason: z.ZodString;
|
|
21
|
-
verifierNodeId: z.ZodOptional<z.ZodString>;
|
|
22
|
-
}, z.core.$strict>>>;
|
|
23
|
-
gateFailures: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
24
|
-
evidence: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
25
|
-
gateId: z.ZodString;
|
|
26
|
-
kind: z.ZodString;
|
|
27
|
-
nodeId: z.ZodString;
|
|
28
|
-
passed: z.ZodBoolean;
|
|
29
|
-
reason: z.ZodOptional<z.ZodString>;
|
|
30
|
-
}, z.core.$strict>>>;
|
|
31
|
-
nodes: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
32
|
-
attempts: z.ZodNumber;
|
|
33
|
-
changedFiles: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
34
|
-
exitCode: z.ZodOptional<z.ZodNumber>;
|
|
35
|
-
gates: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
36
|
-
evidence: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
37
|
-
gateId: z.ZodString;
|
|
38
|
-
kind: z.ZodString;
|
|
39
|
-
nodeId: z.ZodString;
|
|
40
|
-
passed: z.ZodBoolean;
|
|
41
|
-
reason: z.ZodOptional<z.ZodString>;
|
|
42
|
-
}, z.core.$strict>>>;
|
|
43
|
-
nodeId: z.ZodString;
|
|
44
|
-
profile: z.ZodOptional<z.ZodString>;
|
|
45
|
-
runnerId: z.ZodOptional<z.ZodString>;
|
|
46
|
-
status: z.ZodEnum<{
|
|
47
|
-
passed: "passed";
|
|
48
|
-
failed: "failed";
|
|
49
|
-
pending: "pending";
|
|
50
|
-
running: "running";
|
|
51
|
-
}>;
|
|
52
|
-
}, z.core.$strict>>>;
|
|
53
|
-
runId: z.ZodOptional<z.ZodString>;
|
|
54
|
-
schedule: z.ZodOptional<z.ZodObject<{
|
|
55
|
-
id: z.ZodOptional<z.ZodString>;
|
|
56
|
-
path: z.ZodOptional<z.ZodString>;
|
|
57
|
-
}, z.core.$strict>>;
|
|
58
|
-
task: z.ZodObject<{
|
|
59
|
-
context: z.ZodOptional<z.ZodUnknown>;
|
|
60
|
-
original: z.ZodString;
|
|
61
|
-
}, z.core.$strict>;
|
|
62
|
-
terminalOutcome: z.ZodOptional<z.ZodEnum<{
|
|
63
|
-
FAIL: "FAIL";
|
|
64
|
-
PASS: "PASS";
|
|
65
|
-
BLOCKED: "BLOCKED";
|
|
66
|
-
CANCELLED: "CANCELLED";
|
|
67
|
-
}>>;
|
|
68
|
-
verifier: z.ZodDefault<z.ZodObject<{
|
|
69
|
-
evidence: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
70
|
-
nodeId: z.ZodOptional<z.ZodString>;
|
|
71
|
-
reason: z.ZodOptional<z.ZodString>;
|
|
72
|
-
violations: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
73
|
-
verdict: z.ZodOptional<z.ZodEnum<{
|
|
74
|
-
FAIL: "FAIL";
|
|
75
|
-
PASS: "PASS";
|
|
76
|
-
}>>;
|
|
77
|
-
}, z.core.$strict>>;
|
|
78
|
-
version: z.ZodLiteral<1>;
|
|
79
|
-
workflowId: z.ZodString;
|
|
80
|
-
}, z.core.$strict>;
|
|
81
|
-
type PipelineGoalState = z.infer<typeof pipelineGoalStateSchema>;
|
|
82
|
-
interface CreateGoalStateOptions {
|
|
83
|
-
runId?: string;
|
|
84
|
-
scheduleId?: string;
|
|
85
|
-
schedulePath?: string;
|
|
86
|
-
task: string;
|
|
87
|
-
taskContext?: PipelineTaskContext;
|
|
88
|
-
workflowId: string;
|
|
89
|
-
}
|
|
90
|
-
declare function createGoalState(options: CreateGoalStateOptions): PipelineGoalState;
|
|
91
|
-
declare function parseGoalState(value: unknown): PipelineGoalState;
|
|
92
|
-
declare function applyGoalStateEvent(state: PipelineGoalState, event: PipelineRuntimeEvent): PipelineGoalState;
|
|
93
|
-
declare function reconstructGoalStateFromEvents(options: CreateGoalStateOptions, events: PipelineRuntimeEvent[]): PipelineGoalState;
|
|
94
|
-
declare function recordGoalStateChangedFiles(state: PipelineGoalState, nodeId: string, files: string[]): PipelineGoalState;
|
|
95
|
-
declare function recordGoalStateContinuationAttempt(state: PipelineGoalState, attempt: {
|
|
96
|
-
promptPath?: string;
|
|
97
|
-
reason: string;
|
|
98
|
-
verifierNodeId?: string;
|
|
99
|
-
}): PipelineGoalState;
|
|
100
|
-
declare function markGoalStateBlocked(state: PipelineGoalState, reason: string): PipelineGoalState;
|
|
101
|
-
declare function goalStateCompletionEvidence(state: PipelineGoalState): {
|
|
102
|
-
evidence: string[];
|
|
103
|
-
passed: boolean;
|
|
104
|
-
};
|
|
105
|
-
declare function goalStateContinuationInput(state: PipelineGoalState): {
|
|
106
|
-
acceptance: {
|
|
107
|
-
evidence: string[];
|
|
108
|
-
id: string;
|
|
109
|
-
verdict: "FAIL" | "PASS";
|
|
110
|
-
violations?: string[] | undefined;
|
|
111
|
-
}[];
|
|
112
|
-
changedFiles: string[];
|
|
113
|
-
currentNodeId: string | undefined;
|
|
114
|
-
exactNextRequirement: string;
|
|
115
|
-
failedGates: {
|
|
116
|
-
evidence: string[];
|
|
117
|
-
gateId: string;
|
|
118
|
-
kind: string;
|
|
119
|
-
nodeId: string;
|
|
120
|
-
passed: boolean;
|
|
121
|
-
reason?: string | undefined;
|
|
122
|
-
}[];
|
|
123
|
-
failureSignature: string;
|
|
124
|
-
originalTask: string;
|
|
125
|
-
priorAttempts: {
|
|
126
|
-
attempt: number;
|
|
127
|
-
reason: string;
|
|
128
|
-
promptPath?: string | undefined;
|
|
129
|
-
verifierNodeId?: string | undefined;
|
|
130
|
-
}[];
|
|
131
|
-
taskContext: unknown;
|
|
132
|
-
verifier: {
|
|
133
|
-
evidence: string[];
|
|
134
|
-
nodeId?: string | undefined;
|
|
135
|
-
reason?: string | undefined;
|
|
136
|
-
violations?: string[] | undefined;
|
|
137
|
-
verdict?: "FAIL" | "PASS" | undefined;
|
|
138
|
-
};
|
|
139
|
-
};
|
|
140
|
-
declare function goalStateFailureSignature(state: PipelineGoalState): string;
|
|
141
|
-
declare function goalStateArtifactPath(runDirectory: string): string;
|
|
142
|
-
declare function saveGoalState(state: PipelineGoalState, runDirectory: string): void;
|
|
143
|
-
declare function loadGoalState(path: string): PipelineGoalState;
|
|
144
|
-
declare function loadGoalStateFromRunDirectory(runDirectory: string): PipelineGoalState;
|
|
145
|
-
//#endregion
|
|
146
|
-
export { CreateGoalStateOptions, PipelineGoalState, applyGoalStateEvent, createGoalState, goalStateArtifactPath, goalStateCompletionEvidence, goalStateContinuationInput, goalStateFailureSignature, loadGoalState, loadGoalStateFromRunDirectory, markGoalStateBlocked, parseGoalState, pipelineGoalStateSchema, reconstructGoalStateFromEvents, recordGoalStateChangedFiles, recordGoalStateContinuationAttempt, saveGoalState };
|
|
@@ -1,339 +0,0 @@
|
|
|
1
|
-
import { uniqueStrings } from "../strings.js";
|
|
2
|
-
import { goalStateNextRequirement } from "./goal-state/goal-requirement.js";
|
|
3
|
-
import { z } from "zod";
|
|
4
|
-
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
|
-
import { join } from "node:path";
|
|
6
|
-
//#region src/runtime/goal-state/goal-state.ts
|
|
7
|
-
const OUTCOME_VALUES = [
|
|
8
|
-
"BLOCKED",
|
|
9
|
-
"CANCELLED",
|
|
10
|
-
"FAIL",
|
|
11
|
-
"PASS"
|
|
12
|
-
];
|
|
13
|
-
const NODE_STATUS_VALUES = [
|
|
14
|
-
"failed",
|
|
15
|
-
"passed",
|
|
16
|
-
"pending",
|
|
17
|
-
"running"
|
|
18
|
-
];
|
|
19
|
-
const VERDICT_VALUES = ["FAIL", "PASS"];
|
|
20
|
-
const MAX_EVIDENCE_ITEMS = 8;
|
|
21
|
-
const MAX_EVIDENCE_LENGTH = 500;
|
|
22
|
-
const evidenceSchema = z.array(z.string().max(MAX_EVIDENCE_LENGTH)).max(32);
|
|
23
|
-
const acceptanceCriterionVerdictSchema = z.object({
|
|
24
|
-
evidence: evidenceSchema.default([]),
|
|
25
|
-
id: z.string().min(1),
|
|
26
|
-
violations: evidenceSchema.optional(),
|
|
27
|
-
verdict: z.enum(VERDICT_VALUES)
|
|
28
|
-
}).strict();
|
|
29
|
-
const goalGateAttemptSchema = z.object({
|
|
30
|
-
evidence: evidenceSchema.default([]),
|
|
31
|
-
gateId: z.string().min(1),
|
|
32
|
-
kind: z.string().min(1),
|
|
33
|
-
nodeId: z.string().min(1),
|
|
34
|
-
passed: z.boolean(),
|
|
35
|
-
reason: z.string().min(1).optional()
|
|
36
|
-
}).strict();
|
|
37
|
-
const goalNodeStateSchema = z.object({
|
|
38
|
-
attempts: z.number().int().nonnegative(),
|
|
39
|
-
changedFiles: z.array(z.string().min(1)).default([]),
|
|
40
|
-
exitCode: z.number().int().optional(),
|
|
41
|
-
gates: z.array(goalGateAttemptSchema).default([]),
|
|
42
|
-
nodeId: z.string().min(1),
|
|
43
|
-
profile: z.string().min(1).optional(),
|
|
44
|
-
runnerId: z.string().min(1).optional(),
|
|
45
|
-
status: z.enum(NODE_STATUS_VALUES)
|
|
46
|
-
}).strict();
|
|
47
|
-
const continuationAttemptSchema = z.object({
|
|
48
|
-
attempt: z.number().int().positive(),
|
|
49
|
-
promptPath: z.string().min(1).optional(),
|
|
50
|
-
reason: z.string().min(1),
|
|
51
|
-
verifierNodeId: z.string().min(1).optional()
|
|
52
|
-
}).strict();
|
|
53
|
-
const pipelineGoalStateSchema = z.object({
|
|
54
|
-
acceptance: z.array(acceptanceCriterionVerdictSchema).default([]),
|
|
55
|
-
blockedReasons: evidenceSchema.default([]),
|
|
56
|
-
changedFiles: z.array(z.string().min(1)).default([]),
|
|
57
|
-
continuationAttempts: z.array(continuationAttemptSchema).default([]),
|
|
58
|
-
gateFailures: z.array(goalGateAttemptSchema).default([]),
|
|
59
|
-
nodes: z.record(z.string().min(1), goalNodeStateSchema).default({}),
|
|
60
|
-
runId: z.string().min(1).optional(),
|
|
61
|
-
schedule: z.object({
|
|
62
|
-
id: z.string().min(1).optional(),
|
|
63
|
-
path: z.string().min(1).optional()
|
|
64
|
-
}).strict().optional(),
|
|
65
|
-
task: z.object({
|
|
66
|
-
context: z.unknown().optional(),
|
|
67
|
-
original: z.string()
|
|
68
|
-
}).strict(),
|
|
69
|
-
terminalOutcome: z.enum(OUTCOME_VALUES).optional(),
|
|
70
|
-
verifier: z.object({
|
|
71
|
-
evidence: evidenceSchema.default([]),
|
|
72
|
-
nodeId: z.string().min(1).optional(),
|
|
73
|
-
reason: z.string().min(1).optional(),
|
|
74
|
-
violations: evidenceSchema.optional(),
|
|
75
|
-
verdict: z.enum(VERDICT_VALUES).optional()
|
|
76
|
-
}).strict().default({ evidence: [] }),
|
|
77
|
-
version: z.literal(1),
|
|
78
|
-
workflowId: z.string().min(1)
|
|
79
|
-
}).strict();
|
|
80
|
-
function createGoalState(options) {
|
|
81
|
-
return parseGoalState({
|
|
82
|
-
acceptance: [],
|
|
83
|
-
blockedReasons: [],
|
|
84
|
-
changedFiles: [],
|
|
85
|
-
continuationAttempts: [],
|
|
86
|
-
gateFailures: [],
|
|
87
|
-
nodes: {},
|
|
88
|
-
...options.runId ? { runId: options.runId } : {},
|
|
89
|
-
...options.scheduleId || options.schedulePath ? { schedule: {
|
|
90
|
-
...options.scheduleId ? { id: options.scheduleId } : {},
|
|
91
|
-
...options.schedulePath ? { path: options.schedulePath } : {}
|
|
92
|
-
} } : {},
|
|
93
|
-
task: {
|
|
94
|
-
...options.taskContext ? { context: options.taskContext } : {},
|
|
95
|
-
original: options.task
|
|
96
|
-
},
|
|
97
|
-
verifier: {},
|
|
98
|
-
version: 1,
|
|
99
|
-
workflowId: options.workflowId
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
function parseGoalState(value) {
|
|
103
|
-
return pipelineGoalStateSchema.parse(value);
|
|
104
|
-
}
|
|
105
|
-
function applyGoalStateEvent(state, event) {
|
|
106
|
-
const next = cloneGoalState(state);
|
|
107
|
-
switch (event.type) {
|
|
108
|
-
case "workflow.planned":
|
|
109
|
-
for (const node of event.nodes) upsertNode(next, node.id, {
|
|
110
|
-
profile: node.profile,
|
|
111
|
-
runnerId: node.runnerId,
|
|
112
|
-
status: "pending"
|
|
113
|
-
});
|
|
114
|
-
break;
|
|
115
|
-
case "node.start":
|
|
116
|
-
case "agent.start":
|
|
117
|
-
upsertNode(next, event.nodeId, {
|
|
118
|
-
attempts: event.attempt,
|
|
119
|
-
profile: event.profile,
|
|
120
|
-
runnerId: event.runnerId,
|
|
121
|
-
status: "running"
|
|
122
|
-
});
|
|
123
|
-
break;
|
|
124
|
-
case "node.finish":
|
|
125
|
-
upsertNode(next, event.nodeId, {
|
|
126
|
-
attempts: event.attempt,
|
|
127
|
-
exitCode: event.exitCode,
|
|
128
|
-
profile: event.profile,
|
|
129
|
-
runnerId: event.runnerId,
|
|
130
|
-
status: event.status === "passed" ? "passed" : "failed"
|
|
131
|
-
});
|
|
132
|
-
break;
|
|
133
|
-
case "gate.finish":
|
|
134
|
-
recordGateAttempt(next, {
|
|
135
|
-
evidence: safeEvidence(event.evidence),
|
|
136
|
-
gateId: event.gateId,
|
|
137
|
-
kind: event.kind,
|
|
138
|
-
nodeId: event.nodeId,
|
|
139
|
-
passed: event.passed,
|
|
140
|
-
...event.reason ? { reason: event.reason } : {}
|
|
141
|
-
});
|
|
142
|
-
break;
|
|
143
|
-
case "node.output.recorded":
|
|
144
|
-
recordStructuredVerdicts(next, event);
|
|
145
|
-
break;
|
|
146
|
-
case "workflow.finish":
|
|
147
|
-
next.terminalOutcome = event.outcome;
|
|
148
|
-
break;
|
|
149
|
-
default: break;
|
|
150
|
-
}
|
|
151
|
-
return parseGoalState(next);
|
|
152
|
-
}
|
|
153
|
-
function reconstructGoalStateFromEvents(options, events) {
|
|
154
|
-
return events.reduce(applyGoalStateEvent, createGoalState(options));
|
|
155
|
-
}
|
|
156
|
-
function recordGoalStateChangedFiles(state, nodeId, files) {
|
|
157
|
-
const next = cloneGoalState(state);
|
|
158
|
-
const uniqueFiles = uniqueChangedFiles(files);
|
|
159
|
-
upsertNode(next, nodeId, { changedFiles: uniqueFiles });
|
|
160
|
-
next.changedFiles = uniqueChangedFiles([...next.changedFiles, ...uniqueFiles]);
|
|
161
|
-
return parseGoalState(next);
|
|
162
|
-
}
|
|
163
|
-
function recordGoalStateContinuationAttempt(state, attempt) {
|
|
164
|
-
const next = cloneGoalState(state);
|
|
165
|
-
next.continuationAttempts.push({
|
|
166
|
-
attempt: next.continuationAttempts.length + 1,
|
|
167
|
-
...attempt.promptPath ? { promptPath: attempt.promptPath } : {},
|
|
168
|
-
reason: attempt.reason,
|
|
169
|
-
...attempt.verifierNodeId ? { verifierNodeId: attempt.verifierNodeId } : {}
|
|
170
|
-
});
|
|
171
|
-
return parseGoalState(next);
|
|
172
|
-
}
|
|
173
|
-
function markGoalStateBlocked(state, reason) {
|
|
174
|
-
const next = cloneGoalState(state);
|
|
175
|
-
next.blockedReasons = safeEvidence([...next.blockedReasons, reason]);
|
|
176
|
-
next.terminalOutcome = "BLOCKED";
|
|
177
|
-
return parseGoalState(next);
|
|
178
|
-
}
|
|
179
|
-
function goalStateCompletionEvidence(state) {
|
|
180
|
-
const evidence = [];
|
|
181
|
-
const verifierPassed = state.verifier.verdict === "PASS" && state.verifier.evidence.length > 0 && (state.verifier.violations?.length ?? 0) === 0;
|
|
182
|
-
evidence.push(verifierPassed ? `verifier '${state.verifier.nodeId ?? "verify"}' passed with evidence` : "missing passing verifier evidence");
|
|
183
|
-
for (const item of acceptanceCompletionEvidence(state)) evidence.push(item);
|
|
184
|
-
return {
|
|
185
|
-
evidence,
|
|
186
|
-
passed: verifierPassed && evidence.every((item) => !item.startsWith("missing ")) && evidence.every((item) => !item.includes(" failed")) && evidence.every((item) => !item.includes(" violations"))
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
function goalStateContinuationInput(state) {
|
|
190
|
-
return {
|
|
191
|
-
acceptance: state.acceptance,
|
|
192
|
-
changedFiles: state.changedFiles,
|
|
193
|
-
currentNodeId: currentFailedNodeId(state),
|
|
194
|
-
exactNextRequirement: goalStateNextRequirement(state),
|
|
195
|
-
failedGates: state.gateFailures.filter((gate) => !gate.passed),
|
|
196
|
-
failureSignature: goalStateFailureSignature(state),
|
|
197
|
-
originalTask: state.task.original,
|
|
198
|
-
priorAttempts: state.continuationAttempts,
|
|
199
|
-
taskContext: state.task.context,
|
|
200
|
-
verifier: state.verifier
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
function goalStateFailureSignature(state) {
|
|
204
|
-
return [
|
|
205
|
-
...state.gateFailures.map((gate) => [
|
|
206
|
-
gate.nodeId,
|
|
207
|
-
gate.gateId,
|
|
208
|
-
gate.reason ?? "",
|
|
209
|
-
...gate.evidence
|
|
210
|
-
].join("/")),
|
|
211
|
-
state.verifier.verdict === "FAIL" ? [
|
|
212
|
-
state.verifier.nodeId ?? "verify",
|
|
213
|
-
state.verifier.reason ?? "",
|
|
214
|
-
...state.verifier.evidence,
|
|
215
|
-
...state.verifier.violations ?? []
|
|
216
|
-
].join("/") : "",
|
|
217
|
-
...state.acceptance.filter((item) => item.verdict === "FAIL").map((item) => [
|
|
218
|
-
"acceptance",
|
|
219
|
-
item.id,
|
|
220
|
-
...item.evidence,
|
|
221
|
-
...item.violations ?? []
|
|
222
|
-
].filter(Boolean).join("/"))
|
|
223
|
-
].filter(Boolean).join("\0");
|
|
224
|
-
}
|
|
225
|
-
function goalStateArtifactPath(runDirectory) {
|
|
226
|
-
return join(runDirectory, "goal-state.json");
|
|
227
|
-
}
|
|
228
|
-
function saveGoalState(state, runDirectory) {
|
|
229
|
-
writeFileSync(goalStateArtifactPath(runDirectory), `${JSON.stringify(parseGoalState(state), null, 2)}\n`);
|
|
230
|
-
}
|
|
231
|
-
function loadGoalState(path) {
|
|
232
|
-
return parseGoalState(JSON.parse(readFileSync(path, "utf8")));
|
|
233
|
-
}
|
|
234
|
-
function loadGoalStateFromRunDirectory(runDirectory) {
|
|
235
|
-
const path = goalStateArtifactPath(runDirectory);
|
|
236
|
-
if (!existsSync(path)) throw new Error(`goal state artifact not found: ${path}`);
|
|
237
|
-
return loadGoalState(path);
|
|
238
|
-
}
|
|
239
|
-
function recordGateAttempt(state, gate) {
|
|
240
|
-
upsertNode(state, gate.nodeId, {});
|
|
241
|
-
state.nodes[gate.nodeId].gates.push(gate);
|
|
242
|
-
if (!gate.passed) {
|
|
243
|
-
state.gateFailures.push(gate);
|
|
244
|
-
if (isVerifierGate(gate)) state.verifier = {
|
|
245
|
-
evidence: safeEvidence([...state.verifier.evidence, ...gate.evidence]),
|
|
246
|
-
nodeId: gate.nodeId,
|
|
247
|
-
...gate.reason ? { reason: gate.reason } : {},
|
|
248
|
-
...state.verifier.violations ? { violations: state.verifier.violations } : {},
|
|
249
|
-
verdict: "FAIL"
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
function recordStructuredVerdicts(state, event) {
|
|
254
|
-
const output = event.output;
|
|
255
|
-
if (!isRecord(output)) return;
|
|
256
|
-
const acceptance = output.acceptance;
|
|
257
|
-
if (Array.isArray(acceptance)) state.acceptance = acceptance.flatMap((item) => {
|
|
258
|
-
if (!(isRecord(item) && isVerdict(item.verdict)) || typeof item.id !== "string") return [];
|
|
259
|
-
return [{
|
|
260
|
-
evidence: safeEvidence(Array.isArray(item.evidence) ? item.evidence : []),
|
|
261
|
-
id: item.id,
|
|
262
|
-
...optionalEvidence("violations", item.violations),
|
|
263
|
-
verdict: item.verdict
|
|
264
|
-
}];
|
|
265
|
-
});
|
|
266
|
-
if (isVerifierNode(event) && isVerdict(output.verdict)) state.verifier = {
|
|
267
|
-
evidence: safeEvidence(Array.isArray(output.evidence) ? output.evidence : []),
|
|
268
|
-
nodeId: event.nodeId,
|
|
269
|
-
...optionalEvidence("violations", output.violations),
|
|
270
|
-
verdict: output.verdict
|
|
271
|
-
};
|
|
272
|
-
}
|
|
273
|
-
function upsertNode(state, nodeId, patch) {
|
|
274
|
-
const current = state.nodes[nodeId] ?? {
|
|
275
|
-
attempts: 0,
|
|
276
|
-
changedFiles: [],
|
|
277
|
-
gates: [],
|
|
278
|
-
nodeId,
|
|
279
|
-
status: "pending"
|
|
280
|
-
};
|
|
281
|
-
state.nodes[nodeId] = {
|
|
282
|
-
...current,
|
|
283
|
-
...patch,
|
|
284
|
-
attempts: Math.max(current.attempts, patch.attempts ?? 0),
|
|
285
|
-
changedFiles: uniqueChangedFiles([...current.changedFiles, ...patch.changedFiles ?? []]),
|
|
286
|
-
gates: patch.gates ?? current.gates,
|
|
287
|
-
nodeId
|
|
288
|
-
};
|
|
289
|
-
}
|
|
290
|
-
function isVerifierGate(gate) {
|
|
291
|
-
return gate.kind === "verdict" || gate.nodeId.includes("verif");
|
|
292
|
-
}
|
|
293
|
-
function isVerifierNode(event) {
|
|
294
|
-
return event.nodeId.includes("verif") || Boolean(event.profile?.includes("verif")) || Boolean(event.schemaPath?.includes("verify"));
|
|
295
|
-
}
|
|
296
|
-
function isVerdict(value) {
|
|
297
|
-
return value === "PASS" || value === "FAIL";
|
|
298
|
-
}
|
|
299
|
-
function safeEvidence(value) {
|
|
300
|
-
return (value ?? []).flatMap((item) => typeof item === "string" ? [item] : []).slice(0, MAX_EVIDENCE_ITEMS).map((item) => item.slice(0, MAX_EVIDENCE_LENGTH));
|
|
301
|
-
}
|
|
302
|
-
function optionalEvidence(key, value) {
|
|
303
|
-
const evidence = Array.isArray(value) ? safeEvidence(value) : [];
|
|
304
|
-
return evidence.length > 0 ? { [key]: evidence } : {};
|
|
305
|
-
}
|
|
306
|
-
function acceptanceCompletionEvidence(state) {
|
|
307
|
-
const expected = expectedAcceptanceIds(state.task.context);
|
|
308
|
-
if (expected.length === 0) return ["acceptance criteria not required by task context"];
|
|
309
|
-
const byId = new Map(state.acceptance.map((item) => [item.id, item]));
|
|
310
|
-
return expected.map((id) => {
|
|
311
|
-
const item = byId.get(id);
|
|
312
|
-
if (!item) return `missing acceptance criterion '${id}' evidence`;
|
|
313
|
-
if (item.verdict !== "PASS") return `acceptance criterion '${id}' failed`;
|
|
314
|
-
if (item.evidence.length === 0) return `missing acceptance criterion '${id}' evidence`;
|
|
315
|
-
if ((item.violations?.length ?? 0) > 0) return `acceptance criterion '${id}' has violations`;
|
|
316
|
-
return `acceptance criterion '${id}' passed with evidence`;
|
|
317
|
-
});
|
|
318
|
-
}
|
|
319
|
-
function expectedAcceptanceIds(context) {
|
|
320
|
-
if (!(isRecord(context) && Array.isArray(context.acceptanceCriteria))) return [];
|
|
321
|
-
return context.acceptanceCriteria.flatMap((item) => isRecord(item) && typeof item.id === "string" ? [item.id] : []);
|
|
322
|
-
}
|
|
323
|
-
function currentFailedNodeId(state) {
|
|
324
|
-
return Object.values(state.nodes).filter((node) => node.status === "failed").at(-1)?.nodeId;
|
|
325
|
-
}
|
|
326
|
-
function uniqueChangedFiles(values) {
|
|
327
|
-
return uniqueStrings(values, {
|
|
328
|
-
filterEmpty: true,
|
|
329
|
-
sort: true
|
|
330
|
-
});
|
|
331
|
-
}
|
|
332
|
-
function cloneGoalState(state) {
|
|
333
|
-
return JSON.parse(JSON.stringify(state));
|
|
334
|
-
}
|
|
335
|
-
function isRecord(value) {
|
|
336
|
-
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
337
|
-
}
|
|
338
|
-
//#endregion
|
|
339
|
-
export { applyGoalStateEvent, createGoalState, goalStateArtifactPath, goalStateCompletionEvidence, goalStateContinuationInput, goalStateFailureSignature, loadGoalState, loadGoalStateFromRunDirectory, markGoalStateBlocked, parseGoalState, pipelineGoalStateSchema, reconstructGoalStateFromEvents, recordGoalStateChangedFiles, recordGoalStateContinuationAttempt, saveGoalState };
|