From a57cabe5f3490d3f070e5e5d741f345704f8673c Mon Sep 17 00:00:00 2001 From: Ricky Lee Whittemore II Date: Mon, 1 Jun 2026 12:50:08 -0400 Subject: [PATCH 1/3] Use CLAUDE.local.md instead of CLAUDE.md for Claude Code context MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Claude Code's CLAUDE.local.md is the correct home for machine-specific, uncommitted project context — it sits alongside a committed CLAUDE.md without interfering with it. This change updates the claude agent target to write there instead, and adds a one-time migration that strips any existing Agent Tools marker block from CLAUDE.md on the next enable or regenerate. Fixes #77 --- CHANGELOG.md | 5 +++++ src/main.ts | 39 +++++++++++++++++++++++++++++++++++---- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cde40af..4e30677 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file, per [the Ke ## [Unreleased] +### Changed + +- Write Claude Code project context to `CLAUDE.local.md` instead of `CLAUDE.md`, so teams with a committed `CLAUDE.md` are no longer affected. `CLAUDE.local.md` is Claude Code's native local-override file for machine-specific, uncommitted instructions (props [@rickalee](https://github.com/rickalee) via [#77](https://github.com/10up/localwp-agent-tools/issues/77)). +- On first enable or regenerate, any Agent Tools marker block previously written to `CLAUDE.md` is automatically migrated out (the block is removed, leaving the rest of `CLAUDE.md` intact). + ## [0.2.1] - 2026-03-19 ### Fixed diff --git a/src/main.ts b/src/main.ts index 0a77c3c..571c344 100644 --- a/src/main.ts +++ b/src/main.ts @@ -46,8 +46,8 @@ const AGENT_TARGETS: Record = { label: 'Claude Code', mcpConfigPath: '.mcp.json', mcpConfigTopLevelKey: 'mcpServers', - contextFilePath: 'CLAUDE.md', - gitignoreEntries: ['.mcp.json', 'CLAUDE.md'], + contextFilePath: 'CLAUDE.local.md', + gitignoreEntries: ['.mcp.json', 'CLAUDE.local.md'], }, cursor: { label: 'Cursor', @@ -397,6 +397,27 @@ async function updateGitignore(dirPath: string, agents: AgentTarget[]): Promise< await fs.writeFile(gitignorePath, content, 'utf-8'); } +// --------------------------------------------------------------------------- +// Migration Helpers +// --------------------------------------------------------------------------- + +/** + * Removes any Agent Tools marker block from CLAUDE.md left by older versions + * that wrote context there instead of CLAUDE.local.md. + */ +async function migrateClaude(projectPath: string): Promise { + const legacyPath = path.join(projectPath, 'CLAUDE.md'); + if (!(await fs.pathExists(legacyPath))) return; + + const content = await fs.readFile(legacyPath, 'utf-8'); + const markerRegex = new RegExp( + `${escapeRegex(CONTEXT_MARKER_START)}[\\s\\S]*?${escapeRegex(CONTEXT_MARKER_END)}`, + ); + if (!markerRegex.test(content)) return; + + await removeContextFile(legacyPath, 'claude'); +} + // --------------------------------------------------------------------------- // Core Functions // --------------------------------------------------------------------------- @@ -415,10 +436,15 @@ async function setupSite(site: Local.Site, notifier: any, projectDir: string, ag const siteConfig = await buildSiteConfig(site); siteConfigRegistry.register(siteConfig); - // 2. Generate project context + // 2. Migrate legacy CLAUDE.md content to CLAUDE.local.md (no-op if already clean) + if (agents.includes('claude')) { + await migrateClaude(projectPath); + } + + // 3. Generate project context const contextContent = generateProjectContext(site); - // 3. For each selected agent, write configs + // 4. For each selected agent, write configs for (const agent of agents) { const agentConfig = AGENT_TARGETS[agent]; @@ -607,6 +633,11 @@ async function regenerateConfig(site: Local.Site): Promise { const siteConfig = await buildSiteConfig(site); siteConfigRegistry.register(siteConfig); + // Migrate legacy CLAUDE.md content on regenerate (no-op if already clean) + if (agents.includes('claude')) { + await migrateClaude(projectPath); + } + const contextContent = generateProjectContext(site); for (const agent of agents) { From 28913cf68021ccf687529d134f2be0e7d392ca5a Mon Sep 17 00:00:00 2001 From: Clayton Collie Date: Fri, 4 Sep 2026 08:39:20 +0200 Subject: [PATCH 2/3] (fix): Remove legacy CLAUDE.md marker block on disable, project dir change, and agent change --- src/helpers/utils.ts | 10 ++++++++ src/main.ts | 49 +++++++++++++++++++------------------ tests/helpers/utils.test.ts | 24 +++++++++++++++++- 3 files changed, 58 insertions(+), 25 deletions(-) diff --git a/src/helpers/utils.ts b/src/helpers/utils.ts index 6e137ba..c3827a7 100644 --- a/src/helpers/utils.ts +++ b/src/helpers/utils.ts @@ -35,3 +35,13 @@ export function buildWpCliEnv(config: SiteConfig): NodeJS.ProcessEnv { ...(config.dbPort ? { DB_PORT: String(config.dbPort) } : {}), }; } + +/** + * True when `content` holds at least one complete block that starts with + * `start` and ends with `end`. Used to decide whether a file we did not + * create still carries an Agent Tools marker block. + */ +export function hasMarkerBlock(content: string, start: string, end: string): boolean { + const pattern = new RegExp(`${escapeRegex(start)}[\\s\\S]*?${escapeRegex(end)}`); + return pattern.test(content); +} diff --git a/src/main.ts b/src/main.ts index f0facb8..486ec7c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -12,6 +12,7 @@ import { } from './helpers/paths'; import { SiteConfig, SiteConfigRegistry } from './helpers/site-config'; import { findAvailablePort, savePort, removePortFile, removePortFileSync } from './helpers/port'; +import { hasMarkerBlock } from './helpers/utils'; import { createMcpHttpServer, startMcpHttpServer, stopMcpHttpServer, closeSessionsForSite } from './mcp-server'; import { LocalApi, CreateSiteOptions, CreateSiteResult, ServiceVersion, ServiceVersions } from './tools'; import { @@ -45,6 +46,8 @@ interface AgentTargetConfig { mcpConfigTopLevelKey: string; /** Path to project context/instructions file, relative to project dir */ contextFilePath: string; + /** Context files older versions wrote for this agent; our marker block is removed from them */ + legacyContextFilePaths?: string[]; /** Extra entries to add to .gitignore */ gitignoreEntries: string[]; } @@ -55,6 +58,7 @@ const AGENT_TARGETS: Record = { mcpConfigPath: '.mcp.json', mcpConfigTopLevelKey: 'mcpServers', contextFilePath: 'CLAUDE.local.md', + legacyContextFilePaths: ['CLAUDE.md'], gitignoreEntries: ['.mcp.json', 'CLAUDE.local.md'], }, cursor: { @@ -410,20 +414,20 @@ async function updateGitignore(dirPath: string, agents: AgentTarget[]): Promise< // --------------------------------------------------------------------------- /** - * Removes any Agent Tools marker block from CLAUDE.md left by older versions - * that wrote context there instead of CLAUDE.local.md. + * Removes any Agent Tools marker block from context files that older versions + * wrote for an agent (for example CLAUDE.md, before the move to CLAUDE.local.md). + * Files without our marker block are left untouched. */ -async function migrateClaude(projectPath: string): Promise { - const legacyPath = path.join(projectPath, 'CLAUDE.md'); - if (!(await fs.pathExists(legacyPath))) return; +async function removeLegacyContextFiles(projectPath: string, agent: AgentTarget): Promise { + for (const relativePath of AGENT_TARGETS[agent].legacyContextFilePaths ?? []) { + const legacyPath = path.join(projectPath, relativePath); + if (!(await fs.pathExists(legacyPath))) continue; - const content = await fs.readFile(legacyPath, 'utf-8'); - const markerRegex = new RegExp( - `${escapeRegex(CONTEXT_MARKER_START)}[\\s\\S]*?${escapeRegex(CONTEXT_MARKER_END)}`, - ); - if (!markerRegex.test(content)) return; + const content = await fs.readFile(legacyPath, 'utf-8'); + if (!hasMarkerBlock(content, CONTEXT_MARKER_START, CONTEXT_MARKER_END)) continue; - await removeContextFile(legacyPath, 'claude'); + await removeContextFile(legacyPath, agent); + } } // --------------------------------------------------------------------------- @@ -444,15 +448,10 @@ async function setupSite(site: Local.Site, notifier: any, projectDir: string, ag const siteConfig = await buildSiteConfig(site); siteConfigRegistry.register(siteConfig); - // 2. Migrate legacy CLAUDE.md content to CLAUDE.local.md (no-op if already clean) - if (agents.includes('claude')) { - await migrateClaude(projectPath); - } - - // 3. Generate project context + // 2. Generate project context const contextContent = generateProjectContext(site); - // 4. For each selected agent, write configs + // 3. For each selected agent, write configs for (const agent of agents) { const agentConfig = AGENT_TARGETS[agent]; @@ -461,7 +460,8 @@ async function setupSite(site: Local.Site, notifier: any, projectDir: string, ag const serverEntry = buildMcpServerEntry(agent, mcpServerPort, site.id); await mergeMcpConfig(mcpConfigPath, serverEntry, agentConfig.mcpConfigTopLevelKey); - // Write project context + // Write project context (and clean up any context file an older version wrote) + await removeLegacyContextFiles(projectPath, agent); const contextPath = path.join(projectPath, agentConfig.contextFilePath); await writeContextFile(contextPath, contextContent, agent); } @@ -506,6 +506,7 @@ async function teardownSite(site: Local.Site, notifier: any): Promise { await removeMcpConfigEntry(path.join(projectPath, agentConfig.mcpConfigPath), agentConfig.mcpConfigTopLevelKey); await removeContextFile(path.join(projectPath, agentConfig.contextFilePath), agent); + await removeLegacyContextFiles(projectPath, agent); } // 4. Clean up .gitignore @@ -541,6 +542,7 @@ async function changeProjectDir(site: Local.Site, newProjectDir: string, notifie await removeMcpConfigEntry(path.join(oldPath, agentConfig.mcpConfigPath), agentConfig.mcpConfigTopLevelKey); await removeContextFile(path.join(oldPath, agentConfig.contextFilePath), agent); + await removeLegacyContextFiles(oldPath, agent); } await updateGitignore(oldPath, []); @@ -556,6 +558,7 @@ async function changeProjectDir(site: Local.Site, newProjectDir: string, notifie serverEntry, agentConfig.mcpConfigTopLevelKey, ); + await removeLegacyContextFiles(newPath, agent); await writeContextFile(path.join(newPath, agentConfig.contextFilePath), contextContent, agent); } @@ -592,6 +595,7 @@ async function updateAgents(site: Local.Site, newAgents: AgentTarget[], notifier await removeMcpConfigEntry(path.join(projectPath, agentConfig.mcpConfigPath), agentConfig.mcpConfigTopLevelKey); await removeContextFile(path.join(projectPath, agentConfig.contextFilePath), agent); + await removeLegacyContextFiles(projectPath, agent); } // Add configs for newly selected agents @@ -607,6 +611,7 @@ async function updateAgents(site: Local.Site, newAgents: AgentTarget[], notifier serverEntry, agentConfig.mcpConfigTopLevelKey, ); + await removeLegacyContextFiles(projectPath, agent); await writeContextFile(path.join(projectPath, agentConfig.contextFilePath), contextContent, agent); } } @@ -641,11 +646,6 @@ async function regenerateConfig(site: Local.Site): Promise { const siteConfig = await buildSiteConfig(site); siteConfigRegistry.register(siteConfig); - // Migrate legacy CLAUDE.md content on regenerate (no-op if already clean) - if (agents.includes('claude')) { - await migrateClaude(projectPath); - } - const contextContent = generateProjectContext(site); for (const agent of agents) { @@ -657,6 +657,7 @@ async function regenerateConfig(site: Local.Site): Promise { serverEntry, agentConfig.mcpConfigTopLevelKey, ); + await removeLegacyContextFiles(projectPath, agent); await writeContextFile(path.join(projectPath, agentConfig.contextFilePath), contextContent, agent); } } diff --git a/tests/helpers/utils.test.ts b/tests/helpers/utils.test.ts index 29b8a4c..5ad384a 100644 --- a/tests/helpers/utils.test.ts +++ b/tests/helpers/utils.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, afterEach } from 'vitest'; -import { buildWpCliEnv } from '../../src/helpers/utils'; +import { buildWpCliEnv, hasMarkerBlock } from '../../src/helpers/utils'; import type { SiteConfig } from '../../src/helpers/site-config'; function makeSiteConfig(overrides: Partial = {}): SiteConfig { @@ -71,3 +71,25 @@ describe('buildWpCliEnv', () => { expect(env.DB_PASSWORD).toBe('root'); }); }); + +describe('hasMarkerBlock', () => { + const start = ''; + const end = ''; + + it('returns true when a complete marker block is present', () => { + const content = `# My notes\n\n${start}\nGenerated context\n${end}\n\nMore notes\n`; + expect(hasMarkerBlock(content, start, end)).toBe(true); + }); + + it('returns false for a file without our markers', () => { + expect(hasMarkerBlock('# My notes\n\nHand-written content\n', start, end)).toBe(false); + }); + + it('returns false when only the start marker is present', () => { + expect(hasMarkerBlock(`${start}\nTruncated block\n`, start, end)).toBe(false); + }); + + it('returns false for an empty file', () => { + expect(hasMarkerBlock('', start, end)).toBe(false); + }); +}); From 56c693d69512044ff900f69ff8f54e4775fda1dc Mon Sep 17 00:00:00 2001 From: Clayton Collie Date: Fri, 4 Sep 2026 08:39:20 +0200 Subject: [PATCH 3/3] (chore): Update README and changelog for the CLAUDE.local.md move --- CHANGELOG.md | 4 ++-- README.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38c7e62..6e04c09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,8 +13,8 @@ All notable changes to this project will be documented in this file, per [the Ke ### Changed -- Write Claude Code project context to `CLAUDE.local.md` instead of `CLAUDE.md`, so teams with a committed `CLAUDE.md` are no longer affected. `CLAUDE.local.md` is Claude Code's native local-override file for machine-specific, uncommitted instructions (props [@rickalee](https://github.com/rickalee) via [#77](https://github.com/10up/localwp-agent-tools/issues/77)). -- On first enable or regenerate, any Agent Tools marker block previously written to `CLAUDE.md` is automatically migrated out (the block is removed, leaving the rest of `CLAUDE.md` intact). +- Write Claude Code project context to `CLAUDE.local.md` instead of `CLAUDE.md`, so teams with a committed `CLAUDE.md` are no longer affected. `CLAUDE.local.md` is Claude Code's native local-override file for machine-specific, uncommitted instructions (props [@rickalee](https://github.com/rickalee) via [#78](https://github.com/10up/localwp-agent-tools/pull/78)). +- Any Agent Tools marker block that an older version wrote to `CLAUDE.md` is removed on the next enable, regenerate, disable, project directory change, or agent change. The rest of `CLAUDE.md` is left intact. Teams that committed the generated block will see it removed from `CLAUDE.md` (props [@rickalee](https://github.com/rickalee) via [#78](https://github.com/10up/localwp-agent-tools/pull/78)). ## [0.2.1] - 2026-03-19 diff --git a/README.md b/README.md index af47408..5559d02 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ When you click "Enable" on a site in Local, the add-on: 1. **Registers the site with the MCP server** — a single HTTP server running in Local's main process that gives AI tools access to WP-CLI, error logs, configuration, and site management 2. **Writes MCP config** (`.mcp.json`, `.cursor/mcp.json`, etc.) — auto-configured with the correct HTTP endpoint for each agent -3. **Generates project context** (`CLAUDE.md`, `.cursorrules`, etc.) — site context including PHP/MySQL versions, active plugins, theme, and file structure +3. **Generates project context** (`CLAUDE.local.md`, `.cursorrules`, etc.) — site context including PHP/MySQL versions, active plugins, theme, and file structure 4. **Updates `.gitignore`** — so generated files aren't committed Then open the site folder in your AI tool of choice and you're ready to go. @@ -31,7 +31,7 @@ Sites remain registered even when stopped, so the MCP endpoint is always reachab | Agent | MCP Config | Context File | | --------------- | -------------------- | --------------------------------- | -| Claude Code | `.mcp.json` | `CLAUDE.md` | +| Claude Code | `.mcp.json` | `CLAUDE.local.md` | | Cursor | `.cursor/mcp.json` | `.cursorrules` | | Windsurf | `.windsurf/mcp.json` | `.windsurfrules` | | VS Code Copilot | `.vscode/mcp.json` | `.github/copilot-instructions.md` |