Commit Graph

1773 Commits

Author SHA1 Message Date
gsxdsm
2df891fd53 chore: changeset for release.yml auto-trigger on v* tags
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 10:48:35 -07:00
gsxdsm
2a3535893b feat(FN-5622): add goals REST API with store accessor and route handlers
Introduces a Goals REST API (`GET/POST/PUT /api/goals` and `GET/PUT /api/goals/:id`) backed by a new `@fusion/core` goal store and typed goal types, including comprehensive route and store test coverage. Documentation on architecture and storage is updated to reflect the new domain, and a changeset

Fusion-Task-Id: FN-5622

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fusion-Task-Id: FN-5622
2026-05-28 10:32:16 -07:00
gsxdsm
e84673c632 feat(FN-5618): handle deleted source issues in TaskCard
Implements source issue delete handling in the task dashboard, including prompt-based user interaction for source-linked issues, new GitHub tracking state logic in `packages/dashboard/src/github-tracking-state.ts`, and corresponding test coverage, with documentation added to `docs/task-management.md

Fusion-Task-Id: FN-5618

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fusion-Task-Id: FN-5618
2026-05-28 08:57:59 -07:00
gsxdsm
0a04837e7a chore(release): v0.35.0
Version bump via changesets.
2026-05-28 08:20:50 -07:00
gsxdsm
e0a7dd7793 Merge pull request #1100 from plarson/feat/rtk-pi-bash-rewrite
feat(engine): add opt-in RTK bash rewriting
2026-05-28 08:13:54 -07:00
gsxdsm
d76b6f9503 feat(FN-5615): add dynamic system panel height to dashboard-tui
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
2026-05-27 21:31:13 -07:00
gsxdsm
8a0fbf00b9 feat(FN-5611): strip react devtools from bun compile builds
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
2026-05-27 21:31:13 -07:00
gsxdsm
d767e2ecbd feat(FN-5601): add OpenAI Responses API type support to custom providers
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
2026-05-27 21:31:12 -07:00
gsxdsm
da34bd06e3 feat(FN-5595): add oauth relogin banner with validity logger
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
2026-05-27 21:31:12 -07:00
Phil Larson
199204989e feat(engine): add opt-in RTK bash rewriting 2026-05-27 11:51:05 -07:00
gsxdsm
2f80c6ea7b chore(release): v0.34.0
Version bump via changesets.
2026-05-26 23:45:59 -07:00
gsxdsm
390bd7f923 perf(dashboard): cache gh CLI checks and defer SQLite integrity scan
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>
2026-05-26 23:41:28 -07:00
gsxdsm
4e4830f592 fix(engine): harden merge finalize SQLite bind and recover bare merge subjects
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>
2026-05-26 21:57:31 -07:00
gsxdsm
6a6c6fdbfd perf(dashboard): speed up startup and eliminate API request storms
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>
2026-05-26 21:57:31 -07:00
gsxdsm
903806f6fb Merge pull request #978 from titosemi/fix/chat-view-message-truncation
fix(dashboard): wire scroll-to-top pagination for chat history instead of loading all messages
2026-05-26 21:20:23 -07:00
gsxdsm
91b5d77934 Merge pull request #974 from titosemi/fix/chat-sessions-multi-project-scope-clean
fix(dashboard): use per-project chatStore in chat session API routes
2026-05-26 08:27:02 -07:00
gsxdsm
1fb905afb7 feat(FN-5585): add planning branch controls to planning mode modal
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
2026-05-25 16:32:57 -07:00
gsxdsm
5eacd79b43 feat(FN-5584): merge fusion/fn-5584 2026-05-25 15:27:06 -07:00
gsxdsm
06a107dcba fix(FN-5584): restore planning fallback when primary provider API key is missing
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>
2026-05-25 14:36:33 -07:00
gsxdsm
e4099f2ff5 Merge pull request #1063 from agustinsacco/feat/editable-agent-name
feat: make agent name editable in review step of creation modal
2026-05-25 14:21:45 -07:00
gsxdsm
65c04fa36a Merge pull request #937 from abeperl/feat/fn-024-add-dependencies-to-task-update
feat: Add optional dependencies parameter to fn_task_update tool
2026-05-25 14:18:01 -07:00
gsxdsm
629aa29b65 fix(dashboard): use fs.mkdir for cloudflared local-bin fallback on Windows
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>
2026-05-25 13:57:04 -07:00
gsxdsm
3a81e65e1d Merge branch 'main' into feat/fn-024-add-dependencies-to-task-update 2026-05-25 13:28:32 -07:00
gsxdsm
8650480622 Merge branch 'main' into fix/chat-sessions-multi-project-scope-clean 2026-05-25 13:28:29 -07:00
gsxdsm
6085e91295 Merge branch 'main' into fix/chat-view-message-truncation 2026-05-25 13:28:27 -07:00
gsxdsm
56e3ee0454 Merge branch 'main' into feat/editable-agent-name 2026-05-25 13:27:40 -07:00
gsxdsm
88c465cfc0 fix(ci,engine): repair test sharding, case-variant ambiguity detection, post-merge CI
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>
2026-05-25 10:24:21 -07:00
Agustin Sacco
bad675994a feat: make agent name editable in review step of creation modal 2026-05-24 04:57:53 -04:00
gsxdsm
0a6da9f4ce feat(FN-5583): handle notification deep links in useDeepLink hook
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
2026-05-24 00:11:08 -07:00
gsxdsm
2d04cbe09d chore(release): v0.33.0
Version bump via changesets.
2026-05-23 23:12:21 -07:00
gsxdsm
8741ea0f56 chore: remove changeset referencing non-workspace package
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 23:10:23 -07:00
gsxdsm
ba72c27bbb chore: remove invalid changeset referencing non-workspace package
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 23:08:46 -07:00
gsxdsm
4f38ed152a fix(dashboard): clear "needs action" on recent advances after manual sync
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>
2026-05-23 20:49:30 -07:00
gsxdsm
b8919b7bb4 chore(test-isolation): detect live engine lock + prune stale tests
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>
2026-05-23 20:18:08 -07:00
gsxdsm
0c0839eeb6 fix(merger): retry on non-FF ref-advance instead of failing the task (FN-5576)
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>
2026-05-23 19:56:37 -07:00
gsxdsm
a6a57dc40a test: guard tests from killing the live dashboard port
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>
2026-05-23 19:51:36 -07:00
gsxdsm
5d35b64bd1 fix(dashboard): remove duplicate integration-advances UI; Sync working tree skips origin
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>
2026-05-23 19:24:35 -07:00
gsxdsm
6e7f1e570e fix(dashboard): useMergeAdvanceNotice tests should waitFor toBeDefined
`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>
2026-05-23 18:39:36 -07:00
gsxdsm
de67c5137f fix(dashboard): pull syncs the worktree to local integration tip, not just to origin
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>
2026-05-23 18:23:24 -07:00
gsxdsm
acf3502a25 fix(merger): refuse no-op finalize when modifiedFiles claims work was done
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>
2026-05-23 17:42:21 -07:00
gsxdsm
d5cfa92c23 fix(dashboard): close 7 review findings on extended-status hardening
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>
2026-05-23 17:31:39 -07:00
gsxdsm
408e20bdc6 fix(merger): prevent tasks landing in Done with no commit on main
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>
2026-05-23 17:16:45 -07:00
gsxdsm
ef12df4363 fix(dashboard): close 8 review findings on git-status + integration-branch setting
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>
2026-05-23 17:10:59 -07:00
gsxdsm
916047c2ae feat(dashboard): Integration branch is a dropdown of local branches + Custom…
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>
2026-05-23 16:44:47 -07:00
gsxdsm
85786e7893 feat(dashboard): extended integration-branch status in Git Manager
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>
2026-05-23 16:38:00 -07:00
gsxdsm
084bdc67f4 feat(dashboard): expose integrationBranch setting in the settings modal
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>
2026-05-23 16:31:48 -07:00
gsxdsm
99359b6536 fix(dashboard): unbreak banner dismiss + suppress when auto-sync handled it
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>
2026-05-23 15:57:48 -07:00
gsxdsm
d8493f9467 feat(dashboard): expose mergeAdvanceAutoSync in the project settings modal
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>
2026-05-23 15:50:03 -07:00
gsxdsm
dc944949b1 fix(engine,dashboard): close 7 review findings on merger auto-sync
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>
2026-05-23 15:13:17 -07:00
gsxdsm
4c31e885bd feat(engine): merger auto-syncs project-root checkout after ref advance
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>
2026-05-23 14:41:11 -07:00