FN-5615 improves TUI token visibility by dynamically sizing the system panel height and clarifying the manual token copy hint, with regression tests added in the dashboard-tui app and a changeset for the patch release.
Fusion-Task-Id: FN-5615
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fusion-Task-Id: FN-5615
Complete the standalone build pipeline fix by setting `DEV=false` during bun compile to eliminate React DevTools from production executables, with hardened regression tests and documentation.
Fusion-Task-Id: FN-5611
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fusion-Task-Id: FN-5611
Added OpenAI Responses API as a new custom provider type, wiring `apiType: "responses"` through the core registry, engine routes, and dashboard UI with a dropdown selector; includes test coverage across the registry, routes, and component layers.
Fusion-Task-Id: FN-5601
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fusion-Task-Id: FN-5601
This merge implements an OAuth relogin banner feature (FN-5595) that displays in the dashboard when OAuth tokens expire. The feature includes a new `OAuthReloginBanner` component with styling and tests, an OAuth validity logger in the engine for tracking token state, and corresponding API route inte
Fusion-Task-Id: FN-5595
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fusion-Task-Id: FN-5595
Cold-start dashboard responsiveness went from ~99s to ~6-11s. CPU profiling
identified two synchronous-spawn hotspots blocking the event loop:
- `GitHubTrackingReconciler` scanned up to 200 done tasks per startup,
each call into `getIssue` invoking `isGhAvailable()` + `isGhAuthenticated()`
via `execFileSync`. `gh auth status` makes a network roundtrip, so 400
sync spawns ≈ 71s of pure event-loop blocking (69% of cold-start CPU).
Memoized both checks with a 60s TTL; `resetGhAvailabilityCache()` is
exported for login/logout flows that need immediate invalidation.
- `PRAGMA integrity_check(100)` walks every page of the SQLite file (~7s
per database, multiple DBs × projects). The deferred check was scheduled
3s after init — right in the responsiveness-critical window. Pushed to
60s so the user is already interacting before it runs; check itself is
unchanged.
Also yields the event loop between major InProcessRuntime init phases and
between self-healing recovery steps (34 per project), defers orphan-task
AI agent resumption by 30s (env-overridable, auto-zero under Vitest), and
ships an opt-in `FUSION_TRACE_EL_LAG=/path/to/file` event-loop lag tracer
that diagnosed all of the above.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two compounding bugs surfaced as `feat(FN-XXXX): merge fusion/fn-XXXX`
commits landing on main:
1. The verification-fix finalize path could bind `undefined` to SQLite
parameter 4 (`commitSha`) of `upsertTaskCommitAssociation` under the
parallel-attempt race, failing the merge over a denormalization
write after the commit had already landed. Centralized both
duplicated callsites into a helper that validates each git output
before binding.
2. Four self-healing/aiMergeTask recovery sites copied
`classification.commit.subject` verbatim into
`mergeDetails.mergeCommitMessage`, persisting the tier-3
`merge ${branch}` fallback when it ended up on the landed commit.
New `regenerateBareMergeSubject` helper detects the bare pattern
and rebuilds a descriptive subject via the AI summarizer. Cosmetic
only — the git commit is not amended.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Multiple coordinated fixes for the perceived "dashboard takes forever to
load" complaint. Per-page-load HTTP requests drop from ~177 to ~101 and
duplicate per-project InProcessRuntime creation is eliminated.
- engine: shouldUseHybridExecutor no longer auto-enables for local-only
multi-project setups (set FUSION_HYBRID_EXECUTOR=1 to force). The
duplicate-runtime path was running self-healing twice per project and
contending on the same SQLite file. ProjectEngineManager already
handles N local projects with one InProcessRuntime each.
- dashboard cli: parallelized independent store inits, started
CentralCore.init early in background, ran plugin loading concurrently
with extension resolution. Sequenced SQLite store inits to avoid a
TOCTOU race in addColumnIfMissing migrations across TaskStore /
AutomationStore / PluginStore / AgentStore (all open the same
.fusion/fusion.db). Restored try/catch around HybridExecutor.initialize
and engineManager.ensureEngine so a paused or broken cwd project no
longer aborts dashboard startup.
- dashboard client: added in-flight request dedupe wrapped around the
top API offenders. /api/plugins/ui-slots drops from 17x to 1x per load.
dedupe.forceFresh redirects ALL in-flight waiters to receive the fresh
post-mutation response, not just the forcing caller. Generation
counters in useAgents and AgentListModal protect against slow polls
overwriting fresh state.
- dashboard SSE: agent event handler now debounces 250ms with a
trailing-edge guard so multi-agent activity bursts coalesce to at
most 2 refetches per burst.
- dashboard route: PATCH /api/projects/:id with isolationMode change
returns 503 with actionable guidance when HybridExecutor is
unavailable, instead of silently persisting a config the live runtime
won't honor.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds planning branch controls to the PlanningModeModal, extending the planning API and routes with supporting tests and documentation. The changeset bumps `@runfusion/fusion` as a minor release.
Fusion-Task-Id: FN-5585
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fusion-Task-Id: FN-5585
The top-level promptWithFallback bypassed the session-attached rich fallback
path that runs isRetryableModelSelectionError + swapPromptSession, so errors
like "No API key for provider: anthropic" propagated without trying the
configured planning fallback. Restore the dispatch with a WeakSet re-entry
guard that preserves the FN-4900 recursion fix for plugin-runtime sessions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace execFileAsync("mkdir", ["-p", ...]) with fs.mkdir({ recursive: true })
in the cloudflared install fallback path. The -p flag is Unix-only and breaks
on Windows cmd.exe ("A subdirectory or file -p already exists"). Test mocks
updated to verify the fs.mkdir call instead of the shelled-out mkdir.
The original report covered both this site and packages/engine/src/worktree-hooks.ts;
the latter was already converted to fs.mkdir independently, so only the
dashboard route change is needed.
Co-Authored-By: kenlin8827 <kenlin8827@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Test shards 3 and 4 were silently failing on every open PR because vitest's
CLI parser was treating `--shard X/Y` as positional file filters whenever the
arg arrived after a `--` separator. Removing the `--` in ci-test-shard.mjs
restores per-shard slicing; verified locally that shard 1/4 and 2/4 now run
distinct subsets.
The two consistently-failing engine tests:
1. self-healing in-review-branch-rebind ambiguous case-variant detection:
dedup keyed on lowercase branch name collapsed two physically distinct
refs (allowed on Linux ext4) into one candidate, so the "applied" path
ran instead of "ambiguous-candidates". Dedup now keys on the resolved
SHA — macOS APFS still collapses (same ref, same SHA), Linux keeps both
(distinct SHAs) and the ambiguity skip path fires as designed.
2. worktree-acquisition resume-misbinding spy: the production
verifyResumeBranchNotMisbound returns early when `git merge-base HEAD main`
fails, which is exactly what happens on shallow checkouts. Bumping the
test-shards checkout to fetch-depth: 0 makes CI mirror the local git
state these engine tests rely on.
Also adds `push: branches: [main]` to PR Checks so regressions like this
(which slipped into v0.33.0 with no post-merge run) go red immediately
on landing instead of being discovered on the next PR.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds support for project-only notification deep links in the dashboard hook, with comprehensive test coverage across all deep-link URL shapes and a changeset for release.
Fusion-Task-Id: FN-5583
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fusion-Task-Id: FN-5583
The Git Manager's recent integration-advances list derived `needsAction`
purely from the original `merge:auto-sync` audit-event outcome, so it kept
showing "(N need action)" after the operator clicked "Sync working tree" or
fixed up the worktree by hand. `collectRecentMergeAdvances` now also checks
whether each advance's `toSha` is reachable from HEAD — if it is, the
worktree already contains that advance and `needsAction` is false
regardless of what the audit trail recorded.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three coupled fixes to make `pnpm test:full` exit cleanly when the local
`fn` dashboard is running:
1. scripts/check-test-isolation.mjs — replace timing-based "is the
engine writing?" heuristic with a deterministic check: if
`.fusion/engine.lock.lock/` exists (proper-lockfile's held-lock
marker), the dir is engine-active and auto-skipped from violation
reporting. The 2-second mutability probe is retained as a backstop
for dirs with another external writer but no live lock. Also adds
`engine.lock` / `engine.lock.lock/` to RUNTIME_IGNORE_PATTERNS so
a mid-test engine start/stop doesn't trip the signature compare.
2. packages/dashboard/.../__tests__/GitManagerModal.test.tsx — prune
the Status-panel Sync button + Recent-advances-events describe
blocks. Their UI was removed in 5d35b64bd ("remove duplicate
integration-advances UI") but the tests stayed and were timing
out at 1s each. The Remotes-panel Sync describe is kept because
the `remotes-sync-integration-tip-btn` still exists.
3. packages/engine/.../merge-reuse-task-worktree.slow.test.ts —
update the happy-path assertion to reflect 4c31e885b
("merger auto-syncs project-root checkout after ref advance").
Before that change, the merger's `update-ref` advance left the
project root's working tree stale, so `git status --porcelain`
would differ after the merge. With auto-sync, the new file is
tracked + clean at HEAD, so status doesn't change. Verify the
file actually landed via `git ls-files` instead.
After this, `pnpm test:full` exits 0 with the local dashboard running.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When the squash commit was built off a stale integration tip, the FF guard
in advanceIntegrationBranchRef refused the swap with reason
`non-fast-forward-advance` — but the caller only mapped `concurrent-advance`
to IntegrationBranchConcurrentAdvanceError, so the non-FF case fell through
as a plain Error and failed the task. Both reasons share a root cause
(integration moved during the merge window), so they now share the
FN-4500/FN-5083 rebind/retry path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a static pretest check and a runtime vitest-setup wrapper that block
shell/process calls matching `kill|pkill|killall|fuser|lsof ... <port>` or
`.listen(<port>)` against reserved Fusion ports. Reserved set is dynamic:
default 4040 plus $PORT, $FUSION_SERVER_PORT, $FUSION_RESERVED_PORTS, and any
port responding to /api/health on 4040..4045 at worker startup.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two cleanups in Git Manager → Status:
- Removed the duplicate "Sync local tip" button (gm-integration-actions)
and the second "Recent integration advances" list (gm-recent-advances)
that rendered above the highlighted block. Also dropped the dead
mergeAdvanceEvents state, fetcher, and SSE subscription that only
fed the deleted UI.
- Sync working tree is now pure-local. Added skipOriginFetch to
PullGitBranchOptions.integration (and the matching POST /api/git/pull
body field). When set, pullGitBranch skips tryFastForwardFromOrigin
entirely — the sequence is just auto-stash → git reset --hard
refs/heads/<integration> → restore stash. The Sync button passes
skipOriginFetch: true because the "N need action" recovery is for
catching the worktree up to a *local* merger ref-advance; touching
origin could silently pull in unrelated remote commits.
Help disclosure rewritten to reflect the pure-local behavior.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`notice` is `events.find(...)` which returns `undefined` (not `null`)
when no match. `waitFor(() => expect(...).not.toBeNull())` exited
immediately because `undefined !== null` — the test never actually
waited for the api mock to resolve. Sometimes the followup assertions
happened to land after the events fetched (test passed by luck);
sometimes they ran while notice was still undefined and the assertions
failed.
Switched all five waitFor sites to `.toBeDefined()` so they actually
block on the events-fetch resolution.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When the merger advances local `refs/heads/<integrationBranch>` via
`update-ref` without pushing, the user's project-root worktree HEAD
(symbolic to that branch) follows immediately to the new sha, but the
working files and index don't. The integration-mode pull only ran
`git merge --ff-only origin/<branch>`, which short-circuits as
"already up to date" when local is ahead of origin — leaving the
worktree visibly stale even though "Pull completed" was reported.
Pull now explicitly `git reset --hard <localIntegrationTip>` after
the origin fast-forward step. The autostash above protects user edits,
so the reset is safe regardless of whether the origin FF ran.
Regression test in routes-git.test.ts simulates the
local-ahead-of-origin scenario and asserts the reset-to-local-tip is
issued.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Third root-cause fix in the FN-5475 sweep. When `aiMergeTask` /
`recoverNoOpReviewTasks` classified a task as `proven-no-op` or
`no-changes-finalized`, both call sites moved the task to Done while
clearing `modifiedFiles: []` — silently destroying the audit trail when the
work product was uncommitted in the worktree, squashed against the wrong
branch, or dropped by reuse-handoff churn. This was the load-bearing site
of the FN-5490 / FN-5517 / FN-5526 / FN-5540 lost-work patterns.
Both call sites now check `task.modifiedFiles.length` before finalizing as
no-op. If the task claims work was done but no commit landed, the task is
moved back to `todo` with progress preserved and a new
`task:finalize-lost-work-blocked` audit event is emitted. The next
executor run re-attempts the work; the operator sees the audit event in
the timeline.
The post-hoc `reconcileDoneTaskIntegrity` path is intentionally NOT gated
— it cleans up already-Done tasks (legacy state) and is out-of-scope for
prevention. 9 lost-work tasks already in this state at sweep time are
cataloged in docs/incidents/2026-05-23-lost-work-tasks.md for fresh
re-spec.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Settings inheritance (high):
- Restored the value !== initialProjectValue gate on the non-model
project branch. Previously every effective/inherited project key was
persisted as an explicit override on every save.
Git Manager UI lie in remote-only mode:
- "Local <branch> vs origin" card now renders "no local tracking"
instead of a green "Synced" badge when integrationTipSource ===
"remote-only" (no local branch to compare).
- New dedicated "HEAD vs origin/<branch>" card surfaces a meaningful
distance in remote-only mode.
isIndexStale correctness:
- Walks up to 16 reflog entries so multi-hop misses (A→B→C without
sync) are detected; the prior check only consulted @{1}.
- Gated on isOnIntegrationBranch === true so a feature-branch worktree
whose HEAD happens to descend from <integration>@{1} no longer trips
the FN-INDEX-DESYNC warning.
Enumeration-failed events surfaced:
- collectRecentMergeAdvances pairs events with (taskId, newSha) when
both are present, falls back to taskId-only for early-failure events
(e.g. "enumeration-failed") that have neither path nor newSha. The
diagnostic outcome now surfaces on the matching advance instead of
being silently dropped.
aheadOfIntegration semantics no longer shift:
- Split into three distinct fields: aheadOfIntegration (HEAD vs local),
aheadOfIntegrationRemote (HEAD vs origin/<branch>),
aheadOfOriginIntegration (local vs origin). Consumers no longer have
to read integrationTipSource to know which comparison they got.
currentBranch failure no longer masks wrong-branch state:
- Distinguish "command threw" (transient git error) from "command
succeeded with empty stdout" (legitimate detached HEAD). New
currentBranchDetectionFailed field lets the UI surface "branch
detection unavailable" on a real failure.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two root-cause fixes for the "fake done" patterns surfaced while debugging
FN-5475's stuck preflight (it depended on FN-5233, which the board reported
as Done but whose squash had stranded on a sibling fusion/fn-* branch).
1. resolveTaskMergeTarget rejects fusion/fn-* sibling branches as a merge
destination — when a task's baseBranch was inherited from a sibling/dependent
dispatch, the merger detached onto and squashed against that branch instead
of advancing main. New audit event surfaces the steering miss so the
underlying baseBranch-propagation bug stays observable.
2. self-healing findLandedTaskCommit verifies ownership against each grep
candidate's body before attribution. The previous code blindly accepted the
first hit of `git log --grep=FN-XXXX` (which matches the entire commit
message); FN-5441 and FN-5446 were both marked done against an unrelated
FN-5483 commit whose body merely mentioned them in prose. commitOwnedByTask
is also tightened: trailers must be line-anchored and the subject fallback
must match conventional-commit form, not a bare substring.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Settings (data-loss):
- Non-model project keys now use null-as-delete instead of dropping
undefined via JSON.stringify, so clearing Integration branch (Use
dropdown / auto-detect) actually clears the persisted value.
isIndexStale (false-positive AND false-negative):
- Replaced the empty-worktree heuristic with a reflog-anchored check:
stale iff refs/heads/<integration>@{1} exists, HEAD is descendant of
it, and `git diff-index --cached <prevTip>` is empty.
Auto-sync attribution in collectRecentMergeAdvances:
- Match auto-sync events by (taskId, newSha) instead of taskId-only;
re-merged tasks no longer have older advances mislabeled with the
newest outcome.
- Compare worktreePath after realpathSync on both sides; macOS symlink
paths no longer cause permanent "needs action" false positives.
Extended path no longer 500s:
- Route wraps computeExtendedGitStatus in try/catch and falls back to
basic status on failure. Inner `branch --show-current` wrapped too
so detached HEAD / non-git rootDir doesn't throw.
Integration branch falls back to remote-only ref:
- When refs/heads/<branch> is missing, use refs/remotes/origin/<branch>
as the integration tip. New `integrationTipSource` field
("local"|"remote-only"|"missing") drives a UI badge.
Copy commit hash:
- Short-SHA copy is the default and matches what's displayed; a
separate "full" button copies the 40-char headSha. Previously the
single button silently copied the full SHA when extended was on.
Detached HEAD:
- isOnIntegrationBranch left undefined when currentBranch is empty so
the UI doesn't render "(not on <integration>)" against a
no-branch state.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the plain text input with a <select> that lists the project's
local branches (loaded via fetchGitBranches when the Merge section
becomes visible) plus an (auto-detect) default and a Custom… option for
branches that don't exist locally yet.
Branch list is deduped + sorted with common integration names (main,
master, trunk, develop) pinned to the top so the typical case is one
click. Choosing Custom… swaps in a text input with a "Use dropdown"
link to revert.
A previously-saved value not in the loaded list (branch deleted
locally, or initial render before fetch resolves) falls through to the
custom input automatically so the operator can still see + edit it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Repository Status panel now answers "what is the actual state of my
project root vs the integration branch?" so operators can be sure of
the picture even when the Merge Advance Notice banner is dismissed.
GET /api/git/status accepts ?extended=1 and returns additional optional
fields:
- integrationBranch + integrationBranchSource (settings|origin-head|fallback)
- integrationTipSha / originIntegrationTipSha
- aheadOfIntegration / behindIntegration (HEAD vs local integration tip)
- aheadOfOriginIntegration / behindOriginIntegration (local tip vs origin)
- dirtyDetails {staged, modified, untracked, conflicted, sample}
- indexStaleVsHead (surfaces the FN-INDEX-DESYNC scenario)
- stashCount
- recentMergeAdvances: up to 5 merge:integration-ref-advance events
joined with merge:auto-sync outcomes; needsAction flag flips when
auto-sync didn't successfully bring this worktree forward
GitManagerModal renders all of it:
- Existing cards get sub-text: branch shows "not on <integration>",
Working Tree shows staged/modified/untracked/conflicted breakdown
- Second row: Integration branch + source, HEAD-vs-integration,
local-vs-origin, stash count
- Yellow warning panel when indexStaleVsHead surfaces the merger's
stale-index situation with a recovery hint
- Recent integration-branch advances list, color-coded by needsAction,
shows the per-advance auto-sync outcome so operators can audit
even after dismissing the banner
All fetchGitStatus calls in GitManagerModal switched to extended:true.
Other callers unaffected — extra fields are optional.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a text input for the canonical integration branch directly under
the Auto-completion mode select. Visible regardless of direct vs PR
mode since the setting applies to both.
Blank preserves the existing auto-resolution cascade
(integrationBranch → baseBranch → origin/HEAD → main). Setting it to
master/trunk/develop/etc. pins the resolution explicitly.
Field trims whitespace and stores undefined when cleared so the
auto-resolution stays active.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two bugs were keeping the Merge Advance Notice banner stuck on screen
even when there was nothing for the user to do:
- Dismiss was dead: the `notice` memo never applied dismissedShas, so
clicking close (or a successful Pull, which calls dismiss()) updated
localStorage but the filter immediately re-matched the same event.
- Auto-sync success was ignored: with mergeAdvanceAutoSync defaulting
to "stash-and-ff", the merger snaps the project-root checkout
forward as part of the merge — nothing left to pull — but the banner
kept appearing. Clicking Pull then hit /api/git/pull which fetched
origin (no change, the merger only advanced the local ref) and
returned pull-clean with no real work done.
The notice memo now (a) filters dismissedShas, and (b) suppresses any
advance event whose autoSync entry for the current user's worktreePath
reports clean-sync or synced-with-edits-restored. Conflict + skipped
outcomes still surface so the user can recover.
Tests: dismiss removes the banner; clean-sync suppresses; pop-conflict
still surfaces; sibling-worktree success doesn't suppress this user.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the missing form control for the auto-sync mode introduced by the
merger hook. Lives next to Direct merge commit routing / Integration
worktree inside the merge-strategy panel and only renders when
mergeStrategy === "direct". Three options with descriptions cribbed from
docs/settings-reference.md:
- Stash + fast-forward (default): preserve local edits across the snap
- Fast-forward only: skip dirty worktrees, surface the banner instead
- Off: legacy behavior; project root stays stale until manual pull
Value is normalized through normalizeMergeAdvanceAutoSyncMode on both the
merged-settings and scoped-settings load paths so a missing/invalid
stored value cleanly falls back to the default.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Data-loss fixes in syncWorktreeToHead:
- Untracked-restore checks `git ls-tree -r --name-only HEAD` to skip
paths the new tip added as tracked files; user bytes stay in the
stage dir instead of clobbering merged content.
- Apply-failure on a deleted/renamed file: conflictedFiles falls back
to parsing `diff --git a/<p> b/<p>` headers when --diff-filter=U
returns nothing.
- All git invocations pass `-c core.quotePath=false` so non-ASCII
paths round-trip through copyFileSync.
- Stash-and-ff re-verifies rev-parse HEAD === newSha right before
each `reset --hard HEAD` (TOCTOU). On mismatch we bail with patch
preserved on disk.
- Stage dir lifecycle moved into try/finally with preserveStageDir
flag — kept whenever the user's edits live only in patchPath; rm'd
on all clean exits.
- Patch written to disk before the apply attempt, not only on
failure, so a crash between snapshot and apply doesn't lose edits.
Multi-worktree-same-branch fix:
- New getRegisteredWorktreeBranches returns Array<{branch,path}>
instead of collapsing into a Map. Multiple worktrees can share a
branch via `git worktree add --force -b`; merger now syncs all of
them rather than silently skipping all but the last.
Contract + surfacing fixes:
- JSDoc on merge:auto-sync GitMutationType now lists the actually-
emitted outcome strings + stage enum.
- GET /api/tasks/merge-advance-events joins merge:auto-sync events
within ±5min of the advance and returns them in a new
`autoSync: AutoSyncOutcome[]` field; useMergeAdvanceNotice exposes
the same shape so the banner can surface pop-conflicts (including
patchPath) instead of dropping them.
Hygiene:
- Merger now reads the setting via normalizeMergeAdvanceAutoSyncMode
instead of an inline check + `as unknown` cast.
New tests:
- Untracked-collides-with-tracked preserves merged content.
- Apply failure on deleted file populates conflictedFiles from
patch header.
- Route surfaces autoSync outcomes (clean-sync + pop-conflict)
joined within the time window.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After advanceIntegrationBranchRef ff-updates refs/heads/<integrationBranch>,
the merger now enumerates other worktrees on that branch and reconciles
each one's index + working tree to the new tip via syncWorktreeToHead.
Not a git pull — origin may still be at the previous tip without
pushAfterMerge, so pull --ff-only is a no-op and a naive stash/pull/pop
ends with the worktree restored to the old state. Instead the new
worktree-ref-sync helper:
1. Diffs the worktree against the previous tip to isolate real edits
from the stale-index "phantom diff" against the new HEAD.
2. Snaps clean worktrees forward via reset --hard HEAD.
3. In stash-and-ff mode with real edits, captures them as a binary patch
against the previous tip, snaps to HEAD, then git apply --3way to
restore. Untracked files are saved + restored separately. Patch
conflicts surface as synced-with-pop-conflict with the patch left on
disk for manual recovery.
Per-worktree outcome emitted as merge:auto-sync (new GitMutationType).
Per-step pull:fast-forward / stash:push / stash:pop / stash:pop-conflict
that pass through the auditor are tagged metadata.autoSync=true.
Isolated in its own try-catch so an auto-sync failure can't fail the
already-landed merge. Default behavior is mergeAdvanceAutoSync="stash-and-ff";
"off" preserves the legacy surprise behavior.
Backstopped by merger-auto-sync.slow.test.ts: clean-sync snaps both index
and files forward, ff-only with real edits is a no-op, stash-and-ff
preserves untracked locals across the snap, task worktrees on fusion/fn-*
are skipped, empty branch map emits nothing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Standalone implementation of the stash → ff → pop pipeline used by the
upcoming mergeAdvanceAutoSync merger hook. Returns a discriminated union
(clean-pull | stash-pull-pop | stash-pop-conflict | skipped-dirty |
skipped-not-on-branch | failed) and emits structured audit events via an
optional callback. The dashboard's user-triggered Pull keeps using the
existing /api/git/pull integration path; smartPull stays free of AI
conflict resolution so the merger's post-advance auto-sync is safe to run
inline without escalating to a model call.
Backstopped by smart-pull.slow.test.ts (engine-slow lane): clean-pull,
stash-pull-pop, ff-only skip, off-branch skip, audit-emitter exception
tolerance.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Schema for what the merger should do in other worktrees still checked out
on the integration branch when it advances the branch ref. Modes:
off — legacy (user pulls manually)
ff-only — fast-forward only when other worktree is clean
stash-and-ff — Smart Pull pipeline (default)
Threads through DEFAULT_PROJECT_SETTINGS, PROJECT_SETTINGS_KEYS (auto via
Object.keys), the docs settings table, and parity + persistence tests.
Merger consumption lands in the follow-up engine change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The route was extracting getRunAuditEvents off scopedStore and calling it as
a bare function, which made this.db.prepare(...) throw. useMergeAdvanceNotice
silently swallowed the error, so the banner never rendered after merges.
Call the method on the store reference instead so this is preserved.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a "session runtime resolved" audit event that flows through the engine's main execution lanes — triage, executor, reviewer, merger, heartbeat, step-session-executor, and mission-execution-loop — with runtime mutation support and test coverage, plus a compile-fix for the merger auditor wiring.
Fusion-Task-Id: FN-5544
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fusion-Task-Id: FN-5544
Changes the default merge strategy from squash to direct by flipping `directMergeCommitStrategy` in the settings schema and types, with the core implementation in `merger-ref-update-advance.ts`. Also aligns a heartbeat executor test assertion with the FN-5060 deduplication shape.
Fusion-Task-Id: FN-5255
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fusion-Task-Id: FN-5255
Follow-up to bf4428c00 (FF-only ref advance). After the prevention fix
new orphans can't form, but pre-fix orphans like f6358ce4 on
fusion/fn-5419 still need a path back onto the integration branch.
Adds an `orphan-our-advance` classification to contamination recovery:
a "unique" foreign commit whose Fusion-Task-Id trailer points at a
`done` task AND that is unreachable from refs/heads/<integrationBranch>
is treated as a stranded merger output.
For these, the executor attempts a fast-forward rehome onto the
integration branch via advanceIntegrationBranchRef (which still enforces
the FF-only invariant). When successful, the orphan sha is added to the
existing `shasToDrop` set so the same recovery pass that drops
already-upstream/misrouted commits also drops the now-upstream orphan.
Non-FF orphans (diverged from current integration tip) are refused.
Doing a cherry-pick onto the integration branch from inside automated
recovery would introduce conflict-resolution surface that's too high
blast radius for a never-event recovery path. The refusal log line
includes the exact `git cherry-pick <sha>` command an operator can run
manually.
Two new GitMutationType audit events:
- merger:orphan-rehome-ff (successful FF rehome)
- merger:orphan-rehome-refused (non-FF, manual cherry-pick required)
Tests in merger-orphan-rehome.test.ts cover classification (orphan,
not-done, already-reachable, no-trailer) and the rehome operation
(FF success advances the ref + emits the audit event; non-FF refusal
emits the hint and leaves the ref untouched).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>