Repair the scoped core store/type baseline drift without broadening unrelated line-count debt.
- Add the FN-7046 CI note explaining the scoped re-ratchet for store.ts and types.ts.
- Raise only the core store.ts and types.ts grandfathered ceilings to their current generated sizes.
Files changed:
scripts/check-file-line-count.mjs | 3 +++
scripts/line-count-baseline.json | 4 ++--
2 files changed, 5 insertions(+), 2 deletions(-)
Fusion-Task-Id: FN-7046
Fusion-Task-Lineage: 1c64b426-9c51-427d-bacc-e527dffa9d90
Split the oversized Command Center area test suite into focused files while preserving shared fixtures and coverage.
- Extract shared date ranges, fixtures, and helpers into areas.test-harness.tsx.
- Move GitHub and Signals area assertions into a dedicated sibling suite.
- Keep the remaining areas.test.tsx below the 2000-line cap with existing coverage intact.
Files changed:
.../areas/__tests__/areas.github-signals.test.tsx | 406 +++++++++++++
.../areas/__tests__/areas.test-harness.tsx | 302 ++++++++++
.../command-center/areas/__tests__/areas.test.tsx | 648 +--------------------
3 files changed, 730 insertions(+), 626 deletions(-)
Fusion-Task-Id: FN-7044
Fusion-Task-Lineage: 63886ec9-e9c3-410e-bbc6-c5116cddb168
Fix the workflow results settings display for configured optional steps.
- Treat workflow step definitions as missing only when the lookup has no entry.
- Preserve empty descriptions for found optional-group steps such as Code Review.
- Add regression coverage for configured, checkbox, and ordering displays.
- Add a patch changeset for the published Fusion package.
Files changed:
.../fn-7041-workflow-step-definition-not-found.md | 7 +++++
.../app/components/WorkflowResultsTab.tsx | 9 +++++-
.../__tests__/WorkflowResultsTab.test.tsx | 36 ++++++++++++++++++++++
3 files changed, 51 insertions(+), 1 deletion(-)
Fusion-Task-Id: FN-7041
Fusion-Task-Lineage: 84698437-d4d3-4b68-9c5f-c8b15f513230
## Problem
Operators intermittently saw **"all my global settings reset"** —
including the global concurrency cap surfacing as `gate=semaphore` with
a low value in scheduler queue logs.
## Root cause
`CentralCore` is supposed to live at `~/.fusion/fusion-central.db`, but
`resolveGlobalDir(dir)` returns an explicit dir **verbatim**, and
several production call sites passed the **project** `.fusion/` dir:
- `store.getSecretsStore()` → `new CentralCore(store.getFusionDir())`
- dashboard secrets/proxy/node/secrets-sync/settings-sync routes → `new
CentralCore(store.getFusionDir())`
Each spawned a **stray per-project central DB**
(`<project>/.fusion/fusion-central.db`) seeded with **default** global
state (`globalMaxConcurrent=4`, empty secrets, default
`centralSettings`) that shadowed the real global DB whenever a
read/write hit one of those paths. Confirmed on disk: 14+ stray DBs at
default `4` vs the real `~/.fusion` at the operator's actual value.
## Fix
- Add `TaskStore.getGlobalSettingsDir()` returning the **resolved global
dir** (`string`); route the secrets store + all the affected dashboard
routes through it instead of `getFusionDir()`.
- `getSecretsStore()` also passes the global dir to `MasterKeyManager`
(co-locates the master key; makes the path test-exercisable).
- Add a `resolveGlobalDir()` **guard** that throws on a project-local
`.fusion/` dir (basename `.fusion` with a `.git` parent), with an
explicit `FUSION_ALLOW_PROJECT_LOCAL_GLOBAL_DIR` opt-out for
legitimately version-controlled custom global dirs. Inert under VITEST.
## Tests
- `global-settings-guard.test.ts` — guard rejects project/worktree
`.fusion` dirs, allows home + custom non-repo dirs.
- `store-secrets-store-global-dir.test.ts` — **symptom-based**:
`getSecretsStore()` creates the central DB in the global dir and
**never** spawns a stray project-local `fusion-central.db`.
- Added `getGlobalSettingsDir()` to route-test mock stores (the new
getter is now called by the routes).
## Operator note
Existing stray project-local `fusion-central.db` files (all
default/empty in practice) should be removed; on the affected machine
they were quarantined to `.legacy-central-db-backup-*` folders.
## Verification
- `pnpm --filter @fusion/core run typecheck` / `@fusion/dashboard`
typecheck — clean
- core guard + regression tests pass; previously-affected route suites
(proxy, nodes-sync, browse-directory, secrets-sync) pass (313/313)
- `pnpm check:changesets` — clean; eslint on changed files — clean
Companion to #1786 (global concurrency slider UI), which is independent.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- stage-review-badge-begin -->
---
<a href="https://stagereview.app/Runfusion/Fusion/pull/1787">
<picture>
<source media="(prefers-color-scheme: dark)"
srcset="https://stagereview.app/assets/gh-open-in-stage-dark.svg">
<img src="https://stagereview.app/assets/gh-open-in-stage-light.svg"
alt="Open in Stage">
</picture>
</a>
<!-- stage-review-badge-end -->
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Fixed intermittent resets of global settings (including the global
concurrency cap).
* Ensured global settings/secrets are read from and written to the
correct global storage location rather than a project-local one.
* Added a safeguard to prevent using a project-local settings directory
when running inside a repository.
* **Tests**
* Added regression coverage for global directory resolution/guard
behavior.
* Updated route and secrets sync tests to validate the global directory
selection used for proxying and sync.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## What
Adds a **Global Max Concurrent** slider to two operator surfaces and
groups the Scheduling settings by scope.
- **Footer engine menu** (`EngineControlMenu`) and **Command Center →
Concurrency card** (`CommandCenterControls`) each get a global-cap
slider.
- **Settings → Scheduling** (`SchedulingSection`) now groups fields
under labeled **"Global — applies to all projects"** and **"This
project"** subheadings with scope badges (clearer on mobile, and
surfaces the existing `globalMaxConcurrent` field that was easy to
miss).
## How
Both sliders are backed by a single shared `useGlobalConcurrency` hook
(module-level store + subscribers), so they read/write **one source of
truth** and **revalidate after every `PUT /api/global-concurrency`**.
This avoids the last-writer-wins / stale-clobber races that two
independent per-component caches would cause (which would ironically
re-create a "global cap resets" symptom). The hook also:
- treats a fetch error as **non-interactive** (slider disabled, not
stuck showing `1` and persistable),
- surfaces a **save-state** indicator (Saving…/Saved/Save failed)
mirroring the project sliders,
- **flushes a pending edit on menu close / unmount** so a quick
drag-then-dismiss isn't silently dropped.
## Review
These race/feedback behaviors were hardened in response to a multi-agent
`/code-review` (correctness, adversarial, reliability, frontend-races
all flagged the per-component-cache approach). Independent of the
companion fix PR (global-settings reset).
## Verification
- `pnpm --filter @fusion/dashboard run typecheck` — clean
- eslint on changed files — clean
- Not yet browser-verified (next step).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- stage-review-badge-begin -->
---
<a href="https://stagereview.app/Runfusion/Fusion/pull/1786">
<picture>
<source media="(prefers-color-scheme: dark)"
srcset="https://stagereview.app/assets/gh-open-in-stage-dark.svg">
<img src="https://stagereview.app/assets/gh-open-in-stage-light.svg"
alt="Open in Stage">
</picture>
</a>
<!-- stage-review-badge-end -->
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added a global concurrency slider that applies across all projects.
* Global and per-project scheduling controls are now clearly separated
with scope labels and badges.
* The concurrency setting now stays in sync across different dashboard
views and saves automatically after brief pauses.
* **Bug Fixes**
* Improved feedback for loading, saving, and error states.
* Prevented confusion between global settings and project-specific
settings, especially on smaller screens.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Removes the legacy workflow-step EXECUTION path now that the graph records results
(U2): delete runWorkflowSteps(), the workflow-step seam + runWorkflowStep primitive
(runtime-primitives, workflow-node-handlers, authoritative-driver), and the legacy
execute() step blocks. Keeps task.workflowStepResults + its store write path (the
graph's sink) and executeWorkflowStep/executeScriptWorkflowStep (reused by the graph).
- Watchdog recoverCompletedTask now re-enters via maybeExecuteWorkflowGraph so the
graph re-runs pending gates, records results, and owns the in-review/back-for-fix
transition (KTD-2).
- maybeExecuteWorkflowGraph fails CLOSED (parks) when a store lacks
getTaskWorkflowSelection AND the task has enabled pre-merge steps — closing the
FN-7039 silent-skip class without changing minimal-store implementation runs (KTD-5).
KNOWN GAP (follow-up): the FN-4343 per-step workflowStepScopeEnforcement leak check
lived only in runWorkflowSteps and is NOT yet replicated on the graph path. Merge-time
File Scope enforcement (FileScopeViolationError, squash overlap) is unaffected.
Plan U4.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Avoid the legacy-migration side effect on the hot path: run the cheap,
read-only checks (basename === ".fusion" && parent has .git) FIRST, and
only call resolveGlobalDirForHome() (which can perform a one-time rename)
when a dir actually looks project-local.
- Normalize paths before the home-dir comparison (realpathSync when present,
else resolve) so a trailing slash, doubled separator, or symlinked home
doesn't make the legitimate home global dir trip the guard.
- Harden the browse-directory regression test: the mock returns a global dir
DISTINCT from getFusionDir() and asserts CentralCore is constructed with the
global dir, so a revert to getFusionDir() now fails the test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Double-commit race: null pendingValueRef synchronously at commit() start
so the close-flush + unmount-cleanup guards can't both fire and send a
duplicate PUT for the same value.
- Clamp ceiling: clamp setValue against max(SLIDER_BASE_MAX, currentValue)
instead of Number(raw) (which had no effective upper bound for
programmatic callers).
- Out-of-band staleness: force-revalidate on each activate so a Settings-modal
save (which writes globalMaxConcurrent directly, bypassing this store) is
reflected when a slider surface reopens.
- Load-error label: add an explicit error branch in both EngineControlMenu
and CommandCenterControls so a failed initial load shows "Load failed"
instead of "Ready" while the slider is disabled.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## Why this exists
PR #1780 was merged while its head was only the first commit
(`c5fbe08c0`, the original bounded-lane fix). Two follow-up commits were
pushed to the branch **after** that merge and never landed on `main`.
This PR lands them.
Confirmed absent from `main`: `existingChangedTestFilesInPackage`,
`GATE_COVERED_MEMORY_ENVELOPE_PACKAGES`, `CORE_SCOPED_AFFECTED_PACKAGE`,
the 13-min watchdog ceiling, and `workers=4` — all 0 occurrences on
origin/main.
## What's in here (the two stranded commits)
**1. Greptile P1/P2 review fixes (`8297762`)**
- **P2:** filter directly-changed test files to paths that still exist
on disk (`existingChangedTestFilesInPackage`) so deleted/renamed `.test`
paths never reach `vitest run` positionally; all-deletions diff falls
into the delegate path.
- **P1:** gate-coverage-aware delegation
(`GATE_COVERED_MEMORY_ENVELOPE_PACKAGES`) — engine delegation keeps its
accurate "curated subset ran above" note; dashboard/core delegation now
`console.warn`s that the gate doesn't cover them (CI full-suite is the
backstop) instead of a silent false-green.
**2. Core bounding + watchdog + workers (`2cff1864c`) — the actual
remedy for the remaining timeouts**
- **`@fusion/core` is now a bounded memory-envelope package.** It was
the remaining timeout path: core is the hub ~everything imports (~354
test files), so a core source edit made `vitest --changed` expand to
~the whole core suite and blow past the engine's 15-min kill → SIGKILL +
task restart. PR #1780 only covered engine/dashboard.
- **Watchdog `changed` ceiling 20min → 13min** so the script fails a
runaway lane itself (exit 124, no restart) *before* the engine's 15-min
kill restarts the whole task.
- **Scoped-affected workers 1 → 4** (operator decision) — the fan-out
guard now bounds the set, so the OOM scenario that justified `=1` is
prevented at the source. Heap stays 6144MB/worker.
## Verification
- `scripts/__tests__/test-changed.test.mjs` 117/117;
`run-vitest-watchdog.test.mjs` 15/15; eslint clean. (Re-confirmed on
this branch.)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- stage-review-badge-begin -->
---
<a href="https://stagereview.app/Runfusion/Fusion/pull/1785">
<picture>
<source media="(prefers-color-scheme: dark)"
srcset="https://stagereview.app/assets/gh-open-in-stage-dark.svg">
<img src="https://stagereview.app/assets/gh-open-in-stage-light.svg"
alt="Open in Stage">
</picture>
</a>
<!-- stage-review-badge-end -->
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Increased parallel test execution for scoped runs in engine and
dashboard areas.
* Added dedicated scoped test handling for core with bounded resources.
* **Bug Fixes**
* Test selection now ignores deleted or renamed test files, preventing
failed runs on missing paths.
* Improved fallback handling when no directly changed tests remain in a
package.
* Adjusted watchdog timing for changed-test runs to better match
expected limits.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
greptile: my earlier existence-check anchor was necessary but insufficient.
`rootDir` (= process.cwd() when FUSION_PROJECT_DIR is unset) drives ALL workspace
discovery (readWorkspacePatterns / listWorkspacePackageInfos /
packageHasVitestConfig). Launched from a package subdir, cwd-based discovery
found no packages, so decideExecutionPlan saw "no affected package", ran only the
gate, and exited successfully WITHOUT running the live changed package tests.
Fix the root cause: resolveRepoRoot() resolves the git toplevel as the fallback
(FUSION_PROJECT_DIR still the explicit override; cwd only when git can't report a
toplevel). This is correct from any cwd inside the repo, including a git worktree
(how the engine runs per-task verification). repoRootForExistence is now
redundant and removed; the existence check defaults back to rootDir.
Demonstrated: resolveRepoRoot() from packages/core (no FUSION_PROJECT_DIR) now
resolves the repo root and finds the workspace. +1 regression test. 121/121.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
getUnifiedTaskProgress reads task.workflowStepResults (graph-written in U2) and
no longer needs workflowStepNameLookup — names come from result.workflowStepName.
Removed the lookup prop + fetchWorkflowSteps-name plumbing end-to-end (App,
MainContent, RightDock, Board, Column, Lane, WorktreeGroup, TaskCard). Added the
running state (pending + startedAt) and a distinct advisory_failure (amber, non-
blocking) vs failed (red) treatment; advisory_failure counts as complete.
Plan U3.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Prior WIP added a default-on code-review optional-group to builtin:coding on the
success path (execute -> browser-verification -> code-review -> review). 9 exact
visitedNodeIds expectations across 3 suites predated that node and were failing.
Updated expectations to match the real traversal (insert code-review in position).
No product code touched; no assertions weakened.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Removes real wall-clock waits and per-test rebuilds from the slowest
test files, replacing them with deterministic seams. **No assertions
weakened, no timeouts widened, no retries added** — this is anti-pattern
removal per FN-5048, verified by re-running each file.
## Changes
| File | What | Result |
|---|---|---|
| `dashboard/.../insights-routes.test.ts` | Boot server+store **once**
in `beforeAll` (was `createServer` + `TaskStore.init` per test ×24);
reset insight tables per test for isolation; drive sweeper via fake
timers | test-exec **~3.7s → ~0.8s** |
| `core/.../db.test.ts` | Fixed 150ms write-lock hold → manual stdin
signal-release (keeps the real OS-lock contention under test); fixed a
real EPIPE on redundant release | 152 pass, non-flaky / 8 runs; −300ms
dead wait |
| `core/.../mission-store.test.ts` | 4 real `setTimeout` sleeps (only
there to force distinct timestamps) → `vi.setSystemTime` controlled
clock | anti-pattern removed |
| `core/.../agent-store.test.ts` | 1 real ordering-sleep → injected
`renewedAt` clock; **assertions strengthened** to pin exact timestamp
values | anti-pattern removed |
| `engine/.../in-process-runtime.test.ts` | Fake the one real 25ms
sleep; drop its inflated 30s per-test timeout | anti-pattern removed |
## Honest accounting
- The **real wins** are `insights-routes` (per-test server boot
eliminated, ~75% execution-time cut) and `db` (dead lock-hold removed).
- The **timestamp-sleep removals** (mission-store, agent-store,
in-process-runtime) are small absolute wins — the headline per-file
durations (16–25s) were **full-suite shard contention, not in-file dead
time** (each runs in 3–10s isolated). But they eliminate the FN-5048
real-wait anti-pattern, so a hub edit no longer drags real sleeps into
every `--changed` selection.
- **`workflow-routes.test.ts` was evaluated for splitting and
deliberately NOT split.** A measured A/B showed the 4-way split
*regressed* wall-clock (6s → 11s): the file is import/transform-bound
(per-file esbuild + `@fusion/core`/express import ≈ 5s > the ~4.3s test
runtime), and per-test store migration was already amortized by
`installInMemoryDbSnapshot`. Splitting only multiplies the dominant
fixed cost. Left intact.
## Verification
- `core` 612/612, `dashboard` 24/24, `engine` 78/78 (file-scoped).
- `tsc --noEmit` clean on all 3 packages; eslint clean.
Follow-up (not in this PR): `scripts/test-timings.json` is stale (its
former #1 file no longer exists) — refresh via `pnpm test:velocity --
--measure --write-report` so the watchdog budgets and velocity report
reflect reality.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- stage-review-badge-begin -->
---
<a href="https://stagereview.app/Runfusion/Fusion/pull/1784">
<picture>
<source media="(prefers-color-scheme: dark)"
srcset="https://stagereview.app/assets/gh-open-in-stage-dark.svg">
<img src="https://stagereview.app/assets/gh-open-in-stage-light.svg"
alt="Open in Stage">
</picture>
</a>
<!-- stage-review-badge-end -->
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Tests**
* Made several test suites more deterministic by replacing real-time
delays with controlled timers and fixed timestamps.
* Improved lock and task checkout tests to use manual release signals,
reducing timing-related flakiness.
* Streamlined route test setup/teardown for faster, more reliable runs.
* Added safer cleanup around timer-based tests to avoid intermittent
failures.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Enabled optional-group nodes now upsert their outcome into the existing
task.workflowStepResults field keyed by node id, and emit [pre-merge] logs at
parity with the legacy runWorkflowSteps path. Disabled groups stay byte-inert.
Reuses the existing WorkflowStepResult type + store.updateTask path (no new
table/type/store method). Unblocks the unified progress bar for graph-run steps.
Plan U2.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- P2 (greptile): anchor the changed-test existence check at the git repo root
(repoRootForExistence via `git rev-parse --show-toplevel`) instead of rootDir,
so a script run from a package subdir without FUSION_PROJECT_DIR no longer
forms a doubled path and silently drops live tests into the delegate path.
+1 regression test (default root resolves to repo root).
- coderabbit: fix stale "1-worker lane" wording in the delegation log (now
"heavy memory-envelope lane") and the "single-worker envelope" test title,
both stale after the 1->4 worker change.
test-changed 118/118, eslint clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## Why
Diagnosing "tasks take too long" showed the dominant end-to-end
wall-clock is **waiting**, not work: tasks sit in `todo` (queue) and
`in-review` (merge wait) far longer than the agent actually runs. Today
only `cumulativeActiveMs` exists, which measures **in-progress time
only** — every other stage's dwell had to be reconstructed by hand from
agent logs.
## What
Adds `columnDwellMs?: Record<string, number>` to `Task` — a per-column
accumulator (column name → cumulative ms), recorded at the **same store
column-transition seam** as `cumulativeActiveMs` (`moveTaskInternal`).
On each move it adds `columnMovedAt(new) − columnMovedAt(prev)` to the
bucket for the column being left:
- clamped `>= 0` (clock skew safe);
- unparseable/missing prior timestamp and zero-dwell moves are skipped
(no spurious buckets);
- second visits **add** to the existing bucket (multi-visit churn is
captured);
- flag-independent — keys off the generic `columnMovedAt` delta, so it
runs for both the workflow-hook and legacy-inline move paths.
This makes per-stage dwell directly queryable, the same way
`productivity-analytics.ts` already consumes `cumulativeActiveMs`.
## Persistence
JSON-text task column, following the v129 `workspaceWorktrees` precedent
exactly: `SCHEMA_SQL` column + `SCHEMA_VERSION` 129→130 + a versioned
`addColumnIfMissing` migration. Additive and behavior-preserving —
pre-existing rows start NULL and accumulate from their next transition.
Survives archive/restore (added to the archive-entry mapping).
## Tests
`src/__tests__/store-execution-timing.test.ts` — new regression asserts
dwell across
`todo→in-progress→in-review→done→todo→in-progress→in-review` accumulates
the right per-column ms (second visits add) and survives a `getTask` DB
round-trip.
```
pnpm --filter @fusion/core exec vitest run src/__tests__/store-execution-timing.test.ts ... --reporter=dot
→ store-execution-timing 5/5, schema suites (goals/secrets) green, 17/17 total
```
Migration chain verified end-to-end (secrets-schema test climbs v11/v82
→ v130). No hardcoded literal version assertions in the suite; schema
tests assert against the `SCHEMA_VERSION` constant.
`@fusion/core` is private — no changeset.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- stage-review-badge-begin -->
---
<a href="https://stagereview.app/Runfusion/Fusion/pull/1781">
<picture>
<source media="(prefers-color-scheme: dark)"
srcset="https://stagereview.app/assets/gh-open-in-stage-dark.svg">
<img src="https://stagereview.app/assets/gh-open-in-stage-light.svg"
alt="Open in Stage">
</picture>
</a>
<!-- stage-review-badge-end -->
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added per-column dwell timing to tasks, showing how long work spent in
each column across multiple visits.
* Preserved this timing data when tasks are archived and restored.
* **Bug Fixes**
* Task timing now updates correctly during column moves, including
repeated returns to the same column.
* Existing data can be upgraded to the new timing format without
breaking stored tasks.
* **Tests**
* Added coverage for multi-step task movement and data reloading to
verify timing totals stay accurate.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Add a "Global Max Concurrent" slider to the footer engine menu and the
Command Center Concurrency card, and group the Scheduling settings by
Global vs Project scope so the global cap isn't mistaken for a per-project
setting (clearer on mobile).
Both sliders are backed by a single shared `useGlobalConcurrency` hook
(module-level store) so they read/write one source of truth and revalidate
after every PUT /api/global-concurrency — fixing the last-writer-wins and
stale-clobber races a per-component cache would cause. The hook treats a
fetch error as non-interactive (slider disabled, not stuck at 1), surfaces
a save-state indicator, and flushes a pending edit on menu close / unmount
so a quick drag-then-dismiss is never silently dropped.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addresses code-review findings on the global-settings reset fix:
- getGlobalSettingsDir() now returns a resolved `string` (was `string |
undefined`), so the project-local `.fusion` guard fires at the getter
call site instead of leaking CentralCore's undefined-default semantics.
- getSecretsStore() passes the resolved global dir to MasterKeyManager so
the master key co-locates with the global central DB and the path is
exercisable under tests (a bare new MasterKeyManager() throws in VITEST).
- resolveGlobalDir() guard gains an explicit FUSION_ALLOW_PROJECT_LOCAL_GLOBAL_DIR
opt-out so a legitimately version-controlled custom global dir (dotfiles
repo with a .git parent) is not hard-rejected.
- Add a symptom-based regression test (store-secrets-store-global-dir) proving
the secrets central DB lands in the global dir and never spawns a stray
project-local fusion-central.db.
- Add getGlobalSettingsDir() to route-test mock stores (CentralCore is mocked,
so it mirrors getFusionDir()) and FNXC comments to the remaining route sites.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Six-reviewer doc review reshaped two foundational decisions:
- Results model: graph writes the existing task.workflowStepResults field
keyed by node id (no new table) — avoids upgrade data-loss for in-review
tasks and collapses U2/U3. Sibling-table spike reverted.
- Migration: reconcile with the existing migration-109 fragment scheme
instead of infeasible node-injection into read-only built-in workflows.
Also folds in: store-fallback fail-closed (legacy execute calls not all dead),
watchdog re-entry contract, plugin-row migration from persisted fields,
full progress-bar render-state spec, and circular-dependency fix.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace real wall-clock waits and per-test rebuilds in the slowest test files
with deterministic seams. No assertions weakened, no timeouts widened, no
retries added — anti-pattern removal only.
- insights-routes.test.ts: boot the server + store ONCE in beforeAll (was a full
createServer + TaskStore.init per test x24), reset insight tables per test for
isolation, drive the sweeper via fake timers. Test-execution time ~3.7s -> ~0.8s.
- db.test.ts: convert the fixed 150ms write-lock hold to manual stdin signal-
release; keeps the real OS-lock contention under test, removes 2x150ms dead
wait. Fixed a real EPIPE on redundant release. 152 pass, non-flaky over 8 runs.
- mission-store.test.ts / agent-store.test.ts: replace real setTimeout sleeps
used only to force distinct timestamps with a controlled clock (vi.setSystemTime
/ injected renewedAt). agent-store assertions strengthened to pin exact values.
- in-process-runtime.test.ts: fake the one real 25ms sleep, drop its inflated
30s per-test timeout.
Honest note: the timestamp-sleep removals are small absolute wins (the headline
per-file durations were full-suite shard contention, not in-file dead time) but
eliminate the FN-5048 real-wait anti-pattern. workflow-routes.test.ts was
evaluated for splitting and deliberately NOT split — measured A/B showed the
split regressed wall-clock (the file is import/transform-bound, already amortized
by installInMemoryDbSnapshot), so splitting only multiplies fixed import cost.
Verified: core 612/612, dashboard 24/24, engine 78/78; typecheck + eslint clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Production code constructed `new CentralCore(store.getFusionDir())`, pointing
the central/global DB at the project's `.fusion/` instead of `~/.fusion/`.
`resolveGlobalDir()` returns an explicit dir verbatim, so this spawned stray
per-project `fusion-central.db` files seeded with default global settings
(globalMaxConcurrent=4, empty secrets) that shadowed the real global DB
whenever a read/write hit one of those paths — surfacing as intermittent
"all my global settings reset".
- Add TaskStore.getGlobalSettingsDir() (resolved global dir; undefined→~/.fusion)
- Route the secrets store + secrets/proxy/node/secrets-sync/settings-sync
dashboard routes through it instead of getFusionDir()
- Add a resolveGlobalDir() guard that throws on a project-local `.fusion/`
dir (basename `.fusion` with a `.git` parent); inert under VITEST
- Regression tests in global-settings-guard.test.ts
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
optionalGroupIdSet falls back to builtin:coding (mirroring the executor's
unselected-task resolution) so a toggled built-in group id like
browser-verification is no longer downgraded to a legacy WS-xxx step row the
graph executor never matches. Create-time optional-step controls resolve
builtin:coding when no project default workflow is set so the toggles render.
First unit of the graph-native workflow-step refactor (see
docs/plans/2026-06-25-001-refactor-workflow-steps-graph-native-plan.md).
Fusion-Task-Id: FN-7039
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Three changes to make `pnpm test` reliably minimal and fail gracefully:
- @fusion/core is now a memory-envelope/wide-fan-out package (was unguarded).
It's the hub nearly everything imports (~354 test files), so a core source
edit made `vitest --changed` expand to ~the whole core suite and blow past the
engine's 15-min verification kill -> SIGKILL + task restart. Adding it to
SCOPED_AFFECTED_MEMORY_ENVELOPES applies the wide-fan-out guard (run only
directly-changed core tests, else delegate) and the bounded env. core is NOT
gate-covered, so delegation warns loudly rather than false-greens.
- Lower CLASS_BUDGET_BANDS.changed ceiling 20min -> 13min so the script watchdog
fails a runaway local lane itself (exit 124, no restart) BEFORE the engine's
15-min kill restarts the whole task. A tightening, not a timeout-widening.
Guard test pins ceiling < 900_000ms.
- Raise scoped-affected worker fan-out 1 -> 4 (operator decision). Was 1 only
for OOM safety (FN-6854/FN-6874); the fan-out guard now bounds the set so the
hundreds-of-files OOM driver no longer reaches these workers. Heap stays
6144MB/worker (~4x6GB on the lane) — revisit if a RAM-constrained CI runner
OOMs. Trades FN-5048 worker-knob guidance for throughput, scoped to the
bounded affected lanes only.
Tests: test-changed 117/117, watchdog 15/15, eslint clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- P2: filter directly-changed test files to paths that still exist on disk
(existingChangedTestFilesInPackage) so deleted/renamed .test paths from
`git diff` never reach `vitest run` positionally; all-deletions diff falls
into the delegate-to-gate path.
- P1: make heavy-package delegation gate-coverage-aware
(GATE_COVERED_MEMORY_ENVELOPE_PACKAGES). Engine delegation keeps the accurate
"curated engine-core subset ran above" note; dashboard delegation now warns
that the gate runs no dashboard tests and names the CI full-suite backstop,
so the coverage gap is loud instead of a silent false-green.
- +4 regression tests (115/115).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Split the clean-room AI merger into smaller focused modules while preserving its public behavior.
- Extract prompt builders and review verdict parsing into merger-ai-prompts.
- Extract AI merge worktree lifecycle and cleanup helpers into merger-ai-worktree.
- Re-export the extracted APIs from merger-ai and cover prompt/verdict behavior with tests.
- Remove the merger-ai line-count baseline now that the file is under the guardrail.
Files changed:
.../engine/src/__tests__/merger-ai-prompts.test.ts | 86 ++++
packages/engine/src/merger-ai-prompts.ts | 312 ++++++++++++
packages/engine/src/merger-ai-worktree.ts | 287 +++++++++++
packages/engine/src/merger-ai.ts | 555 ++-------------------
scripts/line-count-baseline.json | 1 -
5 files changed, 723 insertions(+), 518 deletions(-)
Fusion-Task-Id: FN-7029
Fusion-Task-Lineage: 59adc31f-7386-4008-b74f-8fb9bbae078a
Command Center analytics now honor picker presets and open-ended date bounds.\n\n- Serialize All time with an explicit upper bound and preserve one-sided custom/preset query params.\n- Resolve server analytics ranges as open windows for from-only and to-only requests instead of defaulting them away.\n- Cover picker query serialization and range-consuming Command Center endpoints with regression tests.\n- Document the restored picker contract and add a patch changeset.\n\nFiles changed:\n .changeset/fn-7019-command-center-range.md | 7 ++\n docs/dashboard-guide.md | 3 +-\n .../components/command-center/DateRangePicker.tsx | 12 ++-\n .../command-center/areas/__tests__/areas.test.tsx | 54 ++++++++++++-\n .../components/command-center/areas/areaShared.ts | 8 +-\n .../register-command-center-routes.test.ts | 92 +++++++++++++++++++++-\n .../src/routes/register-command-center-routes.ts | 28 ++++---\n 7 files changed, 183 insertions(+), 21 deletions(-)
Fusion-Task-Id: FN-7019
Fusion-Task-Lineage: 327f8f45-8ad8-4103-8cc4-efcf2af6a73a
Adds `columnDwellMs?: Record<string, number>` to Task — a per-column
accumulator (column name -> cumulative ms) recorded at the same store
column-transition seam as `cumulativeActiveMs`. On every move it adds
`columnMovedAt(new) - columnMovedAt(prev)` to the bucket for the column
being left, clamped >= 0; unparseable/missing prior timestamps and 0-dwell
moves are skipped, and second visits add to the existing bucket.
Motivation: `cumulativeActiveMs` only measures in-progress time. Diagnosis
of slow tasks showed the dominant wall-clock is *waiting* (queue time in
todo, review wait in in-review), which previously had to be reconstructed
from agent logs. This makes per-stage dwell directly queryable, like
productivity-analytics already consumes cumulativeActiveMs.
Persisted as a JSON-text task column following the v129 workspaceWorktrees
precedent: SCHEMA_SQL column + SCHEMA_VERSION 129->130 + versioned
addColumnIfMissing migration. Additive and behavior-preserving; pre-existing
rows start NULL and accumulate from their next transition. Survives
archive/restore. @fusion/core is private — no changeset.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Keep discovered skill rows within the left pane by truncating long text values.
- Wrap skill names in a truncatable text span while keeping chevrons fixed-size.
- Add ellipsis rules and compact font sizing for skill names, paths, and sources.
- Cover long, short, and empty metadata skill rows with a dashboard test.
- Add a patch changeset for the published CLI package.
Files changed:
.changeset/fn-7027-skill-list-truncation.md | 7 +++
packages/dashboard/app/components/SkillsView.css | 28 ++++++++++
packages/dashboard/app/components/SkillsView.tsx | 2 +-
.../app/components/__tests__/SkillsView.test.tsx | 64 ++++++++++++++++++++++
4 files changed, 100 insertions(+), 1 deletion(-)
Fusion-Task-Id: FN-7027
Fusion-Task-Lineage: ae50f1aa-e58b-4374-abae-38ca28e4073a
Make the dashboard AI session needs-input banner less prominent while preserving visibility rules.
- Reduce the session notification banner spacing, text scale, max heights, and mobile density.
- Cover banner visibility on missions, planning, hidden, empty, and board-session states.
- Add a patch changeset for the published CLI package.
Files changed:
.changeset/fn-7020-session-banner-compact.md | 7 +
.../app/components/SessionNotificationBanner.css | 47 +++--
.../dashboard/__tests__/DashboardBanners.test.tsx | 227 +++++++++++++++++++++
3 files changed, 263 insertions(+), 18 deletions(-)
Fusion-Task-Id: FN-7020
Fusion-Task-Lineage: bd48da60-29ef-4871-ae27-8a1a48f744ea
Optimize the split SettingsModal test suite to avoid default user-event overhead while preserving interaction coverage.
- Add a shared no-delay SettingsModal test user with pointer-event tree checks disabled.
- Route split SettingsModal tests through the shared fast user and use fireEvent for simple form mutations.
- Shorten modal readiness checks to wait on settings loading instead of querying Save repeatedly.
Files changed:
.../__tests__/SettingsModal.general.test.tsx | 124 +++++++--------
.../__tests__/SettingsModal.models-auth.test.tsx | 136 ++++++++--------
.../SettingsModal.remote-notifications.test.tsx | 61 ++++----
.../SettingsModal.scheduling-merge.test.tsx | 174 ++++++++++-----------
.../__tests__/SettingsModal.test-harness.tsx | 32 ++--
.../__tests__/SettingsModal.testMode.test.tsx | 5 +-
.../__tests__/SettingsModal.worktrunk.test.tsx | 5 +-
.../__tests__/SettingsModalNodeRouting.test.tsx | 7 +-
8 files changed, 273 insertions(+), 271 deletions(-)
Fusion-Task-Id: FN-7007
Fusion-Task-Lineage: 42cf8ce2-3da7-4607-8790-5bf46c07b417
## Problem
Fusion runs `pnpm test` as per-task verification. We have hard evidence
from a real task's agent log (FN-7011) that it repeatedly hit the
engine's **15-minute** verification timeout
(`VERIFICATION_TIMEOUT_WORKSPACE_MS = 900_000`) and got SIGKILLed — **9
separate 15.0-minute timeouts in one task, ~2.8h wasted**, after which
the engine restarts the task and re-runs the same lane.
## Root cause
`pnpm test` → `scripts/test-changed.mjs` changed-affected lane runs
`vitest run --changed <base>` for the heavy packages (`@fusion/engine`,
`@fusion/dashboard`), pinned to `workers=1` by the OOM-safety envelope.
But `vitest --changed` does **unbounded transitive module-graph
expansion**: a single changed *hub* source file selects ~the whole
package suite. Measured empirically — one `self-healing.ts`-class change
selects **8,393 test entries** (79s just to *list* them). At 1 worker
that blows past the 15-min kill; the script's own watchdog ceiling for
this class is 20 min, so it never engages. Prior FN-6854/FN-6877 work
fixed *OOM* but not *wall-clock*.
## Fix
A bounded, **git-only** guard in `scripts/test-changed.mjs` (no vitest
probe, no graph build, no widened timeouts/retries/workers — all
forbidden by AGENTS.md):
- New `changedSourceFilesAffectingPackage(pkg, changedFiles, …)` returns
changed **non-test source** within the package's own dir, any transitive
workspace-dependency dir, or the shared
`packages/core/src/__test-utils__` tree.
- In the affected lane for a heavy memory-envelope package: if that list
is non-empty (wide-fan-out risk), run **only the directly-changed test
files** (bounded to the diff) instead of `--changed`; if no test files
changed, **delegate cross-cutting coverage to the merge gate** (already
run first in changed mode) and skip. Test-only diffs keep normal `vitest
--changed`.
- Delegated/partially-tested packages are excluded from the pass-cache
so a partial pass is never recorded as full.
Mirrors the codebase's existing "delegate cross-cutting coverage to the
gate" philosophy (the reverse-dependent blast cap), one level down.
`pnpm test:full` remains the explicit full sweep.
## Proof
- Predictor unit checks + regression suite: `node --test
scripts/__tests__/test-changed.test.mjs` → **111/111 pass** (~3s).
- Bounded path: the explicit-changed-file engine run completes in
**2.79s** vs 79s just to *list* the 8,393-entry fan-out.
- `eslint scripts/test-changed.mjs
scripts/__tests__/test-changed.test.mjs` → exit 0.
- `pnpm build` green; `pnpm verify:fast` green.
No changeset: internal test tooling, not the published
`@runfusion/fusion` package (per AGENTS.md).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- stage-review-badge-begin -->
---
<a href="https://stagereview.app/Runfusion/Fusion/pull/1780">
<picture>
<source media="(prefers-color-scheme: dark)"
srcset="https://stagereview.app/assets/gh-open-in-stage-dark.svg">
<img src="https://stagereview.app/assets/gh-open-in-stage-light.svg"
alt="Open in Stage">
</picture>
</a>
<!-- stage-review-badge-end -->
## What
Adds a configurable built-in **Code Review** diff-review step to the
Fusion coding workflows. It is a workflow prompt-gate step — built
entirely on the existing workflow-step machinery, **not** engine
verification code.
## How (mirrors browser-verification exactly)
- **New catalog template** `code-review` in `WORKFLOW_STEP_TEMPLATES`
(`packages/core/src/types.ts`): `name: "Code Review"`, `toolMode:
"readonly"`, `gateMode: "advisory"` (non-blocking default, same as
browser-verification), `phase: "pre-merge"`. The prompt drives a strong
diff-review focused on the value tests miss — correctness/logic bugs,
broken edge cases, intent-vs-implementation mismatch, regressions in
touched paths, error handling, and contract/signature changes. It reads
`git diff` against the base + changed files, cites `file:line`,
fast-bails APPROVE on trivial/out-of-scope diffs, and ends with exactly
the shared trailing verdict JSON
`{"verdict":"APPROVE|APPROVE_WITH_NOTES|REVISE","notes":"..."}`. No
verdict-parsing code added — the existing gate machinery parses it.
- **New optional-group module**
`packages/core/src/builtin-code-review-group.ts` mirroring
`builtin-browser-verification-group.ts`: resolves the `code-review`
template and builds a **default-OFF** `optional-group` node with stable
group id `code-review` and distinct inner node id `code-review-step`,
sourcing prompt/toolMode/gateMode from the catalog.
- **Wired** into `builtin-coding-workflow-ir.ts` and
`builtin-stepwise-coding-workflow-ir.ts` on the pre-merge path next to
browser-verification: `… → browser-verification → code-review → review`
(failure → end). Default OFF / opt-in via task `enabledWorkflowSteps`;
disabled → byte-inert pass-through.
## Default off / opt-in
The step is **default OFF** and advisory. It only runs when a task's
`enabledWorkflowSteps` includes `code-review`, and
`resolveDefaultOnOptionalGroupIds` never auto-seeds it. Operators can
promote it to a blocking gate.
## Tests
New `builtin-code-review-group.test.ts` (template fields, default-OFF
group node with stable/distinct ids, pre-merge wiring + parse round-trip
for both built-ins, opt-in toggle advertised but never seeded). Updated
the verdict-contract, optional-steps resolver, and
builtin-coding-workflow-ir edge tests. Relevant core workflow suite:
**141 passed**. `tsc --noEmit` clean, eslint clean (0 errors).
## Scope
Pure `packages/core/**` change (+ changeset). No engine files touched.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- stage-review-badge-begin -->
---
<a href="https://stagereview.app/Runfusion/Fusion/pull/1779">
<picture>
<source media="(prefers-color-scheme: dark)"
srcset="https://stagereview.app/assets/gh-open-in-stage-dark.svg">
<img src="https://stagereview.app/assets/gh-open-in-stage-light.svg"
alt="Open in Stage">
</picture>
</a>
<!-- stage-review-badge-end -->
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added a new built-in **Code Review** step to the pre-merge workflow,
available in both coding and stepwise coding flows.
* The step is on by default for new tasks but can still be turned off
per task.
* It also appears in the editor palette as a selectable workflow step.
* **Bug Fixes**
* Fixed default workflow setup so default-on steps are preserved
correctly during task creation and restart.
* Updated workflow paths so Code Review is now included before the final
review stage.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->