diff --git a/CHANGELOG.md b/CHANGELOG.md index 5bc5086a1..ed14a8e71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,9 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). `macrozheng/mall` previously reported `20 047 edges` while the DB held `45 629`. +### Changed +- Claude Code: removed the duplicate `` block from `CLAUDE.md` — identical instructions are delivered automatically via the MCP `initialize` response (`SERVER_INSTRUCTIONS`) every session, making the CLAUDE.md copy redundant and costing ~1500 unnecessary tokens per turn. Re-running `codegraph install` strips the block from existing installs. Other targets (Cursor, Codex, opencode, Gemini, Kiro) are unchanged. + ## [0.9.6] - 2026-05-27 - **C/C++ `#include` resolution — bare-basename includes now connect to the diff --git a/__tests__/installer-targets.test.ts b/__tests__/installer-targets.test.ts index 697f8e976..bd69a24f4 100644 --- a/__tests__/installer-targets.test.ts +++ b/__tests__/installer-targets.test.ts @@ -1018,6 +1018,59 @@ describe('Installer targets — partial-state idempotency', () => { expect(fs.readFileSync(file, 'utf-8')).toBe(firstPass); }); + it('claude: install does not write CLAUDE.md instructions block', () => { + const claude = getTarget('claude')!; + const claudeMd = path.join(tmpHome, '.claude', 'CLAUDE.md'); + + claude.install('global', { autoAllow: false }); + + if (fs.existsSync(claudeMd)) { + const content = fs.readFileSync(claudeMd, 'utf-8'); + expect(content).not.toContain(''); + } + }); + + it('claude: install strips existing CLAUDE.md codegraph block (migration path)', () => { + const claude = getTarget('claude')!; + const claudeDir = path.join(tmpHome, '.claude'); + const claudeMd = path.join(claudeDir, 'CLAUDE.md'); + + fs.mkdirSync(claudeDir, { recursive: true }); + fs.writeFileSync(claudeMd, [ + '# My personal Claude instructions', + '', + 'Always respond concisely.', + '', + '', + '## CodeGraph', + '', + 'Old codegraph content here.', + '', + '', + '## Another personal section', + '', + 'Keep this.', + '', + ].join('\n')); + + const result = claude.install('global', { autoAllow: false }); + + // Block is gone, user content outside the markers is preserved. + const content = fs.readFileSync(claudeMd, 'utf-8'); + expect(content).not.toContain(''); + expect(content).not.toContain(''); + expect(content).not.toContain('Old codegraph content here.'); + expect(content).toContain('# My personal Claude instructions'); + expect(content).toContain('Always respond concisely.'); + expect(content).toContain('## Another personal section'); + expect(content).toContain('Keep this.'); + + // Removal is surfaced in result.files. + const instrEntry = result.files.find((f) => f.path === claudeMd); + expect(instrEntry).toBeDefined(); + expect(instrEntry!.action).toBe('removed'); + }); + it('claude: uninstall strips stale hooks written in the npx form (local)', () => { const claude = getTarget('claude')!; const file = seedSettings('local', { diff --git a/src/installer/targets/claude.ts b/src/installer/targets/claude.ts index d5e878824..062e251a5 100644 --- a/src/installer/targets/claude.ts +++ b/src/installer/targets/claude.ts @@ -124,7 +124,16 @@ class ClaudeCodeTarget implements AgentTarget { if (hookCleanup.action === 'removed') files.push(hookCleanup); // 3. CLAUDE.md instructions - files.push(writeInstructionsEntry(loc)); + // Claude Code receives identical guidance via SERVER_INSTRUCTIONS in + // the MCP initialize response every session — writing it to CLAUDE.md + // duplicates ~1500 tokens per turn. Strip the block if a legacy install + // left one, so upgrading users stop paying the cost automatically. + const instr = instructionsPath(loc); + const instrAction = removeMarkedSection(instr, CODEGRAPH_SECTION_START, CODEGRAPH_SECTION_END); + if (instrAction === 'removed') { + files.push({ path: instr, action: 'removed' }); + } + // 'not-found' / 'kept': nothing to report. return { files }; }