Commit Graph

754 Commits

Author SHA1 Message Date
gsxdsm
7ddc34048f 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
3202e578c2 fix(FN-0000): harden sqlite startup validation 2026-04-29 07:58:58 -07:00
gsxdsm
bd14cf84e3 fix(FN-XXX): harden windows path handling 2026-04-29 07:35:23 -07:00
Fusion
995165ea60 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
15e4cba5e9 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
44480c0d88 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
gsxdsm
4446a58b05 chore(release): v0.8.3
Version bump via changesets.
2026-04-28 22:02:56 -07:00
gsxdsm
2fd840def5 chore(release): v0.8.2
Version bump via changesets.
2026-04-28 21:51:32 -07:00
gsxdsm
765e41838c chore(release): v0.8.1
Version bump via changesets.
2026-04-28 21:15:15 -07:00
Fusion
7650f2d2d9 feat(FN-2909): merge fusion/fn-2909 2026-04-28 20:07:54 -07:00
gsxdsm
3b54a03b81 chore(release): v0.8.0
Version bump via changesets.
2026-04-28 19:24:57 -07:00
gsxdsm
2029968d23 fix(FN-2662): honor project model overrides and stabilize tests 2026-04-28 18:21:17 -07:00
gsxdsm
fd99d227bc fix(core): expand merge-blocker statuses to cover all transient states
Audit of task.status assignments across packages/core and packages/engine
surfaced five additional transient/in-flight statuses that should block
auto-merge but weren't in BLOCKING_TASK_STATUSES:

- awaiting-approval — triage spec awaiting user approval
- needs-replan — scheduler/executor/triage signaled re-plan needed
- mission-validation — mission-level validation in flight
- queued — scheduler-side transient state
- stuck-killed — defensive guard; task killed by stuck detector

Each represents a state where finalizing the merge violates user intent
or invariants. Group the set with comments explaining the intent of each
group so future additions can be slotted appropriately.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:21:12 -07:00
gsxdsm
053f6131b9 fix(core): block merging tasks in planning/specifying status
A task in column "in-review" but with status "planning" (or the legacy
"specifying" alias) was passing the merge eligibility check, so the
auto-merger would finalize a branch the user had just moved back to
re-plan. The hardcoded BLOCKING_TASK_STATUSES set didn't include either
status — only failed / awaiting-* / merging-* were checked.

Add both to the set so getTaskMergeBlocker reports them as blockers,
matching the expected user mental model of "moved to planning = won't
auto-merge".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 16:57:26 -07:00
Fusion
cc9181db47 feat(FN-2895): add FTS5 corruption recovery path
- Add Database APIs to rebuild the tasks_fts index and detect FTS5 corruption signatures
- Retry task upserts once after rebuilding FTS5 when corruption errors are encountered
- Extend TaskStore health checks to include FTS5 integrity verification
- Add regression tests for rebuild/integrity behavior and upsert recovery, plus a patch changeset for @runfusion/fusion
2026-04-28 16:51:04 -07:00
gsxdsm
3cf07fa413 feat(core): fully reset in-review → todo so the task starts over
Previously this transition cleared only transient execution state
(status/error/worktree/blockedBy/workflowStepResults), but kept the
prior branch, baseBranch, baseCommitSha, summary, and recovery
counters. That meant retrying a reviewed task resumed on the old
branch with a stale summary instead of starting fresh. Now those
fields are also cleared on in-review → todo. Other reopen paths
(in-progress/done → todo/triage) keep their existing behavior.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 16:21:56 -07:00
gsxdsm
22bac2ddc7 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
gsxdsm
ef202add1b fix(core): re-export MergeConflictStrategy type from package barrel
The engine's merger imports MergeConflictStrategy from @fusion/core, but it
was defined in core/src/types.ts without being re-exported from index.ts,
breaking dashboard typecheck against a clean checkout (no engine dist).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 15:23:19 -07:00
Fusion
cd544e0467 feat(FN-2869): merge fusion/fn-2869 (auto-resolved)
- docs(FN-2869): complete Step 3 — update notification architecture docs
- docs(FN-2869): complete Step 2 — document notification providers settings
- chore(FN-2869): add changeset for pluggable notification providers
2026-04-28 15:20:49 -07:00
Fusion
753604d805 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
621bee1cd6 fix(engine): honor project execution model overrides in spawned children + workflow-step timeout fallback
Two related executor fixes:

