Commit Graph

133 Commits

Author SHA1 Message Date
gsxdsm
9c60eb2821 perf(merger): skip redundant in-merge verification and lockfile-stable installs
Two cuts to wasted work in the merge verification loop:

1. After the in-merge fix agent runs, fingerprint the working tree
   (`git diff HEAD` + `git status --porcelain`, sha256). If the post-fix
   fingerprint matches pre-fix and is non-empty, the agent didn't actually
   change anything — re-running the same failing command can only yield
   the same failure, so log and report the attempt as unsuccessful without
   paying the test/build cost. Empty fingerprints (snapshot tooling failed)
   fall through to the existing re-run path so we never silently swallow a
   real fix.

2. Inside `syncDependenciesForMerge`, hash the active lockfile and compare
   against `node_modules/.fusion-install-marker` (written after each
   successful install). When they match, skip `pnpm install
   --frozen-lockfile` even if `package.json` is staged. Covers the common
   case where `package.json` changes but the lockfile doesn't, and
   amortizes install across auto-recovery re-enqueues that hit the same
   worktree.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 12:24:57 -07:00
Fusion
79a6b2c840 feat(FN-3539): remove duplicate todos entry and update navigation docs
Merged branch removes the duplicate desktop "Todos" nav entry from the Header, syncs the `allowParallelExecution` runtime toggle into `AgentDetailView`, and updates the corresponding docs (agents.md, settings-reference.md, todo-view.md, dashboard-guide.md) to reflect the navigation change. Tests wer

Fusion-Task-Id: FN-3539
2026-05-06 03:52:18 -07:00
gsxdsm
06b9a21ad5 fix(engine): stop tears down in-progress merger and triager sessions
TriageProcessor.stop() previously only halted the polling loop, so
in-flight specify sessions and their reviewer subagents kept streaming
past shutdown. Extracted the existing global-pause teardown into
abortAndDisposeActiveSessions() and call it from stop() too.

aiMergeTask creates three sessions during a merge — autostash resolver,
in-merge verification fix agent, and pull-rebase conflict resolver — but
only the autostash one was registered via onSession. The other two are
now registered (with onSession threaded through pushToRemoteAfterMerge
into the rebase resolver chain), so ProjectEngine.stop() actually
disposes whichever merger session is running when shutdown lands.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 00:05:55 -07:00
gsxdsm
8575c48d85 fix(engine): make merger autostash recovery resilient and AI-resolve conflicts
When rootDir is the developer's primary checkout, the merger stashes
uncommitted edits before its hard resets and applies them back at the
end. Previously a pop conflict logged a single warning and silently
left the stash in place — a subsequent merge would push another
autostash on top, burying the first. Recent FN-3299 work was lost this
way and surfaced two side-by-side fusion-merger-autostash entries in
the local stash list.

Three changes:

- AI auto-resolve on apply conflict. The new
  runAiAgentForAutostashConflict spawns the same createResolvedAgentSession
  path as the in-merge fix-agent, instructs it to clear conflict markers
  in place without committing, and verifies markers are gone post-run.
  On verified success the stash is dropped; on any failure or remaining
  markers the stash is left intact for manual recovery.
- Outcome surfaced via new MergeResult.autostash (AutostashOutcome)
  field so dashboard / CLI / daemon can show developers whether their
  work was reapplied cleanly, AI-resolved, or needs manual recovery.
- Deterministic stash identity. Replaced `git stash push` + label-grep
  (which races against concurrent stashing tools) with `git stash create`
  + `git stash store`, capturing SHA atomically with snapshot creation
  and using it for apply / drop. Untracked files captured via `git add
  -A` before create; cleanup via `git reset --hard` + `git clean -fd`.

Also surfaces orphaned `fusion-merger-autostash:*` entries from prior
runs at merge entry, so they can no longer be silently buried.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 20:47:38 -07:00
Fusion
36493eca0f feat(FN-3498): add self-healing and ownership-aware done-task merge reconci
The merge completes FN-3498 across three steps: adds ownership-aware done-task reconciliation to the merger, prevents branch-missing head SHA pollution during merge operations, and restores workspace typecheck compatibility. Core changes touch the merger (103 lines) and self-healing module (67 lines

Fusion-Task-Id: FN-3498
2026-05-05 13:42:39 -07:00
Fusion
80cdb15b19 feat(FN-3513): add eval domain store, plugin dashboard view registry, and G
This merge adds three major features: an eval domain (`eval-store.ts`, `eval-types.ts`) with persistence schema for evaluation data; a plugin dashboard view registry with navigation integration for third-party dashboard extensions; and GitHub source metadata traceability that locks and enforces issu

Fusion-Task-Id: FN-3513
2026-05-05 12:25:50 -07:00
Fusion
f195245776 feat(FN-3419): remove inline lock indicator from PlanningModeModal
Removed the inline lock indicator from PlanningModeModal and added a new test file covering the planning flow behavior.

Fusion-Task-Id: FN-3419
2026-05-04 17:55:34 -07:00
gsxdsm
d65ed8d28d fix(engine/merger): autostash unrelated rootDir changes around aiMergeTask
The merger issues several `git reset --hard` / `git reset --merge` and
forced-checkout calls against `rootDir` during merge attempts. When
`rootDir` is the developer's primary checkout (common for solo / single-host
setups), those resets silently discard any unrelated unstaged or untracked
changes — we burned dev work this way during FN-3329 (dashboard-tui edits
wiped mid-flight by an unrelated merge run).

Snapshot dirty paths at entry to `aiMergeTask`, stash them under a
recognizable label including the taskId (`-u` to capture untracked), and
pop them in a finally block on every exit path. On pop conflict we leave
the stash intact and log a recovery hint rather than dropping it. Best-
effort: a stash failure logs and proceeds with the old behavior so the
merge itself is never blocked.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 17:22:21 -07:00
gsxdsm
89da1de311 fix(merger): restrict staging to allowlist and harden git invocations
Replaces blanket `git add -A` in `commitOrAmendMergeWithFixes` with an
explicit allowlist of (squash-staged ∪ fix-agent-modified) paths, so
unrelated dirty files in the project root no longer get swept into a
task's squash commit. The in-merge fix agent now snapshots the working
tree before/after its session to capture exactly which files it touched.

Hardens the git invocations the allowlist relies on:
- All `git add` and `git checkout --ours/--theirs` calls switched from
  shell-interpolated `execAsync` to `execFile` array form, eliminating
  path-injection surface and batching per-file spawns into one call.
- `snapshotDirtyFiles` adopts `git -z` NUL-delimited parsing so paths
  with embedded spaces or specials are handled correctly.
- Long allowlist debug logs are truncated to 20 entries with an overflow
  marker.

Refused-to-stage paths emit a warn naming each file so the user can
audit what was filtered.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 13:17:57 -07:00
Fusion
c9e776cb34 feat(FN-3293): add stabilization docs to test audit report
Documentation for test stabilization was finalized by updating the test audit report with 2 additional lines.

