@osolmaz/pi-workflows 0.11.2 → 0.12.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 +15 -10
- package/dist/builtins/autoimplement-command-batches.d.ts +52 -0
- package/dist/builtins/autoimplement-command-batches.js +258 -0
- package/dist/builtins/autoimplement-command-batches.js.map +1 -0
- package/dist/builtins/autoimplement.workflow.d.ts +509 -124
- package/dist/builtins/autoimplement.workflow.js +884 -327
- package/dist/builtins/autoimplement.workflow.js.map +1 -1
- package/dist/builtins/catalog.js +5 -3
- package/dist/builtins/catalog.js.map +1 -1
- package/dist/builtins/index.d.ts +4 -1
- package/dist/builtins/index.js +3 -1
- package/dist/builtins/index.js.map +1 -1
- package/dist/builtins/monitor.workflow.d.ts +2 -4
- package/dist/builtins/monitor.workflow.js +26 -128
- package/dist/builtins/monitor.workflow.js.map +1 -1
- package/dist/builtins/pi-agent-group.d.ts +72 -0
- package/dist/builtins/pi-agent-group.js +1087 -0
- package/dist/builtins/pi-agent-group.js.map +1 -0
- package/dist/builtins/plan-approval.workflow.d.ts +39 -5
- package/dist/builtins/plan-approval.workflow.js +92 -14
- package/dist/builtins/plan-approval.workflow.js.map +1 -1
- package/dist/builtins/plan-change.workflow.d.ts +301 -0
- package/dist/builtins/plan-change.workflow.js +256 -0
- package/dist/builtins/plan-change.workflow.js.map +1 -0
- package/dist/builtins/plan-presentation.js +2 -2
- package/dist/builtins/plan-presentation.js.map +1 -1
- package/dist/builtins/sanity-check.workflow.d.ts +85 -0
- package/dist/builtins/sanity-check.workflow.js +482 -0
- package/dist/builtins/sanity-check.workflow.js.map +1 -0
- package/dist/controllers/sqlite.d.ts +90 -2
- package/dist/controllers/sqlite.js +380 -5
- package/dist/controllers/sqlite.js.map +1 -1
- package/dist/extension/decision-channels.d.ts +2 -2
- package/dist/extension/decision-channels.js +29 -35
- package/dist/extension/decision-channels.js.map +1 -1
- package/dist/extension/deferred-turn-coordinator.d.ts +32 -0
- package/dist/extension/deferred-turn-coordinator.js +143 -0
- package/dist/extension/deferred-turn-coordinator.js.map +1 -0
- package/dist/extension/deferred-turn.d.ts +44 -0
- package/dist/extension/deferred-turn.js +110 -0
- package/dist/extension/deferred-turn.js.map +1 -0
- package/dist/extension/index.js +348 -80
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/session-events.d.ts +2 -2
- package/dist/extension/step-message.d.ts +1 -0
- package/dist/extension/step-message.js.map +1 -1
- package/dist/extension/widget.js +23 -3
- package/dist/extension/widget.js.map +1 -1
- package/dist/herdr/setup.js +4 -4
- package/dist/render/graph-render.js +1 -2
- package/dist/render/graph-render.js.map +1 -1
- package/dist/viewer/render.js +7 -6
- package/dist/viewer/render.js.map +1 -1
- package/dist/workflows/catalog.js +7 -2
- package/dist/workflows/catalog.js.map +1 -1
- package/dist/workflows/command-batch.d.ts +38 -0
- package/dist/workflows/command-batch.js +176 -0
- package/dist/workflows/command-batch.js.map +1 -0
- package/dist/workflows/composition.js +8 -0
- package/dist/workflows/composition.js.map +1 -1
- package/dist/workflows/decision-presentation.d.ts +1 -1
- package/dist/workflows/decision-presentation.js +51 -38
- package/dist/workflows/decision-presentation.js.map +1 -1
- package/dist/workflows/engine.d.ts +2 -2
- package/dist/workflows/engine.js +22 -17
- package/dist/workflows/engine.js.map +1 -1
- package/dist/workflows/errors.d.ts +13 -0
- package/dist/workflows/errors.js +15 -0
- package/dist/workflows/errors.js.map +1 -1
- package/dist/workflows/human-decision.d.ts +16 -4
- package/dist/workflows/human-decision.js +175 -72
- package/dist/workflows/human-decision.js.map +1 -1
- package/dist/workflows/index.d.ts +3 -2
- package/dist/workflows/index.js +2 -1
- package/dist/workflows/index.js.map +1 -1
- package/dist/workflows/progress.d.ts +1 -0
- package/dist/workflows/progress.js +15 -3
- package/dist/workflows/progress.js.map +1 -1
- package/dist/workflows/schema.js +12 -1
- package/dist/workflows/schema.js.map +1 -1
- package/dist/workflows/shell.d.ts +4 -0
- package/dist/workflows/shell.js +6 -0
- package/dist/workflows/shell.js.map +1 -1
- package/dist/workflows/store.js +8 -1
- package/dist/workflows/store.js.map +1 -1
- package/dist/workflows/types.d.ts +37 -48
- package/docs/2026-08-18-herdr-piw-plan.md +1 -1
- package/docs/2026-08-20-durable-workflow-launch-plan.md +19 -23
- package/docs/CONTROLLERS.md +2 -2
- package/docs/DEFERRED_TURNS.md +298 -0
- package/docs/DESIGN_PHILOSOPHY.md +1 -1
- package/docs/HUMAN_DECISIONS.md +30 -40
- package/docs/HUMAN_DECISION_PRESENTATIONS.md +17 -27
- package/docs/MONITOR.md +7 -13
- package/docs/WORKFLOW_COMPOSITION.md +10 -9
- package/docs/WORKFLOW_STEP_MESSAGES.md +5 -5
- package/docs/WORKFLOW_UPDATES.md +28 -5
- package/docs/plans/2026-08-04-controller-runtime-plan.md +1 -1
- package/docs/plans/2026-08-05-always-on-workflows-plan.md +1 -1
- package/docs/plans/2026-08-10-agent-managed-monitor-workflows-plan.md +9 -9
- package/docs/plans/2026-08-13-built-in-workflow-catalog-plan.md +1 -1
- package/docs/plans/2026-08-13-session-addressed-workflow-notifications-plan.md +1 -1
- package/docs/plans/2026-08-16-workflow-updates-plan.md +3 -3
- package/docs/plans/2026-08-17-bundled-skills-plan.md +6 -6
- package/docs/plans/2026-08-19-human-decision-gates-plan.md +5 -5
- package/docs/plans/2026-08-19-human-decision-presentations-plan.md +1 -1
- package/docs/plans/2026-08-19-provider-compatible-workflow-tool-schema-plan.md +1 -1
- package/docs/plans/2026-08-19-workflow-composition-plan.md +6 -6
- package/docs/plans/2026-08-20-autoimplement-blocker-challenge-plan.md +3 -3
- package/docs/plans/2026-08-20-bounded-command-batches-plan.md +206 -0
- package/docs/plans/2026-08-20-herdr-plugin-sync-plan.md +9 -9
- package/docs/plans/2026-08-21-autoimplement-timeout-fallback-plan.md +242 -0
- package/docs/plans/2026-08-21-deferred-turn-intents-plan.md +324 -0
- package/docs/plans/2026-08-21-plan-change-approval-policy-plan.md +322 -0
- package/docs/plans/2026-08-21-sanity-check-plan.md +283 -0
- package/docs/run-bundles.md +8 -6
- package/docs/workflows.md +85 -22
- package/examples/workflows/approved-plan.workflow.ts +19 -46
- package/examples/workflows/command-batch.workflow.ts +50 -0
- package/examples/workflows/sanity-check.workflow.ts +1 -0
- package/herdr-plugin.toml +3 -3
- package/package.json +7 -7
- package/schemas/decision-presentation-v1.schema.json +1 -1
- package/schemas/human-decision-accepted-v1.schema.json +16 -4
- package/schemas/human-decision-answer-attempt-v1.schema.json +1 -1
- package/schemas/human-decision-cancellation-v1.schema.json +1 -1
- package/schemas/human-decision-continuation-v1.schema.json +11 -2
- package/schemas/human-decision-delivery-v1.schema.json +9 -1
- package/schemas/human-decision-receipt-v1.schema.json +9 -1
- package/schemas/human-decision-request-v1.schema.json +25 -5
- package/schemas/human-decision-resolution-v1.schema.json +1 -1
- package/schemas/human-decision-settlement-v1.schema.json +1 -1
- package/skills/autodoc/SKILL.md +33 -2
- package/skills/autoimplement/SKILL.md +93 -12
- package/skills/autoplan/SKILL.md +26 -2
- package/skills/monitor/SKILL.md +66 -24
- package/skills/pi-workflows/SKILL.md +21 -6
- package/skills/sanity-check/SKILL.md +44 -0
- package/src/builtins/autoimplement-command-batches.ts +364 -0
- package/src/builtins/autoimplement.workflow.ts +1075 -370
- package/src/builtins/catalog.ts +5 -3
- package/src/builtins/index.ts +24 -0
- package/src/builtins/monitor.workflow.ts +27 -150
- package/src/builtins/pi-agent-group.ts +1407 -0
- package/src/builtins/plan-approval.workflow.ts +157 -24
- package/src/builtins/plan-change.workflow.ts +321 -0
- package/src/builtins/plan-presentation.ts +2 -2
- package/src/builtins/sanity-check.workflow.ts +769 -0
- package/src/controllers/sqlite.ts +580 -7
- package/src/extension/decision-channels.ts +36 -66
- package/src/extension/deferred-turn-coordinator.ts +171 -0
- package/src/extension/deferred-turn.ts +166 -0
- package/src/extension/index.ts +438 -90
- package/src/extension/session-events.ts +2 -2
- package/src/extension/step-message.ts +1 -0
- package/src/extension/widget.ts +24 -5
- package/src/herdr/setup.ts +4 -4
- package/src/render/graph-render.ts +1 -2
- package/src/viewer/render.ts +7 -6
- package/src/workflows/catalog.ts +7 -2
- package/src/workflows/command-batch.ts +254 -0
- package/src/workflows/composition.ts +9 -0
- package/src/workflows/decision-presentation.ts +56 -43
- package/src/workflows/engine.ts +25 -20
- package/src/workflows/errors.ts +24 -0
- package/src/workflows/human-decision.ts +218 -101
- package/src/workflows/index.ts +20 -11
- package/src/workflows/progress.ts +18 -3
- package/src/workflows/schema.ts +19 -1
- package/src/workflows/shell.ts +10 -0
- package/src/workflows/store.ts +8 -1
- package/src/workflows/types.ts +43 -59
- package/schemas/human-decision-accepted-v2.schema.json +0 -50
- package/schemas/human-decision-delivery-v2.schema.json +0 -36
- package/schemas/human-decision-receipt-v2.schema.json +0 -39
- package/schemas/human-decision-request-v2.schema.json +0 -69
- package/schemas/human-decision-resolution-v2.schema.json +0 -27
|
@@ -1,14 +1,43 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import {
|
|
2
|
+
import { runCommandBatch, } from "../workflows/command-batch.js";
|
|
3
|
+
import { action, agent, compute, defineWorkflow, includeWorkflow, includedResult, } from "../workflows/definition.js";
|
|
3
4
|
import { digest } from "../workflows/human-decision.js";
|
|
4
5
|
import autodocWorkflow, {} from "./autodoc.workflow.js";
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
const TEN_MINUTES_MS = 10 * 60_000;
|
|
6
|
+
import { parseAutoimplementConcurrency, parseCiInspectionBatch, parsePublishedRepositories, parseVerificationCommandPlan, reviewerCommand, } from "./autoimplement-command-batches.js";
|
|
7
|
+
import { parsePlanApprovalPolicy } from "./plan-approval.workflow.js";
|
|
8
|
+
import planChangeWorkflow, {} from "./plan-change.workflow.js";
|
|
9
9
|
const MAX_BLOCKER_CHALLENGES = 3;
|
|
10
10
|
const MAX_CHALLENGE_ITEMS = 5;
|
|
11
11
|
const MAX_CHALLENGE_TEXT = 500;
|
|
12
|
+
const MAX_TIMEOUT_FALLBACKS = 3;
|
|
13
|
+
const MAX_TIMEOUT_FALLBACK_EVIDENCE = 8;
|
|
14
|
+
const TIMEOUT_FALLBACK_SOURCES = [
|
|
15
|
+
"implement",
|
|
16
|
+
"planVerification",
|
|
17
|
+
"verify",
|
|
18
|
+
"fix",
|
|
19
|
+
"publish",
|
|
20
|
+
"addressP2",
|
|
21
|
+
"verifyP2",
|
|
22
|
+
"inspectComments",
|
|
23
|
+
"inspectCi",
|
|
24
|
+
"opportunisticTest",
|
|
25
|
+
"finalizeDelivery",
|
|
26
|
+
];
|
|
27
|
+
const WORK_ATTEMPT_NODES = ["implement", "fix", "addressP2"];
|
|
28
|
+
const TIMEOUT_FALLBACK_ROUTES = {
|
|
29
|
+
implement: ["retry", "replan", "blocked"],
|
|
30
|
+
planVerification: ["retry", "verify", "replan", "blocked"],
|
|
31
|
+
verify: ["retry", "verify", "replan", "blocked"],
|
|
32
|
+
fix: ["retry", "replan", "blocked"],
|
|
33
|
+
publish: ["retry", "replan", "blocked"],
|
|
34
|
+
addressP2: ["retry", "replan", "blocked"],
|
|
35
|
+
verifyP2: ["retry", "replan", "blocked"],
|
|
36
|
+
inspectComments: ["retry", "review", "ci", "replan", "blocked"],
|
|
37
|
+
inspectCi: ["retry", "ci", "deliver", "replan", "blocked"],
|
|
38
|
+
opportunisticTest: ["retry", "ci", "deliver", "replan", "blocked"],
|
|
39
|
+
finalizeDelivery: ["retry", "deliver", "replan", "blocked"],
|
|
40
|
+
};
|
|
12
41
|
function requireRecord(value, label) {
|
|
13
42
|
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
14
43
|
throw new Error(`${label} must be an object`);
|
|
@@ -21,6 +50,12 @@ function requireString(value, label) {
|
|
|
21
50
|
}
|
|
22
51
|
return value.trim();
|
|
23
52
|
}
|
|
53
|
+
function requireStringArray(value, label) {
|
|
54
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
|
|
55
|
+
throw new Error(`${label} must be an array of strings`);
|
|
56
|
+
}
|
|
57
|
+
return [...value];
|
|
58
|
+
}
|
|
24
59
|
function boundedChallengeItems(value, label) {
|
|
25
60
|
if (!Array.isArray(value) || value.length > MAX_CHALLENGE_ITEMS) {
|
|
26
61
|
throw new Error(`${label} must be an array with at most ${MAX_CHALLENGE_ITEMS} items`);
|
|
@@ -33,6 +68,148 @@ function boundedChallengeItems(value, label) {
|
|
|
33
68
|
return text;
|
|
34
69
|
});
|
|
35
70
|
}
|
|
71
|
+
function isTimeoutFallbackSource(nodeId) {
|
|
72
|
+
return TIMEOUT_FALLBACK_SOURCES.includes(nodeId);
|
|
73
|
+
}
|
|
74
|
+
function latestTimedOutStep(context) {
|
|
75
|
+
for (let index = context.state.steps.length - 1; index >= 0; index -= 1) {
|
|
76
|
+
const step = context.state.steps[index];
|
|
77
|
+
if (step?.outcome === "timed_out" && isTimeoutFallbackSource(step.nodeId))
|
|
78
|
+
return step;
|
|
79
|
+
}
|
|
80
|
+
throw new Error("No supported timed-out Autoimplement step is available");
|
|
81
|
+
}
|
|
82
|
+
function latestStepIndex(context, predicate) {
|
|
83
|
+
for (let index = context.state.steps.length - 1; index >= 0; index -= 1) {
|
|
84
|
+
const step = context.state.steps[index];
|
|
85
|
+
if (step && predicate(step))
|
|
86
|
+
return index;
|
|
87
|
+
}
|
|
88
|
+
return -1;
|
|
89
|
+
}
|
|
90
|
+
function latestWorkAttemptIndex(context) {
|
|
91
|
+
return latestStepIndex(context, (step) => WORK_ATTEMPT_NODES.includes(step.nodeId));
|
|
92
|
+
}
|
|
93
|
+
function hasCurrentAcceptedWork(context) {
|
|
94
|
+
const index = latestWorkAttemptIndex(context);
|
|
95
|
+
return index >= 0 && context.state.steps[index]?.outcome === "ok";
|
|
96
|
+
}
|
|
97
|
+
function hasCurrentPublication(context) {
|
|
98
|
+
const workIndex = latestWorkAttemptIndex(context);
|
|
99
|
+
const publicationIndex = latestStepIndex(context, (step) => {
|
|
100
|
+
if (step.outcome !== "ok")
|
|
101
|
+
return false;
|
|
102
|
+
if (step.nodeId === "publish")
|
|
103
|
+
return true;
|
|
104
|
+
if (step.nodeId !== "verifyP2")
|
|
105
|
+
return false;
|
|
106
|
+
const output = step.output;
|
|
107
|
+
return output?.passed === true;
|
|
108
|
+
});
|
|
109
|
+
return publicationIndex > workIndex;
|
|
110
|
+
}
|
|
111
|
+
function parseTimeoutFallback(value, context) {
|
|
112
|
+
const result = requireRecord(value, "timeout fallback");
|
|
113
|
+
const routes = [
|
|
114
|
+
"retry",
|
|
115
|
+
"verify",
|
|
116
|
+
"review",
|
|
117
|
+
"ci",
|
|
118
|
+
"deliver",
|
|
119
|
+
"replan",
|
|
120
|
+
"blocked",
|
|
121
|
+
];
|
|
122
|
+
if (!routes.includes(result.route)) {
|
|
123
|
+
throw new Error(`timeout fallback route must be one of ${routes.join(", ")}`);
|
|
124
|
+
}
|
|
125
|
+
const route = result.route;
|
|
126
|
+
const reason = requireString(result.reason, "timeout fallback reason");
|
|
127
|
+
const evidence = requireStringArray(result.evidence, "timeout fallback evidence").map((item, index) => requireString(item, `timeout fallback evidence[${index}]`));
|
|
128
|
+
if (evidence.length === 0 || evidence.length > MAX_TIMEOUT_FALLBACK_EVIDENCE) {
|
|
129
|
+
throw new Error(`timeout fallback evidence must contain 1 through ${MAX_TIMEOUT_FALLBACK_EVIDENCE} items`);
|
|
130
|
+
}
|
|
131
|
+
const timedOut = latestTimedOutStep(context);
|
|
132
|
+
const source = timedOut.nodeId;
|
|
133
|
+
if (!TIMEOUT_FALLBACK_ROUTES[source].includes(route)) {
|
|
134
|
+
throw new Error(`timeout fallback route ${route} is not safe after timed-out ${source}`);
|
|
135
|
+
}
|
|
136
|
+
if (route === "verify" && !hasCurrentAcceptedWork(context)) {
|
|
137
|
+
throw new Error("timeout fallback cannot route to verification without current accepted work");
|
|
138
|
+
}
|
|
139
|
+
if (["review", "ci", "deliver"].includes(route) && !hasCurrentPublication(context)) {
|
|
140
|
+
throw new Error("timeout fallback cannot move past publication without a current published head");
|
|
141
|
+
}
|
|
142
|
+
if (route === "ci") {
|
|
143
|
+
const comments = context.outputs.inspectComments;
|
|
144
|
+
if (comments?.route !== "ci") {
|
|
145
|
+
throw new Error("timeout fallback cannot route to CI before comment inspection completed");
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (route === "deliver") {
|
|
149
|
+
const ci = (context.outputs.assessTrackedCi ?? context.outputs.inspectCi);
|
|
150
|
+
const classification = context.outputs.classifyCi;
|
|
151
|
+
if (ci?.route !== "green" && classification?.route !== "unrelated") {
|
|
152
|
+
throw new Error("timeout fallback cannot route to delivery before CI is ready");
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return { route, reason, evidence };
|
|
156
|
+
}
|
|
157
|
+
function timeoutFallbackTarget(context) {
|
|
158
|
+
const fallback = context.outputs.timeoutFallback;
|
|
159
|
+
if (fallback.route !== "retry") {
|
|
160
|
+
const routes = {
|
|
161
|
+
verify: "planVerification",
|
|
162
|
+
review: "selectReviewCommands",
|
|
163
|
+
ci: "inspectCi",
|
|
164
|
+
deliver: "finalizeDelivery",
|
|
165
|
+
replan: "redesign",
|
|
166
|
+
blocked: "blocked",
|
|
167
|
+
};
|
|
168
|
+
return { route: routes[fallback.route] };
|
|
169
|
+
}
|
|
170
|
+
return { route: latestTimedOutStep(context).nodeId };
|
|
171
|
+
}
|
|
172
|
+
function timeoutFallbackGuard(context) {
|
|
173
|
+
const timeout = latestTimedOutStep(context);
|
|
174
|
+
const attempts = context.state.steps.filter((step) => step.nodeId === "timeoutFallback" && step.outcome === "ok").length;
|
|
175
|
+
if (attempts >= MAX_TIMEOUT_FALLBACKS) {
|
|
176
|
+
const timeouts = context.state.steps
|
|
177
|
+
.filter((step) => step.outcome === "timed_out" && isTimeoutFallbackSource(step.nodeId))
|
|
178
|
+
.map((step) => ({
|
|
179
|
+
nodeId: step.nodeId,
|
|
180
|
+
attemptId: step.attemptId,
|
|
181
|
+
error: step.error,
|
|
182
|
+
}));
|
|
183
|
+
return {
|
|
184
|
+
route: "blocked",
|
|
185
|
+
reason: `Autoimplement reached the ${MAX_TIMEOUT_FALLBACKS}-fallback timeout safety limit.`,
|
|
186
|
+
evidence: {
|
|
187
|
+
attempts,
|
|
188
|
+
limit: MAX_TIMEOUT_FALLBACKS,
|
|
189
|
+
timeouts,
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
return {
|
|
194
|
+
route: "recover",
|
|
195
|
+
attempt: attempts + 1,
|
|
196
|
+
limit: MAX_TIMEOUT_FALLBACKS,
|
|
197
|
+
timeout: {
|
|
198
|
+
nodeId: timeout.nodeId,
|
|
199
|
+
attemptId: timeout.attemptId,
|
|
200
|
+
error: timeout.error,
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
function throwLatestSupportedFailure(context) {
|
|
205
|
+
for (let index = context.state.steps.length - 1; index >= 0; index -= 1) {
|
|
206
|
+
const step = context.state.steps[index];
|
|
207
|
+
if (step?.outcome === "failed" && isTimeoutFallbackSource(step.nodeId)) {
|
|
208
|
+
throw new Error(step.error ?? `Autoimplement node failed: ${step.nodeId}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
throw new Error("No supported failed Autoimplement step is available");
|
|
212
|
+
}
|
|
36
213
|
function parseBlockerChallenge(value) {
|
|
37
214
|
const result = requireRecord(value, "blocker challenge");
|
|
38
215
|
if (result.route !== "continue" && result.route !== "blocked") {
|
|
@@ -120,20 +297,8 @@ function parseInput(value) {
|
|
|
120
297
|
documents: [...raw.documents],
|
|
121
298
|
};
|
|
122
299
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const raw = requireRecord(input.approval, "autoimplement approval");
|
|
126
|
-
const maxReplans = raw.maxReplans ?? 3;
|
|
127
|
-
if (!Number.isInteger(maxReplans) ||
|
|
128
|
-
maxReplans < 1 ||
|
|
129
|
-
maxReplans > 20) {
|
|
130
|
-
throw new Error("autoimplement approval maxReplans must be from 1 through 20");
|
|
131
|
-
}
|
|
132
|
-
approval = {
|
|
133
|
-
audience: requireString(raw.audience, "autoimplement approval audience"),
|
|
134
|
-
maxReplans: maxReplans,
|
|
135
|
-
};
|
|
136
|
-
}
|
|
300
|
+
const concurrency = parseAutoimplementConcurrency(input.concurrency);
|
|
301
|
+
const approval = parsePlanApprovalPolicy(input.approval);
|
|
137
302
|
return {
|
|
138
303
|
task: requireString(input.task, "autoimplement task"),
|
|
139
304
|
...(input.plan !== undefined ? { plan: input.plan } : {}),
|
|
@@ -148,7 +313,8 @@ function parseInput(value) {
|
|
|
148
313
|
merge: input.merge === true,
|
|
149
314
|
...(documents !== undefined ? { documents: [...documents] } : {}),
|
|
150
315
|
...(documentation !== undefined ? { documentation } : {}),
|
|
151
|
-
|
|
316
|
+
approval,
|
|
317
|
+
concurrency,
|
|
152
318
|
};
|
|
153
319
|
}
|
|
154
320
|
function parseExistingPlan(value) {
|
|
@@ -187,58 +353,50 @@ function parseRoute(value, routes, label) {
|
|
|
187
353
|
}
|
|
188
354
|
return { ...record, route: record.route };
|
|
189
355
|
}
|
|
190
|
-
function
|
|
191
|
-
|
|
192
|
-
if (command.command !== options.command) {
|
|
193
|
-
throw new Error(`${options.label} command must be ${options.command}`);
|
|
194
|
-
}
|
|
195
|
-
if (!Array.isArray(command.args) || command.args.some((arg) => typeof arg !== "string")) {
|
|
196
|
-
throw new Error(`${options.label} args must be an array of strings`);
|
|
197
|
-
}
|
|
198
|
-
const args = [...command.args];
|
|
199
|
-
if (!options.validateArgs(args))
|
|
200
|
-
throw new Error(`${options.label} args are not allowed`);
|
|
201
|
-
const cwd = requireString(command.cwd, `${options.label} cwd`);
|
|
202
|
-
if (!path.isAbsolute(cwd))
|
|
203
|
-
throw new Error(`${options.label} cwd must be absolute`);
|
|
204
|
-
const timeoutMs = command.timeoutMs;
|
|
205
|
-
if (typeof timeoutMs !== "number" ||
|
|
206
|
-
!Number.isInteger(timeoutMs) ||
|
|
207
|
-
timeoutMs <= 0 ||
|
|
208
|
-
timeoutMs > options.maxTimeoutMs) {
|
|
209
|
-
throw new Error(`${options.label} timeoutMs must be at most ${options.maxTimeoutMs}`);
|
|
210
|
-
}
|
|
211
|
-
return { command: options.command, args, cwd, timeoutMs };
|
|
356
|
+
function concurrency(context) {
|
|
357
|
+
return parseAutoimplementConcurrency(context.input.concurrency);
|
|
212
358
|
}
|
|
213
|
-
function
|
|
214
|
-
return
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
359
|
+
async function runAutoimplementBatch(context, kind, commands, maxConcurrency) {
|
|
360
|
+
return await runCommandBatch({ items: commands, maxConcurrency: Math.min(maxConcurrency, Math.max(1, commands.length)) }, {
|
|
361
|
+
signal: context.signal,
|
|
362
|
+
onItemSettled: async (result, completed, total) => {
|
|
363
|
+
if (context.signal.aborted)
|
|
364
|
+
return;
|
|
365
|
+
try {
|
|
366
|
+
await context.publishUpdate({
|
|
367
|
+
type: "command-batch.item",
|
|
368
|
+
key: `${kind}/${result.id}`,
|
|
369
|
+
data: {
|
|
370
|
+
schema: "pi-workflows.command-batch-item.v1",
|
|
371
|
+
batchKind: kind,
|
|
372
|
+
itemId: result.id,
|
|
373
|
+
outcome: result.outcome,
|
|
374
|
+
completed,
|
|
375
|
+
total,
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
catch (error) {
|
|
380
|
+
if (!context.signal.aborted)
|
|
381
|
+
throw error;
|
|
382
|
+
}
|
|
221
383
|
},
|
|
222
384
|
});
|
|
223
385
|
}
|
|
224
|
-
function
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
});
|
|
386
|
+
function commandBatchTimeoutMs(commands, maxConcurrency) {
|
|
387
|
+
if (commands.length === 0)
|
|
388
|
+
return 10_000;
|
|
389
|
+
const concurrency = Math.min(maxConcurrency, commands.length);
|
|
390
|
+
const waves = Math.ceil(commands.length / concurrency);
|
|
391
|
+
const longestItem = Math.max(...commands.map((command) => command.timeoutMs));
|
|
392
|
+
return waves * longestItem + 10_000;
|
|
232
393
|
}
|
|
233
|
-
function
|
|
234
|
-
return
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
allowNonZeroExit: true,
|
|
240
|
-
maxOutputChars: 1_000_000,
|
|
241
|
-
};
|
|
394
|
+
function reviewBatchNeedsRepair(result) {
|
|
395
|
+
return result.items.some((item) => item.outcome === "timedOut" ||
|
|
396
|
+
item.outcome === "cancelled" ||
|
|
397
|
+
(item.outcome === "failed" && item.exitCode === null) ||
|
|
398
|
+
item.stdoutTruncated ||
|
|
399
|
+
item.stderrTruncated);
|
|
242
400
|
}
|
|
243
401
|
function latestOutput(context, nodeIds) {
|
|
244
402
|
for (let index = context.state.steps.length - 1; index >= 0; index -= 1) {
|
|
@@ -246,22 +404,11 @@ function latestOutput(context, nodeIds) {
|
|
|
246
404
|
if (step && nodeIds.includes(step.nodeId))
|
|
247
405
|
return step.output;
|
|
248
406
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
for (let index = context.state.steps.length - 1; index >= 0; index -= 1) {
|
|
253
|
-
const step = context.state.steps[index];
|
|
254
|
-
if (!step)
|
|
255
|
-
continue;
|
|
256
|
-
if (step.nodeId === "repairCiCommand")
|
|
257
|
-
return step.output;
|
|
258
|
-
if (step.nodeId === "inspectCi") {
|
|
259
|
-
const output = step.output;
|
|
260
|
-
if (output.trackingCommand !== undefined)
|
|
261
|
-
return output.trackingCommand;
|
|
262
|
-
}
|
|
407
|
+
for (const nodeId of nodeIds) {
|
|
408
|
+
if (context.outputs[nodeId] !== undefined)
|
|
409
|
+
return context.outputs[nodeId];
|
|
263
410
|
}
|
|
264
|
-
throw new Error(
|
|
411
|
+
throw new Error(`No output found for ${nodeIds.join(" or ")}`);
|
|
265
412
|
}
|
|
266
413
|
function currentPlan(context) {
|
|
267
414
|
const adopted = context.outputs.adoptPlan;
|
|
@@ -276,19 +423,6 @@ function currentPlan(context) {
|
|
|
276
423
|
return discovered.plan;
|
|
277
424
|
return context.input.plan;
|
|
278
425
|
}
|
|
279
|
-
function currentPlanDigest(context) {
|
|
280
|
-
const adopted = context.outputs.adoptPlan;
|
|
281
|
-
if (typeof adopted?.planDigest === "string")
|
|
282
|
-
return adopted.planDigest;
|
|
283
|
-
const documented = context.outputs.documentation;
|
|
284
|
-
if (documented?.exit === "ready" && typeof documented.output?.planDigest === "string") {
|
|
285
|
-
return documented.output.planDigest;
|
|
286
|
-
}
|
|
287
|
-
const plan = currentPlan(context);
|
|
288
|
-
if (plan === undefined)
|
|
289
|
-
throw new Error("autoimplement does not have a selected plan");
|
|
290
|
-
return digest(plan);
|
|
291
|
-
}
|
|
292
426
|
function blockerChallenges(context) {
|
|
293
427
|
return context.state.steps
|
|
294
428
|
.filter((step) => step.nodeId === "challengeBlocker" && step.outcome === "ok")
|
|
@@ -305,6 +439,7 @@ function latestBlockerClaim(context) {
|
|
|
305
439
|
"assessTrackedCi",
|
|
306
440
|
"classifyCi",
|
|
307
441
|
"finalizeDelivery",
|
|
442
|
+
"timeoutFallback",
|
|
308
443
|
];
|
|
309
444
|
for (let index = context.state.steps.length - 1; index >= 0; index -= 1) {
|
|
310
445
|
const step = context.state.steps[index];
|
|
@@ -322,14 +457,6 @@ function recentWorkflowAttempts(context) {
|
|
|
322
457
|
}));
|
|
323
458
|
}
|
|
324
459
|
function latestIssue(context) {
|
|
325
|
-
const approval = context.outputs.approval;
|
|
326
|
-
if (approval?.exit === "replan" && typeof approval.output?.instructions === "string") {
|
|
327
|
-
return {
|
|
328
|
-
source: "human-replan",
|
|
329
|
-
instructions: approval.output.instructions,
|
|
330
|
-
priorPlanDigest: currentPlanDigest(context),
|
|
331
|
-
};
|
|
332
|
-
}
|
|
333
460
|
const ids = [
|
|
334
461
|
"challengeBlocker",
|
|
335
462
|
"classifyImplementation",
|
|
@@ -337,6 +464,7 @@ function latestIssue(context) {
|
|
|
337
464
|
"triageReview",
|
|
338
465
|
"inspectComments",
|
|
339
466
|
"classifyCi",
|
|
467
|
+
"timeoutFallback",
|
|
340
468
|
"adoptPlan",
|
|
341
469
|
];
|
|
342
470
|
for (let index = context.state.steps.length - 1; index >= 0; index -= 1) {
|
|
@@ -357,19 +485,277 @@ function parseFinding(value, severity) {
|
|
|
357
485
|
summary: requireString(finding.summary, `${severity} finding summary`),
|
|
358
486
|
};
|
|
359
487
|
}
|
|
360
|
-
function
|
|
361
|
-
const
|
|
362
|
-
const
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
488
|
+
function parseVerificationForContext(value, context) {
|
|
489
|
+
const plan = parseVerificationCommandPlan(value);
|
|
490
|
+
const implementation = latestOutput(context, ["implement"]);
|
|
491
|
+
const reported = Array.isArray(implementation.repositories)
|
|
492
|
+
? implementation.repositories.filter((entry) => typeof entry === "string")
|
|
493
|
+
: [];
|
|
494
|
+
const request = context.input;
|
|
495
|
+
const roots = new Set((reported.length > 0 ? reported : [request.repository ?? process.cwd()]).map((entry) => path.resolve(entry)));
|
|
496
|
+
for (const command of plan.commands) {
|
|
497
|
+
const cwd = path.resolve(command.cwd);
|
|
498
|
+
if (!roots.delete(cwd)) {
|
|
499
|
+
throw new Error(`verification command cwd was not reported by implementation: ${command.cwd}`);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
if (roots.size > 0) {
|
|
503
|
+
throw new Error(`verification plan is missing reported repositories: ${[...roots].join(", ")}`);
|
|
504
|
+
}
|
|
505
|
+
return plan;
|
|
506
|
+
}
|
|
507
|
+
function currentPublishedRepositories(context) {
|
|
508
|
+
return latestOutput(context, ["verifyP2", "publish"]);
|
|
509
|
+
}
|
|
510
|
+
function parseDeliveryRepository(value, index) {
|
|
511
|
+
const result = requireRecord(value, `delivery repositories[${index}]`);
|
|
512
|
+
const repository = requireString(result.repository, `delivery repositories[${index}].repository`);
|
|
513
|
+
if (!path.isAbsolute(repository)) {
|
|
514
|
+
throw new Error(`delivery repositories[${index}].repository must be absolute`);
|
|
515
|
+
}
|
|
516
|
+
if (typeof result.merged !== "boolean") {
|
|
517
|
+
throw new Error(`delivery repositories[${index}].merged must be a boolean`);
|
|
518
|
+
}
|
|
519
|
+
return {
|
|
520
|
+
repository: path.resolve(repository),
|
|
521
|
+
pr: requireString(result.pr, `delivery repositories[${index}].pr`),
|
|
522
|
+
merged: result.merged,
|
|
523
|
+
reportComment: requireString(result.reportComment, `delivery repositories[${index}].reportComment`),
|
|
524
|
+
reason: requireString(result.reason, `delivery repositories[${index}].reason`),
|
|
367
525
|
};
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
const
|
|
371
|
-
|
|
372
|
-
|
|
526
|
+
}
|
|
527
|
+
function parseDeliveryResult(value, context) {
|
|
528
|
+
const result = requireRecord(value, "delivery result");
|
|
529
|
+
if (result.status !== "completed" && result.status !== "blocked") {
|
|
530
|
+
throw new Error("delivery status must be completed or blocked");
|
|
531
|
+
}
|
|
532
|
+
const request = context.input;
|
|
533
|
+
if (request.merge !== true && result.merged === true) {
|
|
534
|
+
throw new Error("delivery cannot merge without explicit merge: true");
|
|
535
|
+
}
|
|
536
|
+
if (result.status === "blocked")
|
|
537
|
+
return result;
|
|
538
|
+
if (typeof result.merged !== "boolean") {
|
|
539
|
+
throw new Error("completed delivery merged must be a boolean");
|
|
540
|
+
}
|
|
541
|
+
const pr = requireString(result.pr, "completed delivery pr");
|
|
542
|
+
const reportComment = requireString(result.reportComment, "completed delivery reportComment");
|
|
543
|
+
const reason = requireString(result.reason, "completed delivery reason");
|
|
544
|
+
const published = currentPublishedRepositories(context).repositories;
|
|
545
|
+
let repositories;
|
|
546
|
+
if (result.repositories === undefined) {
|
|
547
|
+
if (published.length !== 1) {
|
|
548
|
+
throw new Error("completed delivery repositories must cover every published repository");
|
|
549
|
+
}
|
|
550
|
+
const only = published[0];
|
|
551
|
+
if (only === undefined)
|
|
552
|
+
throw new Error("completed delivery has no published repository");
|
|
553
|
+
repositories = [
|
|
554
|
+
{
|
|
555
|
+
repository: path.resolve(only.repository),
|
|
556
|
+
pr,
|
|
557
|
+
merged: result.merged,
|
|
558
|
+
reportComment,
|
|
559
|
+
reason,
|
|
560
|
+
},
|
|
561
|
+
];
|
|
562
|
+
}
|
|
563
|
+
else {
|
|
564
|
+
if (!Array.isArray(result.repositories)) {
|
|
565
|
+
throw new Error("completed delivery repositories must be an array");
|
|
566
|
+
}
|
|
567
|
+
repositories = result.repositories.map(parseDeliveryRepository);
|
|
568
|
+
}
|
|
569
|
+
const actual = new Map();
|
|
570
|
+
for (const repository of repositories) {
|
|
571
|
+
if (actual.has(repository.repository)) {
|
|
572
|
+
throw new Error(`completed delivery repository is duplicated: ${repository.repository}`);
|
|
573
|
+
}
|
|
574
|
+
actual.set(repository.repository, repository);
|
|
575
|
+
}
|
|
576
|
+
const mergeExpected = request.merge === true;
|
|
577
|
+
for (const expected of published) {
|
|
578
|
+
const repository = actual.get(path.resolve(expected.repository));
|
|
579
|
+
if (repository === undefined || repository.pr !== expected.pr) {
|
|
580
|
+
throw new Error(`completed delivery does not match published repository and PR: ${expected.repository}`);
|
|
581
|
+
}
|
|
582
|
+
if (repository.merged !== mergeExpected) {
|
|
583
|
+
throw new Error(`completed delivery merge result does not match merge policy: ${expected.repository}`);
|
|
584
|
+
}
|
|
585
|
+
actual.delete(repository.repository);
|
|
586
|
+
}
|
|
587
|
+
if (actual.size > 0) {
|
|
588
|
+
throw new Error(`completed delivery contains unpublished repositories: ${[...actual.keys()].join(", ")}`);
|
|
589
|
+
}
|
|
590
|
+
const firstPublished = published[0];
|
|
591
|
+
const first = firstPublished === undefined
|
|
592
|
+
? undefined
|
|
593
|
+
: repositories.find((repository) => repository.repository === path.resolve(firstPublished.repository));
|
|
594
|
+
if (first === undefined ||
|
|
595
|
+
first.pr !== pr ||
|
|
596
|
+
first.merged !== result.merged ||
|
|
597
|
+
first.reportComment !== reportComment) {
|
|
598
|
+
throw new Error("completed delivery top-level compatibility fields must match the first result");
|
|
599
|
+
}
|
|
600
|
+
return { status: "completed", merged: result.merged, pr, reportComment, reason, repositories };
|
|
601
|
+
}
|
|
602
|
+
function parseP2Verification(value, context) {
|
|
603
|
+
const result = requireRecord(value, "P2 verification");
|
|
604
|
+
if (typeof result.passed !== "boolean") {
|
|
605
|
+
throw new Error("P2 verification passed must be a boolean");
|
|
606
|
+
}
|
|
607
|
+
if (result.pushed !== true) {
|
|
608
|
+
throw new Error("P2 verification pushed must be true");
|
|
609
|
+
}
|
|
610
|
+
const refreshed = parsePublishedRepositories(result);
|
|
611
|
+
const previous = latestOutput(context, ["publish"]);
|
|
612
|
+
const expected = new Map(previous.repositories.map((repository) => [repository.id, repository]));
|
|
613
|
+
for (const repository of refreshed.repositories) {
|
|
614
|
+
const prior = expected.get(repository.id);
|
|
615
|
+
if (prior === undefined ||
|
|
616
|
+
prior.repository !== repository.repository ||
|
|
617
|
+
prior.branch !== repository.branch ||
|
|
618
|
+
prior.baseBranch !== repository.baseBranch ||
|
|
619
|
+
prior.pr !== repository.pr ||
|
|
620
|
+
prior.dependencyFingerprint !== repository.dependencyFingerprint) {
|
|
621
|
+
throw new Error(`P2 verification repository does not match publication: ${repository.id}`);
|
|
622
|
+
}
|
|
623
|
+
expected.delete(repository.id);
|
|
624
|
+
}
|
|
625
|
+
if (expected.size > 0) {
|
|
626
|
+
throw new Error(`P2 verification is missing repository ids: ${[...expected.keys()].join(", ")}`);
|
|
627
|
+
}
|
|
628
|
+
return { ...result, repositories: refreshed.repositories };
|
|
629
|
+
}
|
|
630
|
+
function parseCiInspectionForPublished(value, context) {
|
|
631
|
+
const inspected = parseCiInspectionBatch(value);
|
|
632
|
+
const published = currentPublishedRepositories(context);
|
|
633
|
+
const expected = new Map(published.repositories.map((repository) => [repository.id, repository]));
|
|
634
|
+
for (const target of inspected.targets) {
|
|
635
|
+
const repository = expected.get(target.id);
|
|
636
|
+
if (repository === undefined ||
|
|
637
|
+
repository.repository !== target.repository ||
|
|
638
|
+
repository.headRevision !== target.headRevision ||
|
|
639
|
+
repository.pr !== target.pr) {
|
|
640
|
+
throw new Error(`CI target does not match the published repository and head: ${target.id} (${JSON.stringify({ target, repository })})`);
|
|
641
|
+
}
|
|
642
|
+
expected.delete(target.id);
|
|
643
|
+
}
|
|
644
|
+
if (expected.size > 0) {
|
|
645
|
+
throw new Error(`CI inspection is missing repository ids: ${[...expected.keys()].join(", ")}`);
|
|
646
|
+
}
|
|
647
|
+
return inspected;
|
|
648
|
+
}
|
|
649
|
+
function parseTrackedCiAssessment(value, context) {
|
|
650
|
+
const result = requireRecord(value, "tracked CI assessment");
|
|
651
|
+
const inspected = latestOutput(context, ["inspectCi"]);
|
|
652
|
+
const execution = latestOutput(context, ["trackCi"]);
|
|
653
|
+
const expectedIds = execution.batch.items.map((item) => item.id);
|
|
654
|
+
if (!Array.isArray(result.targets)) {
|
|
655
|
+
throw new Error("tracked CI assessment targets must be an array");
|
|
656
|
+
}
|
|
657
|
+
const seen = new Set();
|
|
658
|
+
const targets = result.targets.map((entry, index) => {
|
|
659
|
+
const target = requireRecord(entry, `tracked CI assessment targets[${index}]`);
|
|
660
|
+
const id = requireString(target.id, `tracked CI assessment targets[${index}].id`);
|
|
661
|
+
if (seen.has(id))
|
|
662
|
+
throw new Error(`tracked CI assessment target is duplicated: ${id}`);
|
|
663
|
+
seen.add(id);
|
|
664
|
+
if (target.route !== "green" &&
|
|
665
|
+
target.route !== "failed" &&
|
|
666
|
+
target.route !== "pending" &&
|
|
667
|
+
target.route !== "unavailable") {
|
|
668
|
+
throw new Error(`tracked CI assessment targets[${index}].route is invalid`);
|
|
669
|
+
}
|
|
670
|
+
return {
|
|
671
|
+
id,
|
|
672
|
+
route: target.route,
|
|
673
|
+
reason: requireString(target.reason, `tracked CI assessment targets[${index}].reason`),
|
|
674
|
+
};
|
|
675
|
+
});
|
|
676
|
+
const missing = expectedIds.filter((id) => !seen.has(id));
|
|
677
|
+
const unexpected = [...seen].filter((id) => !expectedIds.includes(id));
|
|
678
|
+
if (missing.length > 0 || unexpected.length > 0 || targets.length !== expectedIds.length) {
|
|
679
|
+
throw new Error(`tracked CI assessment targets must exactly cover watched ids; missing: ${missing.join(", ") || "none"}; unexpected: ${unexpected.join(", ") || "none"}`);
|
|
680
|
+
}
|
|
681
|
+
const trackedRoutes = new Map(targets.map((target) => [target.id, target.route]));
|
|
682
|
+
const routes = inspected.targets.map((target) => trackedRoutes.get(target.id) ?? target.route);
|
|
683
|
+
const route = routes.includes("failed")
|
|
684
|
+
? "failed"
|
|
685
|
+
: routes.includes("pending")
|
|
686
|
+
? "pending"
|
|
687
|
+
: routes.includes("unavailable")
|
|
688
|
+
? "unavailable"
|
|
689
|
+
: "green";
|
|
690
|
+
if (result.route !== route) {
|
|
691
|
+
throw new Error(`tracked CI assessment route must be ${route}`);
|
|
692
|
+
}
|
|
693
|
+
return {
|
|
694
|
+
...result,
|
|
695
|
+
route,
|
|
696
|
+
reason: requireString(result.reason, "tracked CI assessment reason"),
|
|
697
|
+
targets,
|
|
698
|
+
relatedFailures: requireStringArray(result.relatedFailures ?? [], "tracked CI assessment relatedFailures"),
|
|
699
|
+
unrelatedFailures: requireStringArray(result.unrelatedFailures ?? [], "tracked CI assessment unrelatedFailures"),
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
function selectReviewCommands(context) {
|
|
703
|
+
const published = latestOutput(context, ["publish"]);
|
|
704
|
+
const reviewed = reviewRounds(context).flatMap((round) => round.repositories ?? []);
|
|
705
|
+
const repositories = published.repositories.filter((repository) => !reviewed.some((entry) => entry.id === repository.id &&
|
|
706
|
+
entry.headRevision === repository.headRevision &&
|
|
707
|
+
entry.dependencyFingerprint === repository.dependencyFingerprint &&
|
|
708
|
+
entry.invocationSucceeded));
|
|
709
|
+
return {
|
|
710
|
+
route: repositories.length === 0 ? "reuse" : "run",
|
|
711
|
+
repositories,
|
|
712
|
+
commands: repositories.map(reviewerCommand),
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
function parseReviewAssessment(value, context) {
|
|
716
|
+
const review = requireRecord(value, "review assessment");
|
|
717
|
+
if (!Array.isArray(review.repositories)) {
|
|
718
|
+
throw new Error("review repositories must be an array");
|
|
719
|
+
}
|
|
720
|
+
const selected = latestOutput(context, ["selectReviewCommands"]);
|
|
721
|
+
const expected = new Map(selected.repositories.map((repository) => [repository.id, repository]));
|
|
722
|
+
const repositories = review.repositories.map((value, index) => {
|
|
723
|
+
const raw = requireRecord(value, `review repositories[${index}]`);
|
|
724
|
+
const id = requireString(raw.id, `review repositories[${index}].id`);
|
|
725
|
+
const published = expected.get(id);
|
|
726
|
+
if (published === undefined)
|
|
727
|
+
throw new Error(`review repository id was not in the batch: ${id}`);
|
|
728
|
+
expected.delete(id);
|
|
729
|
+
const parseList = (key, severity) => {
|
|
730
|
+
const list = raw[key];
|
|
731
|
+
if (!Array.isArray(list))
|
|
732
|
+
throw new Error(`review repositories[${index}].${key} must be an array`);
|
|
733
|
+
return list.map((item) => parseFinding(item, severity));
|
|
734
|
+
};
|
|
735
|
+
return {
|
|
736
|
+
id,
|
|
737
|
+
repository: published.repository,
|
|
738
|
+
baseBranch: published.baseBranch,
|
|
739
|
+
headRevision: published.headRevision,
|
|
740
|
+
...(published.dependencyFingerprint !== undefined
|
|
741
|
+
? { dependencyFingerprint: published.dependencyFingerprint }
|
|
742
|
+
: {}),
|
|
743
|
+
invocationSucceeded: raw.invocationSucceeded === true,
|
|
744
|
+
p0: parseList("p0", "P0"),
|
|
745
|
+
p1: parseList("p1", "P1"),
|
|
746
|
+
p2: parseList("p2", "P2"),
|
|
747
|
+
lower: parseList("lower", "lower"),
|
|
748
|
+
reason: requireString(raw.reason, `review repositories[${index}].reason`),
|
|
749
|
+
};
|
|
750
|
+
});
|
|
751
|
+
if (expected.size > 0) {
|
|
752
|
+
throw new Error(`review assessment is missing repository ids: ${[...expected.keys()].join(", ")}`);
|
|
753
|
+
}
|
|
754
|
+
const p0 = repositories.flatMap((entry) => entry.p0);
|
|
755
|
+
const p1 = repositories.flatMap((entry) => entry.p1);
|
|
756
|
+
const p2 = repositories.flatMap((entry) => entry.p2);
|
|
757
|
+
const lower = repositories.flatMap((entry) => entry.lower);
|
|
758
|
+
const invocationSucceeded = repositories.every((entry) => entry.invocationSucceeded);
|
|
373
759
|
const route = !invocationSucceeded
|
|
374
760
|
? "command_error"
|
|
375
761
|
: p0.length + p1.length > 0
|
|
@@ -385,6 +771,7 @@ function parseReviewAssessment(value) {
|
|
|
385
771
|
p2,
|
|
386
772
|
lower,
|
|
387
773
|
reason: requireString(review.reason, "review reason"),
|
|
774
|
+
repositories,
|
|
388
775
|
};
|
|
389
776
|
}
|
|
390
777
|
function reviewRounds(context) {
|
|
@@ -392,8 +779,37 @@ function reviewRounds(context) {
|
|
|
392
779
|
.filter((step) => step.nodeId === "assessReview" && step.outcome === "ok")
|
|
393
780
|
.map((step) => step.output);
|
|
394
781
|
}
|
|
782
|
+
function reviewRoundsForOutput(context) {
|
|
783
|
+
const rounds = reviewRounds(context);
|
|
784
|
+
const repositoryIds = new Set(rounds.flatMap((round) => (round.repositories ?? []).map((repository) => repository.id)));
|
|
785
|
+
if (repositoryIds.size > 1)
|
|
786
|
+
return rounds;
|
|
787
|
+
return rounds.map(({ repositories: _repositories, ...round }) => round);
|
|
788
|
+
}
|
|
789
|
+
function ciForOutput(context) {
|
|
790
|
+
const result = latestOutput(context, ["assessTrackedCi", "inspectCi"]);
|
|
791
|
+
const targets = result.targets;
|
|
792
|
+
if (!Array.isArray(targets) || targets.length !== 1)
|
|
793
|
+
return result;
|
|
794
|
+
const { targets: _targets, ...aggregate } = result;
|
|
795
|
+
if (result.route === "green" || result.route === "failed" || result.route === "unavailable") {
|
|
796
|
+
const target = targets[0];
|
|
797
|
+
if (target !== null && typeof target === "object" && !Array.isArray(target)) {
|
|
798
|
+
const record = target;
|
|
799
|
+
return {
|
|
800
|
+
...aggregate,
|
|
801
|
+
reason: aggregate.reason ?? record.reason,
|
|
802
|
+
relatedFailures: aggregate.relatedFailures ?? record.relatedFailures ?? [],
|
|
803
|
+
unrelatedFailures: aggregate.unrelatedFailures ?? record.unrelatedFailures ?? [],
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
return aggregate;
|
|
808
|
+
}
|
|
395
809
|
function latestBlockedReason(context) {
|
|
396
810
|
const candidates = [
|
|
811
|
+
"timeoutFallbackGuard",
|
|
812
|
+
"timeoutFallback",
|
|
397
813
|
"challengeBlockerGuard",
|
|
398
814
|
"challengeBlocker",
|
|
399
815
|
"finalizeDelivery",
|
|
@@ -404,11 +820,10 @@ function latestBlockedReason(context) {
|
|
|
404
820
|
"classifyImplementation",
|
|
405
821
|
"classifyVerification",
|
|
406
822
|
"triageReview",
|
|
407
|
-
"
|
|
823
|
+
"redesign",
|
|
408
824
|
"adoptPlan",
|
|
409
825
|
"findPlan",
|
|
410
826
|
"documentation",
|
|
411
|
-
"approval",
|
|
412
827
|
];
|
|
413
828
|
for (let index = context.state.steps.length - 1; index >= 0; index -= 1) {
|
|
414
829
|
const step = context.state.steps[index];
|
|
@@ -450,36 +865,18 @@ export const autoimplementWorkflow = defineWorkflow({
|
|
|
450
865
|
};
|
|
451
866
|
},
|
|
452
867
|
}),
|
|
453
|
-
|
|
868
|
+
redesign: includeWorkflow(planChangeWorkflow, {
|
|
454
869
|
input: (context) => {
|
|
455
870
|
const request = context.input;
|
|
456
|
-
if (request.approval === undefined) {
|
|
457
|
-
throw new Error("autoimplement approval was entered without an approval policy");
|
|
458
|
-
}
|
|
459
|
-
const plan = currentPlan(context);
|
|
460
|
-
if (plan === undefined)
|
|
461
|
-
throw new Error("autoimplement approval is missing a plan");
|
|
462
|
-
const revisions = context.state.steps.filter((step) => step.nodeId === "approval/approve").length;
|
|
463
871
|
return {
|
|
464
872
|
task: request.task,
|
|
465
|
-
plan,
|
|
466
|
-
planDigest: currentPlanDigest(context),
|
|
467
|
-
audience: request.approval.audience,
|
|
468
|
-
revision: revisions + 1,
|
|
469
|
-
};
|
|
470
|
-
},
|
|
471
|
-
}),
|
|
472
|
-
redesign: includeWorkflow({
|
|
473
|
-
workflow: "autoplan",
|
|
474
|
-
contract: autoplanWorkflow,
|
|
475
|
-
input: (context) => {
|
|
476
|
-
const request = context.input;
|
|
477
|
-
return {
|
|
478
|
-
problem: request.task,
|
|
479
873
|
...(request.scope !== undefined ? { scope: request.scope } : {}),
|
|
480
874
|
...(request.constraints !== undefined ? { constraints: request.constraints } : {}),
|
|
875
|
+
...(request.repository !== undefined ? { repository: request.repository } : {}),
|
|
876
|
+
documents: request.documents ?? request.documentation?.documents ?? [],
|
|
481
877
|
...(currentPlan(context) !== undefined ? { previousPlan: currentPlan(context) } : {}),
|
|
482
878
|
newEvidence: latestIssue(context),
|
|
879
|
+
approval: parsePlanApprovalPolicy(request.approval),
|
|
483
880
|
};
|
|
484
881
|
},
|
|
485
882
|
}),
|
|
@@ -541,41 +938,70 @@ export const autoimplementWorkflow = defineWorkflow({
|
|
|
541
938
|
}),
|
|
542
939
|
adoptPlan: compute({
|
|
543
940
|
run: ({ outputs }) => {
|
|
544
|
-
const result = includedResult(
|
|
941
|
+
const result = includedResult(planChangeWorkflow, outputs.redesign);
|
|
545
942
|
if (result.exit !== "ready")
|
|
546
943
|
throw new Error("redesign did not return a ready plan");
|
|
547
944
|
return {
|
|
548
|
-
route: result.output.changed ? "document" : "blocked",
|
|
549
945
|
plan: result.output.plan,
|
|
550
946
|
planDigest: result.output.planDigest,
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
: "Redesign returned the same plan for the same unresolved evidence.",
|
|
947
|
+
documents: result.output.documents,
|
|
948
|
+
approval: result.output.approval,
|
|
949
|
+
reason: "The changed plan was documented and passed its approval policy.",
|
|
555
950
|
};
|
|
556
951
|
},
|
|
557
952
|
}),
|
|
558
|
-
|
|
559
|
-
run:
|
|
560
|
-
route: input.approval === undefined ? "implement" : "approve",
|
|
561
|
-
}),
|
|
953
|
+
timeoutFallbackGuard: compute({
|
|
954
|
+
run: timeoutFallbackGuard,
|
|
562
955
|
}),
|
|
563
|
-
|
|
564
|
-
|
|
956
|
+
timeoutFallback: agent({
|
|
957
|
+
timeoutMs: 30 * 60_000,
|
|
958
|
+
statusDetail: "choosing a safe timeout fallback",
|
|
959
|
+
prompt: (context) => {
|
|
565
960
|
const request = context.input;
|
|
566
|
-
const
|
|
567
|
-
const
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
961
|
+
const guard = context.outputs.timeoutFallbackGuard;
|
|
962
|
+
const previousFallbacks = context.state.steps
|
|
963
|
+
.filter((step) => step.nodeId === "timeoutFallback" && step.outcome === "ok")
|
|
964
|
+
.map((step) => step.output);
|
|
965
|
+
return [
|
|
966
|
+
"A bounded Autoimplement agent step timed out. Choose the safest existing workflow stage to run next instead of ending the run blindly.",
|
|
967
|
+
"This is a read-only fallback step. Inspect state, but do not edit files, run mutating commands, commit, push, open or update a pull request, post comments, merge, deploy, or release.",
|
|
968
|
+
"Inspect the current repository worktree, branch, diff, and commits. Inspect the remote branch, pull request, review, CI, merge, and final report when they exist and affect the next route.",
|
|
969
|
+
"Do not assume that the timed-out step failed or completed. Use accepted workflow outputs and durable repository or pull-request state.",
|
|
970
|
+
"Before any forward route, confirm that its accepted output belongs to the current work attempt and that observed local and remote heads match the accepted publication. Otherwise retry, replan, or block.",
|
|
971
|
+
"Choose retry only when the timed-out stage must run again. Choose verify when accepted implementation output exists and verification is next. Choose review when accepted publication output exists. Choose ci only after comment inspection routed to CI. Choose deliver only after CI is green or classified unrelated. Choose replan when evidence invalidates the approved plan. Choose blocked only when no safe route exists.",
|
|
972
|
+
"Do not skip required implementation, verification, review, CI, authorization, or delivery checks.",
|
|
973
|
+
`Task: ${request.task}`,
|
|
974
|
+
`Approved plan: ${JSON.stringify(currentPlan(context))}`,
|
|
975
|
+
`Authorized scope: ${request.scope ?? request.repository ?? "the current repository and task"}`,
|
|
976
|
+
`Timeout: ${JSON.stringify(guard)}`,
|
|
977
|
+
`Accepted outputs: ${JSON.stringify(context.outputs)}`,
|
|
978
|
+
`Previous fallback results: ${JSON.stringify(previousFallbacks)}`,
|
|
979
|
+
`Recent workflow attempts: ${JSON.stringify(recentWorkflowAttempts(context))}`,
|
|
980
|
+
].join("\n");
|
|
575
981
|
},
|
|
982
|
+
expectedOutput: `{ "route": "retry" | "verify" | "review" | "ci" | "deliver" | "replan" | "blocked", "reason": "why this is the safest next stage", "evidence": ["state inspected before choosing the route"] }`,
|
|
983
|
+
validate: parseTimeoutFallback,
|
|
984
|
+
}),
|
|
985
|
+
routeTimeoutFallback: compute({
|
|
986
|
+
run: timeoutFallbackTarget,
|
|
987
|
+
}),
|
|
988
|
+
propagateSupportedFailure: compute({
|
|
989
|
+
run: throwLatestSupportedFailure,
|
|
990
|
+
}),
|
|
991
|
+
routeVerifyP2Result: compute({
|
|
992
|
+
run: ({ outputs }) => outputs.verifyP2,
|
|
993
|
+
}),
|
|
994
|
+
routeInspectCommentsResult: compute({
|
|
995
|
+
run: ({ outputs }) => outputs.inspectComments,
|
|
996
|
+
}),
|
|
997
|
+
routeInspectCiResult: compute({
|
|
998
|
+
run: ({ outputs }) => outputs.inspectCi,
|
|
999
|
+
}),
|
|
1000
|
+
routeFinalizeDeliveryResult: compute({
|
|
1001
|
+
run: ({ outputs }) => outputs.finalizeDelivery,
|
|
576
1002
|
}),
|
|
577
1003
|
implement: agent({
|
|
578
|
-
timeoutMs: 60 * 60_000,
|
|
1004
|
+
timeoutMs: 8 * 60 * 60_000,
|
|
579
1005
|
statusDetail: "implementing",
|
|
580
1006
|
prompt: (context) => {
|
|
581
1007
|
const request = context.input;
|
|
@@ -584,12 +1010,14 @@ export const autoimplementWorkflow = defineWorkflow({
|
|
|
584
1010
|
`Plan: ${JSON.stringify(currentPlan(context))}`,
|
|
585
1011
|
`Authorized scope: ${request.scope ?? request.repository ?? "the current repository and task"}`,
|
|
586
1012
|
`Constraints: ${JSON.stringify(request.constraints ?? [])}`,
|
|
1013
|
+
"Before changing files, inspect the current worktree, diff, commits, branch, remote state, and matching pull request. Continue existing work and do not repeat completed effects.",
|
|
587
1014
|
"Follow repository instructions and use the most elegant long-term production-ready implementation without unnecessary work.",
|
|
588
1015
|
"If implementation exposes a new design or scope problem, report it precisely instead of forcing the old plan.",
|
|
1016
|
+
"Report every changed repository as an absolute path so independent verification can be bounded safely.",
|
|
589
1017
|
"Do not merge yet.",
|
|
590
1018
|
].join("\n");
|
|
591
1019
|
},
|
|
592
|
-
expectedOutput: `{ "status": "implemented" | "issue" | "blocked", "summary": "work completed or issue", "files": ["changed file"], "issueKind": "design" | "implementation" | null, "evidence": "new evidence" }`,
|
|
1020
|
+
expectedOutput: `{ "status": "implemented" | "issue" | "blocked", "summary": "work completed or issue", "files": ["changed file"], "repositories": ["absolute repository path changed"], "issueKind": "design" | "implementation" | null, "evidence": "new evidence" }`,
|
|
593
1021
|
validate: (value) => requireRecord(value, "implementation result"),
|
|
594
1022
|
}),
|
|
595
1023
|
classifyImplementation: agent({
|
|
@@ -650,14 +1078,39 @@ export const autoimplementWorkflow = defineWorkflow({
|
|
|
650
1078
|
expectedOutput: `{ "route": "continue" | "blocked", "blockingNow": true | false, "outsideAuthority": true | false, "canProceed": true | false, "reason": "concise reason", "nextAction": "practical action or empty when blocked", "alternativesChecked": ["checked alternative"], "evidence": ["concrete evidence"] }`,
|
|
651
1079
|
validate: parseBlockerChallenge,
|
|
652
1080
|
}),
|
|
653
|
-
|
|
654
|
-
timeoutMs:
|
|
655
|
-
statusDetail: "
|
|
1081
|
+
planVerification: agent({
|
|
1082
|
+
timeoutMs: 15 * 60_000,
|
|
1083
|
+
statusDetail: "planning independent verification commands",
|
|
656
1084
|
prompt: () => [
|
|
657
|
-
"
|
|
658
|
-
"
|
|
659
|
-
"
|
|
660
|
-
"
|
|
1085
|
+
"Select the required local verification commands for the implementation.",
|
|
1086
|
+
"Return one command per independent repository working directory.",
|
|
1087
|
+
"Use exact executables and argument arrays without shell wrappers, environment overrides, stdin, Git or GitHub mutations, package publication, deployment, merge, or release commands.",
|
|
1088
|
+
"Use absolute repository paths, explicit timeouts no longer than 2700000ms, and maxOutputChars no larger than 1000000.",
|
|
1089
|
+
"List checks that cannot run locally under untested.",
|
|
1090
|
+
].join("\n"),
|
|
1091
|
+
expectedOutput: `{ "commands": [{ "id": "stable-id", "command": "npm", "args": ["run", "check"], "cwd": "/absolute/repository", "timeoutMs": 2700000, "maxOutputChars": 1000000 }], "untested": ["remaining check"] }`,
|
|
1092
|
+
validate: parseVerificationForContext,
|
|
1093
|
+
}),
|
|
1094
|
+
runVerification: action({
|
|
1095
|
+
timeoutMs: (context) => {
|
|
1096
|
+
const plan = latestOutput(context, ["planVerification"]);
|
|
1097
|
+
return commandBatchTimeoutMs(plan.commands, concurrency(context).verification);
|
|
1098
|
+
},
|
|
1099
|
+
statusDetail: "running independent verification commands",
|
|
1100
|
+
run: async (context) => {
|
|
1101
|
+
const plan = latestOutput(context, ["planVerification"]);
|
|
1102
|
+
return await runAutoimplementBatch(context, "verification", plan.commands, concurrency(context).verification);
|
|
1103
|
+
},
|
|
1104
|
+
}),
|
|
1105
|
+
verify: agent({
|
|
1106
|
+
timeoutMs: 20 * 60_000,
|
|
1107
|
+
statusDetail: "assessing verification",
|
|
1108
|
+
prompt: (context) => [
|
|
1109
|
+
"Assess the completed local verification commands.",
|
|
1110
|
+
"Set passed true only when every required command succeeded without truncated output.",
|
|
1111
|
+
"Report exact command outcomes, failures, and checks that still need remote verification.",
|
|
1112
|
+
`Command plan: ${JSON.stringify(latestOutput(context, ["planVerification"]))}`,
|
|
1113
|
+
`Command results: ${JSON.stringify(latestOutput(context, ["runVerification"]))}`,
|
|
661
1114
|
].join("\n"),
|
|
662
1115
|
expectedOutput: `{ "passed": true | false, "commands": [{ "command": "exact command", "outcome": "result" }], "failures": ["failure"], "untested": ["remaining check"] }`,
|
|
663
1116
|
validate: (value) => requireRecord(value, "verification result"),
|
|
@@ -680,6 +1133,7 @@ export const autoimplementWorkflow = defineWorkflow({
|
|
|
680
1133
|
statusDetail: "fixing",
|
|
681
1134
|
prompt: (context) => [
|
|
682
1135
|
"Fix the current implementation issue without expanding the approved design.",
|
|
1136
|
+
"Inspect the current diff and commits first. Continue any partial fix and change only work that is still missing.",
|
|
683
1137
|
`Issue: ${JSON.stringify(latestIssue(context))}`,
|
|
684
1138
|
`Current plan: ${JSON.stringify(currentPlan(context))}`,
|
|
685
1139
|
"Stop after the fix so verification can run again.",
|
|
@@ -694,79 +1148,61 @@ export const autoimplementWorkflow = defineWorkflow({
|
|
|
694
1148
|
const request = input;
|
|
695
1149
|
return [
|
|
696
1150
|
"Commit and push the verified implementation before review.",
|
|
1151
|
+
"Inspect the branch, local and remote heads, and matching pull requests first. Do not push an already-pushed head or create a second pull request for the same branch and base.",
|
|
697
1152
|
"Use the existing implementation-plan PR when one exists. Otherwise open a PR and use the pr-description skill for its body.",
|
|
698
1153
|
"Inspect the complete public diff before every push or PR mutation.",
|
|
699
|
-
|
|
1154
|
+
"Report every repository that received a pushed pull request with its absolute repository path, branch, base branch, pushed head revision, and PR URL.",
|
|
1155
|
+
"Include dependencyFingerprint only when a declared dependency result is relevant to review reuse.",
|
|
1156
|
+
`Requested base branch: ${request.baseBranch ?? "discover each repository default branch"}.`,
|
|
700
1157
|
"Do not merge yet.",
|
|
701
1158
|
].join("\n");
|
|
702
1159
|
},
|
|
703
|
-
expectedOutput: `{ "branch": "branch", "baseBranch": "base", "headRevision": "revision", "pr": "URL", "pushed": true }`,
|
|
704
|
-
validate:
|
|
1160
|
+
expectedOutput: `{ "repositories": [{ "repository": "/absolute/repository", "branch": "branch", "baseBranch": "base", "headRevision": "revision", "pr": "URL", "pushed": true, "dependencyFingerprint": "optional digest" }] }`,
|
|
1161
|
+
validate: parsePublishedRepositories,
|
|
705
1162
|
}),
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
prompt: ({ outputs, input }) => {
|
|
709
|
-
const published = outputs.publish;
|
|
710
|
-
const request = input;
|
|
711
|
-
return [
|
|
712
|
-
"Write the exact Pi Reviewer command for the pushed branch.",
|
|
713
|
-
"The executable must be pi-reviewer. Use its configured model and thinking settings.",
|
|
714
|
-
"Use the repository base branch and an absolute repository working directory.",
|
|
715
|
-
"Set timeoutMs to at most 600000.",
|
|
716
|
-
`Published branch: ${JSON.stringify(published)}`,
|
|
717
|
-
`Repository hint: ${request.repository ?? "current repository"}`,
|
|
718
|
-
].join("\n");
|
|
719
|
-
},
|
|
720
|
-
expectedOutput: `{ "command": "pi-reviewer", "args": ["--base", "main"], "cwd": "/absolute/repository", "timeoutMs": 600000 }`,
|
|
721
|
-
validate: parseReviewerCommand,
|
|
1163
|
+
selectReviewCommands: compute({
|
|
1164
|
+
run: selectReviewCommands,
|
|
722
1165
|
}),
|
|
723
|
-
runReview:
|
|
724
|
-
statusDetail: "running
|
|
725
|
-
timeoutMs:
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
repairReviewCommand: agent({
|
|
729
|
-
statusDetail: "correcting reviewer command",
|
|
730
|
-
prompt: (context) => {
|
|
731
|
-
const failed = context.results.runReview;
|
|
732
|
-
return [
|
|
733
|
-
"The Pi Reviewer invocation failed. Diagnose the exact command, arguments, base branch, working directory, and error.",
|
|
734
|
-
"Write a corrected pi-reviewer command. Do not substitute codex review or another reviewer.",
|
|
735
|
-
"If Pi Reviewer or its configuration is missing, report that through the same command shape only when another valid invocation exists; otherwise the next assessment must block.",
|
|
736
|
-
`Failed result: ${JSON.stringify(failed)}`,
|
|
737
|
-
].join("\n");
|
|
1166
|
+
runReview: action({
|
|
1167
|
+
statusDetail: "running pi-reviewer commands",
|
|
1168
|
+
timeoutMs: (context) => {
|
|
1169
|
+
const selected = latestOutput(context, ["selectReviewCommands"]);
|
|
1170
|
+
return commandBatchTimeoutMs(selected.commands, concurrency(context).reviewer);
|
|
738
1171
|
},
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
const
|
|
742
|
-
|
|
743
|
-
return {
|
|
744
|
-
route: "blocked",
|
|
745
|
-
reason: requireString(result.reason, "reviewer command blocker"),
|
|
746
|
-
};
|
|
747
|
-
}
|
|
748
|
-
if (result.route !== "retry")
|
|
749
|
-
throw new Error("reviewer command repair route must be retry or blocked");
|
|
750
|
-
return {
|
|
751
|
-
route: "retry",
|
|
752
|
-
...parseReviewerCommand(result),
|
|
753
|
-
reason: requireString(result.reason, "reviewer command repair reason"),
|
|
754
|
-
};
|
|
1172
|
+
run: async (context) => {
|
|
1173
|
+
const selected = latestOutput(context, ["selectReviewCommands"]);
|
|
1174
|
+
const batch = await runAutoimplementBatch(context, "review", selected.commands, concurrency(context).reviewer);
|
|
1175
|
+
return { route: reviewBatchNeedsRepair(batch) ? "repair" : "assess", batch };
|
|
755
1176
|
},
|
|
756
1177
|
}),
|
|
1178
|
+
repairReviewCommand: agent({
|
|
1179
|
+
statusDetail: "repairing reviewer prerequisites",
|
|
1180
|
+
prompt: (context) => [
|
|
1181
|
+
"One or more pi-reviewer commands failed, timed out, or returned truncated output.",
|
|
1182
|
+
"Diagnose and fix only local reviewer prerequisites or configuration that are in scope.",
|
|
1183
|
+
"Do not change the deterministic executable, base branch, or repository command shape, and do not substitute another reviewer.",
|
|
1184
|
+
"Choose retry only when the same commands can now produce complete reviews. Choose blocked when pi-reviewer or required configuration remains unavailable.",
|
|
1185
|
+
`Failed batch: ${JSON.stringify(context.outputs.runReview)}`,
|
|
1186
|
+
].join("\n"),
|
|
1187
|
+
expectedOutput: `{ "route": "retry" | "blocked", "reason": "diagnosis and action" }`,
|
|
1188
|
+
validate: (value) => parseRoute(value, ["retry", "blocked"], "reviewer command repair"),
|
|
1189
|
+
}),
|
|
757
1190
|
assessReview: agent({
|
|
758
1191
|
statusDetail: "assessing reviewer findings",
|
|
759
1192
|
prompt: (context) => {
|
|
760
|
-
const
|
|
1193
|
+
const selected = latestOutput(context, ["selectReviewCommands"]);
|
|
1194
|
+
const execution = latestOutput(context, ["runReview"]);
|
|
761
1195
|
return [
|
|
762
|
-
"Assess
|
|
763
|
-
"
|
|
764
|
-
"
|
|
1196
|
+
"Assess each completed pi-reviewer result separately.",
|
|
1197
|
+
"Return one repository entry for every selected command, using the exact repository id.",
|
|
1198
|
+
"Set invocationSucceeded false when a complete valid review was not produced.",
|
|
1199
|
+
"Record every finding under P0, P1, P2, or lower and mark it as design or implementation.",
|
|
765
1200
|
"Do not promote P2 findings to P1 merely to force another review round.",
|
|
766
|
-
`
|
|
1201
|
+
`Selected repositories: ${JSON.stringify(selected.repositories)}`,
|
|
1202
|
+
`Reviewer results: ${JSON.stringify(execution.batch)}`,
|
|
767
1203
|
].join("\n");
|
|
768
1204
|
},
|
|
769
|
-
expectedOutput: `{ "invocationSucceeded": true | false, "p0": [{ "kind": "design" | "implementation", "summary": "finding" }], "p1": [], "p2": [], "lower": [], "reason": "assessment" }`,
|
|
1205
|
+
expectedOutput: `{ "repositories": [{ "id": "repository-id", "invocationSucceeded": true | false, "p0": [{ "kind": "design" | "implementation", "summary": "finding" }], "p1": [], "p2": [], "lower": [], "reason": "assessment" }], "reason": "batch assessment" }`,
|
|
770
1206
|
validate: parseReviewAssessment,
|
|
771
1207
|
}),
|
|
772
1208
|
triageReview: compute({
|
|
@@ -785,7 +1221,8 @@ export const autoimplementWorkflow = defineWorkflow({
|
|
|
785
1221
|
statusDetail: "addressing P2 findings",
|
|
786
1222
|
prompt: ({ outputs }) => [
|
|
787
1223
|
"Address valid P2 findings from the last review when the improvement is proportionate and in scope.",
|
|
788
|
-
"
|
|
1224
|
+
"Inspect the current diff and commits first. Do not repeat a P2 change that is already present.",
|
|
1225
|
+
"Do not rerun pi-reviewer solely because P2 work changes files. Verification will run once, then the workflow continues.",
|
|
789
1226
|
`Review: ${JSON.stringify(outputs.assessReview)}`,
|
|
790
1227
|
].join("\n"),
|
|
791
1228
|
expectedOutput: `{ "addressed": ["P2 change"], "skipped": [{ "finding": "finding", "reason": "why" }] }`,
|
|
@@ -796,19 +1233,22 @@ export const autoimplementWorkflow = defineWorkflow({
|
|
|
796
1233
|
statusDetail: "verifying P2 changes",
|
|
797
1234
|
prompt: () => [
|
|
798
1235
|
"Run focused verification for the P2 changes and push the verified result.",
|
|
799
|
-
"
|
|
1236
|
+
"Inspect the local and remote heads first. Do not push again when the verified head is already remote.",
|
|
1237
|
+
"Do not run pi-reviewer again because the previous round had no P0 or P1 findings.",
|
|
1238
|
+
"Re-observe every published PR after the push and return its current repository, branch, base branch, head revision, PR URL, pushed status, and unchanged dependency fingerprint.",
|
|
800
1239
|
"Report exact commands and outcomes.",
|
|
801
1240
|
].join("\n"),
|
|
802
|
-
expectedOutput: `{ "passed": true | false, "commands": [{ "command": "command", "outcome": "result" }], "pushed": true }`,
|
|
803
|
-
validate:
|
|
1241
|
+
expectedOutput: `{ "passed": true | false, "commands": [{ "command": "command", "outcome": "result" }], "pushed": true, "repositories": [{ "repository": "/absolute/repository", "branch": "branch", "baseBranch": "base", "headRevision": "current pushed revision", "pr": "URL", "pushed": true, "dependencyFingerprint": "optional fingerprint" }] }`,
|
|
1242
|
+
validate: parseP2Verification,
|
|
804
1243
|
}),
|
|
805
1244
|
inspectComments: agent({
|
|
806
1245
|
timeoutMs: 20 * 60_000,
|
|
807
1246
|
statusDetail: "checking PR comments",
|
|
808
|
-
prompt: () => [
|
|
809
|
-
"Inspect current inline review comments and PR issue comments.",
|
|
810
|
-
"Reply to and resolve every comment. Ignore stale or irrelevant comments only after explaining why.",
|
|
811
|
-
"Choose redesign for a valid design issue, fix for a local code issue, ci when no actionable comment remains, or blocked for an external blocker.",
|
|
1247
|
+
prompt: (context) => [
|
|
1248
|
+
"Inspect current inline review comments and PR issue comments for every published pull request.",
|
|
1249
|
+
"Handle pull requests one at a time. Reply to and resolve every comment. Ignore stale or irrelevant comments only after explaining why.",
|
|
1250
|
+
"Choose redesign for a valid design issue, fix for a local code issue, ci when no actionable comment remains on any PR, or blocked for an external blocker.",
|
|
1251
|
+
`Published repositories: ${JSON.stringify(currentPublishedRepositories(context))}`,
|
|
812
1252
|
].join("\n"),
|
|
813
1253
|
expectedOutput: `{ "route": "redesign" | "fix" | "ci" | "blocked", "summary": "comment status", "evidence": ["comment or response"] }`,
|
|
814
1254
|
validate: (value) => parseRoute(value, ["redesign", "fix", "ci", "blocked"], "PR comment assessment"),
|
|
@@ -816,60 +1256,61 @@ export const autoimplementWorkflow = defineWorkflow({
|
|
|
816
1256
|
inspectCi: agent({
|
|
817
1257
|
timeoutMs: 10 * 60_000,
|
|
818
1258
|
statusDetail: "checking CI",
|
|
819
|
-
prompt: () => [
|
|
820
|
-
"Inspect
|
|
821
|
-
"
|
|
822
|
-
"
|
|
823
|
-
"
|
|
1259
|
+
prompt: (context) => [
|
|
1260
|
+
"Inspect every published pull request once without waiting for completion.",
|
|
1261
|
+
"Return one target per repository and current PR head.",
|
|
1262
|
+
"Choose green, failed, pending, or unavailable for each target.",
|
|
1263
|
+
"When pending, provide an exact supported gh pr checks --watch or gh run watch command with the repository id, absolute repository cwd, timeoutMs at most 300000, and maxOutputChars at most 1000000. The workflow binds it to the target PR before execution.",
|
|
1264
|
+
"Separate failures caused by this change from unrelated failures. Do not invent an ETA.",
|
|
1265
|
+
`Published repositories: ${JSON.stringify(currentPublishedRepositories(context))}`,
|
|
824
1266
|
].join("\n"),
|
|
825
|
-
expectedOutput: `{ "route": "green" | "failed" | "pending" | "unavailable", "reason": "status", "relatedFailures": ["failure"], "unrelatedFailures": ["failure"], "trackingCommand": { "command": "gh", "args": ["pr", "checks", "--watch"], "cwd": "/absolute/repository", "timeoutMs": 300000
|
|
826
|
-
validate:
|
|
827
|
-
const result = parseRoute(value, ["green", "failed", "pending", "unavailable"], "CI inspection");
|
|
828
|
-
if (result.route === "pending")
|
|
829
|
-
result.trackingCommand = parseCiCommand(result.trackingCommand);
|
|
830
|
-
return result;
|
|
831
|
-
},
|
|
1267
|
+
expectedOutput: `{ "targets": [{ "repository": "/absolute/repository", "headRevision": "revision", "pr": "URL", "route": "green" | "failed" | "pending" | "unavailable", "reason": "status", "relatedFailures": ["failure"], "unrelatedFailures": ["failure"], "trackingCommand": { "id": "repository-id", "command": "gh", "args": ["pr", "checks", "PR URL", "--watch"], "cwd": "/absolute/repository", "timeoutMs": 300000, "maxOutputChars": 1000000 } }] }`,
|
|
1268
|
+
validate: parseCiInspectionForPublished,
|
|
832
1269
|
}),
|
|
833
|
-
trackCi:
|
|
834
|
-
statusDetail: "tracking CI
|
|
835
|
-
timeoutMs:
|
|
836
|
-
|
|
1270
|
+
trackCi: action({
|
|
1271
|
+
statusDetail: "tracking pending CI commands",
|
|
1272
|
+
timeoutMs: (context) => {
|
|
1273
|
+
const inspected = latestOutput(context, ["inspectCi"]);
|
|
1274
|
+
const commands = inspected.targets.flatMap((target) => target.trackingCommand === undefined ? [] : [target.trackingCommand]);
|
|
1275
|
+
return commandBatchTimeoutMs(commands, concurrency(context).ciWatch);
|
|
1276
|
+
},
|
|
1277
|
+
run: async (context) => {
|
|
1278
|
+
const inspected = latestOutput(context, ["inspectCi"]);
|
|
1279
|
+
const commands = inspected.targets.flatMap((target) => target.trackingCommand === undefined ? [] : [target.trackingCommand]);
|
|
1280
|
+
const batch = await runAutoimplementBatch(context, "ciWatch", commands, concurrency(context).ciWatch);
|
|
1281
|
+
const needsRepair = batch.items.some((item) => (item.outcome === "failed" && item.exitCode === null) ||
|
|
1282
|
+
item.stdoutTruncated ||
|
|
1283
|
+
item.stderrTruncated);
|
|
1284
|
+
return { route: needsRepair ? "repair" : "assess", batch };
|
|
1285
|
+
},
|
|
837
1286
|
}),
|
|
838
1287
|
repairCiCommand: agent({
|
|
839
|
-
statusDetail: "
|
|
1288
|
+
statusDetail: "repairing CI watch prerequisites",
|
|
840
1289
|
prompt: (context) => [
|
|
841
|
-
"
|
|
842
|
-
"
|
|
843
|
-
"
|
|
844
|
-
|
|
1290
|
+
"One or more supported CI watch commands failed or returned truncated output.",
|
|
1291
|
+
"Diagnose and fix only local gh prerequisites or authentication that are already authorized.",
|
|
1292
|
+
"Do not change the PR identity or substitute another command form.",
|
|
1293
|
+
"Choose retry only when the same validated commands can now provide useful status. Choose blocked otherwise.",
|
|
1294
|
+
`Failure: ${JSON.stringify(context.outputs.trackCi)}`,
|
|
845
1295
|
].join("\n"),
|
|
846
|
-
expectedOutput: `{ "route": "retry" | "blocked", "
|
|
847
|
-
validate: (value) =>
|
|
848
|
-
const result = requireRecord(value, "CI command repair");
|
|
849
|
-
if (result.route === "blocked") {
|
|
850
|
-
return { route: "blocked", reason: requireString(result.reason, "CI command blocker") };
|
|
851
|
-
}
|
|
852
|
-
if (result.route !== "retry")
|
|
853
|
-
throw new Error("CI command repair route must be retry or blocked");
|
|
854
|
-
return {
|
|
855
|
-
route: "retry",
|
|
856
|
-
...parseCiCommand(result),
|
|
857
|
-
reason: requireString(result.reason, "CI command repair reason"),
|
|
858
|
-
};
|
|
859
|
-
},
|
|
1296
|
+
expectedOutput: `{ "route": "retry" | "blocked", "reason": "diagnosis" }`,
|
|
1297
|
+
validate: (value) => parseRoute(value, ["retry", "blocked"], "CI command repair"),
|
|
860
1298
|
}),
|
|
861
1299
|
assessTrackedCi: agent({
|
|
862
1300
|
statusDetail: "assessing tracked CI",
|
|
863
1301
|
prompt: (context) => {
|
|
864
|
-
const
|
|
1302
|
+
const inspected = latestOutput(context, ["inspectCi"]);
|
|
1303
|
+
const execution = latestOutput(context, ["trackCi"]);
|
|
865
1304
|
return [
|
|
866
|
-
"Assess
|
|
867
|
-
"
|
|
868
|
-
|
|
1305
|
+
"Assess every CI watch result without starting another wait.",
|
|
1306
|
+
"Return one target result for every watched PR and an aggregate route of green, failed, pending, or unavailable.",
|
|
1307
|
+
"A timed-out watch normally remains pending. Separate related from unrelated failures. Do not invent an ETA.",
|
|
1308
|
+
`Initial inspection: ${JSON.stringify(inspected)}`,
|
|
1309
|
+
`Tracking results: ${JSON.stringify(execution.batch)}`,
|
|
869
1310
|
].join("\n");
|
|
870
1311
|
},
|
|
871
|
-
expectedOutput: `{ "route": "green" | "failed" | "pending" | "unavailable", "reason": "status", "relatedFailures": ["failure"], "unrelatedFailures": ["failure"] }`,
|
|
872
|
-
validate:
|
|
1312
|
+
expectedOutput: `{ "route": "green" | "failed" | "pending" | "unavailable", "reason": "status", "targets": [{ "id": "repository-id", "route": "green" | "failed" | "pending" | "unavailable", "reason": "status" }], "relatedFailures": ["failure"], "unrelatedFailures": ["failure"] }`,
|
|
1313
|
+
validate: parseTrackedCiAssessment,
|
|
873
1314
|
}),
|
|
874
1315
|
opportunisticTest: agent({
|
|
875
1316
|
timeoutMs: 30 * 60_000,
|
|
@@ -895,30 +1336,23 @@ export const autoimplementWorkflow = defineWorkflow({
|
|
|
895
1336
|
}),
|
|
896
1337
|
finalizeDelivery: agent({
|
|
897
1338
|
timeoutMs: 30 * 60_000,
|
|
898
|
-
statusDetail: "finalizing
|
|
899
|
-
prompt: (
|
|
900
|
-
const request = input;
|
|
1339
|
+
statusDetail: "finalizing PRs",
|
|
1340
|
+
prompt: (context) => {
|
|
1341
|
+
const request = context.input;
|
|
901
1342
|
return [
|
|
902
1343
|
request.merge === false
|
|
903
|
-
? "Leave
|
|
904
|
-
: "
|
|
905
|
-
"
|
|
906
|
-
"
|
|
1344
|
+
? "Leave every verified PR ready without merging because input disabled merge."
|
|
1345
|
+
: "Handle verified PRs one at a time and merge each unless repository policy or explicit user instructions prohibit it.",
|
|
1346
|
+
"Before each mutation, inspect the current PR head, merge state, and existing final report. Do not merge an already merged expected head or post a duplicate report.",
|
|
1347
|
+
"Use each repository's required merge method.",
|
|
1348
|
+
"Post a final report with the implementation summary and exact validation commands on every PR only when that report is missing.",
|
|
1349
|
+
"Keep the existing top-level merged, pr, reportComment, and reason fields. For several PRs, use the first PR for the top-level compatibility fields and include every result under repositories.",
|
|
907
1350
|
"Return blocked instead of claiming completion when a required merge or report action fails.",
|
|
1351
|
+
`Published repositories: ${JSON.stringify(currentPublishedRepositories(context))}`,
|
|
908
1352
|
].join("\n");
|
|
909
1353
|
},
|
|
910
|
-
expectedOutput: `{ "status": "completed" | "blocked", "merged": true | false, "pr": "URL", "reportComment": "URL or summary", "reason": "result" }`,
|
|
911
|
-
validate:
|
|
912
|
-
const result = requireRecord(value, "delivery result");
|
|
913
|
-
if (result.status !== "completed" && result.status !== "blocked") {
|
|
914
|
-
throw new Error("delivery status must be completed or blocked");
|
|
915
|
-
}
|
|
916
|
-
const request = context.input;
|
|
917
|
-
if (request.merge !== true && result.merged === true) {
|
|
918
|
-
throw new Error("delivery cannot merge without explicit merge: true");
|
|
919
|
-
}
|
|
920
|
-
return result;
|
|
921
|
-
},
|
|
1354
|
+
expectedOutput: `{ "status": "completed" | "blocked", "merged": true | false, "pr": "first PR URL", "reportComment": "first report URL or summary", "reason": "aggregate result", "repositories": [{ "repository": "/absolute/repository", "pr": "URL", "merged": true | false, "reportComment": "URL or summary", "reason": "result" }] }`,
|
|
1355
|
+
validate: parseDeliveryResult,
|
|
922
1356
|
}),
|
|
923
1357
|
blocked: compute({
|
|
924
1358
|
run: (context) => {
|
|
@@ -939,11 +1373,11 @@ export const autoimplementWorkflow = defineWorkflow({
|
|
|
939
1373
|
status: "completed",
|
|
940
1374
|
task: request.task,
|
|
941
1375
|
plan: currentPlan(context),
|
|
942
|
-
implementation: context
|
|
943
|
-
verification: context
|
|
944
|
-
reviewRounds:
|
|
945
|
-
ci: context
|
|
946
|
-
delivery: context
|
|
1376
|
+
implementation: latestOutput(context, ["implement"]),
|
|
1377
|
+
verification: latestOutput(context, ["verifyP2", "verify"]),
|
|
1378
|
+
reviewRounds: reviewRoundsForOutput(context),
|
|
1379
|
+
ci: ciForOutput(context),
|
|
1380
|
+
delivery: latestOutput(context, ["finalizeDelivery"]),
|
|
947
1381
|
};
|
|
948
1382
|
},
|
|
949
1383
|
}),
|
|
@@ -953,7 +1387,7 @@ export const autoimplementWorkflow = defineWorkflow({
|
|
|
953
1387
|
from: "prepare",
|
|
954
1388
|
switch: {
|
|
955
1389
|
on: "$.route",
|
|
956
|
-
cases: { find: "findPlan", document: "documentation", ready: "
|
|
1390
|
+
cases: { find: "findPlan", document: "documentation", ready: "implement" },
|
|
957
1391
|
},
|
|
958
1392
|
},
|
|
959
1393
|
{
|
|
@@ -964,35 +1398,59 @@ export const autoimplementWorkflow = defineWorkflow({
|
|
|
964
1398
|
from: "routeFoundPlan",
|
|
965
1399
|
switch: {
|
|
966
1400
|
on: "$.route",
|
|
967
|
-
cases: { ready: "
|
|
1401
|
+
cases: { ready: "implement", document: "documentation", blocked: "blocked" },
|
|
968
1402
|
},
|
|
969
1403
|
},
|
|
970
1404
|
{ from: "redesign.ready", to: "adoptPlan" },
|
|
971
1405
|
{ from: "redesign.blocked", to: "blocked" },
|
|
1406
|
+
{ from: "adoptPlan", to: "implement" },
|
|
1407
|
+
{ from: "documentation.ready", to: "implement" },
|
|
1408
|
+
{ from: "documentation.blocked", to: "blocked" },
|
|
972
1409
|
{
|
|
973
|
-
from: "
|
|
974
|
-
switch: { on: "$.route", cases: {
|
|
1410
|
+
from: "timeoutFallbackGuard",
|
|
1411
|
+
switch: { on: "$.route", cases: { recover: "timeoutFallback", blocked: "blocked" } },
|
|
975
1412
|
},
|
|
976
|
-
{ from: "
|
|
977
|
-
{ from: "
|
|
1413
|
+
{ from: "timeoutFallback", to: "routeTimeoutFallback" },
|
|
1414
|
+
{ from: "propagateSupportedFailure", to: "blocked" },
|
|
978
1415
|
{
|
|
979
|
-
from: "
|
|
980
|
-
switch: {
|
|
1416
|
+
from: "routeTimeoutFallback",
|
|
1417
|
+
switch: {
|
|
1418
|
+
on: "$.route",
|
|
1419
|
+
cases: {
|
|
1420
|
+
implement: "implement",
|
|
1421
|
+
planVerification: "planVerification",
|
|
1422
|
+
verify: "verify",
|
|
1423
|
+
fix: "fix",
|
|
1424
|
+
publish: "publish",
|
|
1425
|
+
addressP2: "addressP2",
|
|
1426
|
+
verifyP2: "verifyP2",
|
|
1427
|
+
inspectComments: "inspectComments",
|
|
1428
|
+
inspectCi: "inspectCi",
|
|
1429
|
+
opportunisticTest: "opportunisticTest",
|
|
1430
|
+
finalizeDelivery: "finalizeDelivery",
|
|
1431
|
+
selectReviewCommands: "selectReviewCommands",
|
|
1432
|
+
redesign: "redesign",
|
|
1433
|
+
blocked: "blocked",
|
|
1434
|
+
},
|
|
1435
|
+
},
|
|
981
1436
|
},
|
|
982
|
-
{ from: "approval.continue", to: "implement" },
|
|
983
|
-
{ from: "approval.stop", to: "blocked" },
|
|
984
|
-
{ from: "approval.replan", to: "replanGuard" },
|
|
985
1437
|
{
|
|
986
|
-
from: "
|
|
987
|
-
switch: {
|
|
1438
|
+
from: "implement",
|
|
1439
|
+
switch: {
|
|
1440
|
+
on: "$result.outcome",
|
|
1441
|
+
cases: {
|
|
1442
|
+
ok: "classifyImplementation",
|
|
1443
|
+
timed_out: "timeoutFallbackGuard",
|
|
1444
|
+
failed: "propagateSupportedFailure",
|
|
1445
|
+
},
|
|
1446
|
+
},
|
|
988
1447
|
},
|
|
989
|
-
{ from: "implement", to: "classifyImplementation" },
|
|
990
1448
|
{
|
|
991
1449
|
from: "classifyImplementation",
|
|
992
1450
|
switch: {
|
|
993
1451
|
on: "$.route",
|
|
994
1452
|
cases: {
|
|
995
|
-
verify: "
|
|
1453
|
+
verify: "planVerification",
|
|
996
1454
|
redesign: "redesign",
|
|
997
1455
|
fix: "fix",
|
|
998
1456
|
blocked: "challengeBlockerGuard",
|
|
@@ -1007,7 +1465,29 @@ export const autoimplementWorkflow = defineWorkflow({
|
|
|
1007
1465
|
from: "challengeBlocker",
|
|
1008
1466
|
switch: { on: "$.route", cases: { continue: "redesign", blocked: "blocked" } },
|
|
1009
1467
|
},
|
|
1010
|
-
{
|
|
1468
|
+
{
|
|
1469
|
+
from: "planVerification",
|
|
1470
|
+
switch: {
|
|
1471
|
+
on: "$result.outcome",
|
|
1472
|
+
cases: {
|
|
1473
|
+
ok: "runVerification",
|
|
1474
|
+
timed_out: "timeoutFallbackGuard",
|
|
1475
|
+
failed: "propagateSupportedFailure",
|
|
1476
|
+
},
|
|
1477
|
+
},
|
|
1478
|
+
},
|
|
1479
|
+
{ from: "runVerification", to: "verify" },
|
|
1480
|
+
{
|
|
1481
|
+
from: "verify",
|
|
1482
|
+
switch: {
|
|
1483
|
+
on: "$result.outcome",
|
|
1484
|
+
cases: {
|
|
1485
|
+
ok: "classifyVerification",
|
|
1486
|
+
timed_out: "timeoutFallbackGuard",
|
|
1487
|
+
failed: "propagateSupportedFailure",
|
|
1488
|
+
},
|
|
1489
|
+
},
|
|
1490
|
+
},
|
|
1011
1491
|
{
|
|
1012
1492
|
from: "classifyVerification",
|
|
1013
1493
|
switch: {
|
|
@@ -1020,20 +1500,36 @@ export const autoimplementWorkflow = defineWorkflow({
|
|
|
1020
1500
|
},
|
|
1021
1501
|
},
|
|
1022
1502
|
},
|
|
1023
|
-
{ from: "fix", to: "verify" },
|
|
1024
|
-
{ from: "publish", to: "authorReviewCommand" },
|
|
1025
|
-
{ from: "authorReviewCommand", to: "runReview" },
|
|
1026
1503
|
{
|
|
1027
|
-
from: "
|
|
1504
|
+
from: "fix",
|
|
1028
1505
|
switch: {
|
|
1029
1506
|
on: "$result.outcome",
|
|
1030
1507
|
cases: {
|
|
1031
|
-
ok: "
|
|
1032
|
-
|
|
1033
|
-
|
|
1508
|
+
ok: "planVerification",
|
|
1509
|
+
timed_out: "timeoutFallbackGuard",
|
|
1510
|
+
failed: "propagateSupportedFailure",
|
|
1034
1511
|
},
|
|
1035
1512
|
},
|
|
1036
1513
|
},
|
|
1514
|
+
{
|
|
1515
|
+
from: "publish",
|
|
1516
|
+
switch: {
|
|
1517
|
+
on: "$result.outcome",
|
|
1518
|
+
cases: {
|
|
1519
|
+
ok: "selectReviewCommands",
|
|
1520
|
+
timed_out: "timeoutFallbackGuard",
|
|
1521
|
+
failed: "propagateSupportedFailure",
|
|
1522
|
+
},
|
|
1523
|
+
},
|
|
1524
|
+
},
|
|
1525
|
+
{
|
|
1526
|
+
from: "selectReviewCommands",
|
|
1527
|
+
switch: { on: "$.route", cases: { run: "runReview", reuse: "inspectComments" } },
|
|
1528
|
+
},
|
|
1529
|
+
{
|
|
1530
|
+
from: "runReview",
|
|
1531
|
+
switch: { on: "$.route", cases: { assess: "assessReview", repair: "repairReviewCommand" } },
|
|
1532
|
+
},
|
|
1037
1533
|
{
|
|
1038
1534
|
from: "repairReviewCommand",
|
|
1039
1535
|
switch: {
|
|
@@ -1057,13 +1553,45 @@ export const autoimplementWorkflow = defineWorkflow({
|
|
|
1057
1553
|
from: "triageReview",
|
|
1058
1554
|
switch: { on: "$.route", cases: { redesign: "redesign", fix: "fix" } },
|
|
1059
1555
|
},
|
|
1060
|
-
{
|
|
1556
|
+
{
|
|
1557
|
+
from: "addressP2",
|
|
1558
|
+
switch: {
|
|
1559
|
+
on: "$result.outcome",
|
|
1560
|
+
cases: {
|
|
1561
|
+
ok: "verifyP2",
|
|
1562
|
+
timed_out: "timeoutFallbackGuard",
|
|
1563
|
+
failed: "propagateSupportedFailure",
|
|
1564
|
+
},
|
|
1565
|
+
},
|
|
1566
|
+
},
|
|
1061
1567
|
{
|
|
1062
1568
|
from: "verifyP2",
|
|
1569
|
+
switch: {
|
|
1570
|
+
on: "$result.outcome",
|
|
1571
|
+
cases: {
|
|
1572
|
+
ok: "routeVerifyP2Result",
|
|
1573
|
+
timed_out: "timeoutFallbackGuard",
|
|
1574
|
+
failed: "propagateSupportedFailure",
|
|
1575
|
+
},
|
|
1576
|
+
},
|
|
1577
|
+
},
|
|
1578
|
+
{
|
|
1579
|
+
from: "routeVerifyP2Result",
|
|
1063
1580
|
switch: { on: "$.passed", cases: { true: "inspectComments", false: "fix" } },
|
|
1064
1581
|
},
|
|
1065
1582
|
{
|
|
1066
1583
|
from: "inspectComments",
|
|
1584
|
+
switch: {
|
|
1585
|
+
on: "$result.outcome",
|
|
1586
|
+
cases: {
|
|
1587
|
+
ok: "routeInspectCommentsResult",
|
|
1588
|
+
timed_out: "timeoutFallbackGuard",
|
|
1589
|
+
failed: "propagateSupportedFailure",
|
|
1590
|
+
},
|
|
1591
|
+
},
|
|
1592
|
+
},
|
|
1593
|
+
{
|
|
1594
|
+
from: "routeInspectCommentsResult",
|
|
1067
1595
|
switch: {
|
|
1068
1596
|
on: "$.route",
|
|
1069
1597
|
cases: {
|
|
@@ -1076,6 +1604,17 @@ export const autoimplementWorkflow = defineWorkflow({
|
|
|
1076
1604
|
},
|
|
1077
1605
|
{
|
|
1078
1606
|
from: "inspectCi",
|
|
1607
|
+
switch: {
|
|
1608
|
+
on: "$result.outcome",
|
|
1609
|
+
cases: {
|
|
1610
|
+
ok: "routeInspectCiResult",
|
|
1611
|
+
timed_out: "timeoutFallbackGuard",
|
|
1612
|
+
failed: "propagateSupportedFailure",
|
|
1613
|
+
},
|
|
1614
|
+
},
|
|
1615
|
+
},
|
|
1616
|
+
{
|
|
1617
|
+
from: "routeInspectCiResult",
|
|
1079
1618
|
switch: {
|
|
1080
1619
|
on: "$.route",
|
|
1081
1620
|
cases: {
|
|
@@ -1088,10 +1627,7 @@ export const autoimplementWorkflow = defineWorkflow({
|
|
|
1088
1627
|
},
|
|
1089
1628
|
{
|
|
1090
1629
|
from: "trackCi",
|
|
1091
|
-
switch: {
|
|
1092
|
-
on: "$result.outcome",
|
|
1093
|
-
cases: { ok: "assessTrackedCi", failed: "repairCiCommand", timed_out: "opportunisticTest" },
|
|
1094
|
-
},
|
|
1630
|
+
switch: { on: "$.route", cases: { assess: "assessTrackedCi", repair: "repairCiCommand" } },
|
|
1095
1631
|
},
|
|
1096
1632
|
{
|
|
1097
1633
|
from: "repairCiCommand",
|
|
@@ -1112,7 +1648,17 @@ export const autoimplementWorkflow = defineWorkflow({
|
|
|
1112
1648
|
},
|
|
1113
1649
|
},
|
|
1114
1650
|
},
|
|
1115
|
-
{
|
|
1651
|
+
{
|
|
1652
|
+
from: "opportunisticTest",
|
|
1653
|
+
switch: {
|
|
1654
|
+
on: "$result.outcome",
|
|
1655
|
+
cases: {
|
|
1656
|
+
ok: "inspectCi",
|
|
1657
|
+
timed_out: "timeoutFallbackGuard",
|
|
1658
|
+
failed: "propagateSupportedFailure",
|
|
1659
|
+
},
|
|
1660
|
+
},
|
|
1661
|
+
},
|
|
1116
1662
|
{
|
|
1117
1663
|
from: "classifyCi",
|
|
1118
1664
|
switch: {
|
|
@@ -1127,6 +1673,17 @@ export const autoimplementWorkflow = defineWorkflow({
|
|
|
1127
1673
|
},
|
|
1128
1674
|
{
|
|
1129
1675
|
from: "finalizeDelivery",
|
|
1676
|
+
switch: {
|
|
1677
|
+
on: "$result.outcome",
|
|
1678
|
+
cases: {
|
|
1679
|
+
ok: "routeFinalizeDeliveryResult",
|
|
1680
|
+
timed_out: "timeoutFallbackGuard",
|
|
1681
|
+
failed: "propagateSupportedFailure",
|
|
1682
|
+
},
|
|
1683
|
+
},
|
|
1684
|
+
},
|
|
1685
|
+
{
|
|
1686
|
+
from: "routeFinalizeDeliveryResult",
|
|
1130
1687
|
switch: {
|
|
1131
1688
|
on: "$.status",
|
|
1132
1689
|
cases: { completed: "finalize", blocked: "challengeBlockerGuard" },
|