Ensure merger AI activity is persisted to task logs before sessions are torn down.
- Flush AgentLogger output in merger AI-session finally blocks for autostash conflict resolution, autostash hard-fail recovery, and rebase conflict resolution paths
- Guard flush/dispose cleanup with safe try/catch handling to avoid teardown-time logging loss
- Add a regression test asserting logger flush occurs before session disposal during AI rebase conflict resolution
- Export the rebase conflict resolver helper for targeted test coverage
- Add a patch changeset for @runfusion/fusion documenting the agent-log visibility fix
Files changed:
.changeset/fn-5752-merger-log-flush.md | 5 ++++
packages/engine/src/__tests__/merger-conflict-resolution.test.ts | 30 ++++++++++++++++++++++
packages/engine/src/merger.ts | 22 +++++++++++++++-
3 files changed, 56 insertions(+), 1 deletion(-)
Fusion-Task-Id: FN-5752
Fusion-Task-Lineage: 3c6023f0-cc9b-47e1-bfd4-69b40e011d84
Prevent rapid auto-merge toggles from desynchronizing dashboard settings state.
- Track auto-merge state in a ref so each toggle reads and writes the latest value
- Apply optimistic updates from the ref and roll back correctly on failed setting updates
- Add regression coverage for rapid double-toggle behavior in useAppSettings
- Add a patch changeset for @runfusion/fusion describing the dashboard blank-state fix
Files changed:
.changeset/fn-5751-auto-merge-toggle.md | 5 ++++
.../app/hooks/__tests__/useAppSettings.test.ts | 27 ++++++++++++++++++++++
packages/dashboard/app/hooks/useAppSettings.ts | 20 +++++++++++-----
3 files changed, 46 insertions(+), 6 deletions(-)
Fusion-Task-Id: FN-5751
Fusion-Task-Lineage: 1a0090ff-4015-46b8-a974-3accbbdce919
macos-13 runners are too scarce — the darwin-x64 leg sat queued for hours
and blocked the release publish job (which needs all build legs). Ship the
CLI Apple-Silicon-only for macOS; desktop macOS DMG/ZIP stays universal.
Removed from release.yml + test-release.yml matrices, updated ci-workflow
assertions (5→4 targets) and RELEASING.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Windows desktop build now succeeds (artifact builds + uploads), but the
job was marked failed on the post-job cache *save*: build-binaries
(windows-x64) and build-desktop-windows share runner.os/arch and thus the
same cache key, so concurrent saves hit "unable to reserve cache ... another
job may be creating this cache." Adding github.job to the key gives each job
its own cache and eliminates the race.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(b) Windows desktop build flakily failed with "Property 'ok'/'status' does
not exist on type 'Response'" because the workspace resolved multiple
@types/node versions (incl. an ancient 12.20.55 from @manypkg/find-root that
predates global fetch types). plugin-sdk compiles with lib:["ES2022"] and no
DOM lib, so Response comes solely from @types/node — nondeterministic across
runners. Pin via pnpm.overrides "@types/node": "^25.5.2"; collapses the
lockfile to one version. Verified: plugin-sdk build + desktop + full
workspace typecheck all pass.
Also: build-exe-cross tests cross-compiled all 5 platforms on every CI run
(auto-enabled via Boolean(process.env.CI)) — a release-build dependency in
the normal lane. Made them opt-in (FUSION_TEST_BUILD_EXE=1) and pointed the
root test:slow-cli at the CLI slow lane instead of test:pre-release, so
everyday CI no longer depends on release builds. test:pre-release and
test-release.yml still validate binaries deliberately.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
electron-builder emits Fusion-<v>-linux-x86_64.AppImage for x64 (deb uses
amd64, tar.gz uses x64). The verify step globbed -linux-x64.AppImage and
failed with "No Fusion Linux x64 AppImage artifacts produced" even though
the AppImage built. Upload globs already used the broad -linux-*.AppImage.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
electron-builder's fpm target requires author 'email' in package.json;
without it the arm64/x64 .deb build failed (`Please specify author 'email'`).
Surfaced once the electron-builder --x64 --arm64 flags actually parsed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Second pass after the cache/arch fix unblocked `pnpm build` and surfaced
later-stage failures:
- Desktop packaging called `pnpm --filter @fusion/desktop dist:mac -- <args>`,
but pnpm leaks the `--` separator into the script args. electron-builder
stops parsing at `--`, so `--publish never` was ignored — it auto-published
to api.github.com/repos/gsxdsm/fusion/releases and 404'd. The same leak
dropped Linux's `--x64 --arm64`. Switch all four desktop packaging steps to
`pnpm --filter @fusion/desktop exec electron-builder ...`, which forwards
args cleanly (verified locally).
- Windows CLI signing now skips when WINDOWS_CERTIFICATE_BASE64 is absent,
mirroring the macOS guard (was hard-failing the bun-windows-x64 job).
- Desktop build spawns workspace .cmd bins with shell:true on Windows; Node
rejects .cmd/.bat spawns without a shell (EINVAL) since CVE-2024-27980,
which broke `@fusion/desktop build` on the Windows runner.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Prevent done-task diff endpoints from counting base-commit-only lineage as task-owned changes.
- add base-boundary handling in done-task merge SHA resolution so `baseCommitSha` is excluded by default and only consulted for boundary checks
- short-circuit `/diff` and `/file-diffs` responses to empty results when resolved done-task SHA is at or below `baseCommitSha`
- export and use an ancestry helper to detect base-boundary cases, while preserving normal and rebase range behavior
- extend done-task diff route tests with FN-5666 cases for no-op merge SHA, lineage association at base commit, normal post-base commits, and rebase ranges
- add a patch changeset for `@runfusion/fusion`
Files changed:
.changeset/fn-5666-base-commit-exclusive.md | 5 +
.../src/__tests__/routes-diff-done-tasks.test.ts | 217 ++++++++++++++++++++-
.../src/routes/register-session-diff-routes.ts | 57 +++++-
3 files changed, 273 insertions(+), 6 deletions(-)
Fusion-Task-Id: FN-5666
Fusion-Task-Lineage: 782f9875-856a-4d31-9505-0bcd638c9c29
- Commit message: the merge agent writes a concise change-summary subject (not
just the task title), and every landed squash carries board-association
trailers — `Fusion-Task-Id: <taskId>` plus the canonical lineage trailer when
the task has a lineageId — guaranteed via an idempotent amend
(ensureTaskTrailersOnHead) even if the agent omits them.
- Verify-and-fix: the merge prompt instructs the agent to run the project's
tests / type-check / lint after resolving the merge and fix any NEW failure
the merge introduced (not pre-existing breakage) before committing.
- Editable prompt: the AI merge agent's base persona is the editable "merger"
role prompt (Settings -> Prompts); the clean-room / verification / trailer
rules are always appended so a custom prompt can't drop them.
- Reviewer model: reviewer uses the project reviewer/validator model lane
(resolveValidatorSettingsModel); the bespoke merger.reviewerModel setting is
removed.
Changeset updated to cover all AI merger changes.
Tests: trailer present on landed commit (+ lineage when set), editable-prompt
incorporation, new-breakage verification wording.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New default merge path (merger.mode="ai"), self-contained in merger-ai.ts and
dispatched from ProjectEngine.onMerge instead of the legacy aiMergeTask pipeline
(kept for merger.mode="deterministic").
Flow: clean-room detached worktree at the target branch tip → AI agent merges
the task branch + squashes (resolving conflicts) → fresh read-only AI reviewer
audits with corrective retries (blocking vs advisory; advisory lands, unfixable
correctness hard-fails via AiMergeBlockedError; fail-safe verdict parsing) →
land via `git merge --ff-only` when the checkout is on the target (else
update-ref CAS) → sync the local checkout (stash → ff → restore; AI reconciles
a conflicting restore and keeps the original edits in a backup stash;
un-stashable dirt advances the ref + warns) → finalize (delete task branch —
never the integration branch — task→done, remove temp worktree).
- Per-task target branch honored (falls back to the default integration
branch); local checkout synced only when on that target.
- Structurally immune to the dirty-clobber and stale-base/non-FF bug classes of
the legacy path (clean room + FF-by-construction).
- Progress surfaced on the task status pill + task log stream.
- Clear error when the target branch has no local ref.
Settings: merger.mode / merger.reviewerModel / merger.maxReviewPasses, surfaced
in Settings → Merge; legacy merge-mechanics settings hidden when AI mode is on.
Tests: merger-ai.test.ts (verdict parser, clean merge, blocking hard-fail,
advisory land, empty no-op, target-branch isolation, missing-target error,
landSquash clean/other-branch/dirty-restore/AI-resolved). Legacy
merge-orchestration tests pinned to deterministic mode.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Even with FN-5627's merger TOCTOU fix + transient-failure self-healing
sweep + safety-fallback auto-prerebase landed, the merger can still hit
transient failure classes (lease handoff races, brief same-SHA non-FF
advances) for tasks whose branches are particularly out-of-sync. The
self-healing sweep auto-recovers them within bounded budget \u2014 but each
individual failure cycle was firing a ntfy alarm before the recovery
cleared the failed state, producing user-facing alarm spam for tasks
that were never actually stuck.
Two layers of fix:
1. NotificationService.handleTaskUpdated now classifies task.error via
the new shared classifyTransientMergeError helper before scheduling
the deferred failure notification. Transient classes
(lease-handoff-target-not-queued, spurious-concurrent-advance-same-sha)
get logged as suppressed and never schedule a ntfy timer.
2. Defense-in-depth: fireDeferredFailureNotification re-classifies the
error at dispatch time, so a failure scheduled before the suppression
landed on a newer cycle still suppresses if the error matches a
transient class.
The classifier itself moved from self-healing.ts to a new logger-free
transient-merge-error-classifier.ts module so consumers in
NotificationService don't pull createLogger through the import chain and
break test mocks of ../logger.js (per project-memory rule about new
modules using createLogger). self-healing.ts re-exports the symbol for
backward compatibility.
Log prefix for the recovery actions also changed from
'[FN-5627] Auto-recovering...' to 'Auto-recovered:' so that
NotificationService.maybeSuppressTransientFailedNotification's existing
/^Auto-recovered:/ log-prefix check cancels any already-scheduled failure
notification when the sweep runs mid-grace-window.
Tests (3 new):
- transient lease-handoff-target-not-queued failure NOT notified
- transient spurious-concurrent-advance-same-sha failure NOT notified
- genuine different-SHAs concurrent-advance still notifies (control)
Engine suite: 6166 tests pass.
Fusion-Task-Id: FN-5627