db71b4fffb45253d6a13cccbc48877c5dfc2bb0b
4 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
f9f06a4fb7 |
engine tail: nine files to zero — incl. a census-INVISIBLE requeue into a lane that does not exist (−13) (#2797)
Follow-up to #2785. Nine engine files to **zero**, all one question — *"is this task finished?"* — asked in nine places, wrong in every one on a renamed board. ## Census, per file (measured, `--strict` verified) | file | main | here | | --- | ---: | ---: | | `agent-reflection.ts` | 2 | **0** | | `merger-scope-auto-widen.ts` | 2 | **0** | | `worktree-pool.ts` | 2 | **0** | | `cli-agent/state-machine.ts` | 2 | **0** (reclassified — see below) | | `auto-recovery-handlers/branch-worktree.ts` | 1 | **0** | | `cli-agent/task-session.ts` | 1 | **0** (reclassified) | | `merger-integration-worktree.ts` | 1 | **0** | | `merger-orphan-rehome.ts` | 1 | **0** | | `plugin-runner.ts` | 1 | **0** | | **net** | | **−13** | ## The one worth reading: `branch-worktree` had TWO defects, and the census could only see one ```ts if (task.column === "in-progress") { …clear branch… } // counted await this.deps.taskStore.moveTask(task.id, "todo", { … }); // INVISIBLE ``` The census scores **comparisons**. The requeue *destination* is a call argument, so nothing in the backlog ever pointed at it — and it is the worse of the two: a board with no `todo` column was requeued into a lane **that does not exist**. The counted literal is the smaller half (a renamed wip lane meant the stale branch was never cleared, so the card carried a dead branch back into execution). Converting the comparison alone would have dropped a census count and left the board requeuing into nowhere. Destination now resolves through `resolveReboundTarget` (KTD-10 ordering: hold → intake → first column). Reverted **independently**: destination restored → 2 fail (`moveTask` called with `"todo"`, not `"backlog"`); wip test restored → 1 fail (`updateTask` never called). ## The rest - **`plugin-runner`** — `onTaskCompleted` never fired on a renamed board. Every plugin that closes an issue, posts a notification, or records a metric on completion **silently stopped**, with nothing logged. Resolved *asynchronously* inside the existing fire-and-forget seam, not via `resolveTaskWorkflowIrSync` — per `sync-workflow-ir-callsite-allowlist` that reader returns the DEFAULT workflow for every task in production, so a sync guard here would read as converted and still be wrong. The listener is already `void`-dispatched, so awaiting inside it changes no observable ordering (the shape `NotificationService` already uses). - **`merger-orphan-rehome`** — a renamed complete lane made every source task read as unfinished, so orphaned commits were never rehomed and stayed stranded off the integration branch. Resolves by the **trailer id**, not `sourceTask.id`, which the fake store does not populate. - **`agent-reflection`** — `classifyOutcome` returned `null` for every finished task, so both callers treated completed work as nothing to reflect on: one recorded `reflection:skipped` with reason `"not-completed"`, the other silently `continue`d. Reflection captured **nothing at all** on a custom board. - **`worktree-pool`** — shipped tasks' worktrees stayed in the ACTIVE set, so the reclaim pass never returned them and the board walks into worktree exhaustion — a stall whose cause is invisible from the symptom. - **`merger-scope-auto-widen`** — finished cards counted as active claimants, so a merge was blocked by a task that no longer exists in any meaningful sense. - **`merger-integration-worktree`** — a shipped task still counted as a live worktree user, so the integration worktree could never be reused and the merge path took the slower rebuild every time. ## The census OVERSTATED the engine backlog by 3 `cli-agent/state-machine.ts` and `cli-agent/task-session.ts` compare against `done` — but that is a **`CliMachineState`** (`ready`/`busy`/`waitingOnInput`/`done`/`resuming`/`idle`) tracking one CLI agent process. It never reads a board column. The census matches the bare string. Marked `DELIBERATE-LITERAL` rather than left for a later sweep to "convert" a process state into a workflow role. Worth flagging fleet-wide: the backlog total includes at least these three non-columns. ## Revert results (measured, each run) | conversion | reverted → | | --- | --- | | `plugin-runner` complete gate | RENAMED case fails — `onTaskCompleted` never invoked | | `merger-orphan-rehome` source gate | RENAMED case fails — `orphan:false, reason:"source-task-not-done"` | | `branch-worktree` destination | 2 fail — `moveTask` called with `"todo"` | | `branch-worktree` wip test | 1 fail — `updateTask` never called | Each has a **non-vacuous companion** (renamed board, non-complete lane / mid-flight source / non-wip column) so a guard that fired unconditionally would not pass. **Four are NOT revert-proven, and I am not claiming otherwise:** `agent-reflection`, `merger-scope-auto-widen`, `merger-integration-worktree`, `worktree-pool`. Their suites omit a workflow and therefore assert the legacy fallback — they pass before and after. `merger-scope-auto-widen` has no test file at all; `scanIdleWorktrees` is mocked in every suite that touches it and driving it for real needs git worktrees on disk. All four strictly **widen** the finished set (resolved roles ∪ the legacy ids), so default boards are byte-identical. That is the argument for shipping them, not a substitute for coverage. ## Examined and deliberately NOT converted - **`backlog-pressure-reporter:173`** — fed by `listTasks({ column: "todo" })`, a hardcoded **query** filter. On a renamed board `todoFull` is empty and the predicate never runs. Converting it drops a census count and changes nothing observable; the fix belongs at the query layer. - **`auto-merge-finalization:28`** — the catch-arm legacy fallback, which must stay for the same reason `columnRoles.ts` keeps its id fallback. - **`auto-merge-finalization:84`** — only selects between two diagnostic reason strings that are **both** `ok: false`, on a pure validator with no store in scope. Converting it would thread a store through a pure function to change a label. ## Merge resolution note Merging main brought conflicts in `agent-assignment.ts` and `ephemeral-worker-manager.ts`. **Main's versions won both** and mine are dropped: main threads an optional `activeColumns` from `scheduler.ts:2340` (a cleaner seam than widening the store type to resolve internally), and its `isAgentIdle` carries a greptile P1 fix mine lacked — `columnsWithFlag` membership rather than first-per-role, so a workflow declaring two wip lanes has both recognised. That is the fourth time in this program main's version of a contested file was the better one. ## Verification - `pnpm test:gate` — 161 + 487 + 13 + 71, green - `npx tsc -p packages/engine/tsconfig.json --noEmit` — clean - `pnpm lint` — clean - `--strict` exits 0 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Workflow-dependent task completion now recognizes custom lifecycle columns, including renamed boards. * Recovery requeues tasks to the configured destination and clears branch details only from the appropriate work-in-progress column. * Improved handling of completed tasks, orphaned work, shared worktrees, and scope evaluation across custom workflows. * Plugin completion hooks now trigger for any column configured as complete. * **Tests** * Added coverage for renamed workflow columns and custom completion, recovery, and rehoming behavior. * **Documentation** * Clarified CLI state terminology in internal developer comments. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
b9afce306e |
fix(review): schema-version gate, output-filter escape-bypass, generic double-wrap, follow-up resolution
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
243113a3cf |
feat: cli-agent adapter settings, autonomy approval gate, and node editor config (U15)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
c1c99a9de0 |
feat(engine): wire cli-agent executor seam and task session lifecycle (U7)
Add `cli-agent` as a task-execute executor kind. A workflow node with `config.executor === "cli-agent"` drives an engine-owned CLI coding agent through the execute step via the new cli-agent/task-session.ts orchestration: spawn in the worktree, issue the hook token + write hook scripts, inject the prompt after readiness, subscribe to the state machine, and resolve on a positive completion signal (R20 gating). Config is snapshotted at launch; the PTY is reaped (completed) at the in-review handoff. Executor seam: runGraphCustomNode gains a cli-agent branch delegating to runCliAgentNode; the hard-cancel/abort path (awaitAbortInFlightTaskWork + abortAllInFlight) claims and SIGKILLs the CLI session as a first-class surface, marking it killed (never resume-eligible). Re-entry kills any prior live session and launches fresh; follow-up resumes the recorded native session id when supported. A PTY-pool ceiling surfaces as a typed task value, not a stall. Node-config typing extended minimally (WorkflowNodeExecutorKind / WorkflowNodeExecutorConfig in @fusion/core). Tests: cli-agent/__tests__/task-session.test.ts (12) and __tests__/cli-agent-executor.test.ts (9) cover AE1/AE5, hard cancel, re-entry, follow-up, config snapshot, ceiling, and the generic-tier confirm-advance path, using scripted adapters + a mock PTY seam. Engine typecheck clean; full src/cli-agent suite + new tests green (144 passing). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |