Quickly update CLAUDE.md files based on staged git changes. Optimized for speed and simplicity.
Updates CLAUDE.md files based on staged git changes with automatic detection.
/plugin marketplace add Uniswap/ai-toolkit/plugin install development-productivity@uniswap-ai-toolkit/update-claude-md - Fast CLAUDE.md SynchronizationQuickly update CLAUDE.md files based on staged git changes. Optimized for speed and simplicity.
Auto-detect mode (recommended):
/update-claude-md
Analyzes staged changes and updates affected CLAUDE.md files.
Explicit mode:
/update-claude-md apps/slack-oauth-backend
/update-claude-md packages/agents/agnostic
Updates CLAUDE.md for a specific path.
// Verify we're in a git repository
const isGitRepo = await checkGitRepo();
if (!isGitRepo) {
return error('Not a git repository. Cannot detect changes.');
}
# Get all staged files with their status
git diff --cached --name-status
# Output format:
# M apps/slack-oauth-backend/app/api/route.ts
# A libs/data-access/src/new-file.ts
# D old-file.ts
This single command replaces multiple git operations and provides all needed information.
For each staged file:
function findNearestClaudeMd(filePath: string): string | null {
let currentDir = path.dirname(filePath);
const workspaceRoot = process.cwd();
// Walk up directories
while (currentDir !== workspaceRoot) {
const claudeMdPath = path.join(currentDir, 'CLAUDE.md');
if (fs.existsSync(claudeMdPath)) {
return claudeMdPath;
}
currentDir = path.dirname(currentDir);
}
// Check workspace root
const rootClaudeMd = path.join(workspaceRoot, 'CLAUDE.md');
return fs.existsSync(rootClaudeMd) ? rootClaudeMd : null;
}
Group files by their nearest CLAUDE.md:
const groups = new Map<string, string[]>();
for (const file of stagedFiles) {
const claudeMd = findNearestClaudeMd(file);
if (claudeMd) {
if (!groups.has(claudeMd)) groups.set(claudeMd, []);
groups.get(claudeMd).push(file);
}
}
For each CLAUDE.md with changed files:
async function analyzeChanges(claudeMdPath: string, files: string[]): Promise<UpdatePlan> {
// Read current CLAUDE.md
const currentContent = await readFile(claudeMdPath);
// Get diffs for all files in group
const diffs = await Promise.all(files.map((f) => execAsync(`git diff --cached -- "${f}"`)));
// Determine if update needed based on simple heuristics
const needsUpdate = determineIfUpdateNeeded(files, diffs, currentContent);
if (!needsUpdate) return null;
// Generate update suggestions
return generateUpdateSuggestions(files, diffs, currentContent);
}
function determineIfUpdateNeeded(files: string[], diffs: string[], claudeContent: string): boolean {
// Update if:
// 1. New files added (status 'A')
// 2. package.json modified
// 3. project.json modified
// 4. Significant code changes (>50 lines)
// 5. New exports added
const hasNewFiles = files.some((f) => f.startsWith('A\t'));
const hasPackageJson = files.some((f) => f.includes('package.json'));
const hasProjectJson = files.some((f) => f.includes('project.json'));
const hasSignificantChanges = diffs.some((d) => d.split('\n').length > 50);
return hasNewFiles || hasPackageJson || hasProjectJson || hasSignificantChanges;
}
console.log('Will update the following CLAUDE.md files:\n');
for (const [claudeMd, files] of updates) {
console.log(`š ${claudeMd}`);
console.log(` ${files.length} changed file(s)`);
}
const proceed = await askUser('\nProceed with updates? (y/n): ');
if (proceed !== 'y') {
console.log('Cancelled.');
return;
}
async function applyUpdates(updates: Map<string, UpdatePlan>): Promise<void> {
for (const [claudeMdPath, plan] of updates) {
// Read current content
const content = await readFile(claudeMdPath);
// Apply updates (append or smart insert)
const newContent = applyUpdatePlan(content, plan);
// Write back
await writeFile(claudeMdPath, newContent);
console.log(`ā
Updated ${claudeMdPath}`);
}
}
function applyUpdatePlan(content: string, plan: UpdatePlan): string {
// Simple strategies:
// - New files: Add to "Project Structure" or "Key Files" section
// - Dependencies: Add to "Dependencies" section
// - Commands: Add to "Key Commands" section
// - Otherwise: Add note to "Recent Changes" section
let updated = content;
for (const suggestion of plan.suggestions) {
const section = findSection(updated, suggestion.targetSection);
if (section) {
updated = insertIntoSection(updated, section, suggestion.text);
} else {
// Append to end if section not found
updated += `\n\n${suggestion.text}`;
}
}
return updated;
}
console.log(`\nā
Updated ${updates.size} CLAUDE.md file(s)`);
console.log('\nRun "git diff **/*CLAUDE.md" to review changes.');
CRITICAL: All updated CLAUDE.md files MUST remain concise and focused.
[TODO] placeholders instead of long explanationsWhen adding updates:
Priority for content retention (when trimming):
if (file.status === 'A') {
return {
targetSection: 'Project Structure',
text: `- \`${file.path}\` - [TODO: Add description]`,
};
}
if (file.path.endsWith('package.json')) {
const diff = await getDiff(file.path);
const addedDeps = parseAddedDependencies(diff);
return {
targetSection: 'Dependencies',
text: addedDeps.map((d) => `- **${d.name}** (${d.version})`).join('\n'),
};
}
if (file.path.endsWith('project.json')) {
const diff = await getDiff(file.path);
const addedTargets = parseAddedTargets(diff);
return {
targetSection: 'Key Commands',
text: addedTargets
.map(
(t) => `- \`nx ${t.name} ${projectName}\` - ${t.description || '[TODO: Add description]'}`
)
.join('\n'),
};
}
if (linesChanged > 50) {
return {
targetSection: 'Recent Changes',
text: `- Modified \`${file.path}\` (${linesChanged} lines changed)`,
};
}
git diff --cached --name-statusExpected Performance:
95% faster than previous implementation.
// Not a git repository
if (!isGitRepo) {
console.error('ā Not a git repository');
console.log('Use explicit mode: /update-claude-md <path>');
return;
}
// No staged changes
if (stagedFiles.length === 0) {
console.log('No staged changes detected.');
return;
}
// No CLAUDE.md files found
if (groups.size === 0) {
console.log('ā ļø No CLAUDE.md files found for changed files');
console.log('Recommendation: Run /claude-init-plus to create documentation');
return;
}
// Write failed
try {
await writeFile(claudeMdPath, newContent);
} catch (error) {
console.error(`ā Failed to write ${claudeMdPath}: ${error.message}`);
continue;
}
When path is specified:
async function updateExplicitPath(targetPath: string): Promise<void> {
// Find CLAUDE.md in target path
const claudeMdPath = path.join(targetPath, 'CLAUDE.md');
if (!fs.existsSync(claudeMdPath)) {
console.error(`ā No CLAUDE.md found at ${targetPath}`);
console.log('Run /claude-init-plus to create one');
return;
}
// Get all staged files under this path
const stagedFiles = await getStagedFiles();
const relevantFiles = stagedFiles.filter((f) => f.startsWith(targetPath));
if (relevantFiles.length === 0) {
console.log('No staged changes in this path.');
return;
}
// Analyze and update
const plan = await analyzeChanges(claudeMdPath, relevantFiles);
if (!plan) {
console.log('No updates needed.');
return;
}
console.log(`Will update: ${claudeMdPath}`);
const proceed = await askUser('Proceed? (y/n): ');
if (proceed === 'y') {
await applyUpdate(claudeMdPath, plan);
console.log('ā
Updated successfully');
}
}
git add before /update-claude-mdgit diff **/*CLAUDE.md to reviewgit restore CLAUDE.mdgit diff before committing| Aspect | Old | New | Improvement |
|---|---|---|---|
| Git commands | 10-15 | 1 | 90% fewer |
| Lines of code | ~630 | ~100 | 84% less code |
| External tools | jq, comm, markdown-lint | None | 100% reduction |
| User prompts | 3-5 | 1 | 80% fewer |
| Performance | 10-60s | 1-3s | 85-95% faster |
| Complexity | Very high | Low | 95% simpler |