Commit Graph

3101 Commits

Author SHA1 Message Date
gsxdsm
aa534c19af FN-7672: recover durable agents stuck in error state despite active manager
Root-causes 4 correlated CTO-report agent failures where durable non-ephemeral agents got stuck in `error` state indefinitely because the heartbeat scheduler stops ticking error-state agents entirely, and self-healing's recovery sweep previously only considered them when their manager row was missing.

- SelfHealingManager: scope the `managerMissing` gate to the "running" orphan-detection path only, so "error"-state durable agents with a present/active manager now fall through to the existing transient/operator-actionable/active-execution/cooldown/retry-budget recovery guards instead of being skipped outright
- Add FNXC:AgentHeartbeat comment documenting the FN-7672 incident and rationale for the scoping change
- Extend self-healing.test.ts with coverage for manager-present durable agents in error state
- Add changeset (patch) describing the fix for release notes
- Update docs/agents.md accordingly

Files changed:
 .changeset/fn-7672-durable-agent-recovery.md       |   7 ++
 docs/agents.md                                     |   2 +
 packages/engine/src/__tests__/self-healing.test.ts | 129 ++++++++++++++++++++-
 packages/engine/src/self-healing.ts                |  24 +++-
 4 files changed, 160 insertions(+), 2 deletions(-)

Fusion-Task-Id: FN-7672

Fusion-Task-Lineage: 6676dc9e-66e7-4f70-804a-cccf77e8d337

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-08 05:38:01 -07:00
gsxdsm
b8fa2a6652 FN-7670: document negative result of extending engine-core pre-bundle to @fusion/engine relative-import graph
Prototyped extending the @fusion/core pre-bundle alias lever to @fusion/engine's relative-import production graph reached by the 18 gate files, but an A/B showed no clear win over the @fusion/core-only bundle, so the change was not landed and only the rationale is recorded.

- Added an FNXC:EngineTests comment block in packages/engine/vitest.config.ts documenting the FN-7670 prototype (171 first-party files → 35 output files via esbuild multi-entry splitting)
- Recorded the negative A/B result: byte-size growth of 14 separate large root bundles offset per-file-dispatch savings, with no clear win beyond host run-to-run noise
- Left the vitest alias wiring unchanged at the @fusion/core-only bundle state, pointing future attempts to FN-7670's task docs for full analysis and to consider a single combined engine-graph entry instead of 14 separate root entries

Files changed:
 packages/engine/vitest.config.ts | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

Fusion-Task-Id: FN-7670

Fusion-Task-Lineage: efd27f94-a6c4-49c7-a78e-50213fd42a24

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-08 05:20:47 -07:00
gsxdsm
ad9a72176c FN-7669: pre-bundle @fusion/core gate-safe barrel to cut engine-core gate import-phase cost
Prototype and land a rebuilt-every-run esbuild bundle of the @fusion/core gate-safe barrel closure, collapsing the engine-core gate's per-fork Vite SSR import-phase cost (18 forks x ~430-file closure re-resolved from scratch) into a single file load per fork.

- Add scripts/build-engine-core-gate-bundle.mjs: esbuild-bundles packages/core/src/index.gate.ts (220 first-party files, packages:"external" so third-party/node: imports stay external, treeShaking:false to preserve side effects) into packages/core/.gate-bundle/core.mjs + core.meta.json
- Wire the builder into packages/engine/vitest.config.ts's engine-core project globalSetup (alongside the existing vitest-teardown hook) so the bundle is rebuilt fresh before every gate invocation, and repoint the @fusion/core resolve.alias at the bundled output instead of index.gate.ts source
- Place the bundle output at packages/core/.gate-bundle/ as a sibling of packages/core/node_modules/ (not nested inside it) to avoid Vite SSR's external-dep heuristic, which would otherwise silently defeat vi.mock interception for imports nested in the bundle
- Gitignore packages/core/.gate-bundle/ and add a matching ESLint ignore entry so the generated bundle text is never linted or committed
- Add esbuild ^0.25.12 as a root devDependency (pnpm-lock.yaml updated accordingly)
- Document the pre-bundling rationale, placement constraints, and measured A/B wall-time results in docs/testing.md

Verified: pnpm test:gate passes (335/335 engine-core tests, 63/63 CLI ci-shape tests), engine package typecheck clean, eslint clean on touched files.

Files changed:
 .gitignore                                |  11 ++
 docs/testing.md                           |   3 +
 eslint.config.mjs                         |  10 ++
 package.json                              |   1 +
 packages/engine/vitest.config.ts          |  50 ++++++++-
 pnpm-lock.yaml                            |   3 +
 scripts/build-engine-core-gate-bundle.mjs | 174 ++++++++++++++++++++++++++++++
 7 files changed, 247 insertions(+), 5 deletions(-)

Fusion-Task-Id: FN-7669

Fusion-Task-Lineage: 62b06b2a-4ac6-45ae-ac79-9771132bc303

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-08 04:24:30 -07:00
gsxdsm
6902077972 FN-7667: add gate-scoped @fusion/core barrel to decouple engine-core gate from full barrel growth
Introduces a project-scoped @fusion/core barrel used only by the engine-core
gate project, so new feature modules added to the full barrel don't silently
inflate the gate's transform/import cost.

- Add packages/core/src/index.gate.ts, a copy of the full @fusion/core barrel
  minus export statements for modules added since the last re-audit baseline
  (i.e. it still re-exports everything the full barrel does except newly
  added, gate-irrelevant feature modules).
- Update packages/engine/vitest.config.ts to add a project-scoped
  resolve.alias mapping @fusion/core -> packages/core/src/index.gate.ts for
  the engine-core project only; engine-default/engine-reliability/engine-slow
  and @fusion/engine continue to resolve the full barrel.
- Document the gate-safe barrel and its audit procedure in docs/testing.md.

Files changed:
 docs/testing.md                  |    3 +
 packages/core/src/index.gate.ts  | 2102 ++++++++++++++++++++++++++++++++++++++
 packages/engine/vitest.config.ts |   17 +
 3 files changed, 2122 insertions(+)

Fusion-Task-Id: FN-7667

Fusion-Task-Lineage: 054ec89a-d973-44dd-b9ac-ad266f553f01

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-08 03:28:37 -07:00
gsxdsm
f7d9509294 FN-7658: gate same-agent duplicate auto-archiving behind opt-in setting
Duplicate tasks created by the same agent are no longer auto-archived by default; they are flagged for review instead, controlled by a new opt-in project setting.

