import type { GhIssue } from "./gh.js"; import type { IntentGateMode, MergePolicy, PickupScope } from "./config.js"; import { type IntakeApprovalMode, type PickupFilter } from "./issue-order.js"; import type { PRState } from "./pr-state.js"; /** Tokens `reconcile[].action` may carry. A VALUE so a new state cannot ship * without an explicit mapping (same discipline as `PR_STATES`). */ export declare const PLAN_ACTIONS: readonly ["rework", "park", "escalate_once", "resolve_conflict", "fix_ci", "address_review", "address_comments", "automerge", "nudge", "sync_no_push"]; export type PlanAction = (typeof PLAN_ACTIONS)[number]; /** Default state → action. Flags overlay this; they do not invent a ladder. */ export declare const PLAN_ACTION_BY_STATE: Record; export interface PlanFlags { /** Inbox row owes one `issue escalate --for-pr` and nothing else. */ escalateOnce?: boolean; /** Untrusted head — listed, never checked out. */ humanOnly?: boolean; /** Automerge blocker that can never clear by waiting (#305). */ unsatisfiable?: boolean; /** Behind-base is the ONLY automerge blocker (#530). */ behindBaseOnly?: boolean; } /** Overlay order is the pin set: humanOnly › unsatisfiable › escalateOnce › * behindBaseOnly › state map. humanOnly wins because checkout is the expensive * direction; unsatisfiable beats automerge/sync so the loop escalates once. */ export declare function planAction(state: PRState, flags?: PlanFlags): PlanAction; /** True when the only automerge blocker is freshness — the `sync_no_push` pin. */ export declare function isBehindBaseOnly(blockers: string[]): boolean; /** Collapse a `mergeDecision` into the two plan flags it can set. */ export declare function mergePlanFlags(decision: { unsatisfiable?: boolean; blockers: string[]; }): Pick; export type LoopCap = number | "all"; /** Cap the orchestrator already documents (`SHIPFLOW_LOOP_CAP`, else 5). Not a * `config set` knob — a per-pass token — so it stays off the SETTINGS table. */ export declare function resolveLoopCap(): LoopCap; export declare function capSlots(cap: LoopCap): number; export interface LoopPlanPolicies { mergePolicy: MergePolicy; requireCi: boolean; cap: LoopCap; wipLimit: number; pickupScope: PickupScope; intentGate: IntentGateMode; } export interface LoopPlanReconcile { number: number; state: PRState; action: PlanAction; escalateOnce?: boolean; unsatisfiable?: boolean; } export interface LoopPlanAdmit { number: number; title: string; priority: string | null; } export interface LoopPlanDeferred { number: number; reason: string; } export interface LoopPlan { policies: LoopPlanPolicies; reconcile: LoopPlanReconcile[]; admit: LoopPlanAdmit[]; deferred: LoopPlanDeferred[]; } export interface PlanPrInput { number: number; state: PRState; escalateOnce?: boolean; humanOnly?: boolean; unsatisfiable?: boolean; behindBaseOnly?: boolean; } /** Highest `priority:*` token on the issue, same rank table as pickup order. */ export declare function issuePriorityLabel(issue: { labels: { name: string; }[]; }): string | null; /** Why this visible issue is not in `admit[]`. `null` = `isActionableForPickup`. * Reasons are derived from the SAME checks that function applies — not a * second filter — so a new skip in pickup order must land here too. */ export declare function deferReason(issue: GhIssue, filter: PickupFilter): string | null; export declare function planReconcile(prs: PlanPrInput[]): LoopPlanReconcile[]; export declare function planAdmission(opts: { issues: GhIssue[]; claimed: Set; cap: LoopCap; wipActionable: number; wipLimit: number; assignee?: string; intakeMode?: IntakeApprovalMode; label?: string; }): { admit: LoopPlanAdmit[]; deferred: LoopPlanDeferred[]; }; export declare function buildLoopPlan(input: { policies: LoopPlanPolicies; prs: PlanPrInput[]; issues: GhIssue[]; claimed: Set; wipActionable: number; intakeMode?: IntakeApprovalMode; assignee?: string; label?: string; }): LoopPlan; //# sourceMappingURL=loop-plan.d.ts.map