Fusion-Task-Id: FN-3293
2026-05-04 13:17:57 -07:00
gsxdsm
a3ce8e1f8e fix(FN-XXX): unify codex auth and chat fallback 2026-05-03 23:08:05 -07:00
Fusion
be7be0957d feat(FN-3345): fix lint and typecheck issues in engine verification-utils
Cleans up lint and type errors in the engine by removing unused imports from executor and merger, and replacing `any` types with proper type annotations in verification-utils.

Fusion-Task-Id: FN-3345
2026-05-03 20:08:24 -07:00
gsxdsm
9e7de0cecb fix(engine): teach merger fix agent to rebuild stale dist artifacts
The verification-fix agent prompt previously forbade modifying files
unrelated to the failure, which blocked the natural fix when
deterministic merge verification failed because of stale/missing
plugin `dist/` outputs in sibling workspace packages (e.g.
`Failed to resolve import "./cli-spawn.js"` from
`fusion-plugin-hermes-runtime/dist`).

- Add explicit guidance to detect stale-artifact failure signatures
  and rebuild the affected package(s) before editing source.
- Allow the agent to fix pre-existing breakage on the base branch,
  preferring the smallest change that makes verification green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 14:27:02 -07:00
Fusion
68f34dd0f7 feat(FN-3008): notify when model settings fall back to defaults
Merges FN-3008 to add a "fallback-used" notification system: the engine now emits events when AI model fallbacks are triggered, dispatches notifications via ntfy/webhook providers, surfaces a session banner in the dashboard, and exposes a settings toggle to enable or disable these alerts.

Fusion-Task-Id: FN-3008
2026-05-02 19:55:54 -07:00
gsxdsm
117224ba8f fix(engine): give merge commits a real subject in fallback paths
Three merger fallback commit paths (auto-resolve-all-conflicts,
-X theirs/ours side strategy, AI-agent-didn't-commit) hard-coded
`feat(FN-XXXX): merge fusion/fn-xxxx` as the subject and never used
the AI subject summarizer. Route them through buildDeterministicMergeMessage
so they pick up aiSubject when available.

When the AI subject summarizer returns null, derive the subject from
the branch's first step commit (with conventional-commit prefix
stripped, plus `(+N more)` for multi-commit branches) instead of the
bare `merge <branch>` template.

Bump DEFAULT_COMMIT_SUBJECT_TIMEOUT_MS 15s → 30s so slow-first-token
providers complete instead of silently falling back.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 13:32:47 -07:00
Fusion
b4401d5911 feat(FN-3175): document global tool output persistence setting in settings
Added documentation for the global tool output persistence setting to the settings reference guide.

Fusion-Task-Id: FN-3175
2026-05-02 02:07:12 -07:00
Fusion
1f68bc3518 feat(FN-3117): add plugin dashboard views, chat session icons, and SQLite t
This release (v0.15.0) brings significant plugin system enhancements including a new dependency graph plugin with dashboard view, plugin skills in session selection, and extended plugin UI slot metadata. Database improvements add SQLite WAL tuning, integrity checks, and batch writes for agent logs.

Fusion-Task-Id: FN-3117
2026-05-02 00:04:29 -07:00
gsxdsm
f9c08dea7a feat(merger): generate AI summary subject for merge commits
Replace bare `feat(FN-XXXX): merge fusion/fn-XXXX` subjects with an
AI-generated summary describing what landed (e.g. `feat(FN-XXXX): add
webhook handler`). Calls the existing `summarizeCommitSubject` lane
alongside the body summarizer; falls back to `merge <branch>` when the
summarizer is disabled, unavailable, or returns nothing.

Default for `useAiMergeCommitSummary` flips to true so existing
projects without an explicit override pick up the new behavior. The
Settings UI already exposes the toggle.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 04:39:00 -07:00
Fusion
101567052c feat(FN-2989): merge fusion/fn-2989
- Add `CustomProviderForm` component and routing for user-defined OpenAI/Anthropic-compatible endpoints (FN-2987)
- Expand `agent-heartbeat.ts` with richer prompt guidance and criteria updates
- Strengthen executor prompts in `executor.ts` with expanded implementation guidance
- Enrich reviewer prompts in `reviewer.ts` with enhanced review criteria
- Add triage prompt guidance in `triage.ts` for task classification
- Extend merger prompts in `merger.ts` with merge strategy guidance
- Add `agent-document-tools.test.ts` and `agent-tools.test.ts` integration tests
- Expand `store.ts` with new task/document operations; add `task-documents.test.ts` and `store.test.ts`
- Add legacy API route in `dashboard/app/api/legacy.ts` and auth path routing
- Update `ModelOnboardingModal` with custom provider onboarding flow
- Extend `SettingsModal` with custom provider configuration UI and mobile layout tests
- Add changeset for `@runfusion/fusion` minor release

Commits merged:
- feat(FN-2989): complete Step 5 — enrich remaining agent prompts
- feat(FN-2989): complete Step 4 — enrich reviewer prompt criteria
- feat(FN-2989): complete Step 3 — strengthen triage prompt guidance
- feat(FN-2989): complete Step 2 — expand executor guidance prompts
- feat(FN-2989): complete Step 1 — expand heartbeat prompts
- feat(FN-2987): merge fusion/fn-2987
- feat(FN-2959): merge fusion/fn-2959-2

Files changed:
.changeset/custom-openai-anthropic-providers.md    |   5 +
 docs/task-management.md                            |   4 +
 packages/core/src/__tests__/store.test.ts          |  38 +++
 packages/core/src/__tests__/task-documents.test.ts |  32 +++
 packages/core/src/store.ts                         |  34 ++-
 packages/dashboard/app/api/legacy.ts               |  41 ++++
 .../app/components/CustomProviderForm.css          |  45 ++++
 .../app/components/CustomProviderForm.tsx          | 203 ++++++++++++++++
 .../app/components/ModelOnboardingModal.css        |  14 ++
 .../app/components/ModelOnboardingModal.tsx        |  62 ++++-
 .../dashboard/app/components/SettingsModal.css     |  38 +++
 .../dashboard/app/components/SettingsModal.tsx     |  91 +++++++-
 .../__tests__/CustomProviderForm.test.tsx          |  60 +++++
 .../__tests__/ModelOnboardingModal.test.tsx        |  23 ++
 .../components/__tests__/SettingsModal.test.tsx    |  13 ++
 .../__tests__/SettingsModalNodeRouting.test.tsx    |   4 +
 .../components/__tests__/settings-mobile.test.tsx  |   4 +
 packages/dashboard/src/auth-paths.ts               |   4 +
 packages/dashboard/src/routes.ts                   |   2 +
 .../__tests__/custom-provider-routes.test.ts       | 118 ++++++++++
 .../src/routes/register-custom-provider-routes.ts  | 254 +++++++++++++++++++++
 .../src/__tests__/agent-document-tools.test.ts     |  15 ++
 packages/engine/src/__tests__/agent-tools.test.ts  |  43 ++++
 packages/engine/src/agent-heartbeat.ts             | 107 +++++++--
 packages/engine/src/agent-reflection.ts            |   7 +-
 packages/engine/src/agent-tools.ts                 |  28 ++-
 packages/engine/src/cron-runner.ts                 |   4 +
 packages/engine/src/executor.ts                    |  69 +++++-
 packages/engine/src/merger.ts                      |  26 ++-
 packages/engine/src/mission-execution-loop.ts      |   6 +
 packages/engine/src/reviewer.ts                    |  24 ++
 packages/engine/src/step-session-executor.ts       |  11 +-
 packages/engine/src/triage.ts                      |  20 ++
 33 files changed, 1407 insertions(+), 42 deletions(-)

