- syncGroupPrCallback forwards owner/repo to updatePr (multi-project daemons
could 404 or edit an unrelated same-numbered PR via process-cwd fallback)
- merger background reconcile re-reads the group before persisting and skips
the write when the PR snapshot changed (stale-write race vs newer open PR)
- branchContext.groupId trimmed on metadata emit/parse round-trip
- triageSlice non-shared invariant assertions (no groupId, no group row)
Review residuals #3/#4/#6/#10: per-group in-process promotion lock (concurrent
route+auto promotion could double-create PRs), finalized-but-PR-less groups can
be repaired by re-promotion without re-merging, auto-promotion failures emit
merge:branch-group-promotion-failed instead of silent swallow, exported
reconcileBranchGroupPr for out-of-band merged reconciliation, and the merger
sync block drops its (store as any) casts (TaskStore already carries the
methods).
Code review (Tier 2) found two P1s: (1) the early no-op fast-path persisted
mergeConfirmed/mergeTargetBranch without mergeTargetSource, so a shared-group
member landing via it could never satisfy the strict completion predicate —
promotion permanently blocked; thread mergeTarget.source through like the
standard landing sites. (2) createGroupPrCallback's findPrForBranch used
state:'all' and could reuse a closed/merged PR from a prior group, persisting
a terminal prState onto a fresh promotion; create path now matches open PRs
only.
Engine half: real-git E2E covering planning- and mission-sourced groups —
members land on the group branch (never main/sibling), completion-gated single
PR via injected callback, re-promote idempotency, sync on later landing,
abandon→closed, and a self-healing finalize mid-flow staying group-anchored.
Core half: real triageFeature stamps the BG- id, member enumeration, and the
canonical completion gate flipping on landing.
Push the single group PR's body (member checklist, x/N landed) on each member
landing via an injected SyncGroupPrFn — new updatePr/closePr GitHubClient
helpers (gh CLI + API parity); refreshPrInBackground is task-scoped/wrong
direction and intentionally not reused. Sync failures are non-fatal+retryable;
out-of-band closed/merged PRs reconcile prState instead of erroring. New
POST /branch-groups/:id/abandon closes the PR best-effort and marks the group
abandoned. Also fixes the U5-introduced stub-context regression in the U4
dashboard bridge test (missing options).
Group promotion in PR mode previously flipped prState to 'open' without ever
calling GitHub — prNumber/prUrl were never populated. Add an injected
CreateGroupPrFn (mirrors the processPullRequestMerge seam, no engine→dashboard
import): coordinator creates-or-reuses exactly one PR per group, persists
prNumber/prUrl/prState, and leaves state untouched on GitHub failure so
re-promotion retries. Idempotent via persisted prNumber +
getBranchGroupByBranchName. Wired at all three CLI engine-construction sites
(daemon/dashboard/serve).
The dashboard promote route called engine.promoteBranchGroup(groupId) as a
method that never existed — only a standalone coordinator function did — so
the route was dead, masked by a vi.fn mock in the test. Add the real method on
ProjectEngine delegating to the coordinator (resolving store/cwd/settings like
attemptBranchGroupPromotion), and de-mock the test so it now fails if the
method goes missing. No PR-creation behavior yet (U5).
Audit of all shared-member merge + self-healing finalize paths: routing,
merger finalize-success, and the 6 self-healing recovery paths were already
group-branch-safe (FN-5846). Found a residual of the 2026-05-23 lost-work
incident bug #2: already-merged-detector's ancestry strategy used bare
git log --grep first-hit, and the ownership regex made the conventional scope
optional (bare 'feat:' matched). Anchor attribution on trailers or task-scoped
subject; scan candidates instead of accepting the first grep hit. Adds real-git
characterization tests.
Route and coordinator disagreed on landed/complete: the route required
mergeConfirmed + matching mergeTargetBranch, the coordinator accepted bare
column===done/in-review and never checked the branch. Extract canonical
isBranchGroupMemberLanded/isBranchGroupComplete in @fusion/core (stricter
route semantics win — load-bearing for merge-target safety) and consume from
both sides. Tightens promotion gating to fire only when all members are
merge-confirmed onto the group branch.
The FN-5930 squash merge combined two wait-for-exit strategies,
leaving a shadowed 'const exited' and double await. Collapse to
a single register-before-kill pattern.
Fusion-Task-Id: FN-5930
Reduce flakiness in the real-git verification spawn supervision test.\n\n- write the child PID to stdout with an awaited newline flush before the parent exits\n- collapse the scenario branching so the SIGTERM path is mutually exclusive with crash handling\n- await parent process exit during cleanup and document coverage across normal, signal, and crash teardown paths\n\nFiles changed:\n .../verification-spawn-supervision.real-git.test.ts | 9 ++++++---\n 1 file changed, 6 insertions(+), 3 deletions(-)
Fusion-Task-Id: FN-5930
Fusion-Task-Lineage: 20c3fe73-a640-4c34-8a07-edf5bdda4b25
Keep planning summary actions responsive with operation-specific loading indicators.
- pass separate single-task and breakdown loading flags into the planning summary view
- show the Creating spinner only on Create Single Task and the Breaking down spinner only on Break into Tasks while keeping the sibling action disabled
- add regression coverage for both pending-action paths and normalize the restart integration test temp worktree root under /private/tmp
Files changed:
.../dashboard/app/components/PlanningModeModal.tsx | 14 ++-
.../PlanningModeModal.planning-flow.test.tsx | 138 +++++++++++++++++++++
.../src/__tests__/restart.integration.test.ts | 7 +-
3 files changed, 151 insertions(+), 8 deletions(-)
Fusion-Task-Id: FN-5912
Fusion-Task-Lineage: b93da566-0c2b-4ff8-83ea-b6e009dfd650
Add unread mailbox status to the desktop header toggle while keeping pending approvals prioritized.
- show an unread status dot on the desktop header mailbox toggle when unread mail exists without pending approvals
- keep pending-approval indicators taking precedence and hide mailbox indicators while the mailbox view is active
- extend Header coverage for unread-only, pending-only, combined, zero-count, and active-mailbox states
- update restart integration coverage to reuse an existing worktree during orphaned resume concurrency
Files changed:
docs/dashboard-guide.md | 2 +-
packages/dashboard/app/components/Header.tsx | 9 +++++--
.../app/components/__tests__/Header.test.tsx | 26 ++++++++++++++++---
.../src/__tests__/restart.integration.test.ts | 29 ++++++++++++++++++++--
4 files changed, 58 insertions(+), 8 deletions(-)
Fusion-Task-Id: FN-5915
Fusion-Task-Lineage: 3a207e2c-ca58-402f-94cf-3a9e514fe263
Ensure agent-created ntfy notifications fall back to the task description when the title is still empty.
- pass task descriptions through task-created notification payloads
- cover empty-title task creation in notification service tests
- verify ntfy task-created messages render the id+description fallback
- add a patch changeset for the published CLI package
Files changed:
.changeset/fn-5890-task-created-title.md | 5 +++++
.../engine/src/__tests__/notification-service.test.ts | 12 +++++++++++-
packages/engine/src/__tests__/ntfy-provider.test.ts | 16 ++++++++++++++++
packages/engine/src/notification/notification-service.ts | 1 +
4 files changed, 33 insertions(+), 1 deletion(-)
Fusion-Task-Id: FN-5890
Fusion-Task-Lineage: 4a67a4d0-c3a8-4923-a506-7a67a8836127
Retry incomplete executor work with a fresh session after non-continuable session errors.
- add executor recovery handling that clears session state and requeues incomplete tasks to todo while retry budget remains
- preserve terminal failure behavior once the fresh-session retry budget is exhausted
- extend reliability coverage and AGENTS.md backstop notes for the new retry path
Files changed:
AGENTS.md | 1 +
.../post-done-continuation-no-wedge.test.ts | 48 +++++++++++++++++++++-
packages/engine/src/executor.ts | 40 ++++++++++++++++++
3 files changed, 87 insertions(+), 2 deletions(-)
Fusion-Task-Id: FN-5888
Fusion-Task-Lineage: 4ebce260-8923-4599-aced-541533b94543
Persist merge metadata for AI fast-forward landings and done-task recovery.
- capture landed files and shortstat metadata from the single landed commit in the AI merge finalizer
- persist mergeDetails and modifiedFiles for landed squash commits, and record commit associations without setting no-op attribution flags
- extend done-task self-healing to backfill merge metadata when baseCommitSha exists but mergeDetails is empty
- add reliability and self-healing coverage for landed-file persistence, empty AI merges, and recovery behavior
Files changed:
AGENTS.md | 1 +
.../ai-merge-ff-landed-files.test.ts | 150 +++++++++++++++++++++
packages/engine/src/__tests__/self-healing.test.ts | 76 +++++++++++
packages/engine/src/merger-ai.ts | 40 +++++-
packages/engine/src/merger.ts | 30 ++++-
packages/engine/src/self-healing.ts | 11 +-
6 files changed, 303 insertions(+), 5 deletions(-)
Fusion-Task-Id: FN-5874
Fusion-Task-Lineage: a407910c-9ce8-4049-86ef-e80f045c981a
Tighten the branch-conflicts recovery test to use an isolated secondary worktree root.
- create a unique temporary parent directory for the detached secondary worktree
- track the temporary root for cleanup instead of only the nested worktree path
- keep the reanchor regression focused on detached worktree recovery behavior
Files changed:
packages/engine/src/__tests__/branch-conflicts-recovery.test.ts | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
Fusion-Task-Id: FN-5863
Fusion-Task-Lineage: 6cfb02c0-e9fd-456d-a78b-451e3e0707b2
Update Fusion to the latest pi coding agent and pi AI dependency release.
- bump @earendil-works/pi-coding-agent from ^0.77.0 to ^0.78.0 in the CLI, dashboard, and engine packages
- bump @earendil-works/pi-ai from ^0.77.0 to ^0.78.0 where it is consumed
- add a patch changeset documenting the published @runfusion/fusion update and refresh the lockfile
Files changed:
.changeset/fn-5862-pi-coding-agent-0.78.md | 5 +
packages/cli/package.json | 4 +-
packages/dashboard/package.json | 2 +-
packages/engine/package.json | 4 +-
pnpm-lock.yaml | 193 +++++++++++++++++++----------
5 files changed, 138 insertions(+), 70 deletions(-)
Fusion-Task-Id: FN-5862
Fusion-Task-Lineage: abdfa987-7ba5-45f7-8bea-3db3329a760c
Resolve goal tool lookups to the canonical project store from Fusion worktree directories.
- recognize both legacy .worktrees and .fusion/worktrees paths when resolving the project root for pi extensions
- add CLI coverage proving fn_goal_list and fn_goal_show return dashboard-created goals from a Fusion worktree cwd
- extend core worktree-resolution tests and keep the branch conflict recovery test worktree path isolated in tmpdir
- add a patch changeset for the published CLI fix
Files changed:
.changeset/fn-5851-goal-store-resolution.md | 5 +
packages/cli/src/__tests__/goal-store-resolution.test.ts | 104 +++++++++++++++++++++
packages/core/src/__tests__/pi-extensions.test.ts | 14 ++-
packages/core/src/pi-extensions.ts | 12 ++-
packages/engine/src/__tests__/branch-conflicts-recovery.test.ts | 2 +-
5 files changed, 130 insertions(+), 7 deletions(-)
Fusion-Task-Id: FN-5851
Fusion-Task-Lineage: ec4c8239-b953-4df7-9f72-02e17e4b0851
Root cause: node:sqlite SIGSEGVs inside pager_write leave the B-tree
malformed in a way that still opens but fails integrity checks; large
operational-log tables widen the write window where the crash strikes.
- backup: verify every copy with PRAGMA quick_check, quarantine corrupt
copies as *.corrupt, and never rotate out the last verified-good backup
- db: add Database.recoverIfCorrupt() startup guard (wired into
TaskStore.init, disk-backed only, opt out via FUSION_DISABLE_DB_AUTORECOVER)
that rebuilds a malformed db via sqlite3 .recover, preserving the corrupt
original; also fixes the latent `.recover main` invalid-option bug that made
recoverDatabase() always fail
- db: drop lost_and_found* scratch tables on init; add pruneOperationalLogs()
- settings: add operationalLogRetentionDays (default 30, 0 = off) and prune
activityLog/agentLogEntries/runAuditEvents/agentHeartbeats during maintenance
- dashboard: expose retention in Settings -> Backups -> Database Maintenance
Tests: backup 59/59, db 135/135 (incl. real corrupt->recover->reopen),
self-healing cleanup/corruption 10/10, settings 77/77, SettingsModal 460/460.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>