1. Spawned child agents previously bypassed the executor model lane hierarchy
   and used settings.defaultProvider/defaultModelId directly, ignoring
   project-level executionProvider/executionModelId from .fusion/config.json.
   Resolve via resolveExecutorModelPair() so children honor the same
   precedence as the parent executor.

2. Pre-merge workflow step AI calls now have a wall-clock timeout
   (settings.workflowStepTimeoutMs, default 6 min) and fall back to the
   configured validatorFallback / fallback model on timeout. The 20-min
   stuck-detector kill loop was the only escape hatch when a provider's
   streaming API hung mid-response, and the kill triggered a same-provider
   retry — guaranteeing repeat hangs. The runner now races the prompt against
   a timeout; on timeout it disposes the session, logs a clear entry, and
   re-runs the step once with a distinct fallback provider/model. If neither
   completes (or no fallback is configured), the step returns a normal
   failure that flows into the existing handleWorkflowStepFailure retry path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 14:25:56 -07:00
Fusion
a59aeab987 feat(FN-2870): merge fusion/fn-2870 (auto-resolved)
- fix(FN-2870): default cloudflare quick tunnel to enabled
2026-04-28 14:25:56 -07:00
Fusion
d52add6f8c feat(FN-2878): add webhook notification provider support
- Add webhook settings fields and defaults for enablement, URL, format, and event filtering
- Implement WebhookNotificationProvider with payload formatting support for generic, Slack, and Discord endpoints
- Extend NotificationService to manage both ntfy and webhook providers with live settings sync
- Export webhook notification types/providers through engine notification entry points
2026-04-28 14:25:56 -07:00
Fusion
a481a68b16 feat(FN-2865): add notification provider abstractions
- Add shared notification event/payload/provider config types in core
- Introduce notification module exports with provider interface and dispatcher implementation
- Extend settings schema to accept notificationProviders configuration
- Add dispatcher unit coverage for provider filtering, failures, and delivery behavior
2026-04-28 14:25:56 -07:00
gsxdsm
2f7ba29ead fix(dashboard,core): bound AI session prompt() with timeout + abort
Subtask, mission-interview, and milestone/slice-interview sessions could pin
their `generating` state forever when the underlying provider stream stalled
silently or a tool call hung. Wrap each `agent.session.prompt()` in a new
GenerationGuard helper (per-session AbortController + timer) so a stuck turn
becomes a bounded error users can retry. Adds matching `stop*Generation`
exports and threads abort through cleanup so dismissing a modal cancels the
in-flight call instead of leaking it.

Also closes the gh-cli tool hang vector: `runGhAsync` / `runGhJsonAsync` now
accept `{ signal, timeoutMs }` (default 30s). Github-touching extension tools
forward the AI tool's signal so an aborted agent kills the `gh` child instead
of orphaning it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 14:25:55 -07:00
gsxdsm
596982dd75 feat(FN-2862): merge fusion/fn-2862 2026-04-28 14:25:55 -07:00
Fusion
4672d11e3c feat(FN-2854): ship node routing persistence and dashboard workflow updates
- Add effective node routing fields to task types/store, persist them in SQLite, and expand regression coverage for node override guard behavior
- Add dashboard and API support for manual memory dream processing/trigger actions plus expanded memory regression tests
- Improve task creation and task detail model/node UX, including quick chat default model selection and workflow/settings UI polish
- Apply mobile/dashboard UX fixes (form input zoom prevention, expand toggle styling, input layout tweaks) and update extension/docs/changelogs for the release
2026-04-28 14:25:55 -07:00
Fusion
789afc30b0 feat(FN-2721): enforce node override guard across task workflows
- Add a core node-override-guard module, export it from @fusion/core, and enforce conflicts in store updates
- Add API route and CLI extension safeguards so nodeId override updates are blocked when ownership would conflict
- Wire node override routing and validation through dashboard quick-create, list, modal, settings, and task form/detail surfaces
- Add focused unit and integration tests for core guard logic, workflow routes, and dashboard node override UX
2026-04-28 14:25:55 -07:00
gsxdsm
ad4690329a chore(release): v0.7.1
Version bump via changesets.
2026-04-28 00:36:24 -07:00
gsxdsm
774726ad3a fix(core): bump SCHEMA_VERSION to 49 so the nodeId task-column migration runs
Migration 49 (`ALTER TABLE tasks ADD COLUMN nodeId TEXT`) was added with
SCHEMA_VERSION still pinned to 48. Existing DBs at version 48 hit the
`if (version >= SCHEMA_VERSION) return;` early exit, so the column was never
created — `TaskStore.listTasks` then crashed at startup with
`no such column: nodeId` and the dashboard exited before initialization.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 00:04:51 -07:00
gsxdsm
08a4ae01f7 feat(FN-2719): merge fusion/fn-2719 2026-04-27 22:53:51 -07:00
Fusion
a3a6794c41 feat(FN-2718): add unavailable node policy setting and validation
- Define and export unavailable node policy types in core settings interfaces
- Add project-level unavailable node policy default and runtime validation helper
- Add core unit coverage for unavailable node policy parsing and acceptance cases
- Guard Paperclip mint requests to include companyId only when available for type-safe payloads
- Document unavailable node policy in the settings reference
2026-04-27 22:40:08 -07:00
gsxdsm
4a3a8dce7c chore(release): v0.7.0
Version bump via changesets.
2026-04-27 22:34:32 -07:00
gsxdsm
8c554211f3 fix(dashboard): card timer matches stats panel + resizable Changes diff modal
Two related dashboard fixes.

