The vitest subprocess guard's 60 s tracking timer could outlive the test
that spawned it and fire during a later test's afterEach, producing
spurious "Timed out after 60000ms" failures attributed to a different
test name under concurrent recursive test load.
Scope "Left running" reporting + SIGKILL to the current test's procs but
always clear each tracked subprocess's timer so it cannot fire later.
Bump the post-test grace from 200 ms to 1 s to absorb event-loop
contention from slow git shells.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The 769–1024px breakpoint forced 6 columns into the visible width with
`grid-template-columns: repeat(6, minmax(0, 1fr))` and `overflow-x:
hidden`, collapsing columns to ~130–170px on Android tablets and
stacking task card titles one word per line. Switch to
`minmax(260px, 1fr)` with `overflow-x: auto` so columns keep a readable
minimum width and the board scrolls horizontally, matching desktop.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ChatView and QuickChatFAB both had an iOS-specific onTouchStart on the
textarea that called event.preventDefault() and then programmatically
re-focused the input — meant to suppress iOS's visualViewport auto-scroll
on re-focus. On Android, preventDefault on a textarea touchstart blocks
the soft keyboard from opening (programmatic focus() alone does not raise
the Android keyboard — only the default touch action does), so tapping
the main chat or quick chat composer focused the input but the keyboard
never appeared, looking like an instant dismiss.
Gate the touchstart workaround to iOS via isIOS().
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stop losing uncommitted dev edits during task merges.
- `acquireReuseHandoff` no longer throws MergeHandoffRefusedError("working-tree-dirty") on a dirty reused worktree (FN-5138). It autostashes via `git add -A` + `git stash create` + `git stash store`, emits a `merge:reuse-handoff-autostash` audit event with the stash SHA and a recover command, and lets the merge proceed.
- `stashUnrelatedRootDirChanges` no longer silently proceeds when stash creation fails on a dirty tree. It throws a new `AutostashCreationFailedError`; the merger catches it and surfaces a clear "your edits are intact" message before any destructive op runs.
- New failure reason `dirty-worktree-autostash-failed` distinguishes stash failure from the old refusal.
- Tests in `merger-integration-worktree`, `merger-cwd-fallback-removed`, and `reliability-interactions/{integration-worktree-state,merge-reuse-task-worktree,cwd-integration-fallback-removed}` updated to the new contract; the FN-5348 "no cwd fallback" invariant is preserved.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two Android-specific fixes:
1. Keyboard dismissing in main chat. App.tsx derives `mobileKeyboardOpen`
from useMobileKeyboard and uses it to gate the
`project-content--with-mobile-nav` / `--with-footer` className
assignment plus MobileNavBar rendering. When the soft keyboard opened
on Android, those classes were removed and the nav unmounted, shrinking
padding-bottom by ~80px in a single render. Android Chrome treats the
resulting jump of the focused chat input as the focus target moving and
instantly dismisses the keyboard. With interactive-widget=resizes-content
set on Android, the layout viewport itself shrinks with the keyboard, so
the hide-nav-on-keyboard pattern was redundant on Android (and harmful).
The whole pattern is now gated to iOS via isIOS(). iOS path is unchanged.
2. Pinch-zoom on kanban. Android Chrome ignores user-scalable=no for a11y,
and kanban's overflow-x:auto columns combined with the inflated ICB
produce a broken visual when the user zooms out. Adds
touch-action: pan-x pan-y to html,body inside the mobile media query
(keeps scroll panning, blocks pinch-zoom). Chat and MissionManager were
unaffected before because they don't expose a wide horizontal
scrollable region.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The body scroll-lock applied while the keyboard is up in main chat was an
iOS-specific workaround for visualViewport drift. On Android Chrome the same
mutation does the opposite of what we want — applying position:fixed to body
while the soft keyboard is opening causes Chrome to treat it as a focus-
target relayout and dismiss the keyboard instantly, making the main chat
composer unusable on Android.
useMobileScrollLock now early-returns on non-iOS user agents. Android Chrome
doesn't need it: with interactive-widget=resizes-content the layout viewport
shrinks with the keyboard, so there's no drift to compensate for.
Adds an Android-UA test case that asserts the lock is a no-op there.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Some Android Chrome builds (multi-window / split-screen / certain WebViews)
leave window.innerWidth/Height stuck larger than the actual rendered canvas.
DOM, body, and visualViewport report the true dimensions, but position:fixed
uses the ICB, pinning fixed-bottom elements offscreen below the visible area.
JS-side meta override (setAttribute and full replacement) does not force
Chrome to recompute the ICB on those builds.
index.html now publishes the ICB→visualViewport delta as CSS variables
(--icb-bottom-offset, --icb-right-offset) on <html>. MobileNavBar.css and
ExecutorStatusBar.css consume them so the bars pin to the visible viewport
edge regardless of ICB drift. Math is visualViewport-relative so it also
handles pinch-zoom in (offsets compensate) and pinch-zoom out (clamp at 0).
Healthy browsers see 0px and behave unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Broaden mobile media query to include (max-height: 480px) so landscape
phones (which exceed 768 CSS px wide) still render the bottom nav and
mobile board layout instead of desktop horizontally-scrollable columns.
- Guard useMobileKeyboard against pinch-zoom (vv.scale > 1) — Android
Chrome ignores user-scalable=no, and a focused textarea + zoom was
false-positiving keyboard-open and hiding MobileNavBar.
- Read documentElement.clientHeight instead of stale window.innerHeight
when computing keyboard overlap (Android multi-window can leave
innerHeight cached at a wildly different value than the actual layout
viewport — observed 2848 while html was 797).
- Add interactive-widget=resizes-content to the viewport meta so Android
Chrome shrinks the layout viewport with the soft keyboard, matching iOS.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Added soft-delete blocker recovery logic to the scheduler and self-healing systems, enabling reconciliation of stale blocker reasons when tasks are archived or restored. The changes include corresponding tests for the completion guard behavior, scheduler recovery paths, and self-healing integration,
Fusion-Task-Id: FN-5496
The merge delivers the FN-5483 identity-guard bypass for merger-driven commits on detached HEAD, plus Steps 2 and 6 of FN-5479 which gate the limbo counter by enqueue acceptance and document the associated invariant. It also restores the dashboard's PWA and theme-boot contract in index.html. New reg
Fusion-Task-Id: FN-5479
WorktreePool.prepareForTask now rejects empty/"HEAD" base values and verifies
that the worktree's HEAD actually landed at the resolved base SHA after
`git checkout --detach`. This closes the FN-5432 / FN-5255 contamination
pattern where a recycled worktree branched from a stale HEAD (reflog:
"branch: Created from HEAD") and pinned the new task's tip to the previous
occupant's commit.
SelfHealingManager.tryReanchorForeignOnlyContamination is invoked from both
PR-conflict and self-owned-branch-conflict catch sites before the
unrecoverable-pause path. When the conflicted branch carries only foreign
commits (no own work), the branch is reset to base via the existing
recoverForeignOnlyContamination flow instead of being escalated for human
adjudication.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implement the hard-archived delete contract: add a typed `ArchivedTaskDeleteError`, map hard-archived task deletes to HTTP 410 Gone at both the store and routes layers, and document the invariant in the soft-delete verification matrix.
Fusion-Task-Id: FN-5196
- Drop the tablet-tier `.board` grid rule that crammed 6 columns into
≤1024px viewports with no min-width, scrunching column content to
unreadable widths. Tablets now use the default `minmax(300px, 1fr)`
and scroll horizontally like desktop.
- Drop `maximum-scale=1.0, user-scalable=no` from the viewport meta.
Combined with `initial-scale=1.0` those flags trigger Android Chrome
layout bugs in multi-window mode; the Capacitor-feel justification
isn't worth the breakage in a browser-rendered dashboard.
- Broaden the existing iOS scroll-snap stabilization in Board.tsx from
`(max-width: 768px)` to any touch-primary device, and re-run it the
first time tasks populate so Android tablets get the same first-cards-
loaded reflow that mobile already had.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contamination on fusion/<id> branches (FN-5233 was the recent example:
two untrailered feat(FN-5353): commits sitting on fusion/fn-5233) used
to be invisible until merge time, days after it happened. The executor
already runs assertCleanBranchAtBase at worktree acquisition and reclaim
— the gap was the active session window itself.
Add reportBranchAttribution(repoDir, branch, baseSha, taskId) which
walks base..branch and bins every commit into ownTrailed (healthy),
ownUntrailed (subject tag but commit-msg hook didn't fire), foreign
(different FN-id), or unattributed (no subject pattern, no trailer —
typically a hand-merge or plumbing commit). Wire it into the executor
right after captureModifiedFiles in the post-session path: when any
anomaly bucket is non-empty, emit a structured branch:attribution-
anomaly audit event and a task log entry. The audit itself is wrapped
in a try/catch so a probe failure never destabilizes a completing
session. New branch:attribution-anomaly and branch:auto-reattach-
authoritative GitMutationType variants accept the structured metadata
(the latter for the handoff re-attach added earlier this session).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
commitOrAmendMergeWithFixes used to swallow every unexpected throw as
`reason: "unknown-phantom"` and the two callers re-threw a bare
"verification fix finalize failed (unknown phantom)" with no operator-
actionable signal. FN-5422-class wedges (preAttemptHeadSha == currentHead
but branchTip ahead with task-trailered commits) couldn't be diagnosed
without re-running.
The catch now records the original error and probes whether the branch
ref itself is authoritative for the task (tip carries Fusion-Task-Id
trailer, base..branch is foreign-contamination-free). When it is — i.e.
the work isn't lost, the integration worktree just didn't advance — the
catch resets rootDir to preAttemptHeadSha so the next merge attempt
starts from a known baseline instead of inheriting partial squash state,
and returns `reason: "branch-ref-ahead-reset"`. The two callers fold
`originalError=` and `branchAuthority=` into the thrown message so the
real cause (diff-volume gate, file-scope, transient git, etc.) is
visible in the run log.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The merge handoff refused with head-branch-mismatch whenever the reused
worktree's HEAD wasn't on fusion/<id> (detached, recycled to main, or on
a sibling branch), even when the branch ref itself still held a clean,
task-attributed lineage. That wedged FN-5339-class tasks in review for
no good reason.
Add isBranchAuthoritativeForTask in branch-conflicts.ts (branch ref
exists, tip carries Fusion-Task-Id trailer, base..branch is foreign-
contamination-free) and use it in acquireReuseHandoff: when HEAD drifts
but the branch ref is authoritative, run a plain `git checkout <branch>`
inside the already-asserted-clean worktree, re-read HEAD, and emit a
branch:auto-reattach-authoritative audit. Refusal still fires unchanged
when the branch ref is missing, missing the trailer, or contaminated,
so FN-5363 strict-lease and foreign-commit guards remain authoritative.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a manual merge blocker mode (FN-5438) that prevents automatic merging and provides a bypass mechanism to resume, wired through the merger, project engine, and task workflow API routes. Includes tests across core, engine route registration, and project engine layers, plus a changeset and documen
Fusion-Task-Id: FN-5438
Merges the Layer 2.5 scope-auto-widen feature (FN-5226) into the merger: a new evaluator module that automatically widens a task's declared file scope based on git attribution prior to the existing scope partition gate, wired into `merger.ts` with full audit taxonomy, persisted task metadata, and re
Fusion-Task-Id: FN-5226
A task that picked up status='queued' or overlapBlockedBy while waiting in
todo (file-scope overlap with a higher-priority peer) was carrying those
todo-dispatch markers into in-review, where the merge gate then permanently
refused with "task is marked 'queued'". Ghost-review → todo → scheduler
re-queue → stranded-completed-todo recovery → in-review formed a steady-
state loop that never let the task merge.
moveTaskInternal now treats queued/blockedBy/overlapBlockedBy as todo-only
dispatch state and clears them on every transition into in-review. failed
and awaiting-* statuses are left untouched (already covered by an existing
test, plus a new regression test for the queued case).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Close the last fire-and-forget gap from the previous fixes: the task:moved
(away from in-progress) and task:deleted listeners no longer call the
synchronous fire-and-forget `abortInFlightTaskWork`. Instead they track an
awaited disposal promise per task in `pendingTaskDisposals`. The task:moved
(to in-progress) dispatch path awaits any in-flight disposal for the same
task before calling `execute()`, so a fast bounce (in-progress → todo →
in-progress) no longer races the conflict-cleanup path against a still-live
shell.
`awaitAbortInFlightTaskWork` now claims each session surface (activeSessions,
activeStepExecutors, activeWorkflowStepSessions, activeSubagentSessions)
synchronously before awaiting any async abort. This lets concurrent disposal
calls for the same task dedupe naturally — the second call finds the maps
empty and no-ops, preserving the existing single-abort/single-dispose
contract that the soft-delete and user-cancel tests assert.
Adds a regression test in executor-user-cancel covering the re-dispatch
ordering: an immediate task:moved-to-in-progress that follows a still-running
task:moved-away must wait for abort to complete before execute() runs.
The legacy `abortInFlightTaskWork` is removed (no callers).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three independent reliability fixes that all surfaced as the same bug:
live tasks losing their worktrees mid-execution and emitting
`wrong_toplevel` errors.
Fix A — executor stale-self-owned classifier:
`reconcileSelfOwnedActiveSessionForRemoval` now takes a process-active
probe (`executingTaskLock.has`) and a minimum-idle window (default 5s)
in addition to the existing in-memory `activeWorktrees` binding probe.
Recently-registered or still-running entries are refused with
`process-active-refuses` / `too-recent-refuses`, with audit-grade
log lines. Both the pre-remove path
(`reconcileSelfOwnedBeforeRemove`), the post-throw retry in
`removeOwnWorktreeWithReconcile`, and the defensive reconcile in
`removeWorktree` route through the same hardened gates.
Fix B — pause synchronously reaps the agent session:
New `awaitAbortInFlightTaskWork` mirrors the existing fire-and-forget
abort but awaits each `session.abort()` /
`stepExecutor.terminateAllSessions()` /
`workflowSession.abort()`. `parkTaskAfterWorkflowStepPause` calls it
before `moveTask("todo")`, and the `task:updated` user-pause handler
routes through it, so a fast re-dispatch can no longer race a still-
live shell.
Fix C — self-healing realpath + active-task skip:
`reconcileTaskWorktreeMetadata` now realpath-normalizes both sides of
the registry comparison (handling macOS `/private/var/...`) and
refuses to clear `worktree`/`branch` on in-progress or in-review
tasks. The skip emits a new
`task:auto-recover-worktree-metadata-skipped-active` audit event;
executor-level recovery paths remain in charge of active tasks.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Raised room transcript defaults (`messagesBefore` and `daysBefore`) in the core settings schema and updated project-level setting defaults, with corresponding documentation refresh in the settings reference. Added full test coverage for room compaction defaults, pinned room default settings in Setti
Fusion-Task-Id: FN-5374
The unconditional listTasks() in assertNoDependencyCycle was wasted work
for the common no-dependency write and broke the same-agent duplicate
intake fail-open path: tests that stub listTasks to throw had the cycle
check consume the rejection before _maybeAutoArchiveSameAgentDuplicate's
try/catch could swallow it, propagating the error out of createTask.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a `room-coordination.ts` helper module for multi-agent coordination messaging, wires coordination notices into the agent heartbeat path, and ships tests plus documentation for the feature. A changeset prepares the `@runfusion/fusion` package for release.
Fusion-Task-Id: FN-5425
The dashboard's corruption banner refresh action was a no-op for clearing
stale corruption flags after the user repaired the DB. Database.
scheduleBackgroundIntegrityCheck runs the integrity check exactly once at
engine boot and then early-returns forever after, so corruptionDetected
was sticky for the life of the process. POST /api/health/refresh just
read the cached flag back.
Add Database.refreshIntegrityCheck() and TaskStore.refreshDatabaseHealth()
which synchronously re-run the integrity check and update the cached
state, and have the route use them. After REINDEX / fn db --vacuum / any
in-place repair, users can now clear the banner without restarting the
engine.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Exposes the `opencode-go` provider via `startup-model-sync`, wires it into the daemon and serve commands, and adds a refresh-status indicator in the SettingsModal that triggers model reloading whenever the provider key is saved. Includes a changeset, settings documentation, and corresponding tests a
Fusion-Task-Id: FN-5424
The tasks table description column is NOT NULL, so a task arriving with
description == null/undefined would fail the insert with a constraint
error. Default to "" in getTaskPersistValues, matching the ?? null / ?? 0
treatment of other optional fields.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Backups opened a second node:sqlite connection against the live fusion.db
and ran PRAGMA wal_checkpoint(TRUNCATE) before copying. A node:sqlite
SIGSEGV mid-checkpoint (the recurring pager_write crash noted in db.ts)
could leave the main DB file extended-but-zeroed, which is exactly the
failure mode that wiped a 1GB fusion.db tonight.
Replace the in-process checkpoint with a plain cp of the main DB plus any
sibling -wal/-shm files. SQLite replays the WAL on first open, so
uncheckpointed pages survive without us ever opening a second connection
against the live database.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
FN-5407 adds paired central backup support to the Fusion task management system, with both the core backup engine and CLI commands updated to handle central database backup pairs. Documentation was updated to reflect the new capability, and two stabilization fixes were included to handle central bac
Fusion-Task-Id: FN-5407
Removes the speculative orphan requeue mutation path from self-healing, replacing it with an observation-only sweep that no longer attempts to re-enqueue orphaned tasks — a conservative regression that eliminates noisy false-positive recovery attempts. The change includes rewritten unit coverage, a
Fusion-Task-Id: FN-5337
Added manifest-gated checksum verification for cloudflared remote access tunnels: a pinned manifest validator (Step 1) and enforcement logic (Step 2) wired into the settings memory routes, with aligned tests and documentation covering fail-closed install behavior and pending-manifest guidance.
Fusion-Task-Id: FN-5375
Follow-up to 1983dac6e addressing nine findings from a code review of the
FN-5345/FN-5377 engine fixes. Includes a real bug fix (commit-message bypass
of the amend detection), two reliability invariant restorations (FN-4811 +
FN-4954 in the new D3 reuse-fallback path), a resource-leak cleanup, plus
test/audit/taxonomy polish.
HIGH
- D3 reuse-fallback now respects FN-4811 active-session safety: matches whose
path is currently owned by a different task in activeSessionRegistry are
skipped, never silently rebound. Skipped owners are recorded in audit
metadata for forensics.
- D3 reuse-fallback now respects FN-4954 pool-lease bookkeeping: when
recycleWorktrees=true AND a worktree pool is attached, the direct-reuse
shortcut is bypassed and the existing acquireTaskWorktree path is used so
WorktreePool.acquire/.release stays consistent. Without this guard the
new path could trip PoolDoubleLeaseError.
- prepare-commit-msg amend detection tokenizes the parent command line and
stops at the first message-supplying flag (-m/-F/--message/--file/=variants)
so a commit message containing the substring '--amend' cannot bypass the
guard. New regression test in prepare-commit-msg-empty-guard.real-git.test.ts.
MEDIUM
- Early empty-own-diff fast-path extracted into tryEarlyEmptyOwnDiffFinalize()
helper. Removes the exception-as-control-flow sentinel ('skip-early-fast-path:
not-reuse-mode') in favor of a plain if (eligible) { try { ... } catch {} }
block.
- Fast-path best-effort cleans up the stranded fusion/<id> worktree and branch
before completeTask(), so empty-own-diff residuals do not accumulate in
.worktrees/ or the branch namespace. FN-4811 guard ensures we never remove
a foreign-owned worktree.
- Two new audit subtypes in run-audit.ts replace the prior overloading of
merge:reuse-fallback-new-worktree:
- merge:reuse-fallback-pruned-stale-registration
- merge:reuse-fallback-reused-existing-registration
merge:reuse-fallback-new-worktree is now reserved for actual new-worktree
creation. Local emitReuseHandoffAuditEvent type union updated to match.
- New direct classifier test in merger-finalize-unproven.real-git.test.ts
('classifies proven-no-op for empty-own-diff branches') covers the new
branch in classifyOwnedLandedEvidence that self-healing and post-handoff
paths also depend on.
LOW
- Alpine/busybox ps fallback: prepare-commit-msg hook reads /proc/$PPID/cmdline
if 'ps -o args=' returns empty (busybox ps often lacks '-o args=' support).
- New backstop test variant 'FN-5345: empty-own-diff fast-path fires even
when branch is registered to two worktrees' reproduces the actual FN-5345
production wedge geometry where fusion/<id> was double-registered to two
worktrees.
Tests
- Full @fusion/engine suite: 448 files / 5881 tests / 9 skipped, all green
- pnpm lint green, pnpm build green
Three engine-level fixes for the in-review wedge class identified via FN-5345
(verification-only task with empty handoff commit + drifted worktree mapping
escalating to 'merge-deadlock-detected: verified content not on main' after
FN-4999 completion-handoff-limbo recovery exhausts).
D1 — prepare-commit-msg empty-commit guard
Fusion task worktrees install a prepare-commit-msg hook that refuses
'git commit --allow-empty' and other zero-staged-diff commits. Amend
(detected via $2==commit source arg or '--amend' in 'ps -o args= -p $PPID')
and merge/squash/cherry-pick/revert/rebase ceremonies are allowed.
D5 — early empty-own-diff fast-path in aiMergeTask
In reuse-task-worktree integration mode, BEFORE any reuse-handoff acquisition,
detect branches with own_commit_count >= 1 but zero net diff vs merge-base
(git diff --quiet <mergeBase>..<branch>) and finalize as no-op with
mergeDetails.noOpMerge=true + task:auto-recover-finalize-already-on-main
audit (reason: empty-own-diff-early-fast-path). cwd-main integration mode
is unchanged. classifyOwnedLandedEvidence also detects empty-own-diff so
self-healing and post-handoff finalize paths benefit too.
D3 — reuse-fallback consults existing branch registration
Before creating a fresh worktree in merger's reacquireReuseIntegrationWorktree,
consult 'git worktree list --porcelain' for existing registrations of
fusion/<id>. Reuse extant usable registrations directly. Prune stale
registrations first. Eliminates FN-5083-class double-registration where
'git worktree add -f' on an already-registered branch produced two worktrees
both claiming the branch and wedged the next handoff gate.
Tests
- New real-git backstop: prepare-commit-msg-empty-guard.real-git.test.ts
- New reliability interaction backstop in merge-reuse-task-worktree.test.ts
('FN-5345: empty-own-diff branch auto-finalizes via early fast-path')
- commit-msg-trailer.real-git.test.ts updated to use real commits (it was
relying on --allow-empty, which the new guard now refuses)
- Full @fusion/engine suite: 448 files / 5879 tests / 9 skipped, all green
- pnpm lint green, pnpm build green
Removes the branch-recovery CLI surface, orphan-rescue engine primitives, and their associated tests (over 1,500 lines deleted), while restoring a minimal prune-only orphan branch sweep with proper git audit mutation types. Documentation across `cli-reference.md`, `task-management.md`, and `AGENTS.m
Fusion-Task-Id: FN-5329
Implements external integration validation (FN-5321) with a manifest validator scaffold, worktrunk manifest wiring, and an evidence gap detector that runs during spec validation and triage; the reviewer also gates on external integration readiness. Includes tests for manifest, evidence gap, and tria
Fusion-Task-Id: FN-5321
Adds an explicit duplicate-marker guard (FN-5220) spanning core helper, dashboard API endpoint, triage short-circuit, and self-healing sweep to detect and handle duplicate task creation attempts; includes comprehensive test coverage across unit, API, and integration layers plus documentation.
Fusion-Task-Id: FN-5220
The dashboard's git pull endpoint failed outright when the working tree had
local edits or untracked files. It now stashes (including untracked) under a
fusion-dashboard-pull-autostash label, performs the pull, and reapplies the
stash. If reapplying conflicts, the stash is preserved and GitPullResult
surfaces autostashed/stashReapplied/stashConflict plus a message pointing at
the stash label so the user can resolve from the Stashes view.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
In reuse-task-worktree mode the merger detaches HEAD in the task worktree so
the squash commit lands on detached HEAD; nothing previously advanced the
project root's local integration branch, so changes never appeared on main.
Step 5c now applies the squash to projectRootDir via git merge --ff-only,
falling back to a regular merge with AI conflict resolution if main has
diverged. pushAfterMerge (when enabled) now runs from projectRootDir where
the branch was just advanced, so parsePushRemoteTarget can resolve a branch
instead of failing on the worktree's detached HEAD.
Also tightens acquireReuseHandoff: the executor-lease check above the
queue-lease acquisition was non-atomic, letting a local executor grab the
task between checks. Re-check after acquisition and release the queue
lease with a precise diagnostic instead of proceeding into a generic
failure later.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous commit wired targetTaskId through the engine caller but the
store's acquireMergeQueueLease SQL still grabbed the queue head unconditionally,
leaving the no-lease loop intact. This lands the store-side change: when
targetTaskId is provided it attempts a direct-match UPDATE first; only falls
back to queue-head ordering if that row isn't available (backward-compatible).
Adds regression test covering the polluted-queue-head scenario (FN-5363).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Before: when reuse-task-worktree was configured but task worktree was missing/unusable,
merger fell back to cwd-main, losing the task worktree isolation benefit.
After: merger creates a fresh worktree for the task branch inline (using the standard
git worktree add pattern with identity guard installation) and retries the reuse handoff.
Only falls back to cwd-main if fresh acquisition also fails.
Three new audit events track the recovery path:
- merge:reuse-worktree-fresh-acquire — fresh acquisition started
- merge:reuse-worktree-fresh-acquired — fresh worktree created and bound to task
- merge:reuse-fallback-cwd-main — only when fresh acquisition itself fails (last resort)
Behavior:
- Missing/unusable task worktree → fresh worktree created, merge continues from it
- Fresh acquisition fails → cwd-main fallback (last resort, fully audited)
- Genuine liveness conflict (usable worktree but lease refused) → re-thrown, not masked
Regression test covers the missing-worktree case: verifies fresh acquisition + handoff
succeeds without any cwd-main fallback event.