- Add project setting `autoArchiveDuplicateTasksEnabled` (default false) gating the FN-4892 same-agent duplicate intake path
- Add `flagSameAgentDuplicate` path and `nearDuplicateOf` metadata used when auto-archive is disabled; tombstone-resurrection blocking is unchanged
- Wire the setting through core settings schema/types/store, dashboard SchedulingSection UI, and i18n strings
- Update docs (settings-reference.md, task-management.md) to describe the new default-off behavior
- Add a changeset for the @runfusion/fusion minor release
- Extend duplicate-intake, tombstone-window, store-parent-task-dedup, and reliability-interaction tests to cover both flag states

Files changed:
$(cat /tmp/fn7658_stat.txt)

Fusion-Task-Id: FN-7658

Fusion-Task-Lineage: 7d0d1074-1020-48a8-b96f-186154c2c408

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-08 00:23:32 -07:00
gsxdsm
bec8987ce9 FN-7648: gate hold-release on trait-based unplanned-card check, not literal todo column
Blocks planning/intake column cards from entering processing columns regardless of literal column id, so renamed custom intake/planning columns are covered by the same guard as the legacy todo column.

- Add isUnplannedForExecution() in hold-release.ts: true when task.status==="planning", or when the card sits in the legacy todo column or a column carrying the intake trait AND its PROMPT.md still equals the bootstrap stub.
- Route issueRelease() (used by the sweep, promoteHeldTask, and releaseHeldTaskByEvent) through this guard before releasing into any countsTowardWip processing column.
- Update scheduler.ts's reserveSlot guard to use the same trait-based predicate instead of a hardcoded "todo" column id check.
- Add regression tests in hold-release.test.ts and scheduler-workflow-cutover.test.ts covering renamed intake/planning columns.
- Document the invariant in docs/architecture.md and docs/workflow-steps.md.
- Add changeset (patch) describing the fix.

Files changed:
 .changeset/fn-7648-unplanned-intake-cards-never-execute.md |   7 +
 docs/architecture.md                               |   2 +
 docs/workflow-steps.md                             |   2 +
 packages/engine/src/__tests__/hold-release.test.ts | 238 +++++++++++++++++++++
 packages/engine/src/__tests__/scheduler-workflow-cutover.test.ts |  60 +++++-
 packages/engine/src/hold-release.ts                |  60 ++++++
 packages/engine/src/scheduler.ts                   |  26 +--
 7 files changed, 378 insertions(+), 17 deletions(-)

Fusion-Task-Id: FN-7648

Fusion-Task-Lineage: a4b54d30-f86d-4eb9-9cf2-6ac55b6dbe58

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-07 22:06:21 -07:00
gsxdsm
009ce26fd0 FN-7646: prevent OAuth credential clobbering across concurrent Fusion processes
Fix API keys/OAuth credentials in ~/.fusion/agent/auth.json being clobbered when the desktop app and CLI-served web app run concurrently on one machine.

- Reload primary auth storage from disk (primary.reload()) before persisting a refreshed OAuth credential, so a concurrent process's newer login/refresh for the same provider isn't overwritten by this process's stale in-flight refresh.
- Re-check credential identity against the freshly reloaded disk state before writing the refreshed token back.
- Add cross-process regression coverage exercising concurrent auth.json read-modify-write scenarios.
- Add changeset documenting the fix and its dependency on the pi-coding-agent locked per-provider merge (>=0.80.x).

Files changed:
 .changeset/fn-7646-auth-storage-coordination.md    |   7 +
 .../src/__tests__/auth-storage-concurrency.test.ts | 234 +++++++++++++++++++++
 packages/engine/src/auth-storage.ts                |  27 +++
 3 files changed, 268 insertions(+)

Fusion-Task-Id: FN-7646

Fusion-Task-Lineage: de39f08d-2d9f-46ff-b293-c603e3268ecf

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-07 22:06:21 -07:00
gsxdsm
4e8c621e9c FN-7641: fix cards stranded after out-of-band/workspace merges by allowing proven-merge rehome
Fixes a state-machine bug family where cards got stranded after out-of-band or workspace merges landed: store.moveTask now allows a proven-merge recoveryRehome to cross legacy columns (e.g. todo→done), and nodeId='end' finalize no longer silently no-ops — it finalizes on durable merge proof or returns an explicit error, consistently across the dashboard route, the CLI task-update tool, and store.updateTask.

- packages/core/src/store.ts: allow proven-merge recoveryRehome moves across legacy columns (e.g. todo→done) instead of rejecting them
- packages/core/src/node-override-guard.ts: nodeId='end' finalize now checks for durable merge proof and returns an explicit error instead of silently no-op'ing
- packages/dashboard/src/routes/register-task-workflow-routes.ts: dashboard workflow route surfaces the new explicit finalize error/behavior
- packages/cli/src/extension.ts: CLI task-update tool surfaces the same explicit finalize error/behavior
- docs/task-management.md: documented the updated finalize/rehome behavior
- Added regression tests across core (node-override-guard, store-movement, task-node-override), dashboard (register-task-workflow-routes.nodeid-finalize), engine (merger-merge-lifecycle), and CLI (extension) covering the stranded-card invariant
- Added changeset for @runfusion/fusion (patch)

Files changed:
 .changeset/fn-7641-stranded-cards-after-merge.md   |  7 ++
 docs/task-management.md                            |  2 +
 packages/cli/src/__tests__/extension.test.ts       | 59 ++++++++++++++
 packages/cli/src/extension.ts                      | 10 +++
 .../core/src/__tests__/node-override-guard.test.ts | 93 +++++++++++++++++++++
 packages/core/src/__tests__/store-movement.test.ts | 94 ++++++++++++++++++++++
 .../core/src/__tests__/task-node-override.test.ts  | 73 +++++++++++++++++
 packages/core/src/node-override-guard.ts           | 69 +++++++++++++++-
 packages/core/src/store.ts                         | 69 +++++++++++++++-
 ...er-task-workflow-routes.nodeid-finalize.test.ts | 90 +++++++++++++++++++++
 .../src/routes/register-task-workflow-routes.ts    | 10 +++
 .../src/__tests__/merger-merge-lifecycle.test.ts   | 58 +++++++++++++
 12 files changed, 631 insertions(+), 3 deletions(-)

Fusion-Task-Id: FN-7641

Fusion-Task-Lineage: 48ea7851-ee68-48f1-92f9-302d0da5acff

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-07 22:06:20 -07:00
gsxdsm
923bba7082 FN-7645: force re-arm zombie heartbeat timers detected as stale during audit
Fixes the heartbeat timer audit so it repairs not just missing timer registrations but also 'zombie' ones — timer entries that remain present in memory after their underlying interval silently stopped firing. Long-interval (~1h) agents were most affected since a single lost tick compounded into hours of staleness before self-healing noticed.

