Skip to content

Commit cf25555

Browse files
authored
Merge pull request #22 from Litenova-Solutions/release/v3.1.1
Release 3.1.1: extension fixes, codebase-owned versioning, and defensive indexing
2 parents aa26753 + 603d70f commit cf25555

51 files changed

Lines changed: 1725 additions & 72 deletions

Some content is hidden

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

.github/workflows/ci.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ jobs:
2020
with:
2121
dotnet-version: '10.0.x'
2222

23+
- name: Verify version consistency
24+
shell: pwsh
25+
run: ./build/verify-version.ps1
26+
2327
- name: Restore
2428
run: dotnet restore Fuse.slnx
2529

.github/workflows/ext-release.yml

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,34 @@
11
name: Extension release (platform VSIXes)
22

3-
# Produces a platform-specific VSIX per OS/arch with a self-contained Fuse host bundled in (so the extension
4-
# runs with no global install). Manually dispatched or on an ext-v* tag. Each platform builds on its matching
5-
# OS so the host publishes natively; the six VSIXes are uploaded as artifacts for a maintainer to publish.
3+
# Part of the vX.Y.Z release: the same tag that ships the .NET tool ships the VS Code extension. Builds a
4+
# platform-specific VSIX per OS/arch with a self-contained Fuse host bundled in (so the extension runs with no
5+
# global install), each on its matching OS so the host publishes natively, then publishes all six to the VS Code
6+
# Marketplace and Open VSX. Publishing is gated on the VSCE_PAT / OVSX_PAT secrets, so until those are set the
7+
# release still builds the VSIXes as artifacts. A manual dispatch builds artifacts without publishing.
68
on:
79
workflow_dispatch:
810
push:
9-
tags: ['ext-v*']
11+
tags: ['v*.*.*']
1012

