fleet: scheduler.ts 28 → 27 + make the column-role predicates reachable from the other 80% of the backlog (#2690)
## Census **722 → 721**; `packages/engine/src/scheduler.ts` **28 → 27**. Exactly the one site converted. Baseline re-recorded in the same commit — `--strict` flagged the stale allowance itself, and `in-progress` went 138 → 137. ## The unblocker (commit 1) The role helpers live in `packages/dashboard/app/utils/columnRoles.ts`, a dashboard-**app** module. Measured against the census: | Location | Guards | Share | Helpers importable? | |---|---:|---:|---| | `packages/engine/**` | 316 | 43% | no | | `packages/dashboard/app/**` | 150 | 20% | **yes** | | `packages/core/**` | 148 | 20% | no | | `packages/dashboard/src/**` | 78 | 10% | no | | `packages/cli/**` | 24 | 3% | no | **Only 20% of the backlog can call them at all.** #2685 widens the helper *set* correctly; that is coverage, not location. `packages/core/src/column-roles.ts` is the same flags-first / legacy-id-fallback predicate placed where the other 80% can reach it — core already exports `resolveColumnFlags`, so no new resolution machinery comes with it. **Semantics are mirrored from #2685, not invented**, so the two sets cannot answer the same question differently: `complete` EXCLUDES `archived`; `wip` keys on `countsTowardWip` (the same flag capacity arithmetic uses); `review` accepts `mergeBlocker` OR `humanReview`. One addition — `isTerminalColumnRole` for the `!== "done" && !== "archived"` union, the most repeated shape in the backlog. 10 tests cover both modes of all 8 predicates, including the **degraded no-flags fallback** — the half with no coverage when these lived only in the dashboard app — plus the two cases that prove the predicate does something rather than nothing: a renamed column carrying the right trait answers yes, and a legacy id carrying the WRONG trait answers no. ## A trap every fleet worker converting engine code will hit A new core export must be added to **both** `index.ts` and `index.gate.ts`. The `engine-core` gate project resolves `@fusion/core` to a bundle built from `index.gate.ts` (`scripts/build-engine-core-gate-bundle.mjs`). An export present only in `index.ts` is `undefined` at runtime under the gate: 13 `scheduler-workflow-cutover` tests failed with `isWipColumnRole is not a function`, in a file that does not mock `@fusion/core` at all. The symptom points at the consumer, the cause is the barrel. I nearly mis-attributed this. Baseline first: `scheduler-workflow-cutover` is **42 passed on clean main**, so the 13 were mine — not pre-existing. That measurement is the only reason I looked at the barrel instead of "fixing" the tests. ## The conversion (commit 2) `scheduler.ts:1690`'s `isWipColumnTask` was a hand-rolled copy of `isWipColumnRole` — it stored only `countsTowardWip` as a boolean and re-implemented flags-first-then-legacy-id inline. It now stores the resolved flags object and lets the shared predicate decide. Behaviour is identical in all four states: column present with the flag true or false (flags win), column absent from a resolved IR, and IR resolution failed (both defer to the legacy id). | Check | Result | |---|---| | `scheduler-workflow-cutover` | **42 passed** before and after | | 21 scheduler/capacity/hold-release files | **372 passed** | | `pnpm test:gate` | **726 passed** | | `pnpm lint` | clean | ## Flagged and skipped, not guessed **`scheduler.ts:1736` — a latent legacy-vocabulary defect, not a conversion.** `if (task.column !== "in-progress") continue;` gates the file-scope-lease loop on the literal, ~40 lines below capacity arithmetic that is trait-aware. On a renamed WIP column the loop silently does nothing while capacity counts the same cards correctly. Converting it *changes behaviour* on renamed boards (from wrong to right), which the fleet rules put out of scope — so it is flagged here for whoever owns that fix. It is the same class as U10's six legacy-vocabulary defects. **`hold-release.ts:343`** — already marked `DELIBERATE-LITERAL`. It is the legacy half of FN-5719's dual-accept pair; converting it would make both halves compute the same answer, deleting the compatibility signal *and* its divergence detector while looking like a cleanup. Untouched. **`task-merge.ts:254`** — the documented fallback for callers that have not proven lane identity; trait-aware callers pass `skipColumnIdentityCheck`. Untouched. **The other 14 `scheduler.ts` sites** have no flags in scope (e.g. `isLegacyDependencySatisfied(dep: Task | undefined)`, `shouldHoldActiveFileScopeLease(...)` — task-only pure functions). Threading an IR in changes signatures and call graphs: behaviour change, out of scope. This is why the cluster is 28 → 27 and not 28 → 0, and the reachability measurement behind it is #2687. No changeset: `@fusion/*` are private and no `@runfusion/fusion` behaviour changes.
This commit is contained in:
109
packages/core/src/__tests__/column-roles.test.ts
Normal file
109
packages/core/src/__tests__/column-roles.test.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
isArchivedColumnRole,
|
||||
isCompleteColumnRole,
|
||||
isHoldColumnRole,
|
||||
isIntakeColumnRole,
|
||||
isPreImplementationColumnRole,
|
||||
isReviewColumnRole,
|
||||
isTerminalColumnRole,
|
||||
isWipColumnRole,
|
||||
type ColumnRoleTraitFlags,
|
||||
} from "../column-roles.js";
|
||||
|
||||
/*
|
||||
FNXC:WorkflowResolvedColumns 2026-07-30-15:20:
|
||||
Covers BOTH modes of every predicate — the flags path and the degraded no-flags fallback. The
|
||||
fallback is the half that had no test when these lived only in the dashboard app, and it is the half
|
||||
that matters: it runs for any caller holding a task row without a resolved IR, and for a card resting
|
||||
in a column its workflow no longer declares.
|
||||
|
||||
The renamed-column cases are the point of the whole conversion: a column carrying the right trait
|
||||
under a NON-legacy id must answer yes, and a column carrying the legacy id but the WRONG trait must
|
||||
answer no. A predicate that only ever saw default boards would pass while doing nothing.
|
||||
*/
|
||||
|
||||
const F = (f: ColumnRoleTraitFlags): ColumnRoleTraitFlags => f;
|
||||
|
||||
describe("column-role predicates — flags decide when present", () => {
|
||||
it("reads each role off its own trait, under a renamed column id", () => {
|
||||
expect(isIntakeColumnRole(F({ intake: true }), "Inbox")).toBe(true);
|
||||
expect(isHoldColumnRole(F({ hold: true }), "Parking")).toBe(true);
|
||||
expect(isWipColumnRole(F({ countsTowardWip: true }), "Building")).toBe(true);
|
||||
expect(isReviewColumnRole(F({ mergeBlocker: true }), "Checking")).toBe(true);
|
||||
expect(isCompleteColumnRole(F({ complete: true }), "Shipped")).toBe(true);
|
||||
expect(isArchivedColumnRole(F({ archived: true }), "Attic")).toBe(true);
|
||||
});
|
||||
|
||||
it("a legacy id with the WRONG traits answers no — the id must not win over resolved flags", () => {
|
||||
// The conversion's whole claim: once flags resolve, the id is not consulted.
|
||||
expect(isCompleteColumnRole(F({ archived: true }), "done")).toBe(false);
|
||||
expect(isWipColumnRole(F({ hold: true }), "in-progress")).toBe(false);
|
||||
expect(isIntakeColumnRole(F({ hold: true }), "triage")).toBe(false);
|
||||
expect(isReviewColumnRole(F({ complete: true }), "in-review")).toBe(false);
|
||||
});
|
||||
|
||||
it("pre-implementation is the union of intake and hold", () => {
|
||||
expect(isPreImplementationColumnRole(F({ intake: true }), "x")).toBe(true);
|
||||
expect(isPreImplementationColumnRole(F({ hold: true }), "x")).toBe(true);
|
||||
expect(isPreImplementationColumnRole(F({ countsTowardWip: true }), "x")).toBe(false);
|
||||
});
|
||||
|
||||
it("review accepts either separable trait", () => {
|
||||
expect(isReviewColumnRole(F({ humanReview: true }), "x")).toBe(true);
|
||||
expect(isReviewColumnRole(F({ mergeBlocker: true }), "x")).toBe(true);
|
||||
expect(isReviewColumnRole(F({}), "x")).toBe(false);
|
||||
});
|
||||
|
||||
/*
|
||||
The distinction #2685 established, asserted rather than only documented: an archived card is
|
||||
finished but NOT completed, so a throughput surface counting `complete` must not see it.
|
||||
*/
|
||||
it("complete EXCLUDES archived, while terminal includes both", () => {
|
||||
expect(isCompleteColumnRole(F({ archived: true }), "x")).toBe(false);
|
||||
expect(isArchivedColumnRole(F({ complete: true }), "x")).toBe(false);
|
||||
|
||||
expect(isTerminalColumnRole(F({ complete: true }), "x")).toBe(true);
|
||||
expect(isTerminalColumnRole(F({ archived: true }), "x")).toBe(true);
|
||||
expect(isTerminalColumnRole(F({ countsTowardWip: true }), "x")).toBe(false);
|
||||
});
|
||||
|
||||
it("an empty resolved-flags object is authoritative — it does NOT fall back to the id", () => {
|
||||
// `{}` means "traits resolved, this column has none", which is different from "unresolved".
|
||||
expect(isCompleteColumnRole(F({}), "done")).toBe(false);
|
||||
expect(isWipColumnRole(F({}), "in-progress")).toBe(false);
|
||||
expect(isTerminalColumnRole(F({}), "archived")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("column-role predicates — degraded fallback when flags are absent", () => {
|
||||
it("falls back to the legacy id for each role", () => {
|
||||
expect(isIntakeColumnRole(undefined, "triage")).toBe(true);
|
||||
expect(isHoldColumnRole(undefined, "todo")).toBe(true);
|
||||
expect(isWipColumnRole(undefined, "in-progress")).toBe(true);
|
||||
expect(isReviewColumnRole(undefined, "in-review")).toBe(true);
|
||||
expect(isCompleteColumnRole(undefined, "done")).toBe(true);
|
||||
expect(isArchivedColumnRole(undefined, "archived")).toBe(true);
|
||||
});
|
||||
|
||||
it("pre-implementation falls back to BOTH planning ids, merged and pre-merge", () => {
|
||||
// `todo` is the post-U11 merged Planning column; `triage` its pre-merge predecessor, retained
|
||||
// for projects upgraded mid-flight that still hold cards there.
|
||||
expect(isPreImplementationColumnRole(undefined, "todo")).toBe(true);
|
||||
expect(isPreImplementationColumnRole(undefined, "triage")).toBe(true);
|
||||
expect(isPreImplementationColumnRole(undefined, "in-progress")).toBe(false);
|
||||
});
|
||||
|
||||
it("terminal falls back to done OR archived", () => {
|
||||
expect(isTerminalColumnRole(undefined, "done")).toBe(true);
|
||||
expect(isTerminalColumnRole(undefined, "archived")).toBe(true);
|
||||
expect(isTerminalColumnRole(undefined, "in-review")).toBe(false);
|
||||
});
|
||||
|
||||
it("a RENAMED column with no resolved flags answers no — the fallback cannot invent a role", () => {
|
||||
// The honest limit of the degraded mode, asserted so nobody mistakes it for trait resolution.
|
||||
expect(isCompleteColumnRole(undefined, "Shipped")).toBe(false);
|
||||
expect(isWipColumnRole(undefined, "Building")).toBe(false);
|
||||
expect(isIntakeColumnRole(undefined, "Inbox")).toBe(false);
|
||||
});
|
||||
});
|
||||
123
packages/core/src/column-roles.ts
Normal file
123
packages/core/src/column-roles.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
import type { TraitFlags } from "./trait-types.js";
|
||||
|
||||
/*
|
||||
FNXC:WorkflowResolvedColumns 2026-07-30-15:05:
|
||||
Column-ROLE predicates reachable from every package.
|
||||
|
||||
WHY THIS EXISTS AND WHY IT IS NOT A SECOND ABSTRACTION. The role helpers were introduced in
|
||||
`packages/dashboard/app/utils/columnRoles.ts` — a dashboard-APP module. Measured against the census,
|
||||
only 150 of 722 lifecycle guards (20%) sit where that module can be imported; engine (316), core
|
||||
(148), the dashboard SERVER (78) and the CLI (24) cannot reach it at all. This is the SAME
|
||||
flags-first / legacy-id-fallback predicate placed where the other 80% can call it, and core already
|
||||
exports `resolveColumnFlags`, so no new resolution machinery comes with it. See
|
||||
`docs/plans/workflow-owned-merge-stack/fleet-conversion-reachability.md` for the measurement.
|
||||
|
||||
SEMANTICS ARE MIRRORED, NOT INVENTED. The three distinctions below are the ones #2685 established
|
||||
for the dashboard-side set, restated here deliberately so the two cannot answer the same question
|
||||
differently:
|
||||
|
||||
- `isCompleteColumnRole` does NOT count `archived`. An archived card is finished but not
|
||||
*completed*; a throughput surface counting both double-counts it.
|
||||
- `isWipColumnRole` keys on `countsTowardWip` — the same flag capacity arithmetic uses, so a board
|
||||
cannot have a column that counts toward WIP for capacity but not for this predicate.
|
||||
- `isReviewColumnRole` accepts `mergeBlocker` OR `humanReview`. They are separable traits, but
|
||||
every caller converted so far asks "is this card in review", for which both qualify. A caller
|
||||
that needs exactly one should read the flag directly rather than widen this.
|
||||
|
||||
WHY THE LEGACY FALLBACK IS NOT DEAD CODE. Flags are absent in two real states: a card resting in a
|
||||
column its workflow no longer declares (mid-flight upgrade), and any caller holding a task row
|
||||
without a resolved IR. A bare `flags.complete === true` returns false in both — silent degradation,
|
||||
not a visible failure. The fallback is the degraded mode, named once and covered by tests, rather
|
||||
than an inline id comparison repeated per call site.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Legacy column ids, used ONLY when a column has no resolved trait flags.
|
||||
*
|
||||
* NOT lifecycle rules. `todo` is the post-U11 merged planning column; `triage` is its pre-merge
|
||||
* predecessor, retained because a project upgraded mid-flight can still hold cards there while its
|
||||
* workflow no longer declares the column.
|
||||
*/
|
||||
const LEGACY_INTAKE_COLUMN_ID = "triage";
|
||||
const LEGACY_PRE_IMPLEMENTATION_COLUMN_IDS: ReadonlySet<string> = new Set(["todo", "triage"]);
|
||||
const LEGACY_HOLD_COLUMN_ID = "todo";
|
||||
const LEGACY_WIP_COLUMN_ID = "in-progress";
|
||||
const LEGACY_REVIEW_COLUMN_ID = "in-review";
|
||||
const LEGACY_COMPLETE_COLUMN_ID = "done";
|
||||
const LEGACY_ARCHIVED_COLUMN_ID = "archived";
|
||||
|
||||
/** The subset of resolved trait flags these role questions read. */
|
||||
export type ColumnRoleTraitFlags = Pick<
|
||||
TraitFlags,
|
||||
"intake" | "hold" | "countsTowardWip" | "mergeBlocker" | "humanReview" | "complete" | "archived"
|
||||
>;
|
||||
|
||||
/** Does this column play the INTAKE role — the lane a card enters before implementation? */
|
||||
export function isIntakeColumnRole(flags: ColumnRoleTraitFlags | undefined, columnId: string): boolean {
|
||||
return flags ? flags.intake === true : columnId === LEGACY_INTAKE_COLUMN_ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this column a PRE-IMPLEMENTATION lane — intake or a hold?
|
||||
*
|
||||
* Either trait qualifies: both mean work has not started there, so moving a part-done card in
|
||||
* risks discarding steps.
|
||||
*/
|
||||
export function isPreImplementationColumnRole(
|
||||
flags: ColumnRoleTraitFlags | undefined,
|
||||
columnId: string,
|
||||
): boolean {
|
||||
return flags
|
||||
? Boolean(flags.intake || flags.hold)
|
||||
: LEGACY_PRE_IMPLEMENTATION_COLUMN_IDS.has(columnId);
|
||||
}
|
||||
|
||||
/** Does this column play the HOLD role — a lane a card WAITS in rather than works in? */
|
||||
export function isHoldColumnRole(flags: ColumnRoleTraitFlags | undefined, columnId: string): boolean {
|
||||
return flags ? flags.hold === true : columnId === LEGACY_HOLD_COLUMN_ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this column count as WORK IN PROGRESS?
|
||||
*
|
||||
* Keyed on `countsTowardWip` so this predicate and capacity arithmetic cannot disagree.
|
||||
*/
|
||||
export function isWipColumnRole(flags: ColumnRoleTraitFlags | undefined, columnId: string): boolean {
|
||||
return flags ? flags.countsTowardWip === true : columnId === LEGACY_WIP_COLUMN_ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is a card here awaiting REVIEW — a merge-blocking gate or an explicit human approval?
|
||||
*
|
||||
* Either trait qualifies; see the header note on why this is deliberately the union.
|
||||
*/
|
||||
export function isReviewColumnRole(flags: ColumnRoleTraitFlags | undefined, columnId: string): boolean {
|
||||
return flags
|
||||
? Boolean(flags.mergeBlocker || flags.humanReview)
|
||||
: columnId === LEGACY_REVIEW_COLUMN_ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this a terminal-SUCCESS column — work completed, dependencies satisfied?
|
||||
*
|
||||
* Excludes archived: see the header note. Use `isTerminalColumnRole` for "finished either way".
|
||||
*/
|
||||
export function isCompleteColumnRole(flags: ColumnRoleTraitFlags | undefined, columnId: string): boolean {
|
||||
return flags ? flags.complete === true : columnId === LEGACY_COMPLETE_COLUMN_ID;
|
||||
}
|
||||
|
||||
/** Is this column ARCHIVED — globally hidden and out of the lifecycle? */
|
||||
export function isArchivedColumnRole(flags: ColumnRoleTraitFlags | undefined, columnId: string): boolean {
|
||||
return flags ? flags.archived === true : columnId === LEGACY_ARCHIVED_COLUMN_ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is a card here FINISHED either way — completed or archived?
|
||||
*
|
||||
* Exists because the pattern `column !== "done" && column !== "archived"` is the single most
|
||||
* repeated shape in the backlog (e.g. `task-merge.ts` dependency/blocker checks). Naming the union
|
||||
* keeps callers from re-deriving it and from accidentally dropping one half.
|
||||
*/
|
||||
export function isTerminalColumnRole(flags: ColumnRoleTraitFlags | undefined, columnId: string): boolean {
|
||||
return isCompleteColumnRole(flags, columnId) || isArchivedColumnRole(flags, columnId);
|
||||
}
|
||||
@@ -350,6 +350,20 @@ export type {
|
||||
TraitViolationSeverity,
|
||||
TraitAuditWarning,
|
||||
} from "./trait-registry.js";
|
||||
/* FNXC:WorkflowResolvedColumns 2026-07-30-15:05: column-ROLE predicates must be in the GATE barrel
|
||||
too — the engine-core gate project resolves @fusion/core to the bundle built from THIS file, so an
|
||||
export present only in index.ts is undefined at runtime under the gate. */
|
||||
export {
|
||||
isIntakeColumnRole,
|
||||
isPreImplementationColumnRole,
|
||||
isHoldColumnRole,
|
||||
isWipColumnRole,
|
||||
isReviewColumnRole,
|
||||
isCompleteColumnRole,
|
||||
isArchivedColumnRole,
|
||||
isTerminalColumnRole,
|
||||
} from "./column-roles.js";
|
||||
export type { ColumnRoleTraitFlags } from "./column-roles.js";
|
||||
export {
|
||||
BUILTIN_TRAIT_IDS,
|
||||
BUILTIN_TRAIT_DEFINITIONS,
|
||||
|
||||
@@ -381,6 +381,19 @@ export type {
|
||||
TraitViolationSeverity,
|
||||
TraitAuditWarning,
|
||||
} from "./trait-registry.js";
|
||||
/* FNXC:WorkflowResolvedColumns 2026-07-30-15:05: column-ROLE predicates, reachable from every
|
||||
package (the dashboard-app helper set is not importable from engine/core/cli). */
|
||||
export {
|
||||
isIntakeColumnRole,
|
||||
isPreImplementationColumnRole,
|
||||
isHoldColumnRole,
|
||||
isWipColumnRole,
|
||||
isReviewColumnRole,
|
||||
isCompleteColumnRole,
|
||||
isArchivedColumnRole,
|
||||
isTerminalColumnRole,
|
||||
} from "./column-roles.js";
|
||||
export type { ColumnRoleTraitFlags } from "./column-roles.js";
|
||||
export {
|
||||
BUILTIN_TRAIT_IDS,
|
||||
BUILTIN_TRAIT_DEFINITIONS,
|
||||
|
||||
@@ -40,7 +40,8 @@ import { StaleTaskReporter } from "./stale-task-reporter.js";
|
||||
import { BacklogPressureReporter } from "./backlog-pressure-reporter.js";
|
||||
import { UnlinkedMissionsAdvisoryReporter } from "./unlinked-missions-advisory-reporter.js";
|
||||
import { createRunAuditor, generateSyntheticRunId } from "./run-audit.js";
|
||||
import { resolveWorkflowIrForTask, resolveWorkflowIrById, resolveColumnFlags, resolveWorktreeCapacityLimit, resolveLifecycleColumns } from "@fusion/core";
|
||||
import { resolveWorkflowIrForTask, resolveWorkflowIrById, resolveColumnFlags, resolveWorktreeCapacityLimit, resolveLifecycleColumns, isWipColumnRole } from "@fusion/core";
|
||||
import type { ColumnRoleTraitFlags } from "@fusion/core";
|
||||
import type { WorkflowIr, WorkflowIrV2 } from "@fusion/core";
|
||||
import { runHoldReleaseSweep, isUnplannedForExecution, type SlotReservation } from "./hold-release.js";
|
||||
import { moveTaskToReplanColumn } from "./replan-target.js";
|
||||
@@ -1668,27 +1669,36 @@ export class Scheduler {
|
||||
workflowIdByTaskId.set(task.id, "builtin:coding");
|
||||
}
|
||||
}));
|
||||
// Per distinct workflow: columnId → countsTowardWip flag; null when the IR
|
||||
// failed to resolve or has no v2 columns (forces the literal fallback).
|
||||
const wipFlagsByWorkflowId = new Map<string, Map<string, boolean> | null>();
|
||||
/*
|
||||
FNXC:WorkflowResolvedColumns 2026-07-30-15:40 (fleet conversion, scheduler.ts):
|
||||
Per distinct workflow: columnId → resolved trait flags; null when the IR failed to resolve or
|
||||
has no v2 columns, which leaves every lookup undefined and defers to the helper's documented
|
||||
degraded mode.
|
||||
|
||||
Was a hand-rolled copy of `isWipColumnRole`: it stored only `countsTowardWip` as a boolean and
|
||||
re-implemented flags-first-then-legacy-id inline. Storing the flags object instead lets the
|
||||
shared predicate decide, so this scheduler and the role helpers cannot drift on what "counts as
|
||||
WIP" means. Behaviour is identical in all four states — column present with the flag true or
|
||||
false (flags win), column absent from a resolved IR, and IR resolution failed (both fall back
|
||||
to the legacy id).
|
||||
*/
|
||||
const columnFlagsByWorkflowId = new Map<string, Map<string, ColumnRoleTraitFlags> | null>();
|
||||
for (const workflowId of new Set(workflowIdByTaskId.values())) {
|
||||
try {
|
||||
const ir = await resolveWorkflowIrById(this.store, workflowId, wipIrCache);
|
||||
const columns = (ir as WorkflowIrV2).columns;
|
||||
wipFlagsByWorkflowId.set(
|
||||
columnFlagsByWorkflowId.set(
|
||||
workflowId,
|
||||
columns ? new Map(columns.map((c) => [c.id, resolveColumnFlags(c).countsTowardWip === true])) : null,
|
||||
columns ? new Map(columns.map((c) => [c.id, resolveColumnFlags(c)])) : null,
|
||||
);
|
||||
} catch {
|
||||
wipFlagsByWorkflowId.set(workflowId, null);
|
||||
columnFlagsByWorkflowId.set(workflowId, null);
|
||||
}
|
||||
}
|
||||
const isWipColumnTask = (task: Task): boolean => {
|
||||
const flags = wipFlagsByWorkflowId.get(workflowIdByTaskId.get(task.id) ?? "builtin:coding");
|
||||
const wip = flags?.get(task.column);
|
||||
if (wip !== undefined) return wip;
|
||||
return task.column === "in-progress";
|
||||
};
|
||||
const columnFlagsForTask = (task: Task): ColumnRoleTraitFlags | undefined =>
|
||||
columnFlagsByWorkflowId.get(workflowIdByTaskId.get(task.id) ?? "builtin:coding")?.get(task.column);
|
||||
const isWipColumnTask = (task: Task): boolean =>
|
||||
isWipColumnRole(columnFlagsForTask(task), task.column);
|
||||
const wipTaskIds = tasks.filter(isWipColumnTask).map((task) => task.id);
|
||||
let reservedWorktreeSlots = wipTaskIds.length;
|
||||
let reservedConcurrentSlots = reservedWorktreeSlots;
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"generatedFrom": "node scripts/lifecycle-column-census.mjs --strict --update-baseline",
|
||||
"totals": {
|
||||
"column": 722,
|
||||
"column": 721,
|
||||
"role": 5,
|
||||
"status": 186,
|
||||
"deliberate": 17
|
||||
},
|
||||
"byColumnId": {
|
||||
"done": 195,
|
||||
"in-progress": 138,
|
||||
"in-progress": 137,
|
||||
"in-review": 200,
|
||||
"archived": 147,
|
||||
"todo": 42
|
||||
@@ -18,7 +18,7 @@
|
||||
"packages/engine/src/executor.ts": 85,
|
||||
"packages/dashboard/app/components/TaskCard.tsx": 42,
|
||||
"packages/dashboard/app/components/TaskDetailModal.tsx": 30,
|
||||
"packages/engine/src/scheduler.ts": 28,
|
||||
"packages/engine/src/scheduler.ts": 27,
|
||||
"packages/dashboard/src/routes/register-task-workflow-routes.ts": 20,
|
||||
"packages/core/src/task-store/moves.ts": 15,
|
||||
"packages/core/src/store.ts": 12,
|
||||
|
||||
Reference in New Issue
Block a user