Commit Graph

3774 Commits

Author SHA1 Message Date
Fusion
4a9345301a feat(FN-2730): merge fusion/fn-2730
- fix(FN-2729): silence ansi strip lint warning
- test(FN-2729): complete Step 10 — cover node health indicators
- feat(FN-2729): complete Step 9 — colorize CLI node status output
- feat(FN-2729): complete Step 8 — use NodeHealthDot in settings routing status
- test(FN-2729): align ListView node override option expectations
- feat(FN-2729): complete Step 7 — add bulk node status indicators

Fusion-Task-Id: FN-2730
2026-04-29 09:11:44 -07:00
Fusion
9bdc80fa06 feat(FN-2729): merge fusion/fn-2729
- feat(FN-2729): align routing lock and bulk node status indicator
- feat(FN-2729): complete Step 4 — remove duplicated node status CSS
- feat(FN-2729): complete Step 3 — add routing summary health dot
- feat(FN-2729): complete Step 2 — adopt NodeHealthDot in InlineCreateCard
- feat(FN-2729): complete Step 1 — adopt NodeHealthDot in TaskForm

Fusion-Task-Id: FN-2729
2026-04-29 09:05:04 -07:00
gsxdsm
3672700306 chore(release): v0.8.4
Version bump via changesets.
2026-04-29 08:59:49 -07:00
gsxdsm
d945947a05 chore(dashboard): tidy stray workspace artifacts
Two pre-existing local modifications that had been sitting in the
working tree across this session, neither produced by the recovery /
fallback work in adjacent commits. Committing them so the tree is clean.

- packages/dashboard/app/components/SettingsModal.css
  Removes one trailing blank line at end-of-file. No semantic change.

