11 KiB
Metadata Sync System
Overview
The metadata sync system ensures consistency between package.json (source of truth) and all documentation files across the project. It prevents version drift, outdated badges, and manual update errors.
Why We Need This
The Problem
In a typical project lifecycle:
- Developer bumps version in
package.jsonto3.5.0 - Creates a release commit
- Forgets to update version badge in
README.md(still shows3.4.0) - Forgets to update version header in
docs/REFERENCE.md - Forgets to update agent count in
.github/CLAUDE.mdafter adding new agents - Users see inconsistent version information across documentation
- CI builds look professional but contain stale metadata
Result: Confusion, reduced trust, unprofessional appearance.
The Solution
A single automated script that:
- Reads canonical metadata from
package.json - Updates all documentation files in one pass
- Can verify sync status (for CI/CD)
- Supports dry-run mode for safety
- Reports exactly what changed
How It Works
Source of Truth
package.json is the single source of truth for:
| Field | Used For |
|---|---|
version |
Version badges, headers, references |
name |
npm package links, download badges |
description |
Project taglines (future) |
keywords |
SEO metadata (future) |
repository.url |
GitHub links |
homepage |
Website links |
Target Files
The script syncs these files:
| File | What Gets Updated |
|---|---|
README.md |
npm version/download badges |
docs/REFERENCE.md |
Version badges, version headers |
.github/CLAUDE.md |
Agent count, skill count |
docs/ARCHITECTURE.md |
Version references |
CHANGELOG.md |
Latest version header (verify only) |
Dynamic Metadata
Some metadata is computed, not read:
- Agent count - Counts
.yaml/.ymlfiles inagents/directory - Skill count - Counts
.mdfiles inskills/directory
This ensures documentation always reflects current state.
Usage
Basic Sync
npm run sync-metadata
Syncs all files. Output:
📦 Metadata Sync System
========================
Version: 3.5.0
Package: oh-my-claudecode
Agents: 32
Skills: 45
✓ README.md
- npm version badge
✓ docs/REFERENCE.md
- Version badge
- Version header
✓ .github/CLAUDE.md
- Agent count
- Slash command count
✅ Successfully synced 3 file(s)!
Dry Run (Preview Changes)
npm run sync-metadata -- --dry-run
Shows what would change without writing files:
🔍 DRY RUN MODE - No files will be modified
📝 README.md
- npm version badge
📝 docs/REFERENCE.md
- Version badge
📊 2 file(s) would be updated
Run without --dry-run to apply changes
Verify Sync (CI/CD)
npm run sync-metadata -- --verify
Checks if files are in sync. Exits with status code:
0- All files in sync1- Files out of sync (shows which ones)
🔍 Verifying metadata sync...
✓ README.md
✗ docs/REFERENCE.md
- Version badge needs update
❌ Files are out of sync!
Run: npm run sync-metadata
Help
npm run sync-metadata -- --help
When to Run
Manual Workflow
Run sync before committing version changes:
# 1. Bump version
npm version patch
# 2. Sync metadata
npm run sync-metadata
# 3. Commit everything together
git add .
git commit -m "chore: release v3.5.0"
Automated Workflow (Recommended)
Add to package.json:
{
"scripts": {
"version": "npm run sync-metadata && git add ."
}
}
Now npm version patch automatically:
- Bumps version in
package.json - Runs sync script
- Stages synced files
- Creates version commit
Pre-Commit Hook
Add to .husky/pre-commit:
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
# Verify metadata is in sync
npm run sync-metadata -- --verify
if [ $? -ne 0 ]; then
echo "❌ Metadata out of sync! Run: npm run sync-metadata"
exit 1
fi
CI/CD Pipeline
Add verification step to GitHub Actions:
- name: Verify Metadata Sync
run: npm run sync-metadata -- --verify
How to Extend
Adding a New Target File
Edit scripts/sync-metadata.ts:
function getFileSyncConfigs(): FileSync[] {
return [
// ... existing configs ...
{
path: 'docs/NEW-FILE.md',
replacements: [
{
pattern: /version \d+\.\d+\.\d+/gi,
replacement: (m) => `version ${m.version}`,
description: 'Version references',
},
{
pattern: /\*\*\d+ features\*\*/g,
replacement: (m) => `**${getFeatureCount()} features**`,
description: 'Feature count',
},
],
},
];
}
Adding Dynamic Metadata
Add a new function:
function getFeatureCount(): number {
const featuresDir = join(projectRoot, 'features');
const files = readdirSync(featuresDir);
return files.filter(f => f.endsWith('.ts')).length;
}
Use in replacement:
{
pattern: /\*\*\d+ features\*\*/g,
replacement: () => `**${getFeatureCount()} features**`,
description: 'Feature count',
}
Adding New Metadata Sources
Extend the Metadata interface:
interface Metadata {
version: string;
description: string;
keywords: string[];
repository: string;
homepage: string;
npmPackage: string;
// NEW:
author: string;
license: string;
engines: { node: string };
}
Update loadMetadata():
function loadMetadata(): Metadata {
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
return {
// ... existing fields ...
author: packageJson.author || '',
license: packageJson.license || '',
engines: packageJson.engines || { node: '>=20.0.0' },
};
}
Implementation Details
Safe Replacement Strategy
The script uses regex-based replacement with safeguards:
- Read entire file into memory
- Apply all replacements to string
- Compare original vs modified content
- Only write if content changed
This prevents:
- Unnecessary file writes (preserves timestamps)
- Partial updates (atomic operation)
- Permission errors (fails before write)
Pattern Design
Patterns are designed to be:
Specific enough to match only intended content:
// GOOD - matches only npm badge
/\[!\[npm version\]\(https:\/\/img\.shields\.io\/npm\/v\/[^)]+\)/g
// BAD - too broad, matches any badge
/\[!\[[^\]]+\]\([^)]+\)/g
Flexible enough to handle variations:
// Matches: 3.4.0, 10.0.0, 2.1.3-beta
/\d+\.\d+\.\d+(-[a-z0-9]+)?/
Error Handling
The script handles:
- Missing files - Warns but continues
- Invalid package.json - Fails fast with clear error
- Permission errors - Reports and exits
- Regex failures - Reports pattern that failed
Performance
For a typical project:
- Files read: 5-10
- Execution time: <100ms
- Memory usage: <10MB
Scales linearly with number of target files.
Testing
Manual Testing
# 1. Make a change to package.json
npm version patch
# 2. Run dry-run to preview
npm run sync-metadata -- --dry-run
# 3. Apply changes
npm run sync-metadata
# 4. Verify with git
git diff
Automated Testing
The script exports functions for testing:
import { loadMetadata, syncFile, verifySync } from './scripts/sync-metadata.js';
test('loads metadata correctly', () => {
const metadata = loadMetadata();
expect(metadata.version).toMatch(/^\d+\.\d+\.\d+$/);
});
test('syncs README badges', () => {
const config = getFileSyncConfigs().find(c => c.path === 'README.md');
const result = syncFile(config, mockMetadata, true, projectRoot);
expect(result.changed).toBe(true);
});
Troubleshooting
"File not found" warnings
Symptom: Script reports files as not found.
Cause: File moved or deleted.
Fix: Remove from getFileSyncConfigs() or update path.
"No changes detected" but files are stale
Symptom: Script reports no changes, but files show old version.
Cause: Pattern doesn't match current file format.
Fix: Update regex pattern to match actual content.
Version updated but badge still old
Symptom: package.json has new version, badge unchanged.
Cause: Badge may be cached by shields.io CDN.
Fix: Wait 5 minutes or use ?cache=bust parameter.
Permission denied errors
Symptom: Script fails with EACCES.
Cause: Files are read-only or owned by different user.
Fix:
chmod +w docs/*.md
# or
sudo chown $USER docs/*.md
Best Practices
1. Always dry-run first
Before releasing:
npm run sync-metadata -- --dry-run
Review changes, then apply.
2. Sync before committing
Add to your workflow:
npm run sync-metadata && git add -A
3. Use verification in CI
Catch stale docs in pull requests:
- run: npm run sync-metadata -- --verify
4. Keep patterns maintainable
Document complex regex:
{
// Matches: []
// Captures: version number only
pattern: /\[!\[Version\]\(https:\/\/img\.shields\.io\/badge\/version-([^-]+)-[^)]+\)/g,
replacement: (m) => `[]`,
description: 'Version badge in REFERENCE.md',
}
5. Test after package.json changes
After any change to package.json:
npm run sync-metadata -- --verify
Migration Guide
If you're adding this to an existing project:
Step 1: Audit Current State
Find all hardcoded versions:
grep -r "3\.4\.0" docs/ README.md .github/
Step 2: Standardize Format
Choose consistent badge format:
[]
Update all instances manually.
Step 3: Run Initial Sync
npm run sync-metadata
Should report "All files are already in sync".
Step 4: Add to Workflow
Add npm script, pre-commit hook, CI verification.
Step 5: Document for Team
Update CONTRIBUTING.md:
## Releasing
1. Bump version: `npm version patch`
2. Sync metadata: `npm run sync-metadata`
3. Commit and tag
Future Enhancements
Potential improvements:
- Support for multi-language docs (i18n)
- Sync to website/landing page
- Extract feature count from source code
- Auto-update dependency versions in docs
- Integration with release workflow
- Markdown AST-based updates (safer than regex)
- Configuration file for custom patterns
- Plugin system for custom metadata sources