Fusion-Task-Id: FN-2989
2026-04-29 20:27:04 -07:00
Fusion
5c82c3f34a feat(FN-2947): merge fusion/fn-2947
- Add AI-powered merge commit summarization with toggle in Settings (`packages/core/src/ai-summarize.ts`, `packages/dashboard/app/components/SettingsModal.tsx`, `packages/engine/src/merger.ts`)
- Introduce `generateMergeCommitSummary` utility in `@fusion/core` that calls the configured model with a diff prompt; results appear in the commit log for merge commits
- Add `aiMergeSummaryEnabled` project setting and `useAiMergeSummaryEnabled` hook in dashboard
- Update `@runfusion/fusion` CLI extension to surface the new setting
- Add `POST /api/tasks/:id/workflow/pre-merge` and `POST /api/tasks/:id/planning-subtask` routes (`packages/dashboard/src/routes/`)
- Rework InsightsView with two-pane layout for better readability (`packages/dashboard/app/components/InsightsView.tsx`, `packages/dashboard/app/components/InsightsView.css`)
- Fix `task plan` command to surface errors when planning fails (`packages/cli/src/commands/task.ts`)
- Add tests for `ai-summarize`, `agent-heartbeat`, `agent-tools`, and dashboard API routes; refresh onboarding and agents-view mobile tests
- Minor CSS polish: reduce AgentsView panel hover gap, improve CommitDiffTab layout, add ScriptsModal styles
- Changelog and version bumps for `@runfusion/fusion` v0.9.1, CLI alias, and all workspace packages

Commits merged:
- feat(FN-2947): complete Step 7 — add changeset and docs deliverables
- fix(FN-2947): clean unused aiSummary path
- test(FN-2947): complete Step 5 — cover AI merge summary flow
- feat(FN-2947): complete Step 4 — add merge summary settings toggle
- feat(FN-2947): complete Step 3 — wire AI merge summaries into merger
- feat(FN-2947): complete Step 2 — add merge summary setting
- feat(FN-2947): complete Step 1 — add merge commit summarizer
- feat(FN-2970): merge fusion/fn-2970
- feat(FN-2956): merge fusion/fn-2956
- feat(FN-2923): merge fusion/fn-2923
- feat(FN-2945): merge fusion/fn-2945
- chore(release): v0.9.1
- fix(FN-XXX): improve git manager diff layout
- fix(dashboard): rework Insights view with two-pane layout
- fix(FN-XXX): keep experimental views off by default

Files changed:
.changeset/active-agents-no-stuck-connecting.md    |  13 --
 .changeset/active-agents-panel-hoist-heartbeat.md  |  13 --
 .changeset/add-ai-merge-commit-summary.md          |   5 +
 .changeset/fix-agent-heartbeat-terminal-links.md   |   5 -
 .changeset/show-planning-tasks-immediately.md      |   5 -
 CHANGELOG.md                                       |  92 ++++++++++
 docs/settings-reference.md                         |   3 +
 package.json                                       |   2 +-
 packages/cli-alias/CHANGELOG.md                    |  15 ++
 packages/cli-alias/package.json                    |   2 +-
 packages/cli/CHANGELOG.md                          |  12 ++
 packages/cli/package.json                          |   2 +-
 packages/cli/src/__tests__/task-plan.test.ts       |   1 +
 packages/cli/src/commands/__tests__/task.test.ts   |  15 +-
 packages/cli/src/commands/task.ts                  |  15 +-
 packages/cli/src/extension.ts                      |   9 +
 packages/core/CHANGELOG.md                         |   7 +
 packages/core/package.json                         |   2 +-
 packages/core/src/__tests__/ai-summarize.test.ts   |  64 +++++++
 packages/core/src/ai-summarize.ts                  | 114 ++++++++++++
 packages/core/src/index.ts                         |   3 +
 packages/core/src/settings-schema.ts               |   1 +
 packages/core/src/types.ts                         |   4 +
 packages/dashboard/CHANGELOG.md                    |  14 ++
 packages/dashboard/app/App.tsx                     |  28 ++-
 .../app/__tests__/agent-css-classes.test.ts        |   3 +-
 packages/dashboard/app/__tests__/api.test.ts       |  13 ++
 packages/dashboard/app/api/legacy.ts               |   2 +
 packages/dashboard/app/components/AgentsView.css   |  22 +--
 .../dashboard/app/components/CommitDiffTab.tsx     |   2 +-
 .../dashboard/app/components/GitManagerModal.tsx   |  71 ++++----
 packages/dashboard/app/components/InsightsView.css | 200 ++++++++++++++++++---
 packages/dashboard/app/components/InsightsView.tsx | 111 ++++++++----
 .../app/components/ModelOnboardingModal.tsx        |   5 +-
 packages/dashboard/app/components/ScriptsModal.css |  89 +++++++++
 .../dashboard/app/components/SettingsModal.css     |  11 +-
 .../dashboard/app/components/SettingsModal.tsx     |  22 ++-
 .../dashboard/app/components/TaskDetailModal.css   |   5 +
 packages/dashboard/app/components/TodoView.tsx     |   2 +
 .../app/components/__tests__/App.test.tsx          |  33 ++++
 .../app/components/__tests__/InsightsView.test.tsx |  25 ++-
 .../__tests__/ModelOnboardingModal.test.tsx        |   4 +-
 .../app/components/__tests__/QuickChatFAB.test.tsx |  14 +-
 .../__tests__/SettingsModalNodeRouting.test.tsx    |  12 +-
 .../app/components/__tests__/TodoView.test.tsx     |   4 +-
 .../__tests__/agents-view-mobile.test.tsx          |   5 +-
 .../components/__tests__/onboarding-flow.test.tsx  |   2 +-
 .../app/hooks/__tests__/useAppSettings.test.ts     |   1 +
 .../app/hooks/__tests__/useTaskHandlers.test.ts    |   4 +-
 packages/dashboard/app/hooks/useAppSettings.ts     |  12 ++
 packages/dashboard/app/hooks/useTaskHandlers.ts    |   4 +-
 packages/dashboard/package.json                    |   2 +-
 packages/dashboard/src/__tests__/routes.test.ts    |  14 ++
 packages/dashboard/src/routes.ts                   |   4 +
 .../dashboard/src/routes/register-git-github.ts    |  12 ++
 .../src/routes/register-planning-subtask-routes.ts |   3 +
 .../src/routes/register-task-workflow-routes.ts    |   7 +
 packages/desktop/CHANGELOG.md                      |   7 +
 packages/desktop/package.json                      |   2 +-
 packages/engine/CHANGELOG.md                       |  11 ++
 packages/engine/package.json                       |   2 +-
 .../engine/src/__tests__/agent-heartbeat.test.ts   |  10 ++
 .../src/__tests__/agent-tools-delegation.test.ts   |   2 +
 packages/engine/src/__tests__/agent-tools.test.ts  |  37 ++++
 packages/engine/src/__tests__/cron-runner.test.ts  |   4 +
 packages/engine/src/__tests__/merger.test.ts       |  75 ++++++++
 .../src/__tests__/node-routing-policy.test.ts      |  25 ++-
 .../src/__tests__/pr-comment-handler.test.ts       |   8 +
 .../src/__tests__/scheduler-node-routing.test.ts   |  18 +-
 packages/engine/src/__tests__/triage.test.ts       |   2 +
 packages/engine/src/agent-heartbeat.ts             |  13 +-
 packages/engine/src/agent-tools.ts                 |  13 +-
 packages/engine/src/cron-runner.ts                 |   7 +-
 packages/engine/src/executor.ts                    |   2 +-
 packages/engine/src/merger.ts                      | 181 ++++++++-----------
 packages/engine/src/mission-execution-loop.ts      |   8 +
 packages/engine/src/pr-comment-handler.ts          |   5 +
 packages/engine/src/project-engine.ts              |   8 +
 packages/engine/src/routine-runner.ts              |   4 +
 packages/engine/src/scheduler.ts                   |   8 +-
 packages/engine/src/triage.ts                      |   4 +
 packages/mobile/CHANGELOG.md                       |   7 +
 packages/mobile/package.json                       |   2 +-
 packages/pi-claude-cli/CHANGELOG.md                |   7 +
 packages/pi-claude-cli/package.json                |   2 +-
 packages/plugin-sdk/CHANGELOG.md                   |  10 ++
 packages/plugin-sdk/package.json                   |   2 +-
 .../examples/fusion-plugin-auto-label/CHANGELOG.md |   8 +
 .../examples/fusion-plugin-auto-label/package.json |   2 +-
 .../examples/fusion-plugin-ci-status/CHANGELOG.md  |   8 +
 .../examples/fusion-plugin-ci-status/package.json  |   2 +-
 .../fusion-plugin-notification/CHANGELOG.md        |   8 +
 .../fusion-plugin-notification/package.json        |   2 +-
 .../fusion-plugin-settings-demo/CHANGELOG.md       |   8 +
 .../fusion-plugin-settings-demo/package.json       |   2 +-
 plugins/fusion-plugin-hermes-runtime/CHANGELOG.md  |   8 +
 plugins/fusion-plugin-hermes-runtime/package.json  |   2 +-
 .../fusion-plugin-openclaw-runtime/CHANGELOG.md    |   8 +
 .../fusion-plugin-openclaw-runtime/package.json    |   2 +-
 .../fusion-plugin-paperclip-runtime/CHANGELOG.md   |   8 +
 .../fusion-plugin-paperclip-runtime/package.json   |   2 +-
 101 files changed, 1388 insertions(+), 334 deletions(-)

