fix(installer): stop duplicating agent instructions; MCP server is the single source of truth (#529) (#538)
The installer wrote a `## CodeGraph` usage block into each agent's
instructions file (CLAUDE.md / AGENTS.md / GEMINI.md / .cursor/rules /
Kiro steering) that duplicated, almost verbatim, the guidance the MCP
server already emits in its `initialize` response — so agents that
surface MCP instructions (Claude Code) read the same playbook twice
every turn.
All 6 instruction-writing targets (claude, cursor, codex, opencode,
gemini, kiro) now stop writing the block. install self-heals by
stripping a block a previous version wrote (uninstall already did), so
the next `codegraph install`/`uninstall` cleans up existing installs;
upgrading the package alone does not (the leftover block is harmless).
server-instructions.ts is now the single source of truth — the two
steers unique to the old template ("trust codegraph, don't re-verify
with grep" and the not-initialized -> `init -i` hint) are ported there.
Removes the now-dead INSTRUCTIONS_TEMPLATE / CLAUDE_MD_TEMPLATE,
claude-md-template.ts, writeClaudeMd / hasClaudeMdSection, and the
Cursor-only wireProjectSurfaces bootstrap. The install log learned a
"Removed" verb. Tests rewritten to the new contract + self-heal
coverage (140/140 installer tests pass).
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
cea78ceb1b
commit
a9c9e76d8c
+4
-120
@@ -3,7 +3,10 @@
|
||||
*
|
||||
* Tests for installer config-writer fixes:
|
||||
* - readJsonFile error handling
|
||||
* - writeClaudeMd section replacement
|
||||
*
|
||||
* (The CLAUDE.md instructions block is no longer written — see issue
|
||||
* #529. The marker-based install/uninstall self-heal is covered in
|
||||
* `installer-targets.test.ts`.)
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||
@@ -14,11 +17,6 @@ import * as os from 'os';
|
||||
// We test the exported functions from config-writer
|
||||
import {
|
||||
writeMcpConfig,
|
||||
writePermissions,
|
||||
writeClaudeMd,
|
||||
hasMcpConfig,
|
||||
hasPermissions,
|
||||
hasClaudeMdSection,
|
||||
} from '../src/installer/config-writer';
|
||||
|
||||
function createTempDir(): string {
|
||||
@@ -103,118 +101,4 @@ describe('Installer Config Writer', () => {
|
||||
expect(content.customField).toBe('preserved');
|
||||
});
|
||||
});
|
||||
|
||||
describe('writeClaudeMd section replacement', () => {
|
||||
it('should create new CLAUDE.md with markers', () => {
|
||||
const result = writeClaudeMd('local');
|
||||
|
||||
expect(result.created).toBe(true);
|
||||
const content = fs.readFileSync(path.join(tempDir, '.claude', 'CLAUDE.md'), 'utf-8');
|
||||
expect(content).toContain('<!-- CODEGRAPH_START -->');
|
||||
expect(content).toContain('<!-- CODEGRAPH_END -->');
|
||||
expect(content).toContain('## CodeGraph');
|
||||
});
|
||||
|
||||
it('should replace marked section on update', () => {
|
||||
// First write
|
||||
writeClaudeMd('local');
|
||||
|
||||
// Modify file to add custom content before and after
|
||||
const claudeMdPath = path.join(tempDir, '.claude', 'CLAUDE.md');
|
||||
const original = fs.readFileSync(claudeMdPath, 'utf-8');
|
||||
const modified = '## My Custom Section\n\nCustom content\n\n' + original + '\n\n## Another Section\n\nMore content\n';
|
||||
fs.writeFileSync(claudeMdPath, modified);
|
||||
|
||||
// Second write should leave the marked block as-is (byte-identical
|
||||
// body, so result is `created:false, updated:false` — both flags
|
||||
// are off but the surrounding custom content must survive).
|
||||
writeClaudeMd('local');
|
||||
|
||||
const final = fs.readFileSync(claudeMdPath, 'utf-8');
|
||||
expect(final).toContain('## My Custom Section');
|
||||
expect(final).toContain('Custom content');
|
||||
expect(final).toContain('## Another Section');
|
||||
expect(final).toContain('More content');
|
||||
expect(final).toContain('## CodeGraph');
|
||||
});
|
||||
|
||||
it('should use atomic writes (no temp files left behind)', () => {
|
||||
writeClaudeMd('local');
|
||||
|
||||
const claudeDir = path.join(tempDir, '.claude');
|
||||
const files = fs.readdirSync(claudeDir);
|
||||
const tmpFiles = files.filter(f => f.includes('.tmp.'));
|
||||
expect(tmpFiles).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should not overwrite content after unmarked section with ### subsections', () => {
|
||||
// Create a CLAUDE.md with an unmarked CodeGraph section that has ### subsections
|
||||
// followed by another ## section
|
||||
const claudeDir = path.join(tempDir, '.claude');
|
||||
fs.mkdirSync(claudeDir, { recursive: true });
|
||||
const claudeMdPath = path.join(claudeDir, 'CLAUDE.md');
|
||||
fs.writeFileSync(claudeMdPath, [
|
||||
'## Pre-existing Section',
|
||||
'',
|
||||
'Some content',
|
||||
'',
|
||||
'## CodeGraph',
|
||||
'',
|
||||
'### Subsection A',
|
||||
'',
|
||||
'Old codegraph content',
|
||||
'',
|
||||
'### Subsection B',
|
||||
'',
|
||||
'More old content',
|
||||
'',
|
||||
'## Important Section After',
|
||||
'',
|
||||
'This content must not be overwritten!',
|
||||
'',
|
||||
].join('\n'));
|
||||
|
||||
const result = writeClaudeMd('local');
|
||||
expect(result.updated).toBe(true);
|
||||
|
||||
const final = fs.readFileSync(claudeMdPath, 'utf-8');
|
||||
// The section after CodeGraph must be preserved
|
||||
expect(final).toContain('## Important Section After');
|
||||
expect(final).toContain('This content must not be overwritten!');
|
||||
// Pre-existing section should also be preserved
|
||||
expect(final).toContain('## Pre-existing Section');
|
||||
// New CodeGraph content should be present with markers
|
||||
expect(final).toContain('<!-- CODEGRAPH_START -->');
|
||||
expect(final).toContain('<!-- CODEGRAPH_END -->');
|
||||
});
|
||||
|
||||
it('should replace unmarked section without subsections', () => {
|
||||
const claudeDir = path.join(tempDir, '.claude');
|
||||
fs.mkdirSync(claudeDir, { recursive: true });
|
||||
const claudeMdPath = path.join(claudeDir, 'CLAUDE.md');
|
||||
// Note: regex needs \n before ## CodeGraph, so prefix with another section
|
||||
fs.writeFileSync(claudeMdPath, [
|
||||
'## Intro',
|
||||
'',
|
||||
'Preamble',
|
||||
'',
|
||||
'## CodeGraph',
|
||||
'',
|
||||
'Old simple content',
|
||||
'',
|
||||
'## Next Section',
|
||||
'',
|
||||
'Must be preserved',
|
||||
'',
|
||||
].join('\n'));
|
||||
|
||||
writeClaudeMd('local');
|
||||
|
||||
const final = fs.readFileSync(claudeMdPath, 'utf-8');
|
||||
expect(final).toContain('<!-- CODEGRAPH_START -->');
|
||||
expect(final).toContain('## Next Section');
|
||||
expect(final).toContain('Must be preserved');
|
||||
expect(final).not.toContain('Old simple content');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user