- HeartbeatTriggerScheduler audit now computes staleness (elapsed vs repair-stale threshold) up front for every timer-eligible agent, not only for agents missing a timer entry
- Present-but-stale timer entries are now treated as non-advancing and force cleared/re-registered via registerAgent() (which already clears any existing timer before re-arming)
- Fresh (non-stale) present timers are left alone so healthy short-interval agents are never force-re-armed or double-ticked
- Repair reason/log messages now distinguish zombie-timer-rearmed repairs from missing-registration repairs, and the summary log reports counts for each
- Added heartbeat-scheduler tests covering the zombie-timer repair path
- Added changeset and a docs/architecture.md note

Files changed:
 .changeset/fn-7645-heartbeat-rearm.md              |   7 +
 docs/architecture.md                               |   1 +
 .../src/__tests__/heartbeat-scheduler.test.ts      | 223 +++++++++++++++++++++
 packages/engine/src/agent-heartbeat.ts             |  42 +++-
 4 files changed, 266 insertions(+), 7 deletions(-)

Fusion-Task-Id: FN-7645

Fusion-Task-Lineage: 652bc2eb-a660-4306-9f85-d2d5f9ca7e38

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-07 22:06:18 -07:00
gsxdsm
f1db31374a FN-7642: emit diagnostic output for dispatch/infra failures in optional-group and CE gate nodes
Fixes the code-review/plan-review/CE gate workflow node failing with a blank "(no feedback captured)" message when a dispatch or infra exception (not a reviewer verdict) causes the step to fail.

- WorkflowGraphExecutor now synthesizes a non-blank WorkflowStepResult.output when an enabled optional-group (code-review, plan-review, browser-verification) or CE source:"node" skill-gate template node fails via dispatch/infra exception
- Diagnostic output is derived from the node:<id>:error context-patch key, falling back to the failure value, then a stable sentinel
- status, verdict extraction, edge routing, and self-healing's latestFailedPreMergeStep selection are unchanged
- Added regression test coverage: workflow-graph-optional-group-no-feedback.test.ts
- Added changeset (patch) documenting the fix for Runfusion/Fusion#1946

Files changed:
 .changeset/fn-7642-code-review-no-feedback-diagnostic.md          |   7 +
 packages/engine/src/__tests__/workflow-graph-optional-group-no-feedback.test.ts | 246 +++++++++++++++++++++
 packages/engine/src/workflow-graph-executor.ts                    | 104 ++++++++-
 3 files changed, 355 insertions(+), 2 deletions(-)

Fusion-Task-Id: FN-7642

Fusion-Task-Lineage: 1329e907-652f-4230-a945-5a9d7040ae69

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-07 22:06:18 -07:00
gsxdsm
fe5a595984 FN-7622: unify desktop and CLI provider seeding to fix truncated provider list
The Electron desktop app's in-process dashboard server skipped the CLI's provider seeding sequence, so /api/providers and /api/models returned a truncated catalog (missing built-in API-key providers and user customProviders[]) compared to the identical config on the web build.

- Move provider-auth.ts and custom-provider-registry.ts from @fusion/cli into @fusion/engine as the single shared implementation
- Add engine/src/provider-registration.ts exposing seedDashboardProviders(), mirroring the CLI's exact startup order (built-in Zai provider registration -> wrapAuthStorageWithApiKeyProviders -> model merge/refresh -> registerCustomProviders -> settings:updated resubscription)
- Update desktop/src/local-runtime.ts and local-server.ts to call the shared seedDashboardProviders() helper instead of constructing a raw authStorage/modelRegistry
- Convert packages/cli/src/commands/provider-auth.ts and custom-provider-registry.ts into re-export shims preserving unchanged observable behavior
- Add engine/src/__tests__/provider-registration.test.ts and expand desktop local-runtime/local-server tests to cover the shared seeding path
- Add changeset for @runfusion/fusion (patch)

Files changed:
 .changeset/fn-7622-desktop-provider-parity.md      |   7 +
 .../cli/src/commands/custom-provider-registry.ts   | 122 +----
 packages/cli/src/commands/provider-auth.ts         | 517 +--------------------
 .../desktop/src/__tests__/local-runtime.test.ts    |  93 ++++
 .../desktop/src/__tests__/local-server.test.ts     |  62 ++-
 packages/desktop/src/local-runtime.ts              |  33 +-
 packages/desktop/src/local-server.ts               |  21 +-
 .../src/__tests__/provider-registration.test.ts    | 192 ++++++++
 packages/engine/src/custom-provider-registry.ts    | 117 +++++
 packages/engine/src/index.ts                       |  18 +
 packages/engine/src/provider-auth.ts               | 513 ++++++++++++++++++++
 packages/engine/src/provider-registration.ts       | 105 +++++
 12 files changed, 1172 insertions(+), 628 deletions(-)