1. Card timer mismatch: the board card timer chip showed only workflow
   runtime (e.g. <1m on FN-2716) while the task detail Stats panel
   reported "Total execution time" of 7m+ for the same task. Cause —
   the slim board listing strips `task.log` to keep payloads small, so
   the card's client-side `[timing]` log scan returned 0. Now the slim
   path aggregates `[timing] … in <N>ms` durations server-side into a
   new `task.timedExecutionMs` field before stripping the log; the
   card prefers this aggregate, falling back to the client scan when
   the full log is loaded (TaskDetailModal). Wire payload stays slim.

2. View Changes diff modal: defaulted to `90vw × 80vh` and was not
   user-resizable. Switched to `min(95vw, 2200px) × min(90vh, ...)`
   default with `resize: both`, persisted via useModalResizePersist
   (`fusion:changes-diff-modal-size`). Mobile keeps fullscreen layout.
   Overlay dismiss switched to the shared `useOverlayDismiss` hook so
   resize-drags that release on the overlay don't close the modal.

Updated the diff modal's regression tests to match the new constraint
shape (still asserts max-height clamps to viewport via calc()).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 19:55:46 -07:00
gsxdsm
1fe59be960 feat(dashboard,cli): update-check frequency, GitHub star button, more resizable modals
Update-check frequency (end-to-end)
- Add `updateCheckFrequency: "manual" | "on-startup" | "daily" | "weekly"`
  to GlobalSettings (default: "daily").
- Backend `performUpdateCheck` honors the frequency: TTL = day/week,
  `manual` returns cache or empty without hitting npm, `on-startup`
  refreshes once per process lifetime then serves cache. `/refresh`
  route forces network regardless.
- Settings → Updates surfaces a working `<select>` for the cadence,
  disabled when auto-checks are off entirely.
- Tests: 4 new cases covering ttlForFrequency mapping, weekly window,
  manual semantics, on-startup once-per-process behavior.

TUI update notice
- Read cached update result synchronously on TUI startup. When an
  update is available, render a yellow notice line on the splash and
  a colored ● next to the version in the status bar.

Settings modal header polish
- Add "Star on GitHub" pill (icon + Star + cached star count from the
  GitHub API, 1h localStorage TTL) and "Help" button (opens project
  Discussions). Both link to the Runfusion/Fusion repo.
- Star button auto-hides after the user clicks it (intent = star),
  tracked in localStorage `fusion:github-star-clicked`.
- Settings → General gets a "Show Star on GitHub button" checkbox so
  users can hide it preemptively. New global setting
  `showGitHubStarButton: boolean` (default true) gates rendering.

More resizable modals
- Task Detail modal: 85vh default, resize: both, persisted via
  useModalResizePersist (key `fusion:task-detail-modal-size`).
- Quick Chat FAB: full 8-direction resize (4 corners + 4 edges) via
  pointer-event handlers; persisted to
  `fusion:quick-chat-size-<projectId>`. Each handle has the right
  cursor + role="separator" for accessibility.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 17:55:15 -07:00
gsxdsm
dba2299137 fix(core): polling loop falsely emits task:deleted for archived tasks
TaskStore.checkForChanges detects deletions by comparing the in-memory
taskCache against the tasks table. But archiveTask also DELETEs the row
from `tasks` (after copying to archive_db), so any TaskStore instance
polling the same DB sees the archived task vanish and emits
`task:deleted`. The activity-log listener records that as a deletion,
producing entries like "Task FN-NNNN deleted" for tasks that are alive
and well in the archive.

