Back to catalogue
DocumentationFileChanged· README.md
Docs consistency reminder
No more READMEs telling two different stories
When a README changes, injects a reminder listing the sibling READMEs (repo root + packages/*) that carry the same product promise — CLI examples, slugs and wording must stay consistent across them. Pure context injection, never blocks.
What does the Docs consistency reminder hook do?
Docs consistency reminder is a Claude Code FileChanged hook matching README.md. It fires automatically at that lifecycle event — outside the model, so it can't be skipped or forgotten. No more READMEs telling two different stories.
Use cases
- Propagate a CLI flag rename from the npm README to the GitHub README
- Keep the website pitch and both READMEs telling the same story
Tags
#documentation#readme#consistency#marketing
settings.json fragment
{
"hooks": {
"FileChanged": [
{
"matcher": "README.md",
"hooks": [
{
"type": "command",
"command": "node $CLAUDE_PROJECT_DIR/.claude/hooks/file-changed-docs-consistency.mjs"
}
]
}
]
}
}Script · .claude/hooks/file-changed-docs-consistency.mjs
#!/usr/bin/env node
// Rappelle de propager les changements d'un README vers les surfaces sœurs
// (FileChanged README.md). Quand un README change, liste les autres README du
// repo (racine + packages/*) qui portent la même promesse produit et doivent
// rester cohérents (exemples CLI, slugs, wording).
import { readFileSync, existsSync, readdirSync } from 'fs';
import { join } from 'path';
import { fileURLToPath } from 'url';
// Trouve les README "surfaces produit" : racine + packages/*/README.md.
export function findSiblingReadmes({
exists = existsSync,
readdir = readdirSync,
projectDir,
} = {}) {
const surfaces = [];
if (exists(join(projectDir, 'README.md'))) surfaces.push('README.md');
const pkgsDir = join(projectDir, 'packages');
if (exists(pkgsDir)) {
try {
for (const pkg of readdir(pkgsDir)) {
if (exists(join(pkgsDir, pkg, 'README.md'))) surfaces.push(`packages/${pkg}/README.md`);
}
} catch {}
}
return surfaces;
}
export function run(input, {
exists = existsSync,
readdir = readdirSync,
projectDir = process.env.CLAUDE_PROJECT_DIR ?? process.cwd(),
} = {}) {
const filePath = input.file_path ?? '';
if (!filePath.endsWith('README.md') || input.event === 'unlink') return null;
const changed = filePath.startsWith(`${projectDir}/`)
? filePath.slice(projectDir.length + 1)
: filePath;
const siblings = findSiblingReadmes({ exists, readdir, projectDir }).filter((s) => s !== changed);
if (!siblings.length) return null;
return {
hookSpecificOutput: {
hookEventName: 'FileChanged',
additionalContext:
`${changed} changed. These sibling docs share the same product promise and must stay consistent ` +
`(CLI examples, slugs, wording): ${siblings.join(', ')}. ` +
'Check whether the change needs to be mirrored there (and on the website copy if user-facing).',
},
};
}
/* v8 ignore next 5 */
if (process.argv[1] === fileURLToPath(import.meta.url)) {
const input = JSON.parse(readFileSync(0, 'utf8'));
const result = run(input);
if (result) process.stdout.write(JSON.stringify(result));
}