fleet: mission-execution-loop.ts 10 → 2 (one rule, five copies; and why these converted where store.ts's look-alikes could not) (#2711)

Claiming **`packages/engine/src/mission-execution-loop.ts`** (10). Every
one of its 10 census sites is the **same rule written five times**:

```ts
linkedTask.column === "done" || linkedTask.column === "archived"
```

## Census before/after

| | before | after |
|---|---:|---:|
| `mission-execution-loop.ts` | **10** | **2** |

Converted 4 of the 5 copies (8 of 10 sites) to the complete/archived
roles via core's `resolveTaskLifecycleColumns`. Each site already had
`this.taskStore` in scope inside an async method **and already had the
linked task fetched**, so the resolution rides along with a read that
was happening anyway.

## Why these converted where `store.ts`'s look-alikes could not (#2709)

Both read **another task's** column. The difference is not whose column
it is:

- **Here** — async methods, store at hand, one task per call. A
resolution is already affordable.
- **`store.ts`** — synchronous `filter` callbacks over a prefetched
`taskById` map, where per-dep resolution means N awaits inside a sync
predicate on a path that prefetches precisely to avoid per-item I/O.

Same-looking code, opposite verdicts. The distinguishing question is
**"is a resolution already affordable here"**, not "whose column is it"
— worth stating because a fleet worker pattern-matching on the receiver
alone would get both wrong.

## Not deduped, deliberately

The right shape is one predicate used five times rather than five inline
copies — and the FNXC comments above each copy show their intent has
already drifted apart. But introducing that predicate is a **new
abstraction**, which the fleet rules exclude, and it would fold five
reviewable substitutions into one design change. Flagged as the obvious
follow-up instead of smuggled in.

## Remaining: 2

The fifth copy, at the `hasLiveFixTask` site, where the terminal check
is one clause of a longer `Boolean(...)` expression whose other clauses
I would have had to reflow. Reviewability, not difficulty.

## Verification

`pnpm test:gate` **GREEN** (158 + 10 + 487 + 71) · the four mission
suites **150/150** · `pnpm lint` clean · engine `tsc` clean · `--strict`
exits 0.

🤖 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:
gsxdsm
2026-07-30 05:08:10 -07:00
committed by GitHub
parent 03443297db
commit 3b618f2530
2 changed files with 31 additions and 6 deletions

View File

@@ -25,7 +25,9 @@ import type {
Mission,
ValidationDiagnostics,
} from "@fusion/core";
import { MissionRemediationStoppedError, normalizeMissionAssertionType, normalizeValidationDiagnostics, renderValidationFailureDescription } from "@fusion/core";
import { MissionRemediationStoppedError, normalizeMissionAssertionType, normalizeValidationDiagnostics, renderValidationFailureDescription,
resolveTaskLifecycleColumns,
} from "@fusion/core";
import { GitCheckoutMaterializer, type CheckoutMaterializer, type VerificationOutcome } from "./mission-verification.js";
import { createFnAgent, promptWithFallback, type AgentResult } from "./pi.js";
import { mergeEffectiveSettings } from "./effective-settings.js";
@@ -318,7 +320,13 @@ export class MissionExecutionLoop extends EventEmitter {
// If the feature has a linked task that's already done, re-trigger validation
if (feature.taskId) {
const linkedTask = await this.taskStore.getTask(feature.taskId).catch(() => null);
if (linkedTask && (linkedTask.column === "done" || linkedTask.column === "archived")) {
const linkedLifecycle = linkedTask
? await resolveTaskLifecycleColumns(this.taskStore, linkedTask.id)
: undefined;
if (linkedTask && (
linkedTask.column === (linkedLifecycle?.complete ?? "done")
|| linkedTask.column === (linkedLifecycle?.archived ?? "archived")
)) {
await this.processTaskOutcome(feature.taskId);
}
}
@@ -335,7 +343,13 @@ export class MissionExecutionLoop extends EventEmitter {
if (feature.taskId) {
try {
const linkedTask = await this.taskStore.getTask(feature.taskId).catch(() => null);
if (linkedTask && (linkedTask.column === "done" || linkedTask.column === "archived")) {
const linkedLifecycle = linkedTask
? await resolveTaskLifecycleColumns(this.taskStore, linkedTask.id)
: undefined;
if (linkedTask && (
linkedTask.column === (linkedLifecycle?.complete ?? "done")
|| linkedTask.column === (linkedLifecycle?.archived ?? "archived")
)) {
await this.processTaskOutcome(feature.taskId);
}
recoveredCount++;
@@ -361,7 +375,13 @@ export class MissionExecutionLoop extends EventEmitter {
try {
const linkedTask = await this.taskStore.getTask(feature.taskId).catch(() => null);
if (linkedTask && (linkedTask.column === "done" || linkedTask.column === "archived")) {
const linkedLifecycle = linkedTask
? await resolveTaskLifecycleColumns(this.taskStore, linkedTask.id)
: undefined;
if (linkedTask && (
linkedTask.column === (linkedLifecycle?.complete ?? "done")
|| linkedTask.column === (linkedLifecycle?.archived ?? "archived")
)) {
loopLog.log(`Recovery: re-triggering implementing feature ${feature.id} from completed task ${feature.taskId}`);
await this.processTaskOutcome(feature.taskId);
recoveredCount++;
@@ -665,7 +685,12 @@ export class MissionExecutionLoop extends EventEmitter {
if (!taskId) return null;
const linkedTask = await this.taskStore.getTask(taskId).catch(() => null);
const column = linkedTask?.column;
if (!column || column === "done" || column === "archived") return null;
const premergeLifecycle = linkedTask ? await resolveTaskLifecycleColumns(this.taskStore, linkedTask.id) : undefined;
if (
!column
|| column === (premergeLifecycle?.complete ?? "done")
|| column === (premergeLifecycle?.archived ?? "archived")
) return null;
return column;
}

View File

@@ -7,7 +7,6 @@
"packages/dashboard/app/components/TaskDetailModal.tsx": 30,
"packages/engine/src/scheduler.ts": 26,
"packages/core/src/store.ts": 12,
"packages/engine/src/mission-execution-loop.ts": 10,
"packages/core/src/task-store/async-comments-attachments.ts": 9,
"packages/dashboard/app/components/TaskContextMenu.tsx": 9,
"packages/dashboard/src/github-tracking-comments.ts": 9,
@@ -92,6 +91,7 @@
"packages/engine/src/auto-merge-finalization.ts": 2,
"packages/engine/src/cli-agent/state-machine.ts": 2,
"packages/engine/src/merger-scope-auto-widen.ts": 2,
"packages/engine/src/mission-execution-loop.ts": 2,
"plugins/fusion-plugin-even-cards/src/cards/board-cards.ts": 2,
"plugins/fusion-plugin-reports/src/store/report-store.ts": 2,
"packages/cli/src/commands/pr.ts": 1,