Reproduced live: 2048 task:deleted entries in a single ~1ms burst, all
of them present in archive.db. Two TaskStores (CLI/engine and dashboard
server) on the same DB → CLI archives, dashboard polls and false-flags.

Fix: in checkForChanges, batch-query the archive for all missing IDs.
For ids that exist in archived_tasks, emit `task:moved` (to:archived) —
matching what archiveTask emits in-process — so the activity log
records the correct event. For ids not in archive, emit task:deleted as
before (real deletion).

Adds ArchiveDatabase.filterArchived(ids) helper that returns the subset
in archived_tasks via a single SELECT IN query (chunked at 500 to stay
under SQLite's parameter limit).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 17:46:05 -07:00
Fusion
1ded15a40a feat(FN-2694): merge fusion/fn-2694 2026-04-27 08:45:49 -07:00
Fusion
92b8631aad fix(FN-2672): harden automation execution and scheduling flows
- Improve automation startup diagnostics and route handling for manual execution steps
- Add support for full manual automation step execution in dashboard and engine flows
- Expand due-schedule coverage in automation store and dashboard route tests
- Add cron runner regression tests for edge cases and document the automation execution fix via changeset
2026-04-27 03:07:58 -07:00
Fusion
788de7dd8a 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
acbd6580b3 feat(FN-2663): add cached update-check setting, APIs, and banner
- Add global updateCheckEnabled setting to core schema/types and wire dashboard command to cache update checks in the CLI
- Implement dashboard server update-check cache module plus REST routes for status and refresh behavior
- Add dashboard client hook, legacy API helpers, and UpdateAvailableBanner UI to show cached CLI update notices
- Cover update-check server routes, hook behavior, banner rendering, and route registration with focused tests
- Document update-check configuration and API behavior in architecture and settings reference docs
2026-04-27 01:40:07 -07:00
Vhailors
794700d133 fix(core): prevent FirstRunDetector from walking into OS temp directory
`FirstRunDetector.detectExistingProjects()` walks ancestor directories
looking for `.fusion/` projects, stopping only at `homedir()` and the
filesystem root. On systems where a prior Fusion session has left a
`/tmp/.fusion/fusion.db` (e.g. running tests, ephemeral dashboards,
crashed processes), the walk crosses into `/tmp` and incorrectly
"discovers" that stale state as a project.

This was caught by tests in packages/core/src/__tests__/store.test.ts
that intermittently failed when a real Fusion session had been run
on the same box. The walk would find the test's tmp dir AND the host's
real `/tmp/.fusion/fusion.db`, polluting the project-detection invariants.

Fix: import `tmpdir` from `node:os` and add it as a third walk-stop
boundary alongside `homedir()` and `/`. The OS temp directory is a
shared system surface and should never itself host a project.

The bug affected any user with a stale `/tmp/.fusion/` from a previous
session — invisible most of the time, but caused FirstRunDetector to
misclassify project state.

Tested: 4 previously-flaky tests in packages/core now pass deterministically.
2026-04-26 23:13:31 -07:00
gsxdsm
5162531275 chore(release): v0.6.0
Version bump via changesets.
2026-04-26 21:27:49 -07:00
gsxdsm
245a79353d fix(core): bump SCHEMA_VERSION to 48 so v48 migration actually runs
migrate() short-circuits via 'if (version >= SCHEMA_VERSION) return',
so my prior commit's v48 block (adding tasks.verificationFailureCount)
never executed against existing v47 databases. App startup then failed
with 'no such column: verificationFailureCount' on first task SELECT.

Bumping the constant to 48 lets the migration body run on next init.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 21:14:53 -07:00
gsxdsm
53decd1bd2 fix(engine): cap verification-failure bounces, reap unregistered worktrees, dedupe activity log
Three fixes for the worktree-overflow / stuck-task incident:

1. Cap deterministic-verification-failure bounces (fix #2)
   Auto-merge previously bounced an in-review task back to in-progress
   on every verification failure with no upper bound. A single flaky test
   could keep a task ping-ponging in-review→in-progress forever, holding
   its worktree and consuming agent slots. Adds verificationFailureCount
   on Task (DB migration v48), increments on each bounce, and after 3
   failures marks the task failed and creates a follow-up triage task
   so a fresh agent can investigate the underlying flake instead of
   re-running the same fix loop.

2. Reap unregistered orphan worktree dirs even when recycle is on (fix #3)
   cleanupOrphans previously bailed out entirely when recycleWorktrees
   was true, leaving stale dirs (clear-hawk-broken, *-bak, leftover
   crash debris) on disk forever. New reapUnregisteredOrphans pass
   removes only directories that aren't registered git worktrees, so
   the recycle pool keeps its warm worktrees but the trash gets cleared.

3. Idempotence guard on activity-log listener wiring (fix #6)
   setupActivityLogListeners() was registering handlers on every call.
   When init() ran twice, every task:created / task:moved event wrote
   N rows to activityLog, producing the duplicate entries visible in
   the DB. Added activityListenersWired flag so repeated calls no-op.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 21:09:28 -07:00
gsxdsm
330545d45c test(core,dashboard): expand in-memory SQLite to more store tests
Extends the inMemoryDb opt-in established in 4fc58e0bb to single-instance
TaskStore/AgentStore/RoutineStore/PluginStore tests that were still
opening disk-backed fusion.db files. Cross-instance persistence tests
(open store A, close, open store B on same dir) and migration tests that
seed via a sibling Database instance keep their disk-backed stores —
swapping those would silently drop data between instances.

Sites flipped:
- core: store.test.ts (RunMutationContext, memory-toggle, diagnostics
  blocks), store-sort, settings-export, backup, plugin-loader,
  agent-instructions, agent-instructions-bundle, mission-store (all 10
  triage subtests), mission-planning-context.integration
- dashboard: routes.test.ts (Messaging routes block),
  session-reconnect, session-cross-tab, planning

Sites left disk-backed (cross-instance or sibling-Database dependency):
- run-audit*, task-documents, fts5-guard (sibling Database at same dir)
- mission-integration, mission-factory-parity (taskStore2 reopens)
- agent-store checkout-leasing (TaskStore + AgentStore at same dir)
- routes.test.ts AgentStore seed pattern (route handler opens its own)
- cli/extension.test.ts (makeCtx opens its own TaskStore)

All disk-backed tests continue to write to mkdtemp temp dirs — no live-db
risk introduced. Wall-clock impact: core 149s → 19s.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 20:56:31 -07:00
gsxdsm
b60bda82b0 test(core): use in-memory DB for logEntry truncation test
The logEntry()-bounds test does 1005 sequential SQLite writes and was
timing out at 20s on a disk-backed TaskStore. It doesn't exercise
cross-instance persistence, so flip on inMemoryDb to bring it under 8s.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 20:47:37 -07:00
gsxdsm
4fc58e0bba perf(core): opt-in in-memory SQLite for *-store tests
Adds an opt-in `inMemory` flag to `Database`/`ArchiveDatabase` (and
`{ inMemoryDb }` to TaskStore, AgentStore, RoutineStore,
AutomationStore, PluginStore) that swaps the on-disk fusion.db /
archive.db for SQLite's `:memory:` connection. Production callers
never set the flag, so behavior is unchanged.

Test files for each store now flip the flag in `beforeEach`. The
handful of tests that exercise cross-instance persistence (open store
A, close, open store B on same dir, expect data) construct disk-backed
stores explicitly inside the test body, marked with a comment at each
site.

Wall-clock impact:
- core:      69.4s → 18.5s  (3.7× faster, 3038 tests)
- dashboard: 156.6s → 30.0s (5.2× faster — improvement ripples through
                              any test that constructs a TaskStore)

The refactor eliminates the per-test SQLite open + WAL fsync + tmp
dir cleanup loop that dominated setup cost: ~50ms/test → ~5ms/test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 18:45:14 -07:00
gsxdsm
6caab172ba feat(FN-2623): merge fusion/fn-2623 2026-04-26 18:11:09 -07:00
gsxdsm
1dddf3252c chore(release): v0.5.0
Manual version bump to 0.5.0 (changeset version produced 1.0.0 from a
single minor changeset against 0.4.1; that release was rolled back and
the 1.0.0 npm version deprecated).

Aggregates: status terminology refresh (planning/replan), Reviewer
rename, in-review pause behavior, dashboard-tui resize hardening,
dev-server experimental toggle fix, version reporting fix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 17:31:46 -07:00
gsxdsm
45002257fd Revert "chore(release): v1.0.0"
This reverts commit 26e09c7aa0.
2026-04-26 17:26:56 -07:00