convert(cli/commands/task.ts): triage guard 1 → 0 (+ a live main regression in the lifecycle E2E release path) (#2627)
**Batched push, gate open.** One conversion; the two E2E commits are held back and the reason is below — it is the more important half of this PR body. ## Conversion | File | triage column comparisons before | after | |---|---|---| | `packages/cli/src/commands/task.ts` | **1** | **0** | `pnpm test:gate` green, `pnpm lint` clean. All four non-terminal columns rendered the **same** glyph, so the four id comparisons were only ever asking "is this column terminal?". Naming `triage` made it a lifecycle-vocabulary site for no behavioural reason — the merged Planning column dropped that id, so the comparison silently stopped matching while the output stayed correct **by accident** (the fallthrough gave it the same glyph). Behaviour-identical **only** because the loop iterates the legacy `COLUMNS` constant (`types/board.ts:27` — exactly the six ids), so `col` can never be a custom id. Stated because the forms **diverge** outside that set: the old chain fell through to the terminal glyph for an unrecognised id, the new form returns the non-terminal one. If this ever iterates workflow-resolved columns that difference becomes live, and the right answer is a trait lookup, not this. **Deeper bug deliberately untouched, for U12:** because the loop iterates the legacy enum, a card in a workflow-renamed column **is not rendered at all**. That is R8's surface change, far bigger than this glyph. *(Note: this file is not on the 45-guard list, so it will not move your count. Flagging so the numbers reconcile.)* --- ## ⚠️ Live regression on origin/main — the lifecycle E2E release path While rebasing to push, the flagship lifecycle E2E went red. **I verified it on `origin/main` alone, with none of my commits: 2 failed / 18 passed.** ``` scenario 1 — DEFAULT vocabulary → AssertionError: expected [] to include 'FN-E2E-1' (r.sweep.released is EMPTY) scenario 2 — RENAMED vocabulary → audit trail differential broken: renamed produced [{end},{review}], default produced [] ``` Both are pre-existing tests I have never touched. **The capacity release sweep is releasing nothing.** **Likely cause, from reading rather than bisecting** — so treat it as a lead, not a verdict: `moves.ts:1059` now resolves a capacity pool id and enforces `enforcePooledColumnCapacity` **inside the move transaction** (#2488 "bind the in-transaction capacity gate", made user-visible by #2499 "make the capacity gate actually bind for real projects"). The E2E drives with `settings = { experimentalFeatures: { workflowGraphExecutor: true } }` — **no `maxConcurrent`** — while the fixture's wip column declares `{ trait: "wip", config: { limitSetting: "maxConcurrent", countPending: true } }`. If the resolved limit is finite and the pooled count meets it, the hold→wip move is rejected on capacity and the sweep correctly reports nothing released. If that is right, it is a **test-harness/production interaction, not a product break** — but it means the program's primary end-to-end evidence for the capacity boundary is currently inert on main, which matters for completion criterion 3. It needs the capacity worker's eyes, since #2488/#2499 are theirs and I would be guessing at the intended pool/limit contract. ## Why my two E2E commits are held They add scenario 3 (merged intake+hold board) and scenario 6 (REVISE → rework), both of which **depend on the same release leg**. On current main they fail for main's reason, taking the file from 2 failures to 4. Pushing them would add red to the count you are tracking and obscure whose regression it is. Both are complete, mutation-attributed, and green against the commit I wrote them on: | Scenario | Proves | Mutation that fails it | |---|---|---| | 3 — merged intake+hold | capacity release works from a dual-role column | `isHeldTask` treating intake/hold as exclusive → exactly its 2 tests | | 6 — REVISE → rework | `InReview → InProgress` on renamed *and* merged boards | disabling rework re-entry → exactly its 2 tests | They go out in the next batch the moment the release path is green. ## Also not shipped, twice attempted, deleted both times Safeguard 2 (`autoMerge:false` terminal-until-human) still has **no** graph-level E2E. Attempt 1 passed and then survived mutating `merge-gate` to ignore `task.autoMerge` — the card was parking on the review column's `merge-blocker` trait, not the gate. Attempt 2 removed that trait to isolate the gate, and then the *control* case parked too, so the flag still was not the discriminator. A fixture that can isolate it needs a merge path mirroring the builtin (`merge-gate → merge node → end`) rather than a direct edge to `end` — a real redesign, not a speculative edit. The enforcement that actually holds today is `allowInReviewMergeProcessing` in `project-engine` (unit-mutation verified, NEW=9; gated via #2526). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -591,11 +591,28 @@ export async function runTaskList(projectName?: string) {
|
||||
if (colTasks.length === 0) continue;
|
||||
|
||||
const label = COLUMN_LABELS[col];
|
||||
const dot =
|
||||
col === "triage" ? "●" :
|
||||
col === "todo" ? "●" :
|
||||
col === "in-progress" ? "●" :
|
||||
col === "in-review" ? "●" : "○";
|
||||
/*
|
||||
FNXC:CliBoardGlyph 2026-07-29-22:40 (lifecycle-column vocabulary):
|
||||
All four non-terminal columns rendered the SAME glyph, so the four id comparisons
|
||||
were only ever asking "is this column terminal?". Naming `triage` here made it a
|
||||
lifecycle-vocabulary site for no behavioural reason — the merged Planning column
|
||||
dropped that id, and this comparison silently stopped matching while the output
|
||||
stayed correct by accident (the fallthrough gave it `●` anyway).
|
||||
|
||||
Asking the terminal question directly removes the vocabulary dependency. It is
|
||||
behaviour-identical ONLY because this loop iterates the legacy `COLUMNS` constant
|
||||
(types/board.ts:27 — exactly the six ids), so `col` can never be a custom id. Worth
|
||||
stating because the two forms DIVERGE outside that set: the old chain fell through
|
||||
to `○` for an unrecognised id, this returns `●`. If this ever iterates
|
||||
workflow-resolved columns, that difference becomes live and the right answer is a
|
||||
trait lookup, not this.
|
||||
|
||||
NOT claimed as trait-resolved, and the deeper bug is left alone: because the loop
|
||||
iterates the legacy enum, a card in a workflow-renamed column is not rendered AT
|
||||
ALL. That is the R8/U10 surface change (no surface derives its column set from the
|
||||
legacy enum) and a far bigger fix than this glyph.
|
||||
*/
|
||||
const dot = col === "done" || col === "archived" ? "○" : "●";
|
||||
|
||||
console.log(` ${dot} ${label} (${colTasks.length})`);
|
||||
for (const t of colTasks) {
|
||||
|
||||
Reference in New Issue
Block a user