feat(pr): top-level bounded rework + auto-merge gate + legacy-bypass pin (U6)

Generalizes the foreach-only bounded-rework mechanism to the top-level
graph walk so the PR review loop (await-review -> pr-respond -> rework
back) is a legal bounded cycle: a kind:rework back-edge to a stacked node
returns a ReworkSignal the loop-head re-runs up to maxReworkCycles, then
routes outcome:rework-exhausted. Non-rework cycles still throw 'Cycle
detected' (safety preserved); foreach rework unchanged (shared core
constants). Adds createAutoMergeGateHandler (live entity re-fetch +
isPrEntityAutoMergeReady -> auto-on/auto-off). Pins R14: graph-executed
PR tasks merge through pr-merge, never the legacy queue. 122 graph tests
green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-06-05 20:32:24 -07:00
parent 31d4b5335b
commit 96f801c019
9 changed files with 662 additions and 22 deletions

View File

@@ -2,7 +2,7 @@ import { WorkflowIrError, getStepParser } from "@fusion/core";
import type { TaskDetail, TaskStep, WorkflowIrNode } from "@fusion/core";
import type { WorkflowNodeHandler, WorkflowNodeResult } from "./workflow-graph-executor.js";
import { createPrNodeHandlers, type PrNodeDeps } from "./pr-nodes.js";
import { createPrNodeHandlers, createAutoMergeGateHandler, type PrNodeDeps } from "./pr-nodes.js";
export type WorkflowSeamName = "planning" | "execute" | "review" | "merge" | "schedule" | "step-execute";
@@ -554,10 +554,20 @@ export function createDefaultNodeHandlers(
"pr-respond": async () => ({ outcome: "failure", value: "pr-nodes-unwired" }),
"pr-merge": async () => ({ outcome: "failure", value: "pr-nodes-unwired" }),
};
// Auto-merge gate (U6): a `gate` node carrying `config.gate === "auto-merge"`
// routes on live PR-entity state (outcome:auto-on/auto-off) instead of the
// generic context/executable gate. Wired only when PR deps are present; absent
// them it falls back to the generic gate (fail-closed, no silent auto-merge).
const genericGate = createGateHandler(runCustomNode);
const autoMergeGate = deps?.prNodes ? createAutoMergeGateHandler(deps.prNodes) : undefined;
const gate: WorkflowNodeHandler = autoMergeGate
? (node, ctx) =>
node.config?.gate === "auto-merge" ? autoMergeGate(node, ctx) : genericGate(node, ctx)
: genericGate;
return {
prompt: promptLike,
script: promptLike,
gate: createGateHandler(runCustomNode),
gate,
"step-review": createStepReviewHandler(seams),
"parse-steps": parseSteps,
code: createCodeNodeHandler(deps?.runCode),