Skip to content

Commit bb69906

Browse files
authored
Merge pull request #33 from Litenova-Solutions/agent/resilient-workspace-index
Harden workspace identity and index recovery
2 parents 1faae10 + bb294db commit bb69906

68 files changed

Lines changed: 1593 additions & 328 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

AGENTS.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ Build first, then test with `--no-build`. CI verifies build, test, format, and a
3434
- The syntax-tier indexer is provider-driven: a `ILanguageSyntaxProvider` (in `Fuse.Semantics`) claims file extensions and extracts symbols and chunks with no compiler, and `SemanticIndexer` selects providers by extension rather than hardwiring one language. C# is the `CSharpSyntaxProvider` (the existing Roslyn-syntax extractor behind the seam, unchanged); a second language registers a provider without changing the shared indexer. Each indexed file carries a `language` tag (the `files.language` column, schema version 14) set from the selecting provider, so retrieval can filter or blend by language over the language-agnostic tables; symbols and nodes inherit their language through their `file_id`. The semantic (typed-graph) tier is provider-driven through `ISemanticLanguageProvider`: C# is the `CSharpSemanticLanguageProvider` (the shipped wiring analyzers behind the seam, unchanged); a second language registers a provider without changing the shared runner. Tier-1 multi-target captures canonicalize by deterministic primary TFM (`netX.Y`, then `netcoreapp`, then `netstandard`, then legacy; highest version within a family) and union every stable declaration and graph fact across all captured TFMs. Shared facts are stored once with rows in `tfm_availability`; non-primary-only facts must never be discarded. This changes the extraction contract and relational schema together, so both versions must bump and the index rebuilds. `Fuse.Semantics` does not reference `Fuse.Plugins.Languages.CSharp.Roslyn` directly; host composition registers the C# semantic provider from the Roslyn plugin. First-party multi-language semantic work is frozen behind the F6 entry bar (bind a language's real compiler service, not a parser; requires B1 recorded); community syntax providers are welcome through the syntax seam, and the Python and JavaScript syntax providers are maintained, not extended (Decision D10).
3535
- Closed retrieval bets (Decision D5, K3): V1 (graph verbalization) and V2 (per-repo learned ranking) are evidence-killed. The tier-1 localize re-run showed no recall lift (`localize.tier1.json` 15.0 percent versus the 15.0 percent baseline), so a richer build-exact graph is a verification asset (the substrate for `fuse_check`, `fuse_impact`, covering-test selection), not an open-ended-recall lever.
3636
- Persistent cache and index data live in a single SQLite file at `.fuse/fuse.db` (WAL mode). Corrupt `fuse.db` is derived data: `WorkspaceIndexStore` deletes and recreates the file on open (serialized per root) and MCP callers receive `index_rebuilding:` until the next index pass repopulates it.
37+
- Every MCP operation except `fuse_reduce` requires a resolvable Git repository identity. The nearest enclosing directory with a `.git` file or directory is the canonical root, so calls from nested folders share one daemon, writer lock, and `.fuse/fuse.db`. An unresolved folder returns `workspace_identity_unresolved:` before Fuse starts a daemon or writes an index; `fuse_reduce` remains available because it does not use the workspace index. A warm index carries its normalized root and complete file-inventory count and hash. A missing, incomplete, wrong-root, or inventory-inconsistent manifest rebuilds automatically, and every reconcile discovers additions and hard-deletes removed file rows.
3738
- `fuse mcp serve` delegates resident-grade checks to one shared `fuse host` daemon per repository by default (R13, G5). The same daemon owns index writes (open, reconcile, syntax-first cold start, background semantic upgrade) and held compiler state: live doctor, staged refactor, and pooled capture-check requests cross the typed pipe to its warm solution or worker. MCP clients call `fuse/openIndexed` then read the store locally (R19, D13). The host enforces `FUSE_COMPILER_STATE_CAP` (default 1) per root: activating resident, warm-solution, or pooled-worker state releases the other compiler state, so a root cannot accumulate independent held compilation domains. Set `FUSE_DAEMON=0` to serve in-process. `fuse mcp install` writes command-only client config (no env block); agent-first defaults (daemon, auto-update between sessions, background upgrade, build capture) ship in the binary unless explicitly opted out.
3839
- The compiler tools that need a design-time MSBuild workspace (`fuse_refactor`, and `fuse_workspace doctor`'s live load) reuse one held Roslyn `Solution` per root through `WarmSolutionCache` (R42), instead of re-opening `MSBuildWorkspace` on every call. Roslyn solutions are immutable snapshots, so the held solution is forked per refactor for free. When the daemon or an in-process resident server has a watcher, a settled tracked-C# change evicts the held solution and a clean call skips the directory scan (R54); `bin` and `obj` events do not evict because they are outside the signature's source set. Without a watcher, a cheap freshness signature (a pruned `.cs` scan) forces a fresh load when the source changed, so a reused solution yields the same diff a cold load would and a changed tree is never slower. The cache is a hard LRU with an idle window (`FUSE_WARM_SOLUTION_CAP`/`FUSE_WARM_SOLUTION_IDLE_MINUTES`) disposing evicted workspaces, so holding solutions cannot balloon RSS. It is a read/compile cache: it never writes the working tree (D2 apply is the only writer) and never writes the index (D13/R19 single-writer is untouched).
3940
- On the store-backed default (no resident workspace), `fuse_check`'s oracle path keeps a pooled build-capture worker per captured compiler log through `PooledCheckWorker` (R48): the worker rehydrates the log once and answers many checks against the held compilation (each speculative edit forks the in-memory document), so the second and later checks in a session skip the rehydrate. Check honesty is preserved exactly - the pooled worker runs the identical fork-and-diagnostics code (`BuildCaptureRehydrator.CheckHeld`, shared with `CheckFromLog`), so a pooled verdict equals a spawn-per-call verdict and false-green stays 0. The pool is a hard LRU with an idle window (`FUSE_CHECK_WORKER_CAP`/`FUSE_CHECK_WORKER_IDLE_MINUTES`) stopping the worker on eviction; a cold, absent, or failed pooled worker falls back to the spawn-per-call path, so it is never worse.
@@ -168,3 +169,22 @@ Use regular comments for non-obvious `private` or `internal` logic: heuristics,
168169
| Options and DTO records | Summary when the name alone is ambiguous | Rarely needed |
169170

170171
Full contribution workflow: [fuse.codes/docs/project/contributing](https://fuse.codes/docs/project/contributing). Pipeline context: [fuse.codes/docs/internals/pipeline](https://fuse.codes/docs/internals/pipeline). Documentation source lives in `site/content/docs`.
172+
173+
<!-- fuse:begin (managed by `fuse mcp install --rules`; edit outside these markers) -->
174+
## Fuse usage
175+
176+
Use Fuse MCP tools only when the requested folder resolves to a Git repository, except for `fuse_reduce`. Fuse resolves nested folders to the nearest repository root, and every nested path shares that root's warm index. On `workspace_identity_unresolved`, use native file tools and do not retry Fuse MCP tools for that folder. `fuse_reduce` remains available for known files or raw content outside Git repositories.
177+
178+
Use Fuse in .NET repositories when a task needs cross-file context, framework wiring, change impact, or compiler-backed checking. Use native file reads and search for a known file, an exact literal in a small scope, and non-.NET semantic work.
179+
180+
- For a pull request or branch review with a Git base, start with `fuse_review`.
181+
- For a named service, request, route, or config section, use the matching `fuse_find` kind, then use `fuse_context` for source bodies.
182+
- For an open-ended task in a large or unfamiliar repository, use `fuse_find kind=task`. Use `fuse_workspace action=map` only when you need repository orientation.
183+
- For exact symbol identity, paths, or indexed text, use `fuse_find kind=symbol|path|text`. Use `kind=signatures|neighbors` for signatures and relationships.
184+
- Before changing a signature, call `fuse_impact`.
185+
- Before writing a standalone single-file edit, call `fuse_check` with the complete proposed content. It cannot verify a coordinated multi-file overlay.
186+
- Use `fuse_refactor` for its supported solution-wide operations. Review and apply the returned diff with normal editing tools, then run the repository's required gates.
187+
- Use `fuse_test` for focused covering tests. Its selection is a lower bound and does not replace required build, test, format, or lint commands.
188+
- Use `fuse_review` before handoff to inspect scope and impact. Do not treat it as compiler or test proof.
189+
- Respect verification grades and abstentions. A missing or incomplete index warms automatically. While `index_state` reports a build or contention state, use native search and retry. An `upgrade_pending` syntax index remains usable.
190+
<!-- fuse:end -->

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,23 @@ All notable changes to Fuse are documented here. The format is based on Keep a C
44

55
## [Unreleased]
66

7+
## [4.3.0] - 2026-07-16
8+
9+
### Added
10+
11+
- Every MCP tool except `fuse_reduce` now exposes `workspace_identity_unresolved:` when the requested folder is not inside a Git repository. `fuse_reduce` remains available because it does not use the workspace index.
12+
- Warm indexes now carry a repository identity and complete file-inventory manifest in `index_meta`, including a build-state marker, file count, inventory hash, and completion time.
13+
14+
### Changed
15+
16+
- The nearest enclosing `.git` directory or file is now the canonical identity for MCP tools, daemon endpoints, cold-build coordination, writer locks, project-scoped MCP installation, scanning, and queries. Calls from nested folders therefore share one repository-root index. `fuse host`, eager warming, and workspace-scoped MCP operations do not start for unresolved folders.
17+
- `fuse mcp install` output and documentation now separate MCP registration, `--rules` instruction files, `--with-hooks` Claude Code hooks, and the Fuse executable. Project scope resolves to the enclosing Git root and refuses an unresolved folder; user scope controls client availability but does not bypass runtime workspace identity.
18+
- Warm reconciliation now scans the complete inventory, indexes added files, hard-deletes removed file rows and their FTS/co-change data, and automatically replaces the index after a change storm. `fuse_test` now validates or warms the index before covering-test selection and returns the bounded availability header during a build or contention. An explicit `fuse_workspace action=index` refreshes a non-empty daemon-owned index. `fuse index --force` now discards the derived tables before rebuilding.
19+
20+
### Fixed
21+
22+
- A non-empty partial database can no longer suppress repository indexing. A missing, interrupted, wrong-root, count-mismatched, or hash-mismatched manifest triggers an automatic rebuild, covering the case where indexing a nested `bin/Release` folder left only runtime JSON files in the repository database.
23+
724
## [4.2.1] - 2026-07-16
825

926
### Added

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<!-- Single source of truth for the product version, applied to every Fuse assembly so the CLI, the host,
55
and FuseBuildInfo (the index version stamp) all report the same number. The release tag must match this
66
(enforced by build/verify-version.ps1); bump every package in lockstep with build/set-version.ps1. -->
7-
<Version>4.2.1</Version>
7+
<Version>4.3.0</Version>
88
<TargetFramework>net10.0</TargetFramework>
99
<ImplicitUsings>enable</ImplicitUsings>
1010
<Nullable>enable</Nullable>

README.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ coding agents. It indexes a solution through MSBuild and Roslyn, stores the resu
1919
in `.fuse/fuse.db`, and reuses it across agent turns instead of rediscovering the
2020
same structure through repeated file reads and text searches.
2121

22-
From a .NET project directory:
22+
From a .NET project inside a Git repository:
2323

2424
```bash
2525
dotnet tool install -g Fuse
@@ -28,7 +28,11 @@ fuse mcp install --rules
2828

2929
The installer supports Claude Code, Cursor, GitHub Copilot, OpenCode, Kilo Code, Codex,
3030
and Grok Build. Use `--client <name>` to configure one client; the default `all` configures
31-
all seven for the selected scope.
31+
all seven for the selected scope. `fuse mcp install` writes MCP client registration only.
32+
`--rules` also writes the client's documented instruction file, such as `AGENTS.md` or
33+
`CLAUDE.md`; it does not install a skill. `--with-hooks` separately writes project-scoped
34+
Claude Code hooks. See [Connect your coding agent](https://fuse.codes/docs/start/connect-your-ai)
35+
for the exact file and scope matrix.
3236

3337
Reload your MCP client, then ask:
3438

@@ -43,6 +47,14 @@ semantic graph is still upgrading. Run `fuse index` when you want a synchronous
4347
index before connecting the agent. `fuse mcp install --rules` also adds `.fuse/` to
4448
`.gitignore` at project scope.
4549

50+
Every MCP operation except `fuse_reduce` requires a Git repository identity. Fuse walks upward to the nearest
51+
`.git` directory or file, so a call from a nested source or output folder uses the same
52+
repository-root daemon, lock, and index. A non-Git folder returns
53+
`workspace_identity_unresolved:` without starting a daemon or writing an index;
54+
`fuse_reduce` remains available. Each warm index records its repository root and complete
55+
file inventory. A missing or incomplete manifest rebuilds automatically even when the
56+
database already contains file rows.
57+
4658
<p align="center">
4759
<img src="assets/demo/fuse-check-demo.gif" alt="An agent proposes an edit with an invalid OrderOptions member. fuse_check returns CS1061 and a repair packet, then verifies the corrected proposal." width="820">
4860
</p>

mcp-registry/server.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"name": "io.github.Litenova-Solutions/fuse",
44
"title": "Fuse",
55
"description": "Local .NET compiler verification, change impact, and typed wiring for AI agents.",
6-
"version": "4.2.1",
6+
"version": "4.3.0",
77
"repository": {
88
"url": "https://github.com/Litenova-Solutions/Fuse",
99
"source": "github",
@@ -32,7 +32,7 @@
3232
"registryType": "nuget",
3333
"registryBaseUrl": "https://api.nuget.org/v3/index.json",
3434
"identifier": "Fuse",
35-
"version": "4.2.1",
35+
"version": "4.3.0",
3636
"runtimeHint": "dnx",
3737
"transport": {
3838
"type": "stdio"

packaging/winget/Litenova.Fuse.installer.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
PackageIdentifier: Litenova.Fuse
2-
PackageVersion: 4.2.1
2+
PackageVersion: 4.3.0
33
InstallerLocale: en-US
44
InstallerType: inno
55
Scope: user
@@ -9,7 +9,7 @@ InstallModes:
99
UpgradeBehavior: install
1010
Installers:
1111
- Architecture: x64
12-
InstallerUrl: https://github.com/Litenova-Solutions/Fuse/releases/download/v4.2.1/fuse-4.2.1-setup.exe
12+
InstallerUrl: https://github.com/Litenova-Solutions/Fuse/releases/download/v4.3.0/fuse-4.3.0-setup.exe
1313
InstallerSha256: 0000000000000000000000000000000000000000000000000000000000000000
1414
AppsAndFeaturesEntries:
1515
- ProductCode: '{A3F1E2D4-7C5B-4A9E-B8D6-2F0C3E1A4B7D}_is1'

packaging/winget/Litenova.Fuse.locale.en-US.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
PackageIdentifier: Litenova.Fuse
2-
PackageVersion: 4.2.1
2+
PackageVersion: 4.3.0
33
PackageLocale: en-US
44
Publisher: Litenova Solutions
55
PublisherUrl: https://github.com/Litenova-Solutions
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
PackageIdentifier: Litenova.Fuse
2-
PackageVersion: 4.2.1
2+
PackageVersion: 4.3.0
33
DefaultLocale: en-US
44
ManifestType: version
55
ManifestVersion: 1.6.0

0 commit comments

Comments
 (0)