fix: prioritize unfinished branch follower sources

This commit is contained in:
Codex
2026-07-04 09:24:22 +00:00
parent 71c92a468d
commit 6d1e7b6c94
3 changed files with 53 additions and 24 deletions
+2 -24
View File
@@ -34,6 +34,7 @@ import { prioritizedTaskRunItems } from "./cicd-taskruns";
import { runBranchFollowerTaskRunDrillDown } from "./cicd-taskrun-drilldown";
import { runBranchFollowerJobDrillDown, runBranchFollowerRuntimeDrillDown } from "./cicd-job-runtime-drilldown";
import { runBranchFollowerGate } from "./cicd-gates";
import { buildCicdHelp } from "./cicd-help";
import { attachReconcileTimeline, compactReconcileTimeline, finishReconcileStep, finishReconcileTimeline, startReconcileStep, startReconcileTimeline } from "./cicd-reconcile-timeline";
import { orderFollowersForControllerCloseout, shouldYieldAfterAutomaticTrigger } from "./cicd-reconcile-scheduler";
import type { AdapterSummary, BranchFollowerAction, BranchFollowerDebugStep, BranchFollowerGate, BranchFollowerPhase, BranchFollowerRegistry, ControllerSpec, FollowerSpec, FollowerState, K8sFollowerStateRead, K8sStateRead, NativeCloseoutWaitResult, NativeK8sJobResult, NativeStatusSpec, NativeWorkloadSpec, OutputMode, ParsedOptions, StageTiming, TriggerResult } from "./cicd-types";
@@ -55,30 +56,7 @@ const SPEC_REF = "PJ2026-01060703";
const SPEC_VERSION = "draft-2026-07-03-p0-branch-follower";
export function cicdHelp(): unknown {
return {
command: "cicd branch-follower plan|apply|status|run-once|debug-step|cleanup-state|events|logs|taskrun|job|runtime|gate",
output: "text by default; use --json, --raw, or -o json|yaml for machine output",
usage: [
"bun scripts/cli.ts cicd branch-follower plan",
"bun scripts/cli.ts cicd branch-follower apply --confirm --wait",
"bun scripts/cli.ts cicd branch-follower status",
"bun scripts/cli.ts cicd branch-follower status --live",
"bun scripts/cli.ts cicd branch-follower run-once --all --dry-run",
"bun scripts/cli.ts cicd branch-follower run-once --follower hwlab-jd01-v03 --confirm --wait",
"bun scripts/cli.ts cicd branch-follower debug-step --follower web-probe-sentinel-master --step controller-source",
"bun scripts/cli.ts cicd branch-follower debug-step --follower web-probe-sentinel-master --step state-read",
"bun scripts/cli.ts cicd branch-follower debug-step --follower web-probe-sentinel-master --step state-write --confirm",
"bun scripts/cli.ts cicd branch-follower cleanup-state --follower web-probe-sentinel-master --confirm",
"bun scripts/cli.ts cicd branch-follower events --follower agentrun-jd01-v02",
"bun scripts/cli.ts cicd branch-follower logs --follower web-probe-sentinel-master",
"bun scripts/cli.ts cicd branch-follower taskrun --follower hwlab-jd01-v03 --taskrun runtime-ready --logs-tail 120 --json",
"bun scripts/cli.ts cicd branch-follower job --follower agentrun-jd01-v02 --source-commit <sha> --job image-build --json",
"bun scripts/cli.ts cicd branch-follower gate --follower agentrun-jd01-v02 --gate reuse-plan --source-commit <sha> --json",
],
config: DEFAULT_CONFIG_PATH,
spec: `${SPEC_REF} ${SPEC_VERSION}`,
description: "Deploy and inspect the YAML-first Kubernetes branch follower that follows HWLAB v0.3, AgentRun v0.2, and the selected web-probe sentinel master lane without using host worktrees as source authority.",
};
return buildCicdHelp(DEFAULT_CONFIG_PATH, `${SPEC_REF} ${SPEC_VERSION}`);
}
export async function runCicdCommand(_config: UniDeskConfig | null, args: string[]): Promise<RenderedCliResult> {
+29
View File
@@ -0,0 +1,29 @@
// SPEC: PJ2026-01060703 CI/CD branch follower help text.
// Responsibility: bounded CLI help payloads for the branch-follower entrypoint.
export function buildCicdHelp(configPath: string, spec: string): unknown {
return {
command: "cicd branch-follower plan|apply|status|run-once|debug-step|cleanup-state|events|logs|taskrun|job|runtime|gate",
output: "text by default; use --json, --raw, or -o json|yaml for machine output",
usage: [
"bun scripts/cli.ts cicd branch-follower plan",
"bun scripts/cli.ts cicd branch-follower apply --confirm --wait",
"bun scripts/cli.ts cicd branch-follower status",
"bun scripts/cli.ts cicd branch-follower status --live",
"bun scripts/cli.ts cicd branch-follower run-once --all --dry-run",
"bun scripts/cli.ts cicd branch-follower run-once --follower hwlab-jd01-v03 --confirm --wait",
"bun scripts/cli.ts cicd branch-follower debug-step --follower web-probe-sentinel-master --step controller-source",
"bun scripts/cli.ts cicd branch-follower debug-step --follower web-probe-sentinel-master --step state-read",
"bun scripts/cli.ts cicd branch-follower debug-step --follower web-probe-sentinel-master --step state-write --confirm",
"bun scripts/cli.ts cicd branch-follower cleanup-state --follower web-probe-sentinel-master --confirm",
"bun scripts/cli.ts cicd branch-follower events --follower agentrun-jd01-v02",
"bun scripts/cli.ts cicd branch-follower logs --follower web-probe-sentinel-master",
"bun scripts/cli.ts cicd branch-follower taskrun --follower hwlab-jd01-v03 --taskrun runtime-ready --logs-tail 120 --json",
"bun scripts/cli.ts cicd branch-follower job --follower agentrun-jd01-v02 --source-commit <sha> --job image-build --json",
"bun scripts/cli.ts cicd branch-follower gate --follower agentrun-jd01-v02 --gate reuse-plan --source-commit <sha> --json",
],
config: configPath,
spec,
description: "Deploy and inspect the YAML-first Kubernetes branch follower that follows HWLAB v0.3, AgentRun v0.2, and the selected web-probe sentinel master lane without using host worktrees as source authority.",
};
}
+22
View File
@@ -23,8 +23,30 @@ export function shouldYieldAfterAutomaticTrigger(options: ParsedOptions, state:
function followerPriority(state: Record<string, unknown> | undefined): number {
if (state === undefined) return 2;
const phase = typeof state.phase === "string" ? state.phase : null;
if (hasStoredSourceTargetMismatch(state)) return 0;
if (hasUnfinishedObservedSource(state)) return 0;
if (phase !== null && CLOSEOUT_PHASES.has(phase)) return 0;
if (typeof state.inFlightJob === "string" && state.inFlightJob.trim() !== "") return 0;
if (phase === "PendingTrigger" || phase === "Superseded") return 1;
return 2;
}
function hasStoredSourceTargetMismatch(state: Record<string, unknown>): boolean {
const observedSha = nestedString(state, "source", "observedSha");
const targetSha = nestedString(state, "target", "targetSha");
return observedSha !== null && targetSha !== null && observedSha !== targetSha;
}
function hasUnfinishedObservedSource(state: Record<string, unknown>): boolean {
const observedSha = nestedString(state, "source", "observedSha");
const lastSucceededSha = typeof state.lastSucceededSha === "string" ? state.lastSucceededSha : null;
if (observedSha === null) return false;
return lastSucceededSha === null || lastSucceededSha !== observedSha;
}
function nestedString(source: Record<string, unknown>, parentKey: string, childKey: string): string | null {
const parent = source[parentKey];
if (parent === null || typeof parent !== "object" || Array.isArray(parent)) return null;
const value = (parent as Record<string, unknown>)[childKey];
return typeof value === "string" && value.trim() !== "" ? value : null;
}