Fusion-Task-Id: FN-2947
2026-04-29 17:38:25 -07:00
gsxdsm
2febbc9d3a fix(merger): build commit messages from actual content, not branch range
The merge commit message was built from `commitLog`/`diffStat` computed
against `merge-base(branch, main)`. Under squash-merge workflows, when an
earlier task is squash-merged onto main first, branches that forked off
the pre-squash main no longer share ancestry with it — `merge-base`
resolves to a point before the earlier task, and the message describes
work already merged via the prior squash. FN-2952's commit body claimed
11 files / 557 insertions when the actual diff was 2 files / 55 lines.

Subject was also a generic `merge <branch>` regardless of content.

- packages/engine/src/merger.ts: new `computeActualMergeCommitContext`
  helper that derives commitLog/diffStat from the actual integration
  delta (`git diff --cached <integrationTarget> --stat`), filtering
  branch commits by patch-id against the target's recent history to
  drop already-squashed siblings. Wired into both commit-finalization
  sites (`commitOrAmendMergeWithFixes` uses `preAttemptHeadSha`; the
  final amend in `runMergeAttempt` uses `HEAD~1`). Agent-context use of
  the wide range is unchanged.
- packages/engine/src/merger.ts: `buildDeterministicMergeMessage` now
  generates subject and body in parallel via `Promise.all`. Subject is
  composed as `feat(taskId): <ai summary>`, capped at 72 chars, with
  fallback to the legacy `merge <branch>` form on any AI failure.
- packages/core/src/ai-summarize.ts: new `summarizeCommitSubject` and
  `sanitizeCommitSubject` mirroring the body summarizer's structure.
  Same title-summarizer lane, 15s timeout. Sanitizer strips quotes,
  bullets, re-added conventional-commit prefixes, and trailing periods;
  hard-caps at 60 chars.
- packages/core/src/__tests__/ai-summarize.test.ts: 9 tests covering
  the sanitizer's behavior.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 15:22:20 -07:00
gsxdsm
91a05688ee feat(merger): generate richer merge commit messages via AI summarizer
Squash merge commits previously landed with only a bare "merge
fusion/fn-XXXX" subject and a single bullet from the branch's commit
log, leaving git log readers without insight into what actually
changed. Now buildDeterministicMergeMessage calls summarizeCommitBody
(title-summarizer lane when configured, default model otherwise) with
the step commits + diffstat, and emits a three-section body: AI summary
+ Commits merged + Files changed. The deterministic sections always
ship so AI failure / timeout still yields a substantive message.

Also extends summarizeCommitBody to take an optional commitLog and
loosens its prompt for more detail when the change warrants it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 09:54:54 -07:00
gsxdsm
414cbcffda refactor(core,merger): consolidate AI commit-body summarization into ai-summarize.ts
Moves the commit-body AI helper out of merger.ts into the existing
core/ai-summarize.ts module so all short-summary AI work (titles,
chat titles, fallback merge commit bodies) shares one home with
consistent dispatch semantics, error handling, and session lifecycle.

Core (ai-summarize.ts):
- New `summarizeCommitBody(diffStat, rootDir, provider, modelId, opts)`
  exported alongside `summarizeTitle`. Same shape (provider/modelId
  args), same get-engine-or-bail dynamic loading via `getFnAgent`,
  same readonly-tools session, same disposal-in-finally pattern.
