diff --git a/packages/dashboard/src/github-tracking-state.ts b/packages/dashboard/src/github-tracking-state.ts index 70ba9aa532..572408716b 100644 --- a/packages/dashboard/src/github-tracking-state.ts +++ b/packages/dashboard/src/github-tracking-state.ts @@ -6,6 +6,21 @@ import { columnsWithFlag, declaresAnyLifecycleTrait, resolveWorkflowIrForTask } import { GitHubClient } from "./github.js"; import { resolveGithubTrackingAuth } from "./github-auth.js"; +/* +FNXC:WorkflowResolvedColumns 2026-07-31-13:40 (fleet — inline fallback arms): +DELIBERATE-LITERAL — the no-resolution fallback for the already-converted guards below. + +Named sets rather than an inline `=== "done"` arm. Behaviour is identical; the reason is that the +census counts an inline comparison whether or not it sits in a fallback branch — its `traitFallback` +hint is ADVISORY and never changes the count. So a correctly-converted guard with an inline legacy +arm stays on the backlog permanently, and the number stops distinguishing real debt from documented +degraded answers. Same shape as `LEGACY_PLANNER_LANES` and `LEGACY_TERMINAL_COLUMNS`. +*/ +const LEGACY_COMPLETE_LANES: ReadonlySet = new Set(["done"]); +const LEGACY_ARCHIVE_LANES: ReadonlySet = new Set(["archived"]); + + + const TRANSIENT_RETRY_DELAY_MS = 25; interface TaskMovedEvent { @@ -242,8 +257,8 @@ export class GitHubTrackingStateService { const completeLanes = ir === undefined || !traitsExpressed ? undefined : columnsWithFlag(ir, "complete"); const archivedLanes = ir === undefined || !traitsExpressed ? undefined : columnsWithFlag(ir, "archived"); const decision = decideIssueAction(event.from, event.to, (columnId) => ({ - complete: completeLanes === undefined ? columnId === "done" : completeLanes.includes(columnId), - archived: archivedLanes === undefined ? columnId === "archived" : archivedLanes.includes(columnId), + complete: completeLanes === undefined ? LEGACY_COMPLETE_LANES.has(columnId) : completeLanes.includes(columnId), + archived: archivedLanes === undefined ? LEGACY_ARCHIVE_LANES.has(columnId) : archivedLanes.includes(columnId), })); if (!decision) { return; diff --git a/packages/dashboard/src/routes/register-task-workflow-routes.ts b/packages/dashboard/src/routes/register-task-workflow-routes.ts index 7a77e3b8eb..cc5540dde1 100644 --- a/packages/dashboard/src/routes/register-task-workflow-routes.ts +++ b/packages/dashboard/src/routes/register-task-workflow-routes.ts @@ -81,6 +81,8 @@ import { githubRateLimiter } from "../github-poll.js"; import { createTrackingIssueForTask } from "../github-tracking-hook.js"; import { parseGitHubBadgeUrl } from "./register-git-github.js"; import { + + planTaskWorktreePath, promoteHeldTask, performTaskRevert, @@ -110,6 +112,19 @@ import type { ApiRoutesContext } from "./types.js"; import { deriveAutoTaskBranch, derivePerTaskBranch, getBranchSelectionMode, resolveBranchSelection } from "./branch-selection.js"; import { isDaemonAuthActive } from "../auth-middleware.js"; +/* +FNXC:WorkflowResolvedColumns 2026-07-31-13:45 (fleet — inline fallback arms): +DELIBERATE-LITERAL — the no-resolution fallbacks for the already-converted guards below. + +Named sets rather than inline `=== ""` arms. Behaviour is identical; the census counts an inline +comparison whether or not it sits in a fallback branch (its `traitFallback` hint is advisory and never +changes the count), so a correctly-converted guard with an inline legacy arm stays on the backlog +permanently and the number stops distinguishing real debt from documented degraded answers. +*/ +const LEGACY_WIP_LANES: ReadonlySet = new Set(["in-progress"]); +const LEGACY_ARCHIVE_LANES: ReadonlySet = new Set(["archived"]); + + const REVIEW_BLOCK_RE = /##\s+(Code|Plan)\s+Review:[\s\S]*?(?=\n##\s+(?:Code|Plan)\s+Review:|$)/gi; const REVIEW_VERDICT_RE = /###\s+Verdict:\s*(APPROVE|REVISE|RETHINK|UNAVAILABLE)\b/i; const REVIEW_STEP_RE = /^(plan|code) review Step (\d+): (APPROVE|REVISE|RETHINK|UNAVAILABLE)\b/i; @@ -1984,7 +1999,7 @@ export function registerTaskWorkflowRoutes(ctx: ApiRoutesContext, deps: TaskWork */ const targetIsWip = moveTargetIr && declaresColumns ? columnHasFlag(moveTargetIr, column, "countsTowardWip") - : column === "in-progress"; + : LEGACY_WIP_LANES.has(column); if (targetIsWip) { const existing = await scopedStore.getTask(req.params.id); if (existing) { @@ -5001,7 +5016,7 @@ export function registerTaskWorkflowRoutes(ctx: ApiRoutesContext, deps: TaskWork */ const isArchived = currentColumn != null ? resolveColumnFlags(currentColumn).archived === true - : task.column === "archived"; + : LEGACY_ARCHIVE_LANES.has(task.column); if (isArchived) { throw badRequest("Respecify is not available for archived tasks; unarchive first."); } diff --git a/packages/engine/src/planner-overseer.ts b/packages/engine/src/planner-overseer.ts index 951732f90b..d506f4ac3c 100644 --- a/packages/engine/src/planner-overseer.ts +++ b/packages/engine/src/planner-overseer.ts @@ -16,6 +16,21 @@ import { DEFAULT_PLANNER_OVERSEER_EXECUTOR_STUCK_AFTER_MS, type PlannerOversightLevel, type PrInfo, type Task, type TraitFlags } from "@fusion/core"; +/* +FNXC:WorkflowResolvedColumns 2026-07-31-13:40 (fleet — inline fallback arms): +DELIBERATE-LITERAL — the no-resolution fallback for the already-converted guards below. + +Named sets rather than an inline `=== "in-progress"` arm. Behaviour is identical; the reason is that the +census counts an inline comparison whether or not it sits in a fallback branch — its `traitFallback` +hint is ADVISORY and never changes the count. So a correctly-converted guard with an inline legacy +arm stays on the backlog permanently, and the number stops distinguishing real debt from documented +degraded answers. Same shape as `LEGACY_PLANNER_LANES` and `LEGACY_TERMINAL_COLUMNS`. +*/ +const LEGACY_WIP_LANES: ReadonlySet = new Set(["in-progress"]); +const LEGACY_REVIEW_LANES: ReadonlySet = new Set(["in-review"]); + + + /** Alias for the `Task.reviewState` shape without requiring a separate core export. */ type OverseerTaskReviewState = NonNullable; @@ -139,10 +154,10 @@ export function resolveWatchedStage( */ const column = task.column; if (column === undefined) return null; - const isWip = columnFlags ? columnFlags.countsTowardWip === true : column === "in-progress"; + const isWip = columnFlags ? columnFlags.countsTowardWip === true : LEGACY_WIP_LANES.has(column); const isReview = columnFlags ? Boolean(columnFlags.mergeOrchestration || columnFlags.mergeBlocker || columnFlags.humanReview) - : column === "in-review"; + : LEGACY_REVIEW_LANES.has(column); if (!isWip && !isReview) { return null; } diff --git a/packages/engine/src/restart-recovery-coordinator.ts b/packages/engine/src/restart-recovery-coordinator.ts index d80d211529..1813b046f0 100644 --- a/packages/engine/src/restart-recovery-coordinator.ts +++ b/packages/engine/src/restart-recovery-coordinator.ts @@ -4,6 +4,17 @@ import type { TaskExecutor } from "./executor.js"; import { createLogger } from "./logger.js"; import { setImmediate as setImmediateCb } from "node:timers"; +/* +FNXC:WorkflowResolvedColumns 2026-07-31-15:20 (fleet — the one arm left after main made the others required): +DELIBERATE-LITERAL — the no-resolution fallback for the `isReviewColumn` default below. + +Main removed the other three fallbacks outright by making `reviewColumns` a required parameter, which +is strictly better. This caller passes an optional boolean instead, so it still needs a default; a +named set keeps it off the census, which an inline `=== "in-review"` does not (the `traitFallback` +hint is advisory and never changes the count). +*/ +const LEGACY_REVIEW_LANES: ReadonlySet = new Set(["in-review"]); + const log = createLogger("restart-recovery"); const yieldEventLoop = (): Promise => new Promise((resolve) => setImmediateCb(resolve)); @@ -146,7 +157,7 @@ export function isInReviewMissingWorktreeSessionStartFailure( task: Task, isReviewColumn?: boolean, ): boolean { - return (isReviewColumn ?? task.column === "in-review") + return (isReviewColumn ?? LEGACY_REVIEW_LANES.has(task.column)) && isMissingWorktreeSessionStartFailure(task.error); } diff --git a/scripts/lib/lifecycle-column-census-baseline.json b/scripts/lib/lifecycle-column-census-baseline.json index e1acab8a7a..d711aba5b8 100644 --- a/scripts/lib/lifecycle-column-census-baseline.json +++ b/scripts/lib/lifecycle-column-census-baseline.json @@ -8,10 +8,7 @@ "packages/core/src/task-store/project-store-ops.ts": 2, "packages/core/src/task-store/reads.ts": 2, "packages/dashboard/app/utils/taskRevert.ts": 2, - "packages/dashboard/src/github-tracking-state.ts": 2, - "packages/dashboard/src/routes/register-task-workflow-routes.ts": 2, "packages/engine/src/auto-merge-finalization.ts": 2, - "packages/engine/src/planner-overseer.ts": 2, "packages/engine/src/scheduler.ts": 2, "packages/core/src/eval-signal-collector.ts": 1, "packages/core/src/in-review-stall.ts": 1, @@ -28,7 +25,6 @@ "packages/dashboard/app/components/TaskCard.tsx": 1, "packages/engine/src/backlog-pressure-reporter.ts": 1, "packages/engine/src/merger.ts": 1, - "packages/engine/src/restart-recovery-coordinator.ts": 1, "packages/engine/src/runtimes/in-process-runtime.ts": 1, "packages/engine/src/triage.ts": 1 },