1113
jobs:
14+
verify:
15+
name: Verify version
16+
runs-on: ubuntu-latest
17+
steps:
18+
- name: Checkout
19+
uses: actions/checkout@v4
20+
21+
# The codebase owns the version (see build/verify-version.ps1). On a release tag, fail unless the tag
22+
# matches package.json and the sibling packages; on manual dispatch, just check cross-package consistency.
23+
- name: Verify version
24+
shell: pwsh
25+
run: |
26+
if ("${{ github.ref_type }}" -eq "tag") { ./build/verify-version.ps1 -Tag "${{ github.ref_name }}" }
27+
else { ./build/verify-version.ps1 }
28+
1229
package:
1330
name: Package ${{ matrix.vsceTarget }}
31+
needs: verify
1432
runs-on: ${{ matrix.os }}
1533
strategy:
1634
fail-fast: false
@@ -70,3 +88,41 @@ jobs:
7088
with:
7189
name: fuse-vscode-${{ matrix.vsceTarget }}
7290
path: ext/vscode/*.vsix
91+
92+
publish:
93+
name: Publish to marketplaces
94+
needs: package
95+
runs-on: ubuntu-latest
96+
# Only publish on a release tag; a manual dispatch stops at the artifacts. Each step is further gated on its
97+
# token secret, so the job is a clean no-op until the secrets are configured.
98+
if: github.ref_type == 'tag'
99+
env:
100+
VSCE_PAT: ${{ secrets.VSCE_PAT }}
101+
OVSX_PAT: ${{ secrets.OVSX_PAT }}
102+
steps:
103+
- name: Download platform VSIXes
104+
uses: actions/download-artifact@v4
105+
with:
106+
path: vsix
107+
pattern: fuse-vscode-*
108+
merge-multiple: true
109+
110+
- name: Setup Node
111+
uses: actions/setup-node@v4
112+
with:
113+
node-version: '20'
114+
115+
# All six platform VSIXes carry the same version and distinct targets, so one publish uploads them as the
116+
# platform variants of one Marketplace version.
117+
- name: Publish to VS Code Marketplace
118+
if: env.VSCE_PAT != ''
119+
run: npx --yes @vscode/vsce publish --pat "$VSCE_PAT" --packagePath vsix/*.vsix
120+
121+
- name: Publish to Open VSX
122+
if: env.OVSX_PAT != ''
123+
run: |
124+
for f in vsix/*.vsix; do npx --yes ovsx publish "$f" -p "$OVSX_PAT"; done
125+
126+
- name: Report skipped publish
127+
if: env.VSCE_PAT == '' && env.OVSX_PAT == ''
128+
run: echo "::notice::No VSCE_PAT or OVSX_PAT secret configured; the six VSIXes were built as artifacts but not published."

.github/workflows/mcp-registry.yml

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -38,20 +38,16 @@ jobs:
3838
fi
3939
echo "Manifest description length ${len}/100 - OK."
4040
41-
- name: Resolve package version
41+
# server.json is the source of truth for the version (see build/set-version.ps1). On a tag, fail if the
42+
# manifest does not match it; on manual dispatch, just check cross-package consistency. Then read the
43+
# published version from the manifest itself, no tag rewrite.
44+
- name: Verify version and read from manifest
45+
shell: pwsh
4246
run: |
43-
set -euo pipefail
44-
if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then
45-
VERSION="${GITHUB_REF_NAME#v}"
46-
jq --arg v "$VERSION" '.version = $v | .packages[0].version = $v' \
47-
mcp-registry/server.json > mcp-registry/server.tmp
48-
mv mcp-registry/server.tmp mcp-registry/server.json
49-
echo "server.json version set from tag to $VERSION"
50-
else
51-
VERSION="$(jq -r '.version' mcp-registry/server.json)"
52-
echo "No tag; using server.json version $VERSION"
53-
fi
54-
echo "PKG_VERSION=$VERSION" >> "$GITHUB_ENV"
47+
if ("${{ github.ref_type }}" -eq "tag") { ./build/verify-version.ps1 -Tag "${{ github.ref_name }}" }
48+
else { ./build/verify-version.ps1 }
49+
$v = (Get-Content mcp-registry/server.json -Raw | ConvertFrom-Json).version
50+
"PKG_VERSION=$v" >> $env:GITHUB_ENV
5551
5652
- name: Install mcp-publisher
5753
run: |

.github/workflows/publish.yml

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,15 @@ jobs:
2323
with:
2424
dotnet-version: '10.0.x'
2525

26-
- name: Extract version from tag
26+
- name: Verify version and read from codebase
2727
id: version
28-
shell: bash
28+
shell: pwsh
2929
run: |
30-
VERSION="${GITHUB_REF_NAME#v}"
31-
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
30+
# The codebase is the source of truth; fail the release if the tag does not match it.
31+
./build/verify-version.ps1 -Tag "${{ github.ref_name }}"
32+
$v = ([xml](Get-Content Directory.Build.props)).Project.PropertyGroup.Version |
33+
Where-Object { $_ } | Select-Object -First 1
34+
"version=$v" >> $env:GITHUB_OUTPUT
3235
3336
- name: Install Linux publish prerequisites
3437
run: |

.github/workflows/release.yml

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,13 @@ jobs:
2626
with:
2727
dotnet-version: '10.0.x'
2828

29-
- name: Extract version from tag
29+
- name: Verify version and read from codebase
3030
id: version
3131
shell: pwsh
3232
run: |
33-
$version = "${{ github.ref_name }}".TrimStart('v')
33+
./build/verify-version.ps1 -Tag "${{ github.ref_name }}"
34+
$version = ([xml](Get-Content Directory.Build.props)).Project.PropertyGroup.Version |
35+
Where-Object { $_ } | Select-Object -First 1
3436
echo "VERSION=$version" >> $env:GITHUB_OUTPUT
3537
3638
- name: Publish self-contained win-x64
@@ -85,9 +87,14 @@ jobs:
8587
sudo apt-get update
8688
sudo apt-get install -y clang zlib1g-dev
8789
88-
- name: Extract version from tag
90+
- name: Verify version and read from codebase
8991
id: version
90-
run: echo "VERSION=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
92+
shell: pwsh
93+
run: |
94+
./build/verify-version.ps1 -Tag "${{ github.ref_name }}"
95+
$version = ([xml](Get-Content Directory.Build.props)).Project.PropertyGroup.Version |
96+
Where-Object { $_ } | Select-Object -First 1
97+
echo "VERSION=$version" >> $env:GITHUB_OUTPUT
9198
9299
- name: Publish self-contained linux-x64
93100
run: |

AGENTS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ Build first, then test with `--no-build`. CI verifies build, test, format, and a
4040
- External-process arguments are bounded. Commands built from a variable-length path or id list (for example the git invocations in `GitStatsProvider` and `GitChangeDetector`) must chunk the list or pass it via stdin. A single concatenated command line overflows the OS limit on large repos and silently degrades to empty results.
4141
- Tests must actually run. When adding a test, confirm the test command discovers and executes it (the count goes up). The extension contract suite is wired through `ext/vscode/package.json` `test:contract`; a test placed where that script does not reach is dead. A green gate that ran zero new tests is not coverage.
4242
- Behavior changes are not silent. A change that alters throw or propagation behavior (for example SQLite flush exhaustion swallowing instead of rethrowing) is a behavior change; call it out, do not fold it into an "add logging" change.
43+
- Upgrades degrade to a clear message or a rebuild, never an opaque failure. Renaming or removing an MCP tool must keep the old name registered as a deprecation shim (in `FuseDeprecatedTools`) that returns an actionable message naming the replacement, for one major version; a client that cached the old surface across an upgrade must not see a bare `Unknown tool`. A change to the index extraction contract must either bump `WorkspaceIndexSchema.TargetVersion` or ride the Fuse-version stamp (`fuse_version` in `index_meta`, compared by `FuseBuildInfo.IsCompatible`), so a stale on-disk index rebuilds itself rather than serving stale data. The changelog entry names the reload or rebuild.
4344

4445
## MCP Tools
4546

@@ -62,6 +63,8 @@ All numbers come from `tests/benchmarks/results` (the recorded data) and the ben
6263

6364
- Branch off `main`; open a PR via `gh` when verified. Do not merge, self-approve, or enable auto-merge; leave merges to reviewers.
6465
- Keep build, test, and format green. New public API without XML docs is incomplete.
66+
- The product version lives in the codebase, not in the git tag. `Directory.Build.props` `<Version>` is the single source of truth for every .NET assembly; the VS Code extension (`ext/vscode/package.json`), the MCP registry manifest (`mcp-registry/server.json`), and the docs site (`site/package.json`) carry the same number. Bump all of them with `build/set-version.ps1 <version>`, never by hand one at a time. `build/verify-version.ps1` fails CI if the packages disagree, and on a release it fails if the tag does not match the codebase version.
67+
- One tag releases everything. The flow is: run `build/set-version.ps1 X.Y.Z`, commit, then push a matching `vX.Y.Z` tag. That single tag triggers the NuGet publish (`publish.yml`), the GitHub release binaries (`release.yml`), the MCP registry (`mcp-registry.yml`), and the VS Code extension (`ext-release.yml`, which builds the six platform VSIXes and publishes them to the VS Code Marketplace and Open VSX when the `VSCE_PAT` / `OVSX_PAT` secrets are set, otherwise it just uploads the VSIXes as artifacts). Every one of these verifies the tag against the codebase version first, so a mismatched tag fails the release rather than shipping the wrong number. There is no separate extension tag; the old `ext-v*` trigger is retired.
6568

6669
## Writing Style (docs and prose)
6770

CHANGELOG.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,27 @@
22

33
All notable changes to Fuse are documented here. The format is based on Keep a Changelog. Fuse 3.0 is a product overhaul; backward compatibility with 2.x output, commands, and the MCP tool surface is not a goal.
44

5+
## [3.1.1] - 2026-07-01
6+
7+
### Added
8+
9+
- **`fuse update` command.** Updates the global tool with the process-lock choreography a bare `dotnet tool update` cannot do on its own: it stops the running Fuse hosts that hold the file locks (for example the one an editor extension spawns), then hands the update to a small detached script that waits for the current process to exit before replacing the tool files. This works around the fact that a running .NET global tool locks its own files on Windows. Reload the editor window afterward so its MCP client re-handshakes with the new tool surface.
10+
- **Deprecation shims for the retired V2 MCP tool names.** `fuse_toc`, `fuse_skeleton`, `fuse_search`, `fuse_focus`, `fuse_changes`, `fuse_ask`, `fuse_dotnet`, and `fuse_generic` are registered again as thin tools that return an actionable message naming the V3 replacement. A client that cached the old tool surface and was upgraded underneath now gets clear guidance instead of an opaque `Unknown tool` error. They will be removed in the next major.
11+
12+
### Changed
13+
14+
- **The index self-heals on an incompatible upgrade.** The index records the Fuse build that wrote it; when a later run's `major.minor` differs, the index rebuilds automatically before it is read, so a minor upgrade never serves stale extraction. A patch release keeps the contract and does not force a rebuild. `fuse diagnostics` reports both the running version and the version that built the index.
15+
16+
### Changed
17+
18+
- **The product version is owned by the codebase, not the git tag.** `Directory.Build.props` `<Version>` is the single source of truth for every .NET assembly, and the VS Code extension, the MCP registry manifest, and the docs site carry the same number. `build/set-version.ps1` bumps all of them at once and `build/verify-version.ps1` fails CI on any drift, or on a release tag that does not match. This also fixes the index version stamp: `FuseBuildInfo` now reports the real product version (previously the non-CLI assemblies were left at 1.0.0), so the version-drift self-heal and `fuse diagnostics` report correctly.
19+
20+
### Fixed
21+
22+
- **Indexing is defensive against duplicate and foreign content.** The trigger was Claude Code writing full duplicate checkouts under `.claude/worktrees/`, so `fuse_localize` returned ten candidates that were all worktree copies. The fix is layered rather than a single name: (1) discovery prunes any nested version-control root (git worktree, submodule, or embedded clone) anywhere under the workspace, not just `.claude`; (2) for a git repository the file set now comes from git itself (tracked plus untracked-but-not-ignored), so other worktrees and ignored trees are excluded by construction, with the directory walk as the fallback; (3) the excluded-directory set is shared by the scanner and the .NET discoverer, widened to the common tooling, build, and dependency directories, and extensible per repository through a `.fuseignore` file; (4) byte-identical solution copies no longer flip discovery into projects mode; and (5) retrieval collapses byte-identical files, so a query never returns several copies of one source file even if duplication reaches the index.
23+
- **VS Code host connection gave up too early and orphaned the host.** The extension waited only about three seconds for a freshly spawned host to create its named pipe, so a cold .NET host under load (a debugger attached, other extensions busy) surfaced as `connect ENOENT`, and the supervisor left the still-starting host orphaned on the pipe. The connect now waits against a generous deadline, bails out early if the host process exits before it serves, kills the host if it does give up so no orphan squats the pipe, and reports a spawn error. Activation no longer blocks on the connect.
24+
- **VS Code host handshake serialized PascalCase, breaking the extension.** The `fuse host` JSON-RPC endpoint applied the source-generated context as a resolver but not its camelCase naming policy, so the wire emitted `ProtocolVersion` where the extension's `vscode-jsonrpc` client reads `protocolVersion`. Every field deserialized as its default, surfacing as an opaque "Fuse host protocol mismatch: host undefined". The host now sets the camelCase policy on the formatter, and a cross-formatter regression test (a camelCase client, matching the extension) guards it; the prior same-formatter test could not see the casing.
25+
526
## [3.1.0] - 2026-06-30
627

728
Fuse 3.1 sharpens the .NET semantic engine on the modes where 3.0 was weakest: open-ended discovery, language breadth, and first-call latency. Dense retrieval is now on by default and fully offline, the open-ended path refuses and routes instead of guessing on a vague request, and more of the codebase's own vocabulary is searchable without a model. The numbers behind every claim are on the [benchmarks page](https://fuse.codes/docs/project/benchmarks).

Directory.Build.props

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
<Project>
22
<PropertyGroup>
33
<RepoRoot Condition="'$(RepoRoot)' == ''">$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)'))</RepoRoot>
4+
<!-- Single source of truth for the product version, applied to every Fuse assembly so the CLI, the host,
5+
and FuseBuildInfo (the index version stamp) all report the same number. The release tag must match this
6+
(enforced by build/verify-version.ps1); bump every package in lockstep with build/set-version.ps1. -->
7+
<Version>3.1.1</Version>
48
<TargetFramework>net10.0</TargetFramework>
59
<ImplicitUsings>enable</ImplicitUsings>
610
<Nullable>enable</Nullable>

build/pack-tool.ps1

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,22 @@
11
param(
2-
[string]$Version = "2.0.0",
2+
# Empty means read the single source of truth in Directory.Build.props, so a local pack is correctly
3+
# versioned without passing anything. CI passes the file version explicitly after verifying it matches the tag.
4+
[string]$Version = "",
35
[string[]]$Rids = @("win-x64", "linux-x64", "osx-x64")
46
)
57

68
$ErrorActionPreference = "Stop"
79
$PSNativeCommandUseErrorActionPreference = $true
810

911
$root = Split-Path -Parent $PSScriptRoot
12+
13+
if ([string]::IsNullOrWhiteSpace($Version)) {
14+
$propsPath = Join-Path $root "Directory.Build.props"
15+
$Version = ([xml](Get-Content $propsPath)).Project.PropertyGroup.Version |
16+
Where-Object { $_ } | Select-Object -First 1
17+
if ([string]::IsNullOrWhiteSpace($Version)) { throw "No <Version> found in $propsPath" }
18+
Write-Host "Using version $Version from Directory.Build.props"
19+
}
1020
$cliProject = Join-Path $root "src/Host/Fuse.Cli/Fuse.Cli.csproj"
1121
$outputDir = Join-Path $root "src/Host/Fuse.Cli/nupkg"
1222
$runtimeRoot = Join-Path $root "artifacts/runtime"

build/set-version.ps1

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Sets the product version across every package in one step: the .NET source of truth (Directory.Build.props),
2+
# the VS Code extension (package.json plus its lock, via npm), the MCP registry manifest (both version fields),
3+
# and the docs website. Run this, commit, then tag the matching version; build/verify-version.ps1 enforces the
4+
# match in CI.
5+
#
6+
# ./build/set-version.ps1 3.1.2
7+
param(
8+
[Parameter(Mandatory)][string]$Version
9+
)
10+
11+
$ErrorActionPreference = "Stop"
12+
$root = Split-Path -Parent $PSScriptRoot
13+
14+
# .NET: the single <Version> in Directory.Build.props.
15+
$props = Join-Path $root "Directory.Build.props"
16+
(Get-Content $props -Raw) -replace '<Version>[^<]*</Version>', "<Version>$Version</Version>" |
17+
Set-Content $props -NoNewline
18+
Write-Host "Directory.Build.props -> $Version"
19+
20+
# VS Code extension: npm keeps package.json and package-lock.json in sync.
21+
Push-Location (Join-Path $root "ext/vscode")
22+
try { npm version $Version --no-git-tag-version --allow-same-version | Out-Null }
23+
finally { Pop-Location }
24+
Write-Host "ext/vscode/package.json -> $Version"
25+
26+
# MCP registry manifest: both the server version and the package version. Edited by regex so the file layout is
27+
# preserved (ConvertTo-Json would reformat the whole document).
28+
$serverJson = Join-Path $root "mcp-registry/server.json"
29+
(Get-Content $serverJson -Raw) -replace '"version":\s*"[^"]*"', "`"version`": `"$Version`"" |
30+
Set-Content $serverJson -NoNewline
31+
Write-Host "mcp-registry/server.json -> $Version"
32+
33+
# Docs website: pin package.json to the same version.
34+
$siteJson = Join-Path $root "site/package.json"
35+
(Get-Content $siteJson -Raw) -replace '"version":\s*"[^"]*"', "`"version`": `"$Version`"" |
36+
Set-Content $siteJson -NoNewline
37+
Write-Host "site/package.json -> $Version"
38+
39+
& (Join-Path $PSScriptRoot "verify-version.ps1")

0 commit comments

Comments
 (0)