fix(manifest): classify ~/.claude.json:projects MCP as per-repo delta, not shared [skip-docs]

readClaudeJsonProjectSlice(repoPath) returns the slice keyed to the SPECIFIC
repo path (exact / longest-prefix match), so MCP servers under
~/.claude.json:projects are per-repo: they load only in their own project and
differ across repos. B2b-1 wrongly grouped them with the shared global layer
(reasoning from file location, not the keyed slice). The live sweep captures
the shared layer ONCE from the first repo — folding a per-repo slice into it
would silently drop every other repo's claude.json MCP servers.

Corrected: SHARED_GLOBAL_SOURCES = {user, managed}; the only machine-global
MCP is plugin-provided (plugin: prefix). Per-repo MCP (.mcp.json AND the
~/.claude.json project slice) → delta. Tests updated.

[skip-docs]: internal classifier correction, no user-facing surface yet.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-06-23 17:14:28 +02:00
commit 82f881afc4
2 changed files with 24 additions and 16 deletions

View file

@ -171,19 +171,26 @@ export function summarizeByLoadPattern(sources) {
/**
* Source strings (the `source` field buildManifest stamps) that belong to the
* SHARED GLOBAL layer config paid once per machine and identical in every
* repo: the global ~/.claude CLAUDE.md, managed enterprise policy, and the
* global MCP slice of ~/.claude.json. Installed plugins are also shared but are
* matched by the `plugin:` prefix below, not by this set.
* repo: the global ~/.claude CLAUDE.md (`user`) and managed enterprise policy
* (`managed`). Installed plugins are also shared but are matched by the
* `plugin:` prefix below, not by this set.
*
* Deliberately NOT here: `~/.claude.json:projects`. Although that file lives in
* HOME, `readClaudeJsonProjectSlice` returns the slice keyed to the SPECIFIC
* repo path those MCP servers are per-repo, load only in their own project,
* and differ across repos, so they are a delta (folding them into the
* once-counted shared layer would drop every repo's slice but the first). The
* only machine-global MCP is plugin-provided (caught by the `plugin:` prefix).
*/
const SHARED_GLOBAL_SOURCES = Object.freeze(new Set(['user', 'managed', '~/.claude.json:projects']));
const SHARED_GLOBAL_SOURCES = Object.freeze(new Set(['user', 'managed']));
/**
* Classify one manifest source as part of the once-counted shared global layer
* or a per-repo delta (v5.9 B2b). Anything not positively identified as global
* (project / local / .mcp.json / @import / unrecognized) falls to `delta`, so a
* source is never silently folded into the shared layer a wrong fold would
* HIDE machine-wide cost, whereas a wrong delta is at worst attributed visibly
* to a repo.
* (project / local / .mcp.json / ~/.claude.json:projects / @import / unrecognized)
* falls to `delta`, so a source is never silently folded into the shared layer
* a wrong fold would HIDE machine-wide cost, whereas a wrong delta is at worst
* attributed visibly to a repo.
* @param {string} source
* @returns {'shared'|'delta'}
*/