- pr-nodes: forward post-increment entity to deps.respond (responseRounds off-by-one); branch-group entity fallback now keys on task.branchContext.groupId, not task.id - core: hoist shared autoMergeGateReason into @fusion/core pr-entity.ts; CLI + dashboard import the single definition (R13) - cli: fn pr create also writes the unified PR entity (ensure → open), keeping legacy prInfo; resolveBranchHeadOid uses argv-safe execFileAsync (injection fix) - changeset: fn-pr-commands bumped minor → major (breaking CLI removal per AGENTS.md) - tests: bin.test.ts daemon fixture off reserved port 4040; lazy-loaded-views title 19 → 20 views Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
84 lines
3.9 KiB
TypeScript
84 lines
3.9 KiB
TypeScript
// Core-owned predicates for the unified PR entity (PR-lifecycle-as-workflow-nodes, U1).
|
|
//
|
|
// These live in @fusion/core so the dashboard route, the workflow node handlers,
|
|
// and the reconcile all consult one definition and cannot drift — the same
|
|
// discipline that put isBranchGroupMemberLanded in branch-group-completion.ts.
|
|
|
|
import type { PrEntity } from "./types.js";
|
|
|
|
/** Non-terminal lifecycle states — the entity is "live". */
|
|
export function isPrEntityActive(entity: Pick<PrEntity, "state">): boolean {
|
|
return entity.state !== "merged" && entity.state !== "closed" && entity.state !== "failed";
|
|
}
|
|
|
|
/**
|
|
* Whether a piece of work is "PR-backed" for the purpose of keeping it out of
|
|
* the legacy merge pipeline.
|
|
*
|
|
* Merge-target scoping is load-bearing: a shared-group MEMBER landing onto its
|
|
* group branch (mergeTargetSource === "branch-group-integration") is NOT
|
|
* PR-backed even when its group has an open PR entity — only the group's
|
|
* promotion/default-branch merge is. Treating member-integration as PR-backed
|
|
* would deadlock the group (members could never land, so it could never complete
|
|
* and the PR could never advance). Mirrors how isBranchGroupMemberLanded keys on
|
|
* the merge target rather than mere group membership.
|
|
*
|
|
* An unverified entity (imported legacy state GitHub has not corroborated) still
|
|
* counts as PR-backed (R19 hard gate): a possibly-fictional PR must not let the
|
|
* task fall back into the legacy merger and risk a double-merge. The reconcile
|
|
* clears the fiction and releases the task on its first pass.
|
|
*/
|
|
export function isPrBacked(
|
|
entity: Pick<PrEntity, "state"> | null | undefined,
|
|
opts?: { mergeTargetSource?: string },
|
|
): boolean {
|
|
if (!entity || !isPrEntityActive(entity)) return false;
|
|
// Member → group-branch integration is never PR-backed.
|
|
if (opts?.mergeTargetSource === "branch-group-integration") return false;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Whether the entity may participate in auto-merge evaluation or response-run
|
|
* dispatch. Unverified entities are frozen until the reconcile corroborates them
|
|
* (R19) — they are neither auto-merged nor responded to.
|
|
*/
|
|
export function isPrEntityActionable(entity: Pick<PrEntity, "state" | "unverified">): boolean {
|
|
return isPrEntityActive(entity) && !entity.unverified;
|
|
}
|
|
|
|
/**
|
|
* Auto-merge green condition (R10): opted in, approved, all checks concluded
|
|
* successful (pending is NOT green), mergeable known-clean (UNKNOWN blocks), and
|
|
* verified. Re-evaluated by the auto-merge gate after every push.
|
|
*/
|
|
export function isPrEntityAutoMergeReady(
|
|
entity: Pick<PrEntity, "state" | "unverified" | "autoMerge" | "reviewDecision" | "checksRollup" | "mergeable">,
|
|
): boolean {
|
|
if (!isPrEntityActionable(entity)) return false;
|
|
if (!entity.autoMerge) return false;
|
|
if (entity.reviewDecision !== "APPROVED") return false;
|
|
if (entity.checksRollup !== "success") return false;
|
|
// mergeable must be the known-clean state; "unknown"/conflict/undefined all block.
|
|
if (entity.mergeable !== "clean") return false;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* The live auto-merge gate reason shown next to the toggle (R11). Mirrors the
|
|
* auto-merge-ready predicate ordering so every surface (the dashboard route and
|
|
* the `fn pr` CLI) reports the same status and never disagrees with what the gate
|
|
* will actually do. Shared in @fusion/core (R13) so the two surfaces cannot drift.
|
|
*/
|
|
export function autoMergeGateReason(
|
|
entity: Pick<PrEntity, "state" | "unverified" | "autoMerge" | "reviewDecision" | "checksRollup" | "mergeable">,
|
|
): string {
|
|
if (!entity.autoMerge) return "Auto-merge off";
|
|
if (entity.mergeable === "conflicting") return "Blocked: conflict";
|
|
if (entity.reviewDecision !== "APPROVED") return "Waiting for approval";
|
|
if (entity.checksRollup !== "success") return "Waiting for checks";
|
|
if (entity.mergeable !== "clean") return "Waiting for checks";
|
|
if (isPrEntityAutoMergeReady(entity)) return "Ready to merge";
|
|
return "Waiting for checks";
|
|
}
|