Files
fusion/packages/dashboard/app/components/settings/sections/WorktreesSection.search.ts
gsxdsm 743df98aa4 capacity, part 1: merge pinned at 1, worktrees-off mode, and one dead knob deleted (#2502)
First slice of the capacity simplification. Operator: *"just have two
capacity — overall per project agent count and max worktrees. Remove all
other capacities and counts."* Plus two later additions: **merge is
always 1, fixed**, and **worktrees off ⇒ limit by total agents only**.

Three independently revertable commits. No limiter is added anywhere;
one is deleted, one is made structurally absent, and one is pinned.

---

## 1. Merge concurrency ratcheted at 1 (test-only)

I was asked to add a limiter if merge concurrency could be raised. **It
cannot** — there is no setting, workflow property, pool or trait config
anywhere that raises it, so this adds no code and pins what already
holds.

Serialization lives in the **pump**: `drainMergeQueue`’s `mergeRunning`
re-entrancy latch, `activeMergeTaskId` as a single-slot identity, the
`mergeBodyInFlight` next-generation latch, and one `ProjectEngine` per
projectId.

**Not** in the merge-queue lease, which is a per-task ROW (`primaryKey
[projectId, taskId]`) — two tasks can hold leases simultaneously by
construction, and it has exactly one caller (the worktree-reuse
handoff). Ordinary merges never take it. A lease-level test would have
been describing an invariant that layer has never held.

The second half guards the other direction: a merge-concurrency
*setting* would not fail the pump ratchet — it would sit unread until
someone wired it up.

**Revert-proof:** deleting the latch → `expected 1 times, but got 2
times`; deleting the `finally` → latch-stuck; injecting
`maxConcurrentMerges: 2` → fails naming the key; injecting a
`maxParallelLanes` merge-trait field → fails naming the field. Sources
restored byte-identical after each injection.

## 2. `worktreesEnabled` — off means the worktree limit cannot bind

No worktrees-off mode existed (no
`worktreesEnabled`/`useWorktrees`/`worktreeMode` anywhere — only
worktree *configuration*).

**Why not `maxWorktrees: 0`, which needs no new key:** it deadlocks. `??
4` keeps `0` (not nullish), the gate is `used >= limit`, so `0 >= 0`
holds **on an empty board** and nothing ever dispatches — while the
operator-visible reason reads `gate=maxWorktrees; used=0/0`, a limiter
that looks like it is working while the board is dead. It also needs the
Command Center `{min:1}` clamp relaxed. So `0` costs the gate rewrite
*and* the clamp change *and* encodes a mode as a magic value.

**Off is absence, not a big number.** `resolveWorktreeCapacityLimit`
returns `number | null`; `ConcurrencyGateDiagnostic.maxWorktreesGate` is
now optional, so consulting a worktree limit in OFF mode does not
type-check. A gate holding `Infinity` can start binding again the moment
someone "fixes" a comparison; an absent gate cannot.

That paid for itself immediately: making it nullable surfaced a
**second, independent** worktree gate (`activeWorktrees >= maxWorktrees`
early-return) that a skip-by-convention approach would have missed
silently.

**Scope, deliberately:** this is a statement about *counting*, not
isolation. It does not make concurrent agents safe to share one checkout
and builds nothing toward that — the non-worktree paths that exist today
are fallbacks to the operator’s own tree, one of which caused FN-8600.

**Revert-proof:** a resolver ignoring the flag turns both OFF scheduler
tests red while every ON test stays green — they reuse the *same*
fixture (5 in-progress, limit 4) that pre-existing tests prove blocks,
so the pair moves in opposite directions. Removing `disabled:` reddens
the UI test.

## 3. `maxTriageConcurrent` deleted — it controlled nothing

**Measured: zero enforcement reads.** The only `.maxTriageConcurrent`
reference in the repo was a route echoing it back in `/config`. FN-8453
removed the pool it gated and left the knob shipping in
`DEFAULT_SETTINGS`, the settings type, the section registry, the API
response and six i18n catalogs, doing nothing, for releases.

Historical FNXC comments are **updated, not deleted** — they explain a
real past incident; they now say "planning admission slot" so they stop
implying a live setting. Tombstoned so it cannot return.

`/config` loses a field; safe in-repo since `fetchConfig`’s own return
type never declared it.

---

## Two corrections worth recording

- I earlier reported `maxWorktrees` had **no** Settings UI. Wrong —
`WorktreesSection.tsx:47`; my grep was truncated by `head`. It changed
the placement (toggle beside it, rather than a duplicate key in
Scheduling).
- I planned to assert the queued-reason string is rewritten in OFF mode.
Measured that it is **unreachable**: when `maxConcurrent` binds, the
sweep bails before the per-task reason and logs nothing. The test
asserts absence instead.

Two near-misses caught before commit: a pre-existing FN-7505 guard
caught my *new* key missing a description mapping; and editing i18n via
`json.load/dump` silently dropped unrelated duplicate keys
(`autoUpdateAndRestart` in `fr`) — Python keeps only the last of a
duplicated key. Redone textually, every catalog re-validated.

## Verification

`pnpm lint` clean · core/engine/dashboard/i18n typecheck clean · `pnpm
test:gate` green (309 + 10 + 71) · capacity/worktree suites 11/11 ·
engine merge-invariant + scheduler 45/45 · dashboard settings 114/114.
Rebased onto current main and re-verified.

Nothing was booted at any point.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Added a project setting to enable or disable running tasks in
worktrees.
* Disabling worktrees removes worktree capacity limits from task
scheduling.
* The “Max Worktrees” setting is disabled when worktree execution is
turned off.

* **Changes**
* Removed the unused triage concurrency setting from configuration and
dashboard responses.
* Updated scheduling diagnostics and queue messages to reflect disabled
worktree capacity limits.
  * Added localized labels and help text for the new setting.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 18:52:31 -07:00

104 lines
6.0 KiB
TypeScript

/**
* Search entries for the Worktrees section.
*
* FNXC:SettingsSearch 2026-07-15-17:35:
* One entry per descriptor row the section renders, co-located so a setting and its index entry change in the same edit. `settings-search-index.test.ts` fails the build if a descriptor `key` here and in WorktreesSection.tsx ever diverge, which is what keeps the index honest without anyone maintaining a keyword list by hand.
* Labels and help mirror the section's `t()` calls verbatim: the index matches on the copy operators actually read, so a paraphrase here would make search miss the words on screen.
* The section's bespoke controls (worktree copy-file list, worktrees directory picker, sibling-branch-rename toggle, rebase remote select, and the worktrunk block) render no descriptor rows and so carry no entries; the nav entry's own `searchableText` still surfaces the section for those.
*/
import type { SettingsSearchEntry } from "../search/types";
export const worktreesSearchEntries: SettingsSearchEntry[] = [
{
sectionId: "worktrees",
key: "worktreeLimitEnabled",
labelKey: "settings.worktrees.worktreeLimitEnabled",
labelFallback: "Limit concurrent worktrees",
helpKey: "settings.worktrees.worktreeLimitEnabledHelp",
helpFallback: "When on, Max Worktrees caps how many tasks may hold a worktree at once. When off, Max Concurrent Tasks is the only limit. Tasks always run in their own git worktree either way — this does not change where work executes. Default: on.",
/*
FNXC:SettingsSearch 2026-07-28-13:20:
An operator reaching for this is asking a CAPACITY question ("why won't more
tasks start?"). "isolation" is deliberately NOT a keyword: this setting does
not affect isolation, and matching that word would re-create the same false
impression the old label gave (PR #2502 review).
*/
keywords: ["capacity", "parallelism", "agents only", "max worktrees", "concurrency", "limit"],
},
{
sectionId: "worktrees",
key: "maxWorktrees",
labelKey: "settings.worktrees.maxWorktrees",
labelFallback: "Max Worktrees",
helpKey: "settings.worktrees.limitsTotalGitWorktreesIncludingInReviewTasks",
helpFallback: "Limits total git worktrees including in-review tasks. Default: 4.",
/*
FNXC:SettingsSearch 2026-07-15-17:35:
Concurrency vocabulary is indexed here because this key — not `maxConcurrent` — is the real task-parallelism cap (it gates in-progress worktree holders), and operators searching "concurrency" or "parallel" would otherwise land only on Scheduling.
*/
keywords: ["concurrency", "parallel tasks", "capacity", "limit"],
},
{
sectionId: "worktrees",
key: "worktreeInitCommand",
labelKey: "settings.worktrees.worktreeInitCommand",
labelFallback: "Worktree Init Command",
helpKey: "settings.worktrees.shellCommandToRunInEachNewWorktree",
helpFallback: "Shell command to run in each new worktree after creation. No default — unset.",
keywords: ["setup script", "bootstrap", "install dependencies", "post-create"],
},
{
sectionId: "worktrees",
key: "recycleWorktrees",
labelKey: "settings.worktrees.recycleWorktrees",
labelFallback: " Recycle worktrees ",
helpKey: "settings.worktrees.offByDefaultOptInWhenEnabledCompleted",
helpFallback:
"Off by default (opt-in). When enabled, completed task worktrees are returned to an idle pool instead of being deleted, preserving build caches for faster startup",
keywords: ["reuse", "warm pool"],
},
{
sectionId: "worktrees",
key: "showWorktreeGrouping",
labelKey: "settings.worktrees.showWorktreeGrouping",
labelFallback: " Show worktree grouping on the board ",
helpKey: "settings.worktrees.showWorktreeGroupingHelp",
helpFallback:
"Off by default. When enabled, WIP and processing columns always group tasks by worktree and show worktree names, including workflow-mode processing columns.",
keywords: ["group by", "swimlane"],
},
{
sectionId: "worktrees",
key: "worktreeNaming",
labelKey: "settings.worktrees.worktreeNamingStyle",
labelFallback: "Worktree Naming Style",
/*
FNXC:SettingsSearch 2026-07-15-17:35:
Indexed against the enabled help string. The disabled variant ("not applicable when recycling") is a transient state of one checkbox, not a second setting, so indexing it would make search results read as though recycling were on.
*/
helpKey: "settings.worktrees.howToNameFreshWorktreeDirectories",
helpFallback: "How to name fresh worktree directories. Only applies when recycling is off. Default: random.",
keywords: ["folder name", "directory name", "branch naming"],
},
{
sectionId: "worktrees",
key: "worktreeRebaseBeforeMerge",
labelKey: "settings.worktrees.rebaseFromRemoteBeforeMerge",
labelFallback: " Rebase from remote before merge ",
helpKey: "settings.worktrees.whenEnabledTheMergerFetchesFromTheConfigured",
helpFallback:
"When enabled, the merger fetches from the configured remote and rebases the task branch onto the latest default-branch tip before merging — catching concurrent pushes from other collaborators or fusion workers. Any conflicts the rebase surfaces flow into the existing smart/AI resolve pipeline. Default: enabled.",
keywords: ["pull", "up to date", "prerebase"],
},
{
sectionId: "worktrees",
key: "worktreeRebaseLocalBase",
labelKey: "settings.worktrees.alsoRebaseOntoLocalDefaultBranchHEAD",
labelFallback: " Also rebase onto local default-branch HEAD ",
helpKey: "settings.worktrees.inAdditionToTheRemoteRebaseAboveAlso",
helpFallback:
" In addition to the remote rebase above, also rebase the task branch onto the local default-branch HEAD (rootDir). This catches sibling tasks that merged locally but haven't been pushed yet — without it, two concurrent tasks where one deletes code can have the other silently re-introduce it via the fallback strategy. Enabled by default; only disable if it causes issues with your workflow. ",
keywords: ["main", "unpushed", "prerebase"],
},
];