- Differs from `summarizeTitle` in three deliberate ways suited to the
  commit-body job:
    1. Returns null on any failure instead of throwing — the caller is
       always the merger, which has a deterministic fallback chain
       behind it. Throwing would force the merger to wrap every call
       in try/catch.
    2. Accepts an optional `signal` to forward engine-pause / shutdown
       cancellation, plus a configurable `timeoutMs` (default 30s)
       so a wedged AI session can't stall a merge indefinitely.
    3. Larger output ceiling (2000 chars vs title's 60) and larger
       input ceiling (4000 chars truncated diff) — commit bodies are
       multi-line and need more room than a 60-char title.
- Exported alongside `summarizeTitle` from `@fusion/core`. Constants
  (`COMMIT_BODY_SYSTEM_PROMPT`, `MAX_COMMIT_BODY_INPUT_LENGTH`,
  `MAX_COMMIT_BODY_LENGTH`, `DEFAULT_COMMIT_BODY_TIMEOUT_MS`) re-exported
  for callers that want to override behavior.

Engine (merger.ts):
- Dropped the local `aiGenerateCommitBody` function (~70 lines) — it
  duplicated the session-creation pattern from `summarizeTitle` while
  living in a place where future maintainers wouldn't think to look.
- `resolveSafeCommitBody` now imports `summarizeCommitBody` from
  `@fusion/core` and delegates. The cascade behavior is unchanged
  (commitLog → AI → diff stat → synthetic) and the title-summarizer
  model preference is preserved (provider/modelId resolved here, then
  passed through).

Tests:
- 6 new test cases in `ai-summarize.test.ts` covering:
  empty input → null, missing engine → null (graceful, never throws),
  missing engine + model selection → null, pre-aborted signal → null,
  custom timeout (returns quickly under 1s ceiling), exposed constants.
- Core: 3136/3136 pass (was 3130 — +6 new). Engine: 2887/2887 pass.
- Typecheck clean, workspace lint clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 08:30:34 -07:00
gsxdsm
5c2240caab feat(merger,dashboard): use title-summarization model for commit body AI + reword settings
Wires the AI commit-body generator (introduced in 52928ff5e) to the
existing dedicated title-summarization model lane, and updates the
settings UI so users understand the model is now used for two
short-summary jobs instead of just one. Also de-duplicates the
project-scope settings UI which was exposing the same model setting in
two places.

Engine (merger.ts):
- aiGenerateCommitBody now prefers settings.titleSummarizerProvider /
  titleSummarizerModelId over the merger's default model. The
  summarization lane is the right tier for this work — small, fast,
  cheap. Falls back to the default merger model when the summarization
  lane is unset.

Dashboard (SettingsModal.tsx):
- MODEL_LANES summarization lane label updated:
  "Title Summarization Model" → "Title and Git Commit Message
  Summarization Model". Helper text updated to mention the dual purpose
  (auto-generated task titles + fallback merge commit message bodies).
  This change flows through automatically to BOTH the global model
  lanes view and any project-scope rendering of MODEL_LANES.
- Removed the duplicate summarization picker from the project-scope
  Model Lanes section: previously it appeared once under Model Lanes
  AND once under "AI Summarization". Now lives only in the dedicated
  picker so users have a single source of truth in project scope.
- The dedicated picker section heading + description rewritten:
  "AI Summarization" → "AI Title and Git Commit Message Summarization",
  with a paragraph explaining both jobs the model performs.
- Inner dropdown label updated for consistency.

Tests + checks: engine 2887/2887 pass, dashboard typecheck clean,
workspace lint clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 08:22:04 -07:00
gsxdsm
52928ff5e7 fix(engine): guarantee non-empty merge commit body via AI fallback cascade
Audit of merge commit sites surfaced four paths that could produce an
empty commit body when the branch had no unique commits relative to its
base (so `git log <base>..<branch>` returns empty), or when the `git log`
collection itself failed. Subject was always non-empty so git accepted
the commit, but the body was missing — breaking downstream consumers
(release notes, dashboard summaries, mergeDetails) that read it.

The AI merge agent is the primary author of merge commit messages; these
fallback paths only run when the agent didn't commit and the merger has
to commit on its behalf. Previously they used `-m "${commitLog}"` which
silently produced `-m ""` on empty input.

Now uses a 4-tier resolveSafeCommitBody cascade — most informative
first, with a deterministic floor so the function never returns empty
and never throws:

  1. The branch's commit log if non-empty.
  2. AI-generated body via aiGenerateCommitBody — a fresh readonly
     session that summarizes the diff stat into 2–6 bullet points.
     Bounded by a 30s timeout (forwards the caller's abort signal too)
     so engine pause / shutdown tears it down promptly. Any failure
     falls through.
  3. The diff stat itself, formatted as a "Files changed" listing.
  4. A synthetic `- merge <branch>` placeholder.

Wired into all three merger fallback commit sites:
  - Auto-resolved-conflicts commit (Attempt 2 success path)
  - -X ours / -X theirs side-strategy commit (Attempt 3)
  - Agent-didn't-commit fallback commit (post-AI verification)

Also defensive: removed `--allow-empty-message` from the executor's
squash-import commit. The message is hardcoded non-empty (subject +
body), but the flag was a footgun — git would silently accept an empty
message if the construction ever broke. Switched to two `-m` args
(subject and body separately) so empty would now correctly fail at
git's level rather than silently land a blank-message commit.

Tests + checks: engine 2887/2887 pass, typecheck clean, workspace lint
clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 08:19:18 -07:00
gsxdsm
b1d2a9ee43 fix(engine): tighten Layer 3 — pass safety constraint into AI prompt + harden Layer 2 restore
Self-review of the recovery cascade surfaced three issues; this commit
addresses all of them.

1. AI didn't actually receive the safety constraint under Layer 3.

The previous commit logged the safety preamble to the task log via
`store.logEntry`, but the merge agent doesn't read task log entries as
prompt context — so the AI was running blind. The "no silent
re-introduction of main's deletions" guarantee was therefore relying
*entirely* on the deterministic verification gate (test + build),
which is correct as a backstop but doesn't help the AI produce a
correct first attempt.

Fixed by threading `preMergeRebaseFallthrough` through
`MergeAttemptParams` → `executeMergeAttempt` → `runAiAgentForCommit` →
`MergePromptParams` → `buildMergePrompt`, where it now injects an
explicit "⚠️ Pre-merge rebase recovery exhausted" preamble at the top
of the user prompt with three concrete rules:
  - Prefer main's deletion when branch re-adds removed lines
  - Prefer main's version on ambiguous hunks
  - Call `fn_report_build_failure` rather than commit a regression
Also includes the original rebase failure message (truncated) so the
AI has diagnostic context.

The truncated-context retry path also forwards the preamble — it's the
safety constraint, not bulk context, so we keep it even when stripping
diff stat / commit log to fit the window.

2. Layer 2's branch-restore could fail with "uncommitted changes".

When a cherry-pick midway through Layer 2's replay fails, the worktree
is in a half-applied state with conflicts in the index. The previous
restore did `git checkout <branch>` (no -f) followed by
`git reset --hard <originalSha>`. The plain checkout would refuse with
"would overwrite local changes" if there were unmerged paths,
preventing the reset from running and leaving the branch at the
half-replayed tip.

Fixed by reordering: hard-reset to the captured original SHA first
(this clears index/working tree of any cherry-pick state), then
`git checkout -f <branch>` to ensure HEAD points at the named branch,
then a final hard-reset to the original SHA as belt-and-suspenders.
Worst case the worktree is at the original branch tip — never worse
than where Layer 2 started.

3. Pre-existing unrelated lint error blocking workspace lint.

`packages/dashboard/src/server.ts` had an unused `resolve` import from
`node:path` left behind by a recent refactor that extracted
`PACKAGE_VERSION` into its own file. The user explicitly asked to
clean it up so workspace lint passes. One-line drop.

Tests + checks:
- Engine: 2886 / 2886 pass (added safety preamble didn't break any
  existing prompt-content assertions)
- Core: 3120 / 3120 pass
- Workspace lint: clean
- Engine typecheck: clean

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 07:20:20 -07:00
gsxdsm
a74b77cec5 fix(engine): auto-recover from squash-merge orphan rebase failures
Adds a layered recovery cascade to the merger's pre-rebase stage so tasks
no longer get stuck in in-review when their declared dependency was
squash-merged to main and left orphan raw commits in the dependent's
history. Also prevents the orphan situation at the source for new tasks.

Why:
- 13 tasks were stuck in in-review for hours, all hitting the same
  pre-merge rebase abort because they shared 6 raw commits inherited
  from FN-2729's branch (declared baseBranch). FN-2729 was then
  squash-merged to main, turning those raw commits into orphans whose
  content is in main but in a different commit shape, conflicting with
  later-merged tasks. The merger's `smart-prefer-main` strategy
  correctly refused -X ours (which would silently re-introduce main's
  deletions), but the only escape hatch was a 30-min cooldown loop
  that retried the same impossible rebase forever.

Recovery cascade (merger.ts pre-rebase stage):
- Layer 1: surgical `git rebase --onto <main> <dep-tip> <branch>` when
  task.baseBranch is set. Resolves the dep tip from the live branch ref
  or recorded baseCommitSha; peels off the dep's inherited commits
  cleanly. Captures the squash-merge-of-dep case end-to-end.
- Layer 2: generic patch-id duplicate-content stripping. Walks the last
  500 main commits, computes patch-ids, then drops branch commits whose
  patch-id matches and cherry-picks the remainder onto main. Captures
  manual cherry-picks, double-merges, and any other duplicate-content
  variant Layer 1 doesn't see. Restores the branch's pre-mutation SHA
  on partial-failure so worst case leaves the worktree no worse than
  before the recovery attempt.
- Layer 3: AI arbitration fall-through. If Layers 1+2 fail, log the
  situation and proceed to the existing 3-attempt AI merge cascade
  instead of throwing. The deterministic post-merge verification
  (test + build) gates whatever the AI produces — that gate is what
  enforces prefer-main's safety contract under fall-through (no silent
  re-introduction of main's deletions).
- Critical: the unsafe `-X ours` Attempt 3 is suppressed under
  fall-through. AI Attempts 1+2 are the only paths that can complete
  the merge; if both fail and verification rejects them, the task
  bounces back to in-progress via the existing engine path rather than
  silently merging.

Prevention (executor.ts worktree creation):
- When a task declares a non-main `baseBranch`, branch the worktree
  off main (origin/<defaultBranch> when worktreeRebaseBeforeMerge is
  enabled and a remote is resolvable; otherwise local rootDir HEAD)
  and `git merge --squash` the dep's content as a single import commit.
  The dependent branch then carries main's history + 1 commit instead
  of inheriting the dep's raw commits, so a future squash-merge of the
  dep produces patch-id-matching content that rebases cleanly.
- Honors settings: respects `worktreeRebaseBeforeMerge`,
  `worktreeRebaseRemote`, and falls back to local HEAD when no remote
  is resolvable. Fully fail-soft: any squash-import error falls back to
  the legacy fork-from-dep behavior so worktree creation still works
  for setups where the squash flow can't run.

Engine-side last-retry fix (project-engine.ts):
- Changed conflict-retry condition from `currentRetries < MAX` to
  `currentRetries + 1 < MAX` so the bounce-to-in-progress code fires
  in the same engine tick as the failing attempt, rather than relying
  on a setTimeout-scheduled Nth attempt that dies on engine restart.
  Without this, a dev-time engine restart between the 3rd and 4th
  retry left the task with mergeRetries=MAX and only the 30-min
  cooldown sweep could try again.

Tests:
- New "Layer 1 recovery" test asserts the surgical --onto rebase fires
  when baseBranch is set and primary rebase aborts, and that Layer 3
  fall-through is NOT triggered when Layer 1 succeeds.
- Updated the "no silent fall-through to -X ours" test to cover the
  new fall-through path: even after Layers 1+2 fail and the merge
  cascade proceeds, -X ours must not run, and the task log must record
  both the Layer 3 fall-through entry and the Attempt 3 suppression.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 07:06:13 -07:00
Fusion
9286205f4b feat(FN-2915): merge fusion/fn-2915
- feat(FN-2915): include github issue refs in commit workflows
- refactor(dashboard): simplify setup wizard manual step
- fix(engine): prevent phantom merges when verification fix runs without a commit

Fusion-Task-Id: FN-2915
2026-04-28 21:13:30 -07:00
gsxdsm
98c5736d22 fix(engine): prevent phantom merges when verification fix runs without a commit
When the AI merge agent reported a build failure via fn_report_build_failure,
executeMergeAttempt immediately ran `git reset --merge` and threw. The catch
handler then spawned an in-merge fix agent on a clean main and called
amendMergeCommitWithFixes, which blindly amended HEAD — the *previous* task's
merge commit — silently dropping the current task's branch and inheriting
the prior task's stats. The dashboard then reported the new task as merged
with completely unrelated files.

- Drop the immediate reset at the build-failure throw site so the squash
  state survives for the in-merge fix path.
- Capture preAttemptHeadSha at each mergeAttempt and refuse to amend when
  HEAD never moved past it; instead, create a fresh commit from the squash
  + fix changes. If neither HEAD moved nor anything is staged, abort the
  merge instead of fabricating success.
- Move the cleanup reset into the mergeAttempt catch handler (with a
  labeled resetMergeWithWarn helper) so it still fires when the fix path
  is exhausted or disabled.
- Replace the AI-authored commit body with a deterministic body built from
  the branch's actual step-commit subjects after every successful AI merge.
  Stops the recurring problem of merge messages describing files that are
  not in the diff.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 21:04:01 -07:00
gsxdsm
e8e6698038 fix(engine): zero out file stats on empty-merge mergeDetails
Follow-up to 502fddf20: when the merge was empty (no commit made),
filesChanged/insertions/deletions were still being captured from
git show --shortstat HEAD — which describes pre-merge HEAD's commit,
unrelated to this task. Consumers (dashboard, audit log) would render
those numbers next to "no commit landed", which is misleading.

Clear stats to 0 alongside the omitted commitSha. Also drop the stats
line from the agent-log summary when mergeWasEmpty.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:39:59 -07:00
gsxdsm
439ea6320a feat(engine): surface merger activity on the agent-log timeline
Users couldn't see the merger's per-task activity from the dashboard
agent-log view — only the executor's session output was visible. When a
merge took an unexpected path (rebase ran twice, attempt 2 auto-resolved
3 lockfiles, attempt 3 fell back, etc.) the only record was in process
logs, which most users don't have access to.

Add appendAgentLog calls at the high-signal merge events:

- Pre-merge rebase: when each stage (remote → remoteRef, local-base
  → local HEAD) completes successfully
- Each merge attempt start, with attempt number + strategy summary
- Final merge outcome: strategy, attempt count, commit sha, file stats,
  and edge cases (empty merge, deferred sha)

Source attribution uses "merger" so the dashboard can style/filter
these distinct from the executor's per-step messages.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:33:48 -07:00
gsxdsm
9c48fd57a4 fix(engine): make stale-merge recovery robust to includeTaskIdInCommit=false
recoverInterruptedMergingTasks searched for landed commits by grepping
commit subjects for the task ID. Users with includeTaskIdInCommit=false
have commit subjects like `feat: ...` (no task ID), so if the merger
crashed after committing but before storing mergeDetails, recovery would
silently fail to find the commit and incorrectly retry the merge.

Three layered defenses:

1. Emit a Fusion-Task-Id: <id> trailer in every Fusion-managed merge
   commit body. The 4 fallback commit invocations now include
   `-m "Fusion-Task-Id: ..."`. After the AI agent commits, an
   idempotent ensureTaskIdTrailerOnHead() amends the trailer in via
   `git interpret-trailers` (no-op if already present).

2. findLandedTaskCommit now tries three sources in order:
   a. task.mergeDetails.commitSha (if reachable from HEAD)
   b. Fusion-Task-Id trailer grep (anchored regex)
   c. Subject grep (legacy commits)

3. Trailer grep uses an anchored regex `^Fusion-Task-Id: <id>$` so
   it doesn't false-match task IDs appearing as substrings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:32:30 -07:00
gsxdsm
502fddf20e fix(engine): keep mergeDetails.commitSha honest on empty/post-push paths
Two cases where mergeDetails.commitSha was wrong:

1. Empty-squash success paths (mergeAttempt + attemptWithSideStrategy
   return true when nothing was staged) recorded pre-merge HEAD as the
   task's commitSha. That commit had nothing to do with this task —
   misleading the dashboard, audit log, and recovery scans.

2. pushAfterMerge can trigger an internal pull --rebase that rewrites
   HEAD; mergeDetails was captured before push, so the stored sha
   referenced a now-orphaned commit.

Fix:
- Track an empty-merge flag on AiInvocationTracker, set at the three
  squashIsEmpty/staged===0 sites. Metadata block omits commitSha when
  the flag is set.
- After successful pushAfterMerge, recapture HEAD and update
  mergeDetails.commitSha if it changed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:29:21 -07:00
gsxdsm
6d9dcdd755 fix(engine): split pre-merge rebase into independent remote + local-base stages
The pre-merge rebase had the local-base rebase nested inside the
remote-rebase success path, so when no remote resolved (or worktreePath
was missing), the entire block exited without running local-base. That
left smart-prefer-main exposed: with no rebase, the -X ours fallback
would silently re-introduce code main had recently deleted, which is
exactly the case the strategy is meant to prevent.

Restructure so remote rebase (Stage 1) and local-base rebase (Stage 2)
run as independent gates. Local-base rebase still picks up sibling-task
merges that landed locally even when the remote stage was skipped or
disabled, so prefer-main always gets at least one defense.

Also relax the semantic-incompatibility guard: prefer-main now requires
EITHER stage to remain enabled (was: required worktreeRebaseBeforeMerge).

Extracted runLocalBaseRebase() helper to remove duplication between the
two entry points (after Stage 1 vs. standalone) and centralize the
ancestor-check + abort handling.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:24:12 -07:00
gsxdsm
9d09b4c599 fix(engine): refuse to treat non-conflict squash failures as no-op merges
Attempt 2 of the merge cascade caught any git merge --squash failure into
mergeExitedWithConflicts=true. If the failure was non-conflict (pre-commit
hook rejection, IO error, locked repo) and produced no U files, the code
fell into the "all conflicts auto-resolved" branch with empty classified
arrays, ran deterministic verification on pre-merge HEAD, and returned
true — recording merge metadata for a merge that never happened.

Distinguish "exit code 1 with U files" (recoverable) from "any other
failure" (real). When a real failure surfaces with no conflicts, raise a
sentinel MergeNonConflictError that the outer mergeAttempt catch propagates
without retrying — retrying just re-runs the same broken command.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:19:12 -07:00
gsxdsm
8aaa479296 fix(engine): harden smart-prefer-main against silent rebase skips
The smart-prefer-main strategy depends on a successful pre-merge rebase
to honor main's deletions. Previously, three failure modes silently fell
through to the -X ours merge, which would re-introduce code main had
just removed (because branch additions vs main deletions don't textually
conflict and -X ours only resolves content conflicts, not modify/delete).

- Hard-fail when prefer-main is paired with worktreeRebaseBeforeMerge=false
  (semantically incoherent combination)
- Hard-fail when the pre-merge rebase starts and aborts (any of the three
  rebase paths: remote, nested local-base, or fallback local-only)
- Warn (not throw) on environmental silent skips — no remote resolvable
  or no worktreePath — so the gap is observable in logs without breaking
  common test/setup environments

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 16:57:17 -07:00
gsxdsm
ad45c0b1b4 feat(core,engine): split smart merge strategy into prefer-main / prefer-branch
The single "smart" strategy is now two flavors with the new default flipped
to prefer-main. Both share a pre-cascade `git fetch origin <currentBranch>`
+ best-effort fast-forward so a freshly-pushed sibling commit doesn't get
clobbered when the fallback resolves a conflict against a stale base.

- "smart-prefer-main" (new default): -X ours fallback. Protects just-merged
  sibling work from being regressed by a concurrent task branch.
- "smart-prefer-branch": -X theirs fallback. Equivalent to legacy "smart".

Legacy "smart" / "prefer-main" enum values are accepted and normalized via
`normalizeMergeConflictStrategy()` so existing settings.json files migrate
seamlessly. The fast-forward step gracefully degrades on fetch failure or
divergent local main (logs and continues).

Updates settings UI dropdown, test helpers, and adds 5 fetch+ff regression
tests + 7 normalize-helper tests. Lint cleanup of two empty catch blocks
in scripts/release.mjs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 15:44:38 -07:00
Fusion
5e6dbbb6f3 fix(dashboard): hand off cleanly from setup wizard to model onboarding
On a fresh install useAuthOnboarding's effect ran at mount before the
setup wizard's 500ms auto-open timer fired. The one-shot ref locked,
and the resolved fetch could either stack model onboarding on top of
the wizard or never re-trigger after the wizard closed.

- Gate the trigger on projectId being set so the wizard owns the
  bootstrap phase; the auth check only fires once a project exists.
- Re-check setupWizardOpen via a ref when the auth fetch resolves to
  avoid stacking onboarding on top of a wizard opened mid-fetch.
- Release the one-shot in that suppressed branch so the effect retries
  when the wizard closes.

Adds two regression tests: fresh-install handoff and mid-fetch wizard
suppression.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 15:14:18 -07:00
gsxdsm
49e8da1961 fix(FN-2855): scope task diff base to baseCommitSha when branch is null
When a dependency task merges and its branch is deleted, self-healing nulls
the dependent task's baseBranch. Both resolveDiffBase (dashboard) and
resolveTaskDiffBaseRef (merger) defaulted to "main" in that case, widening
the diff range to merge-base(HEAD, main) and surfacing unrelated history —
e.g. FN-2855 reported 108 changed files instead of 16. Skip the merge-base
step when baseBranch is unset and a baseCommitSha is recorded; fall back to
"main" only for legacy tasks lacking both hints.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 14:25:55 -07:00
gsxdsm
4e2131f444 fix: harden cross-platform paths and child-process handling
Replace process.env.HOME fallbacks with os.homedir() in dashboard usage
probes and the hermes plugin profile resolver so unset HOME no longer
yields literal "~" paths. Skip POSIX process-group semantics on Windows
in engine/merger and dashboard-tui's pgrep-based vitest killer. Add
shell: true to npx spawns in CLI skills/extension so .cmd shims resolve
on Windows, and route test:build-exe through cross-env.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 23:27:27 -07:00
gsxdsm
3ff2ef8c8d fix(dashboard): prevent foreign-branch diffs after worktree pool reuse
When the worktree-recycle pool reassigned a path to a new task, the old
task's diff endpoints kept reading the new task's branch state — surfacing
unrelated commits as the original task's "files changed" list.

- Clear task.worktree/branch in the merger after the worktree is released
  to the pool or removed, so the path no longer points anywhere.
- Validate the worktree's current branch matches task.branch in the three
  worktree-backed diff endpoints; on mismatch return empty rather than
  diffing against a foreign branch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 11:04:42 -07:00
gsxdsm
d30edfda5d feat: live task token usage and stats-tab fixes
Capture per-session token usage from pi-coding-agent's getSessionStats()
after each promptWithFallback in the executor and merger paths, so
task.tokenUsage populates live during runs and reflects final totals on
done tasks. Previously the executor never read session usage and only
the heartbeat path bumped agent token totals, leaving task.tokenUsage
undefined even after completion.

Stats panel and done-card timing also now reflect live state: the modal
overlays the SSE-updated task prop on top of the one-shot fullDetail
snapshot, in-progress workflow steps contribute live elapsed to the
Workflow runtime metric, and the done card uses Timed duration (matching
the stats tab) with workflow runtime as fallback. Time indicator labels
coarsened to <1m / Nm / Nh / Nd.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 04:51:29 -07:00
Fusion
4330eef126 feat(FN-2662): enforce model override fallback hierarchy
- Fix triage planning model resolution to fall back through project/global planning settings and default overrides
- Fix reviewer model selection to honor validator-specific settings before default provider/model overrides
- Update merger model resolution to apply default override fallback and align shared task setting types/executor flow
- Add regression coverage for triage, reviewer, and merger fallback behavior and update settings hierarchy documentation
2026-04-27 02:02:01 -07:00
Fusion
809a76a3c2 feat(FN-2659): add merger verification agent log events
- Emit merger agent log entries when deterministic verification starts, runs commands, and completes successfully
- Record tool_result/tool_error details for verification command outcomes including timing and truncated output summaries
- Add agent-log coverage for in-merge verification fix lifecycle events (start, retry, success, and failure)
- Extend merger deterministic verification tests to assert start/success and failure agent log entries
2026-04-27 01:03:16 -07:00
gsxdsm
a2f23eae29 fix(engine): make global pause actually stop in-flight verification
Hitting Stop (globalPause) disposed the AI merge agent session but left
the spawned `pnpm test` / `pnpm build` child processes running until
they finished naturally. With recurring flaky-test loops at Step 5,
that meant Stop had no visible effect — new test runs kept piling up
across multiple worktrees.

Two gaps:
- project-engine.ts onGlobalPause never called mergeAbortController.abort(),
  so subsequent verification commands (gated by the signal) weren't cancelled.
- merger.ts execWithProcessGroup only listened to its own internal
  timeout — passing an AbortSignal had no effect on the in-flight
  child process group.

Fix: abort the controller on global pause, and have execWithProcessGroup
SIGTERM/SIGKILL the detached process group when its signal aborts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 20:51:40 -07:00
Fusion
a24f50ba02 feat(FN-2617): merge fusion/fn-2617 (auto-resolved)
- test(FN-2617): stabilize workflow remediation timeout under full suite
- test(FN-2617): complete Step 5 — add openclaw runtime resolution coverage
- feat(FN-2617): complete Step 4 — route step sessions through runtime resolution
- fix(FN-2617): thread runtimeHint through merger rebase push flow
- feat(FN-2617): complete Step 3 — wire runtimeHint across engine subsystems
- feat(FN-2617): complete Step 2 — thread runtimeHint in executor paths
- feat(FN-2617): complete Step 1 — add runtimeHint extraction helper
2026-04-26 16:14:35 -07:00
gsxdsm
47514942e1 fix(merger): bump verification timeout to 10m and reap process group
The 5-minute exec timeout only killed the immediate shell, leaving
vitest/pnpm worker trees alive. Across retries these accumulated and
thrashed the host, starving the engine and TUI. Switch verification to
spawn-based runner with detached process group so timeouts SIGTERM the
whole tree (SIGKILL after 5s grace), and bump the wallclock to 10m for
larger workspaces. Stream-truncate output instead of relying on ENOBUFS.

Also fix two flaky/race-prone dashboard tests that were red on main and
blocking every in-review task at merge verification.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 16:05:40 -07:00
Fusion
0c696f0107 feat(FN-2571): enforce merger aborts through shutdown and git flow
- Add merger abort primitives and track active merge runs for coordinated cancellation
- Abort in-flight merges during engine shutdown and propagate AbortError through fallback catch paths
- Honor abort signals before commit, push, and dependency sync to prevent post-cancel side effects
- Expand merger and project-engine tests to cover abort propagation and merge-abort-on-stop behavior
2026-04-25 21:39:35 -07:00
gsxdsm
9425d7c798 feat(FN-2490): merge fusion/fn-2490 2026-04-25 07:59:48 -07:00
gsxdsm
1bbc5fcce7 fix(merger,dashboard): handle empty-squash commitSha to keep card and Changes tab in sync
When a branch contained commits already on main (duplicate cherry-picks),
the merger's local squash collapsed to an empty commit. The merger then
recorded that empty commit's SHA on mergeDetails.commitSha. The actual
content landed later on main as a different SHA via PR merge, but the
task kept pointing at the orphaned empty commit.

Symptom: TaskCard showed "N files changed" (falling back to
task.modifiedFiles), but the Changes tab in the modal showed nothing
because the API hit `git diff sha^..sha` on the empty commit and
returned no files.

Two fixes:

1. merger.ts: detect empty squash commits and skip storing commitSha,
   logging clearly. recoverInterruptedMergingTasks → findLandedTaskCommit
   already exists to backfill the right SHA when the real commit lands;
   a missing commitSha is a known fallback path the UI already handles.

2. TaskChangesTab.tsx: when the API returns no files for a done task,
   fall back to task.modifiedFiles (paths only, no patches) with a clear
   note. Mirrors the existing 3-tier fallback in TaskCard.tsx:1090-1124
   so card and modal always agree.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 13:31:34 -07:00