- packages/dashboard/app/components/__tests__/ListView.test.tsx
  Drops one test case ("shows NodeHealthDot for selected bulk node
  override", 15 lines) from the "ListView - Bulk Selection" describe
  block. The test asserted that selecting a bulk node override
  rendered a `.status-dot--online` indicator in the DOM. Sibling tests
  (option labels, batchUpdateTaskModels invocation) are preserved.
  Likely debris from the FN-2729 NodeHealthDot work that didn't survive
  one of the in-flight squash merges; the underlying behavior is still
  exercised by the kept tests.

Verified: pnpm --filter @fusion/dashboard test ListView passes
(125/125), so the removed assertion isn't load-bearing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 08:57:44 -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
daf6dfa2c2 fix(FN-XXX): sync workspace version on release 2026-04-29 08:15:38 -07:00
Fusion
d250f866d6 feat(FN-2729): unify node status indicator with NodeHealthDot
- Resolve merge conflicts in InlineCreateCard and QuickEntryBox using the existing node picker UX
- Replace ad-hoc node status rendering with NodeHealthDot in task creation surfaces
- Keep advanced quick-create controls and portal-based dropdown behavior intact
- Preserve related dashboard styling and tests included in the staged squash

Fusion-Task-Id: FN-2729
2026-04-29 08:15:01 -07:00
gsxdsm
5661188a61 fix(FN-0000): harden sqlite startup validation 2026-04-29 07:58:58 -07:00
gsxdsm
597295e749 fix(FN-XXX): align tui startup update check 2026-04-29 07:54:45 -07:00
gsxdsm
42c27fcc3e fix(FN-XXX): harden windows path handling 2026-04-29 07:35:23 -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
Fusion
ba3d868c2d feat(FN-2944): merge fusion/fn-2944
- test(FN-2944): cover already checked out worktree conflict recovery
- fix(FN-2944): recognize git already checked out worktree conflict
- fix(engine): auto-recover from squash-merge orphan rebase failures

Fusion-Task-Id: FN-2944
2026-04-29 07:10:52 -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
09cf8ccdb7 feat(FN-2948): merge fusion/fn-2948
- feat(FN-2948): complete Step 5 — apply review feedback and finalize delivery
- test(FN-2948): complete Step 3 — update insights section visibility coverage
- feat(FN-2948): complete Step 2 — enhance insight item readability
- feat(FN-2948): complete Step 1 — hide empty insight sections
- feat(FN-2939): merge fusion/fn-2939
- feat(FN-2946): merge fusion/fn-2946

Fusion-Task-Id: FN-2948
2026-04-29 02:42:57 -07:00
Fusion
e891a05950 feat(FN-2939): merge fusion/fn-2939
- feat(FN-2939): complete Step 6 — add verification changeset
- test(FN-2939): complete Step 4 — tighten bundle-output coverage
- feat(FN-2946): merge fusion/fn-2946

Fusion-Task-Id: FN-2939
2026-04-29 02:39:06 -07:00
Fusion
af036992e7 feat(FN-2946): merge fusion/fn-2946
- test(FN-2946): complete Step 2 — add maxLength 40 truncation tests
- feat(FN-2946): complete Step 1 — reduce truncation length to 40
- feat(FN-2941): merge fusion/fn-2941
- feat(FN-2938): merge fusion/fn-2938

Fusion-Task-Id: FN-2946
2026-04-29 02:23:09 -07:00
Fusion
85623f45a2 feat(FN-2941): merge fusion/fn-2941
- feat(FN-2941): complete Step 6 — apply review feedback

Fusion-Task-Id: FN-2941
2026-04-29 02:10:51 -07:00
Fusion
f25aad9ffb feat(FN-2938): merge fusion/fn-2938
- fix(FN-2938): satisfy lint for control-request debug logging
- test(FN-2938): complete Step 4 — update protocol regression tests
- feat(FN-2938): complete Step 3 — remove stdin control request routing
- feat(FN-2938): complete Step 2 — make control handler pure
- feat(FN-2938): complete Step 1 — fix stdin EOF and spawn flags
- feat(FN-2929): merge fusion/fn-2929

Fusion-Task-Id: FN-2938
2026-04-29 02:07:35 -07:00
Fusion
854dc7d1e8 feat(FN-2929): merge fusion/fn-2929
- fix(FN-2929): complete Step 6 — add changeset and docs
- test(FN-2929): update loginProvider API test for origin payload
- test(FN-2929): complete Step 4 — cover oauth redirect rewrite and callback proxy
- fix(FN-2929): complete Step 3 — send browser origin in auth login request
- fix(FN-2929): complete Step 1-2 — rewrite OAuth redirect and add callback proxy
- feat(FN-2934): merge fusion/fn-2934

Fusion-Task-Id: FN-2929
2026-04-29 01:32:37 -07:00
Fusion
2bd552b52a feat(FN-2934): merge fusion/fn-2934
- test(FN-2934): complete Step 2 — cover paused triage actions visibility
- feat(FN-2934): complete Step 1 — show actions for paused triage tasks
- feat(FN-2930): merge fusion/fn-2930

Fusion-Task-Id: FN-2934
2026-04-29 01:11:05 -07:00
Fusion
3ca8bf124a feat(FN-2930): merge fusion/fn-2930
- test(FN-2930): complete Step 2 — add auth redirect rewrite coverage
- feat(FN-2930): complete Step 1 — rewrite OAuth redirect_uri host
- feat(FN-2928): merge fusion/fn-2928

Fusion-Task-Id: FN-2930
2026-04-29 00:45:58 -07:00
Fusion
1f807755aa feat(FN-2928): merge fusion/fn-2928
- test(FN-2928): complete Step 2 — assert hidden usage rows use display none
- fix(FN-2928): complete Step 1 — collapse hidden usage window layout
- feat(FN-2927): merge fusion/fn-2927

Fusion-Task-Id: FN-2928
2026-04-29 00:31:46 -07:00
Fusion
f69ef64b48 feat(FN-2927): merge fusion/fn-2927
- test(FN-2927): complete Step 3 — cover keyboardOpen mobile nav behavior
- feat(FN-2927): complete Step 2 — wire keyboard state into mobile nav
- feat(FN-2927): complete Step 1 — add keyboardOpen hide behavior
- feat(FN-2916): streamline Cloudflare quick tunnel settings UX
- feat(FN-2913): merge fusion/fn-2913

Fusion-Task-Id: FN-2927
2026-04-29 00:23:29 -07:00
Fusion
4ba05c52d8 feat(FN-2916): streamline Cloudflare quick tunnel settings UX
- Default Cloudflare remote access to Quick Tunnel mode in settings documentation
- Replace the Quick Tunnel checkbox with an Advanced (Named Tunnel) details disclosure in SettingsModal
- Auto-sync quick tunnel state from the details open/close state while preserving named tunnel field editing
- Update SettingsModal tests to cover the new advanced-toggle behavior and persisted quick tunnel payload

Fusion-Task-Id: FN-2916
2026-04-29 00:12:35 -07:00
Fusion
d212ee12b3 feat(FN-2913): merge fusion/fn-2913
- feat(FN-2913): complete Step 7 — align InlineCreateCard node picker UX

Fusion-Task-Id: FN-2913
2026-04-29 00:09:39 -07:00
gsxdsm
d2b983225f feat(FN-2911): merge fusion/fn-2911 2026-04-28 23:34:12 -07:00
Fusion
ebd3201153 feat(FN-2901): merge fusion/fn-2901
- fix(FN-2901): reset source issue collapse state on task change
- fix(FN-2901): use issue number in collapsed source issue summary
- feat(FN-2901): complete Step 5 — finalize source issue toggle UX
- test(FN-2901): complete Step 4 — verify collapsible source issue behavior
- feat(FN-2901): complete Step 3 — style collapsible source issue header
- feat(FN-2901): complete Step 2 — add collapsible source issue section
- feat(FN-2901): complete Step 1 — update lucide imports
- feat(FN-2729): merge fusion/fn-2729
- fix(engine): prevent auto-merge cooldown loop on unresolvable conflicts
- feat(FN-2911): improve chat attachment compose and preview UX

Fusion-Task-Id: FN-2901
2026-04-28 23:31:23 -07:00
Fusion
3263babe1b feat(FN-2729): merge fusion/fn-2729
- fix(FN-2729): silence ansi strip lint warning
- test(FN-2729): complete Step 10 — cover node health indicators
- feat(FN-2729): complete Step 9 — colorize CLI node status output
- feat(FN-2729): complete Step 8 — use NodeHealthDot in settings routing status
- test(FN-2729): align ListView node override option expectations
- feat(FN-2729): complete Step 7 — add bulk node status indicators
- fix(engine): prevent auto-merge cooldown loop on unresolvable conflicts
- feat(FN-2911): improve chat attachment compose and preview UX
- feat(FN-2921): merge fusion/fn-2921
- feat(FN-2909): merge fusion/fn-2909
- feat(FN-2914): merge fusion/fn-2914
- feat(FN-2902): merge fusion/fn-2902
- chore(release): v0.8.3
- Update changeset
- fix(tui): swap 3/4 panel hotkeys so they match the help text
- fix(tui): always show auth token and report accurate macOS memory usage

Fusion-Task-Id: FN-2729
2026-04-28 23:23:34 -07:00
gsxdsm
14d2bb7b8e fix(engine): prevent auto-merge cooldown loop on unresolvable conflicts
Tasks were getting stuck in `in-review` forever when auto-merge could not
resolve conflicts within MAX_AUTO_MERGE_RETRIES. The conflict-exhaustion
branch silently cleared `status` (no error, no log entry, no comment),
and the 30-min cooldown sweep would reset retries and re-attempt the
same impossible merge — looping silently with no user-facing surface.

Why:
- FN-2918 and FN-2903 both spent hours in this loop with no error/comment
  visible on the task. The only log evidence was repeated
  "Auto-merge retry cooldown elapsed (30m idle)" entries with no
  follow-up outcome.

How to apply:
- Every merge failure now writes a `<Manual|Auto>-merge failed: <msg>`
  entry to the task log so the dashboard surfaces the reason.
- Conflict-retry exhaustion now bounces the task back to `in-progress`
  with a comment + log entry so the executor re-rebases against main
  and retries — mirroring the verification-failure-bounce pattern.
- New `mergeConflictBounceCount` task field caps outer bounces
  (`MAX_MERGE_CONFLICT_BOUNCES = 2`); past the cap, the task is parked
  in `in-review` with `status="failed"` and a follow-up triage task is
  created so a human can resolve the conflict manually.
- Non-conflict and non-direct-strategy errors now also set
  `status="failed"` so the cooldown sweep can't re-pick them up.
- `canMergeTask` skips tasks with `status="failed"` so terminal
  failures (verification cap, bounce cap, non-conflict error) are no
  longer eligible for cooldown re-attempts.

Schema migration v52 adds the `mergeConflictBounceCount` column.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 23:17:10 -07:00
Fusion
bdfb08f380 feat(FN-2911): improve chat attachment compose and preview UX
- Add attachment-first chat compose flow with paperclip picker, drag-and-drop, and paste support in ChatView input
- Render pending attachment preview chips with per-file remove actions and drag-over visual affordance
- Display sent message attachments with image/file presentation and accessible focus/interaction styling
- Update ChatView tests and icon mocks to cover attachment-only sends, file selection, and preview behavior

Fusion-Task-Id: FN-2911
2026-04-28 23:11:22 -07:00
Fusion
83ad6e45ab feat(FN-2921): merge fusion/fn-2921
- feat(FN-2921): complete Step 2 — apply filename-first truncation in change lists
- feat(FN-2921): complete Step 1 — prioritize filename-first path truncation

Fusion-Task-Id: FN-2921
2026-04-28 22:43:51 -07:00
Fusion
fc837ad349 feat(FN-2909): merge fusion/fn-2909
- test(FN-2909): complete Step 2 — add v50→v51 attachment migration coverage
- feat(FN-2914): merge fusion/fn-2914
- feat(FN-2902): merge fusion/fn-2902

Fusion-Task-Id: FN-2909
2026-04-28 22:24:13 -07:00
Fusion
2e5413984f feat(FN-2914): merge fusion/fn-2914
- fix(FN-2914): remove divider line between close and shortcuts buttons in terminal
- feat(FN-2902): merge fusion/fn-2902
- chore(release): v0.8.3
- Update changeset
- fix(tui): swap 3/4 panel hotkeys so they match the help text
- fix(tui): always show auth token and report accurate macOS memory usage
- chore(release): v0.8.2
- feat(FN-2895): merge fusion/fn-2895

Fusion-Task-Id: FN-2914
2026-04-28 22:17:32 -07:00
Fusion
67226f47c2 feat(FN-2902): merge fusion/fn-2902
- feat(FN-2902): complete Step 6 — address review feedback
- test(FN-2902): stabilize bundle-output assertions for pi-claude-cli
- test(FN-2902): complete Step 4 — cover grouped tool-call rendering
- feat(FN-2902): complete Step 3 — add grouped tool-call styles
- feat(FN-2902): complete Step 2 — group multiple tool calls in QuickChatFAB
- feat(FN-2902): complete Step 1 — group multiple tool calls in ChatView
- chore(release): v0.8.3
- Update changeset
- fix(tui): swap 3/4 panel hotkeys so they match the help text
- fix(tui): always show auth token and report accurate macOS memory usage
- chore(release): v0.8.2
- feat(FN-2895): merge fusion/fn-2895
- fix(cli): validate agentId in fn_task_create/update and unblock bundle tests

Fusion-Task-Id: FN-2902
2026-04-28 22:14:07 -07:00
gsxdsm
d10f33210f chore(release): v0.8.3
Version bump via changesets.
2026-04-28 22:02:56 -07:00
gsxdsm
013c814f54 Update changeset 2026-04-28 22:02:30 -07:00
gsxdsm
4c3e88cfde fix(tui): swap 3/4 panel hotkeys so they match the help text
3 now jumps to Stats and 4 to Utilities, matching the documented
"System/Logs/Stats/Utilities/Settings" order. PANEL_ORDER updated so
prev/next cycle navigation follows the same sequence.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 21:59:48 -07:00
gsxdsm
a6e6e36266 fix(tui): always show auth token and report accurate macOS memory usage
Pin the Token chip after URL in the System panel so it stays visible
(wrapping to a new row at narrow widths) instead of being pushed off-panel.
Switch system memory readings to os.availableMemory() so macOS no longer
reports ~96% used by ignoring reclaimable inactive/cached pages.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 21:57:47 -07:00
gsxdsm
7a4970e2d6 chore(release): v0.8.2
Version bump via changesets.
2026-04-28 21:51:32 -07:00
Fusion
6bb919864b feat(FN-2895): merge fusion/fn-2895
- feat(FN-2895): complete Step 7 — documentation and delivery

Fusion-Task-Id: FN-2895
2026-04-28 21:50:27 -07:00
gsxdsm
c14f2a3991 fix(cli): validate agentId in fn_task_create/update and unblock bundle tests
fn_task_create and fn_task_update accepted any string as `agentId` and
wrote it verbatim onto `task.assignedAgentId`, letting hallucinated IDs
(e.g. `agent-executor-001`) appear as agent badges in the dashboard.
Mirror the validation already used by fn_delegate: look the agent up via
AgentStore and reject unknown or ephemeral/runtime-managed agents. Null
still clears the field on update.

Also clean up two stale failures in bundle-output.test that predated this
change:
- pi-claude-cli no longer imports cross-spawn, so drop the dependency and
  its orphan type-decl file.
- Loosen the spawn-import regex to match `spawn` anywhere in the
  destructured import (the source has additional named imports).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 21:34:34 -07:00
gsxdsm
afef995eb7 chore(release): v0.8.1
Version bump via changesets.
2026-04-28 21:15:15 -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
5bbb8bb2e9 refactor(dashboard): simplify setup wizard manual step
Lead with Project Name, then Project Directory. Move setup-mode toggle
(existing vs clone) and the repository URL field into Advanced Settings
so the default path is one question — pick a folder, name it, register —
without the welcome blurb and mode picker getting in the way.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 21:05:38 -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
Fusion
0684a18a6f feat(FN-2909): merge fusion/fn-2909 2026-04-28 20:07:54 -07:00
Fusion
88a148d7dd fix(FN-2906): refine chat render toggles and quick chat resume state
- Remove btn-icon styling from ChatView render toggle controls and apply dedicated borderless toggle styling
- Update ChatView and QuickChatFAB behavior so quick chat streaming state is preserved when resuming sessions
- Add regression tests for QuickChatFAB and useQuickChat to cover resume and streaming-state handling

Fusion-Task-Id: FN-2906
2026-04-28 19:56:05 -07:00
Fusion
b248ffc6e6 fix(FN-2904): preserve quick chat streaming state on resume
- Keep active streaming responses alive when resuming an existing quick chat session instead of resetting stream state
- Update useQuickChat session-resume logic to only restart waiting indicators when appropriate
- Adjust QuickChatFAB waiting indicator behavior so it reflects real in-flight assistant activity
- Add focused hook and component tests covering same-session resume and waiting-indicator regressions

Fusion-Task-Id: FN-2904
2026-04-28 19:43:29 -07:00