Fusion-Task-Id: FN-7622
Fusion-Task-Lineage: fb6fbbf3-745e-4623-b7af-11471e13f138
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-07 22:04:51 -07:00
gsxdsm
1add12d703 fix: resolve full-suite CI failures across engine + dashboard (shards 1-4) (#1947)
## Summary

Fixes the failing **full-suite** CI run on `main` ([run
28874651861](https://github.com/Runfusion/Fusion/actions/runs/28874651861))
— all 4 test shards were red. ~32 test files failing across engine +
dashboard (src + app), rooted in ~13 distinct causes from recent main
commits. All resolved; the merge gate and full engine/dashboard suites
are green locally.

## Root causes & fixes

### Engine (shards 1 & 2)
- **`appendAgentLog` 6th timing arg (FN-7503, `2797803c0`)** —
`agent-logger.ts` now passes an optional
`{durationMs,timeToFirstTokenMs}` 6th arg; many
executor/heartbeat/merger tests asserted the old 5-arg form. Added a
shared timing-tolerant helper `agent-log-assertions.ts` (asserts
`taskId/text/type`, tolerant of the timing object) and applied it across
affected files — so future timing fields won't re-break every executor
test.
- **`reconcileSupersededGeneratedFixFeatures` (mission)** —
`mission-execution-loop.ts` calls a method the test's missionStore mock
lacked; added a no-op stub (the real `MissionStore` already implements
it).
- **`ModelFallbackExhaustedError` / `proseSignalsClearApproval` /
`extractJsonObjectCandidates` missing from `vi.mock`** — converted stale
hand-written mocks (`../pi.js`, `../reviewer.js` in
`executor-test-helpers.ts`) to `importOriginal`-spread so real exports
carry through.
- **Workspace product fixes (2):**
- `merger-ai.ts` — `landWorkspaceTask` now recovers the integration-tip
sha as `landedSha` when the A1 trailer-fallback proved a sub-repo landed
but its sha was never persisted, so `finalizeWorkspaceTask` can build
merge proof (was stranding partial-land retries in-review).
- `worktree-acquisition.ts` — `acquireWorkspaceRepoWorktree` strips the
shared project `integrationBranch/baseBranch` overrides before
forwarding to `acquireTaskWorktree` (FN-7360's `freshStartPoint` was
resolving an absent shared branch).
- **FN-7360 extra `git symbolic-ref` exec** — updated worktree
exec-count assertions for the new `resolveIntegrationBranch` call.
- **Planner-overseer / stepwise-workflow / workflow-graph /
workflow-prompt / executor-step-session / liveness-gate / checkout /
ce-workflow / triage-split** — test-alignments for intentional behavior
changes (FN-7229 retry-cap, FN-7265 review-node removal, FN-7335
pause-abort logging, FN-7577 recovery-budget, FN-7577 overseer denial
loop, specifyTask single promptWithFallback call, FN-4944
already-on-main noop log, FN-7486 ownership short-circuit).

### Dashboard API (shard 3)
- **`store.on('task:moved')` (FN-7337)** — `createServer` now registers
the listener; backed the 4 affected MockStores with EventEmitter (shared
root cause across chat-routes.rooms, register-git-github,
routes-run-cited-goals, routes-sandbox-audit).
- **`routes-agent-import`** — core mock converted to
`importOriginal`-spread (was missing FN-7444 planning-deepening
constants).
- **`session-resume-history`** — engine mock missing
`resolveMcpServersForStore`.
- **`task-create-workflow-route`** — `builtin:legacy-coding`
defaultSteps now include `plan-review` (FN-7224/7226).
- **GitLab parity** — added the missing `[GitLab Parity Inventory]`
cross-link in `docs/signals-connectors.md`.

### Dashboard app (shard 4)
- Test-alignments for intentional product changes: FN-7057 (workflow
selection preservation), FN-7340 (footer concurrency geometry), FN-7156
(Missions overview default), FN-7342/FN-6825 (board scroll + workflow
switcher), FN-7352 (openDetailTask 3rd arg), FN-7261 (backdrop dismiss
default-off), FN-7234 (non-authoritative fetch failures), plus a missing
`fetchWorkflowOptionalSteps` mock.

### MCP coverage
- `mcp-surface-coverage` forwarding needle updated for FN-7446's
`resolvePlanningMcpServers` helper.

## Approach notes
- Each fix is the **minimal** change at the correct source (test-update
where a recent commit intentionally changed behavior; product-fix for
the 2 real regressions). No assertion was loosened/deleted to force a
pass; no timeout appeasement.
- Coordination: work was partitioned by package across parallel
subagents (engine / dashboard-src / dashboard-app) with Main as the sole
git committer (path-scoped commits) after an early shared-index reset
wiped in-progress edits — process was tightened mid-flight.

## Verification
- **Full engine suite**: green (9231 passed; the lone local-only
`custom-providers-openai-completions` import error is stale local
`pi-ai@0.79.9` vs the lockfile's `0.80.3` — CI's fresh install resolves
`/compat`; it passed in the original CI run).
- **Dashboard API** (`dashboard-api-quality-backfill`): 242 files / 3185
tests / 0 failures.
- **Dashboard app** (`dashboard-app-quality-backfill`): all targeted
files green (37 + 95 tests).
- **Merge gate** (`pnpm test:gate`): engine-core 326 + ci-shape 63, plus
nohup/4040/appeasement/changeset-format checks — all pass.
- 2 changesets added for the published-`@runfusion/fusion` behavior
fixes (workspace landedSha, sub-repo worktree branch-strip).

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Improved reliability for partial workspace land retries by recovering
the exact proven landed commit so durable merge proofs can complete.
* Fixed per-sub-repo worktree creation by removing invalid branch
override settings, preventing worktree-add failures.
* Dashboard stability updates: preserve mobile board scroll during
stabilization/restore, correct task filtering when workflows are
missing, ensure the Chat tab appears for done tasks, and refine
modal-dismiss and responsive popover behavior.
* **Documentation**
* Expanded the GitLab connector section with GitLab parity context and a
GitLab Parity Inventory reference.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-07 22:04:17 -07:00
gsxdsm
203c734340 fix(engine): require exact trailer line, not substring, for proven landed commit (Greptile P1)
findProvenLandedCommit now keeps --grep as a prefilter but verifies each
candidate carries an actual 'Fusion-Task-Id: <taskId>' trailer line via
git show -s --format=%B, so a later commit that merely mentions the trailer
text in its body cannot be selected. Regression covers a body-mention
intervening commit.
2026-07-07 10:25:11 -07:00
gsxdsm
518c5420f2 fix(engine): recover exact proven landed commit, not current tip (Greptile P1)
findProvenLandedCommit returns the task's own trailer commit (or recorded
landedSha when still an ancestor) instead of rev-parse on the integration
tip, so an intervening sub-repo land can't attribute a later unrelated
commit. Regression: intervening commit after lost persist recovers tipAfterFirst.
2026-07-07 10:09:28 -07:00
gsxdsm
93d1f702f0 test(engine): update MCP forwarding coverage needle for FN-7446 resolvePlanningMcpServers helper 2026-07-07 09:50:34 -07:00
gsxdsm
b7aa8b38e4 test(engine): fix pi-create-fn-agent RTK/pauseForApproval + step-session-executor logging/terminal-activity assertions 2026-07-07 09:46:00 -07:00
gsxdsm
f93ce52689 test(engine): fix ModelFallbackExhaustedError pi mock + FN-7360 worktree exec counts + sync conflict mapping 2026-07-07 09:37:18 -07:00
gsxdsm
b6a8f6430f test(engine): simulate fallback split-close in single promptWithFallback call (specifyTask no longer loops) 2026-07-07 09:02:28 -07:00
gsxdsm
38406c3442 test(engine): use shared appendAgentLog timing-tolerant helper in heartbeat/triage tests (FN-7503) 2026-07-07 08:55:27 -07:00
gsxdsm
58d085efab test(engine): spread real reviewer exports in executor-test-helpers mock (3167dbc83); align stepwise/graph/prompt-override tests (FN-7265/7335) 2026-07-07 08:54:49 -07:00
gsxdsm
82e06e37c8 test(engine): fix executor step-session/liveness-gate/checkout/ce-workflow mocks (FN-7229 retry-cap + workflow verdict wiring) 2026-07-07 08:43:58 -07:00
gsxdsm
90a6b569c4 test(engine): wire planner-overseer intervention denial loop to failed-signal snapshot (FN-7577) 2026-07-07 08:42:47 -07:00
gsxdsm
f2202619e0 fix(engine): recover workspace landedSha + strip shared branch overrides for sub-repo worktrees (FN-7360) 2026-07-07 08:42:47 -07:00
gsxdsm
dacbead012 test(engine): stub readCommitTaskOwnership in worktrunk-self-healing to isolate git worktree prune plumbing 2026-07-07 08:40:44 -07:00
gsxdsm
76014898e8 test(engine): add shared appendAgentLog timing-tolerant helper; fix merger-merge-details assertions (FN-7503) 2026-07-07 08:39:25 -07:00
gsxdsm
4ae71b2a15 test(engine): pin FN-4944 already-on-main fast-path noop log in post-finalize test 2026-07-07 08:37:04 -07:00
gsxdsm
443005d9b2 test(engine): repair project-engine mocks (OAuthRefreshScheduler + WS fail-closed getTask) 2026-07-07 08:37:04 -07:00
gsxdsm
f3c50de7e7 test(engine): stub reconcileSupersededGeneratedFixFeatures on mission-validation-trigger-gap mocks
recoverActiveMissions (mission-execution-loop.ts:263) calls
missionStore.reconcileSupersededGeneratedFixFeatures per slice; the 5
MissionExecutionLoop-backed mocks here omitted it, so recovery threw
(TypeError) at the slice loop and aborted before processTaskOutcome /
ensureFeatureAssertionLinked / startValidatorRun ran — 4 tests failed.

Add a no-op stub (matches mission-execution-loop.test.ts reference) with
an FNXC:MissionReconcile note. No-op is correct: supersession is not
exercised by these tests.
2026-07-07 08:24:35 -07:00
gsxdsm
860e42d1bf Merge branch 'main' into fix/push-after-merge-nff-retry-loop 2026-07-07 07:37:41 -07:00
fusion-merge-train
f70974b1af fix(engine): bounded retry loop for push-after-merge non-fast-forward rejections
A single retry can still lose the race on busy repos if origin moves again
in the pull-rebase/push window. Generalize the one-shot retry into a bounded
loop (3 attempts, 2s/5s/10s backoff), re-pulling+rebasing before each push
attempt and bailing early once the failure is no longer non-fast-forward.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 16:17:35 +02:00
fusion-merge-train
4a4819b3ee fix(engine): persist push-after-merge failures instead of dropping them
When pushAfterMerge fails (or throws) in direct-merge mode, the daemon
only wrote to the process-wide mergerLog and a transient MergeResult
field, then unconditionally marked the task done. There was no durable
record on the task or in the audit trail, so a diverged local main
could go unnoticed indefinitely — this is how our local main drifted
162 commits from origin before it was caught by hand.

Record the failure through the two channels merger.ts already has for
this: the dormant "push:origin" GitMutationType via audit.git(), and a
task log entry via store.logEntry(). Both calls are best-effort
(.catch(() => undefined)) so a logging failure can't abort the merge
flow itself — task completion behavior is unchanged, the failure is
just no longer invisible.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 13:55:33 +02:00
gsxdsm
9e5c025113 FN-7608: block executors on pending approvals instead of allowing workarounds
Executors could previously treat a pending approval as a normal turn end and go hunt for ungated workarounds instead of stopping. This change makes wait-for-approval a hard suspend point.

- wait-for-approval now suspends the in-flight executor session via awaitAbortInFlightTaskWork
- Dedupe identical pending approvals so repeated waits don't pile up
- Executor prompts now carve out awaiting-approval as a legitimate turn end (agent-prompts.ts)
- Extend provisioning-gate and agent-action-gate coverage for the new suspend/carveout behavior
- Add changeset (patch) documenting the fix for release notes
- Update docs/agents.md and docs/architecture.md to describe the new blocking behavior

Files changed:
 .changeset/fn-7608-awaiting-approval-blocking.md   |   7 ++
 docs/agents.md                                     |   1 +
 docs/architecture.md                               |   1 +
 packages/core/src/agent-prompts.ts                 |   5 +
 .../engine/src/__tests__/agent-action-gate.test.ts |  82 +++++++++++++
 .../executor-approval-gate-suspend.test.ts         | 128 +++++++++++++++++++++
 .../executor-approval-prompt-carveout.test.ts      |  61 ++++++++++
 packages/engine/src/agent-heartbeat.ts             |  13 +++
 packages/engine/src/executor.ts                    |  28 +++++
 packages/engine/src/pi.ts                          |  22 +++-
 .../sandbox/__tests__/provisioning-gate.test.ts    |  29 +++++
 packages/engine/src/sandbox/provisioning-gate.ts   |  11 ++
 12 files changed, 384 insertions(+), 4 deletions(-)

Fusion-Task-Id: FN-7608

Fusion-Task-Lineage: 9e42d8ee-bda7-4ef1-b159-46c2100bbc48

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-06 19:03:07 -07:00
gsxdsm
203f879c8f FN-7611: respect workflow intake column on task creation
Task creation surfaces stopped hardcoding column:"triage", so new tasks now land in the selected-or-default workflow's resolved intake column instead of always jumping to Planning/triage.

- Removed hardcoded column:"triage" override in engine's createTaskCreateTool (fn_task_create), letting TaskStore.createTask resolve the landing column from the workflow's intake-trait column.
- Removed the equivalent hardcoded override in the pi extension's fn_task_create, and updated its response text to echo the actual landing column instead of a fixed "Column: triage" string.
- Fixed signal-route, GitHub-import, and planning-subtask-route task creation to stop forcing column when no workflowId is given (or, for planning subtask routes, even when one is provided).
- Custom workflows with a non-triage intake column (e.g. Inbox) now correctly capture new cards inert until released, while the default builtin:coding workflow still resolves to "triage" byte-identically.
- Added regression coverage (agent-tools-intake-column.test.ts, extension-workflow-tools.test.ts) and a patch changeset documenting the fix.

Files changed:
 .changeset/fn-7611-intake-column.md                |   7 ++
 .../src/__tests__/extension-workflow-tools.test.ts |  70 +++++++++++
 packages/cli/src/extension.ts                      |  10 +-
 .../src/__tests__/register-signal-routes.test.ts   |   8 +-
 .../dashboard/src/__tests__/routes-github.test.ts  |   2 -
 .../dashboard/src/routes/register-git-github.ts    |  16 ++-
 .../src/routes/register-planning-subtask-routes.ts |  24 +++-
 .../dashboard/src/routes/register-signal-routes.ts |   8 +-
 .../__tests__/agent-tools-intake-column.test.ts    | 138 +++++++++++++++++++++
 packages/engine/src/__tests__/agent-tools.test.ts  |   1 -
 packages/engine/src/agent-tools.ts                 |  21 +++-
 11 files changed, 288 insertions(+), 17 deletions(-)

Fusion-Task-Id: FN-7611

Fusion-Task-Lineage: daf7f755-b1c7-4859-b74f-f15593d5e79e

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-06 19:03:07 -07:00
gsxdsm
44442622c5 FN-7609: show gated action payload details on approval requests
Approval cards previously showed only a generic gating message with no visibility into the underlying command/arguments being approved, and repeated pending requests for the same action could pile up as duplicates.

- Add GatedActionApprovalDetails component to render the gated command/arguments payload on agent-gating approval cards in MailboxView
- Persist approvalDedupeKey in targetAction.context and a payload-bearing summary via buildAgentGatedActionSummary in permanent-agent-gating
- Wire agent-heartbeat, executor, and pi to pass through the richer gated-action context/summary
- Add changeset (patch) documenting the fix
- Update docs/dashboard-guide.md
- Add/extend tests: GatedActionApprovalDetails, MailboxView, permanent-agent-gating, pi-create-fn-agent

Files changed:
 .changeset/FN-7609-gated-action-approval-payload.md            |  7 ++
 docs/dashboard-guide.md                                        |  1 +
 packages/core/src/types.ts                                     |  8 +++
 .../app/components/GatedActionApprovalDetails.css              | 50 ++++++++++++++
 .../app/components/GatedActionApprovalDetails.tsx              | 72 +++++++++++++++++++
 packages/dashboard/app/components/MailboxView.tsx               | 12 ++++
 .../__tests__/GatedActionApprovalDetails.test.tsx               | 66 ++++++++++++++++++
 .../app/components/__tests__/MailboxView.test.tsx                | 41 +++++++++++
 .../src/__tests__/permanent-agent-gating.test.ts                 | 31 +++++++++
 .../src/__tests__/pi-create-fn-agent.test.ts                     | 80 ++++++++++++++++++++++
 packages/engine/src/agent-heartbeat.ts                           | 19 ++++-
 packages/engine/src/executor.ts                                  | 19 ++++-
 packages/engine/src/permanent-agent-gating.ts                    | 53 ++++++++++++++
 packages/engine/src/pi.ts                                        |  6 ++
 14 files changed, 461 insertions(+), 4 deletions(-)

Fusion-Task-Id: FN-7609

Fusion-Task-Lineage: 80a6bb5b-79f7-4b78-9204-402c2dea6171

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-06 19:03:06 -07:00
gsxdsm
60081fb1f4 FN-7610: route workspace-mode tasks around PR-merge auto-merge strategy
Fixes workspace-mode (workspaceWorktrees) tasks failing auto-merge under mergeStrategy=pull-request, where processPullRequestMergeTask threw "could not determine repository" because the workspace root is a container of independent git sub-repos, not itself a git repo.

- Hoist an isWorkspaceTask check in ProjectEngine's merge dispatch (project-engine.ts) before the mergeStrategy branch, so workspace tasks always fall through to the existing direct/landWorkspaceTask path regardless of configured mergeStrategy.
- Add processPullRequestMergeTask and syncGroupPrCallback defense-in-depth guards (task-lifecycle.ts) that throw the new named WorkspaceTaskMergeError if a workspace task ever reaches the PR-merge path.
- Add engine tests covering multi-repo, single-repo, and zero-commit no-op workspace tasks under mergeStrategy=pull-request, plus a non-regression test for the legacy single-worktree PR path.
- Add CLI tests asserting the new guards throw WorkspaceTaskMergeError.
- Add a patch changeset describing the fix.

Files changed:
 .changeset/fn-7610-workspace-pr-merge-routing.md   |   7 ++
 .../src/commands/__tests__/task-lifecycle.test.ts  |  56 +++++++++
 packages/cli/src/commands/task-lifecycle.ts        |  33 ++++-
 .../engine/src/__tests__/project-engine.test.ts    | 140 +++++++++++++++++++++
 packages/engine/src/project-engine.ts              |  18 ++-
 5 files changed, 252 insertions(+), 2 deletions(-)

Fusion-Task-Id: FN-7610
Fusion-Task-Lineage: 31768b77-d9a9-4a79-a055-bbc6b228a1c4
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-06 19:03:06 -07:00
gsxdsm
ed136b1e76 fix(engine): repair two stale test mocks broken by #1910 (#1919)
Two engine test files fail on current `main` (17c4007). Both are stale
**test-mock** breakages — no production code is touched.

### 1. `restart.integration.test.ts`
Its `vi.mock("../pi.js", …)` factory replaces the module wholesale but
omits `ModelFallbackExhaustedError`. `triage.ts` guards its catch block
with `err instanceof ModelFallbackExhaustedError` (imported from
`pi.js`), so evaluating that guard throws *"No
ModelFallbackExhaustedError export is defined on the mock"*.

Fix: export a plain `Error`-subclass stub from the factory. No restart
test enters the fallback-exhausted branch, so `instanceof` simply
returns `false` — a faithful stub.

### 2. `reliability-interactions/mission-validation-trigger-gap.test.ts`
Two recovery-path `missionStore` mocks omit `getMission`. #1910's
mission-active gate now walks `getSlice → getMilestone → getMission`
inside `resolveFeatureMission`. The resulting throw is swallowed by
`processTaskOutcome`'s `catch`, aborting recovery before it can ensure
assertions / start the validator run — surfacing as
`ensureFeatureAssertionLinked` asserted called-once but seen 0 times.

Fix: add `getMission` returning an active mission to both mocks.

### Verification
```
npx vitest run src/__tests__/reliability-interactions/mission-validation-trigger-gap.test.ts src/__tests__/restart.integration.test.ts
Test Files  2 passed (2)
Tests  54 passed (54)
```

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Tests**
* Improved coverage for mission recovery and restart flows, making
validation scenarios more reliable.
* Fixed test mocks so recovery and triage paths can run without
unexpected errors during assertions.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-06 13:11:45 -07:00
gsxdsm
cc98852027 fix(engine): defer validator fail when the judged workspace predates the merged code (#1929)
## Problem

The mission validator runs its read-only judge session with `cwd:
this.rootDir` — the engine's **main working copy**. When a task's merge
landed on the remote (or in another worktree) and `rootDir` was never
fetched/reset to that commit, the judge reads **pre-merge files** and
returns a spurious `fail`.

#1917's premerge column guard does not catch this case: by the time
validation runs the task column is already `done`, so execution falls
through to `handleValidationFail` and mints a **bogus Fix Feature** for
code that is actually correct and merged.

## Fix

A symmetric second guard in the `fail` branch, placed **after** the
#1917 premerge column check:

- `isValidationWorkspaceStale(feature)` resolves the task's integration
SHA and runs `git merge-base --is-ancestor <sha> HEAD` in `rootDir`.
- **Only affirmative staleness evidence defers.** `--is-ancestor` exit
`1` (the SHA is *not* an ancestor of HEAD → the workspace predates the
merge) → defer the fail to **inconclusive**, so a later validation
judges the merged code.
- Every other outcome trusts the fail: exit `0` (ancestor → workspace is
fresh), no integration SHA available, or a bad/unknown object (exit
`128`).

Fail-open doctrine, matching #1917: a guard may only ever **defer** a
fail, never **suppress** one on missing or unreadable data.

## Tests

Four real-git cases in `mission-execution-loop.test.ts` (skipped when
`git` is unavailable):

1. Judged checkout predates the merged commit → fail deferred to
inconclusive, no Fix Feature minted, emits `validation:inconclusive`.
2. Merged commit is an ancestor of HEAD (fresh workspace) → normal fail
path, Fix Feature minted, emits `validation:failed`.
3. Task carries no integration SHA → fail open (normal fail).
4. Integration SHA is an unknown object (exit 128) → fail open (normal
fail).

Verified with stash-red/restore-green discipline: with the production
guard stashed, case (1) goes red while the three fail-open guardrails
stay green — proving case (1) exercises the fix. Full file: 62 passed.
`tsc --noEmit`: clean.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Validation failures are now deferred when the workspace appears to be
out of date with merged changes, reducing incorrect failure reports.
* Tasks with linked work continue to use the usual failure path when the
current workspace is up to date.
* Staleness checks now avoid masking real validation failures when no
merge reference is available or when the reference can’t be verified.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-06 13:11:21 -07:00
fusion-merge-train
58683f9610 fix(engine): defer validator fail when the judged workspace predates the merged code
The mission validator runs its read-only judge session with cwd:
this.rootDir — the engine's main working copy. When a task's merge landed
on the remote or in another worktree and rootDir was never fetched/reset to
it, the judge reads PRE-merge files and returns a spurious `fail`. #1917's
premerge column guard doesn't catch this: the task column is already `done`,
so it falls through to handleValidationFail and mints a bogus Fix Feature.

Add a symmetric second guard in the fail branch, after the premerge column
check: isValidationWorkspaceStale resolves the task's integration SHA and
runs `git merge-base --is-ancestor <sha> HEAD` in rootDir. Only affirmative
staleness evidence (exit 1 = NOT an ancestor) defers the fail to
inconclusive; exit 0 (ancestor/fresh), a missing SHA, or a bad object
(exit 128) all trust the fail. Fail-open: a guard may DEFER a fail, never
SUPPRESS one on missing or unreadable data.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 08:23:07 +02:00
Phil Larson
3d60f67fab fix(missions): address generated fix review blockers 2026-07-05 22:14:22 -07:00
Phil Larson
3744fbcc2f fix(missions): supersede stale generated fix features 2026-07-05 21:50:28 -07:00
gsxdsm
6c9989c847 fix(engine): short-circuit zero-commits-ahead branch before AI-merge clean-room churn (#1920)
## Problem

When a coding agent produces **zero commits** relative to base, the
AI-merge path wedges the card terminally:

1. `runAiMerge` → `landOneRepo` builds a clean-room worktree and runs a
dependency install.
2. On a non-workspace land the dep-install step throws hard (`if
(!ctx.nonFatalDependencySync) throw depsErr;`).
3. The throw is transient-classified and retried up to
`MAX_AUTO_MERGE_TRANSIENT_RETRIES` → `Auto-merge transient retries
exhausted (3/3)`.
4. The card is parked `failed` (→ archived), even though the correct
outcome for an empty branch is a no-op finalize.

The truly-empty branch *would* reach `outcome: "empty"` anyway via
`mergeAndReview` producing no `squashSha` — but only **after** the
throw-prone churn that fails first.

The canonical `aiMergeTask`/`classifyOwnedLandedEvidence` path already
has an early empty-own-diff fast-path; the `runAiMerge` → `landOneRepo`
path did not.

## Fix

Short-circuit inside `landOneRepo`, right after the `tipSha` computation
and **before** the clean-room build, when the branch is a confident zero
commits ahead of the integration tip:

```ts
const aheadRaw = await git(["rev-list", "--count", `${integrationBranch}..${branch}`], repoRootDir).catch(() => "");
if (Number.parseInt(aheadRaw.trim(), 10) === 0) {
  await audit.git({ type: "merge:ai-empty", target: integrationBranch, metadata: { taskId, tipSha } });
  return { outcome: "empty", tipSha, integrationBranch };
}
```

- Returns the **identical** `{ outcome: "empty", tipSha,
integrationBranch }` shape and the same `merge:ai-empty` audit event the
downstream already handles, so `runAiMerge`'s empty-outcome handling
(block-to-todo / no-op finalize) is unchanged.
- **Only** short-circuits on a confident `0`: a git failure yields `""`
→ `parseInt` → `NaN` (≠ 0) and falls through to the normal path — no
behavior change on error.
- Placed in `landOneRepo` (not `runAiMerge`) because it is shared by
both the single-repo and workspace per-repo callers.

## Test

New test in `merger-ai.test.ts` asserts the merge agent is **never
invoked** for a 0-ahead branch, the result is a no-op, `main` is
unmoved, and the card moves to `done` with `preserveProgress`. Without
the fix the branch reaches the clean room and `mergeAndReview` invokes
the merge agent, so the assertion fails — it genuinely guards the
short-circuit.

Full engine merge-suite regression run is green (merger-ai,
workspace-merger and lease variants, cleanup, dependency-sync,
group-merge, classify-owned-landed-evidence).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Improved merge handling for branches with no new commits ahead of the
target branch.
* Empty or already-synced branches now finish as a no-op instead of
triggering merge work.
* Tasks in this scenario still move to done, while the main branch
remains unchanged.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-05 21:37:26 -07:00
gsxdsm
9b7921b7d8 fix(engine): defer validator fail to inconclusive while the linked task is unmerged (#1917)
## Problem

`MissionExecutionLoop.runFeatureValidation` treats a validator "fail"
verdict as authoritative regardless of whether the linked task's code
has actually landed. When validation fires while the task is still
mid-pipeline — an in-review PR, an external merge train, a deferred base
sync — the validator judges a checkout that predates the merge,
concludes the feature "is not present", and `handleValidationFail` mints
a Fix feature for work that is already done.

We hit this in production (2026-07-05): a recovery-path validation ran
against four features whose implementing tasks were in-review in an
external merge pipeline. All four "failed" → four duplicate Fix tasks
were created one minute after the real work merged. Worse, the Fix
tasks' planned file scopes included hot shared files, so their
file-scope leases serialized the entire board until they were manually
archived.

## Fix

Before dispatching a `fail` verdict, resolve the linked task's column.
If it affirmatively shows the task has **not** completed (any column
other than `done`/`archived`), route the outcome to
`handleValidationInconclusive` (R21 — completes the run as `blocked`,
logs `verification_inconclusive`, notifies autopilot, **spawns no Fix
feature**) with a "code not merged yet — validation deferred" reason. A
later validation (post-merge recovery pass) judges the real merged code.

**Fails open by design** — the guard may only ever *defer* a fail, never
suppress one on missing data. Missing `taskId`, missing task, unreadable
store, or unknown column all fall through to the normal
`handleValidationFail` path:

```ts
private async getPremergeTaskColumn(taskId: string | undefined): Promise<string | null> {
  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;
  return column;
}
```

The vanilla flow is unaffected: the scheduler triggers validation on
`toColumn === "done"`, so by the time a normally-triggered validation
runs the task is already `done` and the guard is a no-op. Only
recovery-path / re-validation runs that race an unmerged task are
deferred.

## Tests

Three new tests in `mission-execution-loop.test.ts` (`premerge guard`
describe):

1. fail verdict + linked task `in-review` → routes to inconclusive: no
Fix feature, run completed as `blocked`, `validation:inconclusive`
emitted (not `validation:failed`), `verification_inconclusive` mission
event logged
2. fail verdict + linked task `done` → normal fail path: Fix feature
created, `validation:failed` emitted
3. fail verdict + `taskStore.getTask` rejects → fails open to the normal
fail path

`npx vitest run src/__tests__/mission-execution-loop.test.ts`: 58/58
green. `npx tsc --noEmit`: clean. Full engine suite: the 46 failures
across 24 files present on my branch fail **identically on clean
`17c4007`** (verified by re-running the same files on a detached
checkout of upstream main) — all pre-existing/environment-dependent,
none related to this change.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Validation failures now account for whether the linked task is
actually merged. If the task is still in progress, the result is marked
as inconclusive instead of creating a fix flow.
* Added clearer handling when task details can’t be read, so normal
failure behavior still applies.
* Improved validation status reporting and event logging for merged vs.
unmerged task states.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-05 21:37:04 -07:00
gsxdsm
51f354dec1 Merge branch 'main' into fix/stale-test-mocks 2026-07-05 21:36:07 -07:00
gsxdsm
f7d1346724 Merge branch 'main' into fix/validator-premerge-guard 2026-07-05 21:04:34 -07:00
gsxdsm
1ee2e3a27c Merge branch 'main' into fix/transient-auth-retry 2026-07-05 21:02:11 -07:00
gsxdsm
9bbb16c4a1 Merge branch 'main' into fix/empty-branch-merge-wedge 2026-07-05 21:01:51 -07:00
gsxdsm
78d570747f fix(engine): self-heal failed in-review cards whose PR merged on the remote (#1922)
## Problem

A transient error at merge time can flag an in-review card `failed` even
when its PR actually squash-merged on the remote (human merge,
merge-train, etc.). `recoverAlreadyMergedReviewTasks` runs the
already-merged **evidence detector** only against the **local** base
ref. If this process never fetched the merge, the owned commit is absent
locally → the detector returns `null` → `landed` is null → the card
never finalizes and **holds its file-scope lease forever**, wedging
every other task that touches the same files.

## Fix — fetch-then-prove

When a `failed` in-review candidate has a recorded PR
(`getPrimaryPrInfo`) and the local base yields no owned commit:

1. best-effort `git fetch origin <base>` (new `refreshRemoteBaseRef`
helper), then
2. re-run the **same** evidence detector against `origin/<base>`.

The detector's owned-commit proof and every foreign-ownership guard
inside it remain the **sole** finalize gate, so this only un-wedges a
genuinely-merged task — it never phantom-finalizes on unproven state.

**Safety:**
- Gated on a recorded PR — no PR ⇒ nothing could have merged remotely ⇒
no fetch.
- Fail-closed — a fetch error (offline / auth / no remote) is swallowed;
if `origin/<base>` can't be resolved the card is left untouched.
- No new dependency, no github client seam — direct git only.

## Tests

Two real-git tests in `self-healing-already-merged.real-git.test.ts`,
both verified to **fail without the prod change**:

- **fetch-then-prove positive:** a PR squash-merges on a bare remote
while the local base stays stale → recovery fetches, proves the owned
SHA against `origin/main`, and finalizes the card to `done`
(`mergeConfirmed: true`, worktree removed).
- **phantom-finalize guard:** remote base advances with a commit owned
by a *different* task → the fetch still runs, but the detector proves
nothing → the card is left `failed`/`in-review`, never healed.

Full self-heal suite: 594 passed. Engine typecheck clean.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Improved recovery for review tasks that appear already merged when the
local base branch is stale.
* The app now refreshes the remote base branch before re-checking merge
status, helping finalize tasks correctly and clean up completed
worktrees.
* Added coverage for cases where the remote base has moved forward with
either the merged commit or unrelated changes.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-05 20:59:01 -07:00
gsxdsm
ba9c9b5ca7 Merge branch 'main' into fix/self-heal-merged-pr-stale-base 2026-07-05 20:47:22 -07:00
gsxdsm
80a05e41fe Merge branch 'main' into fix/hermes-runtime-chat-state 2026-07-05 20:45:47 -07:00
gsxdsm
eb86555797 chore(release): v0.56.1
Version bump via changesets.
2026-07-05 19:57:09 -07:00