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:
parent
872b8ac281
commit
82f881afc4
2 changed files with 24 additions and 16 deletions
|
|
@ -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'}
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -203,11 +203,12 @@ describe('splitManifestByOwnership — shared-global vs per-repo-delta (unit, v5
|
|||
assert.equal(classifyOwnership('user'), 'shared');
|
||||
assert.equal(classifyOwnership('managed'), 'shared');
|
||||
assert.equal(classifyOwnership('plugin:foo'), 'shared');
|
||||
assert.equal(classifyOwnership('~/.claude.json:projects'), 'shared');
|
||||
// Per-repo delta: the repo's own project/local contribution.
|
||||
assert.equal(classifyOwnership('project'), 'delta');
|
||||
assert.equal(classifyOwnership('local'), 'delta');
|
||||
assert.equal(classifyOwnership('.mcp.json'), 'delta');
|
||||
// ~/.claude.json:projects is a per-repo MCP slice (keyed by repo path), NOT shared.
|
||||
assert.equal(classifyOwnership('~/.claude.json:projects'), 'delta');
|
||||
// import + anything unrecognized → delta (never silently folded into the shared layer).
|
||||
assert.equal(classifyOwnership('import'), 'delta');
|
||||
assert.equal(classifyOwnership('mystery'), 'delta');
|
||||
|
|
@ -218,7 +219,7 @@ describe('splitManifestByOwnership — shared-global vs per-repo-delta (unit, v5
|
|||
src('user', 'always', 100), // global CLAUDE.md → shared
|
||||
src('managed', 'always', 50), // managed policy → shared
|
||||
src('plugin:foo', 'always', 30), // plugin agent/rule → shared
|
||||
src('~/.claude.json:projects', 'always', 20), // global MCP → shared
|
||||
src('~/.claude.json:projects', 'always', 20), // per-repo MCP slice → delta
|
||||
src('project', 'always', 10), // project CLAUDE.md/rule → delta
|
||||
src('local', 'always', 5), // local → delta
|
||||
src('.mcp.json', 'always', 7), // project MCP → delta
|
||||
|
|
@ -226,14 +227,14 @@ describe('splitManifestByOwnership — shared-global vs per-repo-delta (unit, v5
|
|||
];
|
||||
const { shared, delta } = splitManifestByOwnership(sources);
|
||||
|
||||
it('folds global/user/managed/plugin/global-MCP into the shared layer', () => {
|
||||
assert.equal(shared.always.tokens, 100 + 50 + 30 + 20); // 200
|
||||
assert.equal(shared.always.count, 4);
|
||||
it('folds global/user/managed/plugin into the shared layer', () => {
|
||||
assert.equal(shared.always.tokens, 100 + 50 + 30); // 180
|
||||
assert.equal(shared.always.count, 3);
|
||||
});
|
||||
|
||||
it('attributes project/local/.mcp.json/import to the per-repo delta', () => {
|
||||
assert.equal(delta.always.tokens, 10 + 5 + 7 + 3); // 25
|
||||
assert.equal(delta.always.count, 4);
|
||||
it('attributes project/local/.mcp.json/claude.json-slice/import to the per-repo delta', () => {
|
||||
assert.equal(delta.always.tokens, 20 + 10 + 5 + 7 + 3); // 45
|
||||
assert.equal(delta.always.count, 5);
|
||||
});
|
||||
|
||||
it('preserves load-pattern buckets within each layer', () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue