From ea1c20efabdbbf6897840d5d5aaca6a53bdcd559 Mon Sep 17 00:00:00 2001 From: Avi Fenesh Date: Sun, 22 Feb 2026 02:14:01 +0200 Subject: [PATCH 1/4] feat: sync CLAUDE.md to all standalone plugin repos Add a template and generator script that produces a consistent CLAUDE.md for each plugin repo during the existing sync workflow. The template includes plugin-specific components (agents, skills, commands) plus universal critical rules, model selection, and core priorities. - templates/CLAUDE.md.tmpl: Mustache-style template with conditionals - scripts/generate-claudemd.js: Zero-dependency renderer - scripts/generate-claudemd.test.js: 6 tests covering all shapes - .github/workflows/sync.yml: Add CLAUDE.md generation step, web-ctl --- .github/workflows/sync.yml | 20 ++-- scripts/generate-claudemd.js | 102 ++++++++++++++++++++ scripts/generate-claudemd.test.js | 151 ++++++++++++++++++++++++++++++ templates/CLAUDE.md.tmpl | 60 ++++++++++++ 4 files changed, 327 insertions(+), 6 deletions(-) create mode 100644 scripts/generate-claudemd.js create mode 100644 scripts/generate-claudemd.test.js create mode 100644 templates/CLAUDE.md.tmpl diff --git a/.github/workflows/sync.yml b/.github/workflows/sync.yml index f75701b..d162fa9 100644 --- a/.github/workflows/sync.yml +++ b/.github/workflows/sync.yml @@ -3,14 +3,14 @@ name: Sync to consumers on: push: branches: [main] - paths: ['lib/**'] + paths: ['lib/**', 'templates/**', 'scripts/generate-claudemd.js'] jobs: sync: runs-on: ubuntu-latest strategy: matrix: - repo: [agentsys, next-task, ship, enhance, deslop, learn, consult, debate, drift-detect, repo-map, sync-docs, audit-project, perf] + repo: [agentsys, next-task, ship, enhance, deslop, learn, consult, debate, drift-detect, repo-map, sync-docs, audit-project, perf, web-ctl] steps: - name: Checkout agent-core uses: actions/checkout@v4 @@ -24,11 +24,19 @@ jobs: token: ${{ secrets.SYNC_TOKEN }} path: target + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + - name: Sync lib run: | rm -rf target/lib/ cp -r source/lib/ target/lib/ + - name: Generate CLAUDE.md + run: node source/scripts/generate-claudemd.js --target target --template source/templates/CLAUDE.md.tmpl + - name: Check for changes id: diff working-directory: target @@ -45,11 +53,11 @@ jobs: git config user.name "agent-core-bot" git config user.email "noreply@agent-sh.github.io" git checkout -b "$BRANCH" - git add lib/ - git commit -m "chore: sync core lib from agent-core" + git add lib/ CLAUDE.md + git commit -m "chore: sync core lib and CLAUDE.md from agent-core" git push origin "$BRANCH" gh pr create \ --repo agent-sh/${{ matrix.repo }} \ - --title "chore: sync core lib from agent-core" \ - --body "Automated sync of lib/ from [agent-core](https://github.com/agent-sh/agent-core)." \ + --title "chore: sync core lib and CLAUDE.md from agent-core" \ + --body "Automated sync of lib/ and CLAUDE.md from [agent-core](https://github.com/agent-sh/agent-core)." \ --head "$BRANCH" diff --git a/scripts/generate-claudemd.js b/scripts/generate-claudemd.js new file mode 100644 index 0000000..d6db18a --- /dev/null +++ b/scripts/generate-claudemd.js @@ -0,0 +1,102 @@ +#!/usr/bin/env node +'use strict'; + +const fs = require('node:fs'); +const path = require('node:path'); + +function parseArgs(argv) { + const args = {}; + for (let i = 2; i < argv.length; i += 2) { + const key = argv[i].replace(/^--/, ''); + args[key] = argv[i + 1]; + } + return args; +} + +function renderTemplate(template, vars) { + let result = template; + + // Process conditional sections: {{#key}}...{{/key}} + result = result.replace(/\{\{#(\w+)\}\}\n([\s\S]*?)\{\{\/\1\}\}\n/g, (_, key, block) => { + const section = vars[key]; + if (!section || !section.items || section.items.length === 0) { + return ''; + } + // Replace {{items}} inside the block with bullet list + const itemList = section.items.map(item => `- ${item}`).join('\n'); + return block.replace('{{items}}', itemList); + }); + + // Replace simple variables + result = result.replace(/\{\{(\w+)\}\}/g, (_, key) => { + return vars[key] !== undefined ? vars[key] : ''; + }); + + return result; +} + +function main() { + const args = parseArgs(process.argv); + + if (!args.target) { + console.error('[ERROR] --target is required'); + process.exit(1); + } + if (!args.template) { + console.error('[ERROR] --template is required'); + process.exit(1); + } + + const targetDir = path.resolve(args.target); + const templatePath = path.resolve(args.template); + + // Read template + let template; + try { + template = fs.readFileSync(templatePath, 'utf8'); + } catch (err) { + console.error(`[ERROR] Cannot read template: ${err.message}`); + process.exit(1); + } + + // Read package.json + let pkg; + try { + pkg = JSON.parse(fs.readFileSync(path.join(targetDir, 'package.json'), 'utf8')); + } catch (err) { + console.error(`[ERROR] Cannot read package.json: ${err.message}`); + process.exit(1); + } + + // Extract plugin name (strip @agentsys/ prefix) + const pluginName = (pkg.name || '').replace(/^@agentsys\//, ''); + const description = pkg.description || ''; + + // Read components.json (optional) + let components = { agents: [], skills: [], commands: [] }; + const componentsPath = path.join(targetDir, 'components.json'); + if (fs.existsSync(componentsPath)) { + try { + components = JSON.parse(fs.readFileSync(componentsPath, 'utf8')); + } catch (err) { + console.error(`[WARN] Cannot parse components.json: ${err.message}`); + } + } + + const vars = { + pluginName, + description, + agents: { items: components.agents || [] }, + skills: { items: components.skills || [] }, + commands: { items: components.commands || [] }, + }; + + const output = renderTemplate(template, vars); + + // Write CLAUDE.md + const outputPath = path.join(targetDir, 'CLAUDE.md'); + fs.writeFileSync(outputPath, output, 'utf8'); + console.log(`[OK] Generated ${outputPath}`); +} + +main(); diff --git a/scripts/generate-claudemd.test.js b/scripts/generate-claudemd.test.js new file mode 100644 index 0000000..aec6c84 --- /dev/null +++ b/scripts/generate-claudemd.test.js @@ -0,0 +1,151 @@ +#!/usr/bin/env node +'use strict'; + +const { describe, it, beforeEach, afterEach } = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); +const os = require('node:os'); +const { execFileSync } = require('node:child_process'); + +const SCRIPT = path.join(__dirname, 'generate-claudemd.js'); +const TEMPLATE = path.join(__dirname, '..', 'templates', 'CLAUDE.md.tmpl'); + +function makeTmpDir() { + return fs.mkdtempSync(path.join(os.tmpdir(), 'claudemd-test-')); +} + +function run(targetDir) { + return execFileSync('node', [SCRIPT, '--target', targetDir, '--template', TEMPLATE], { + encoding: 'utf8', + stdio: ['pipe', 'pipe', 'pipe'], + }); +} + +function writeJson(dir, filename, data) { + fs.writeFileSync(path.join(dir, filename), JSON.stringify(data, null, 2)); +} + +describe('generate-claudemd', () => { + let tmpDir; + + beforeEach(() => { + tmpDir = makeTmpDir(); + }); + + afterEach(() => { + fs.rmSync(tmpDir, { recursive: true, force: true }); + }); + + it('generates full CLAUDE.md with all components (next-task shape)', () => { + writeJson(tmpDir, 'package.json', { + name: '@agentsys/next-task', + description: 'Master workflow orchestrator', + }); + writeJson(tmpDir, 'components.json', { + agents: ['ci-fixer', 'ci-monitor', 'delivery-validator', 'exploration-agent', + 'implementation-agent', 'planning-agent', 'simple-fixer', + 'task-discoverer', 'test-coverage-checker', 'worktree-manager'], + skills: ['discover-tasks', 'orchestrate-review', 'validate-delivery'], + commands: ['delivery-approval', 'next-task'], + }); + + run(tmpDir); + const output = fs.readFileSync(path.join(tmpDir, 'CLAUDE.md'), 'utf8'); + + assert.match(output, /^# next-task/m); + assert.match(output, /> Master workflow orchestrator/); + assert.match(output, /## Agents/); + assert.match(output, /- ci-fixer/); + assert.match(output, /- worktree-manager/); + assert.match(output, /## Skills/); + assert.match(output, /- discover-tasks/); + assert.match(output, /## Commands/); + assert.match(output, /- next-task/); + assert.match(output, /## Critical Rules/); + assert.match(output, /## Model Selection/); + assert.match(output, /## Core Priorities/); + }); + + it('generates CLAUDE.md with only commands (ship shape)', () => { + writeJson(tmpDir, 'package.json', { + name: '@agentsys/ship', + description: 'Complete PR workflow', + }); + writeJson(tmpDir, 'components.json', { + agents: [], + skills: [], + commands: ['ship-ci-review-loop', 'ship-deployment', 'ship-error-handling', 'ship'], + }); + + run(tmpDir); + const output = fs.readFileSync(path.join(tmpDir, 'CLAUDE.md'), 'utf8'); + + assert.match(output, /^# ship/m); + assert.ok(!output.includes('## Agents')); + assert.ok(!output.includes('## Skills')); + assert.match(output, /## Commands/); + assert.match(output, /- ship$/m); + }); + + it('generates CLAUDE.md with all empty components', () => { + writeJson(tmpDir, 'package.json', { + name: '@agentsys/empty-plugin', + description: 'An empty plugin', + }); + writeJson(tmpDir, 'components.json', { + agents: [], + skills: [], + commands: [], + }); + + run(tmpDir); + const output = fs.readFileSync(path.join(tmpDir, 'CLAUDE.md'), 'utf8'); + + assert.match(output, /^# empty-plugin/m); + assert.ok(!output.includes('## Agents')); + assert.ok(!output.includes('## Skills')); + assert.ok(!output.includes('## Commands')); + assert.match(output, /## Critical Rules/); + }); + + it('handles missing components.json gracefully', () => { + writeJson(tmpDir, 'package.json', { + name: '@agentsys/no-components', + description: 'No components file', + }); + + run(tmpDir); + const output = fs.readFileSync(path.join(tmpDir, 'CLAUDE.md'), 'utf8'); + + assert.match(output, /^# no-components/m); + assert.ok(!output.includes('## Agents')); + assert.ok(!output.includes('## Skills')); + assert.ok(!output.includes('## Commands')); + }); + + it('strips @agentsys/ prefix from plugin name', () => { + writeJson(tmpDir, 'package.json', { + name: '@agentsys/my-plugin', + description: 'Test', + }); + + run(tmpDir); + const output = fs.readFileSync(path.join(tmpDir, 'CLAUDE.md'), 'utf8'); + + assert.match(output, /^# my-plugin/m); + assert.ok(!output.includes('@agentsys/')); + }); + + it('handles name without prefix', () => { + writeJson(tmpDir, 'package.json', { + name: 'plain-name', + description: 'No prefix', + }); + + run(tmpDir); + const output = fs.readFileSync(path.join(tmpDir, 'CLAUDE.md'), 'utf8'); + + assert.match(output, /^# plain-name/m); + }); +}); diff --git a/templates/CLAUDE.md.tmpl b/templates/CLAUDE.md.tmpl new file mode 100644 index 0000000..8a7574c --- /dev/null +++ b/templates/CLAUDE.md.tmpl @@ -0,0 +1,60 @@ +# {{pluginName}} + +> {{description}} + +{{#agents}} +## Agents + +{{items}} + +{{/agents}} +{{#skills}} +## Skills + +{{items}} + +{{/skills}} +{{#commands}} +## Commands + +{{items}} + +{{/commands}} +## Critical Rules + +1. **Plain text output** - No emojis, no ASCII art. Use `[OK]`, `[ERROR]`, `[WARN]`, `[CRITICAL]` for status markers. +2. **No unnecessary files** - Don't create summary files, plan files, audit files, or temp docs. +3. **Task is not done until tests pass** - Every feature/fix must have quality tests. +4. **Create PRs for non-trivial changes** - No direct pushes to main. +5. **Always run git hooks** - Never bypass pre-commit or pre-push hooks. +6. **Use single dash for em-dashes** - In prose, use ` - ` (single dash with spaces), never ` -- `. +7. **Report script failures before manual fallback** - Never silently bypass broken tooling. +8. **Token efficiency** - Be concise. Save tokens over decorations. + +## Model Selection + +| Model | When to Use | +|-------|-------------| +| **Opus** | Complex reasoning, analysis, planning | +| **Sonnet** | Validation, pattern matching, most agents | +| **Haiku** | Mechanical execution, no judgment needed | + +## Core Priorities + +1. User DX (plugin users first) +2. Worry-free automation +3. Token efficiency +4. Quality output +5. Simplicity + +## Dev Commands + +```bash +npm test # Run tests +npm run validate # All validators +``` + +## References + +- Part of the [agentsys](https://github.com/agent-sh/agentsys) ecosystem +- https://agentskills.io From d78ddde30f6a8a37a124ea31f8126ea3e86892e1 Mon Sep 17 00:00:00 2001 From: Avi Fenesh Date: Sun, 22 Feb 2026 02:17:36 +0200 Subject: [PATCH 2/4] fix: address review findings - arg validation, error paths, workflow hardening - Validate CLI args (reject missing values, non-flag tokens) - Add try/catch around writeFileSync - Fix TOCTOU in components.json reading - Make conditional block trailing newline optional in regex - Use String() for items and global replace for {{items}} - Workflow: use git status --porcelain for untracked file detection - Workflow: add fail-fast: false, permissions block, --base main - Workflow: use env var for matrix.repo, include repo name in branch - Template: add auto-generated comment - Tests: add error-path tests (missing target, missing package.json, malformed components.json, stdout verification) --- .github/workflows/sync.yml | 21 +++++++++------ scripts/generate-claudemd.js | 30 ++++++++++++++------- scripts/generate-claudemd.test.js | 45 ++++++++++++++++++++++++++++++- templates/CLAUDE.md.tmpl | 1 + 4 files changed, 79 insertions(+), 18 deletions(-) diff --git a/.github/workflows/sync.yml b/.github/workflows/sync.yml index d162fa9..d1a2bab 100644 --- a/.github/workflows/sync.yml +++ b/.github/workflows/sync.yml @@ -5,10 +5,14 @@ on: branches: [main] paths: ['lib/**', 'templates/**', 'scripts/generate-claudemd.js'] +permissions: + contents: read + jobs: sync: runs-on: ubuntu-latest strategy: + fail-fast: false matrix: repo: [agentsys, next-task, ship, enhance, deslop, learn, consult, debate, drift-detect, repo-map, sync-docs, audit-project, perf, web-ctl] steps: @@ -24,11 +28,6 @@ jobs: token: ${{ secrets.SYNC_TOKEN }} path: target - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - - name: Sync lib run: | rm -rf target/lib/ @@ -41,15 +40,20 @@ jobs: id: diff working-directory: target run: | - git diff --quiet && echo "changed=false" >> $GITHUB_OUTPUT || echo "changed=true" >> $GITHUB_OUTPUT + if [ -n "$(git status --porcelain)" ]; then + echo "changed=true" >> $GITHUB_OUTPUT + else + echo "changed=false" >> $GITHUB_OUTPUT + fi - name: Create PR if: steps.diff.outputs.changed == 'true' working-directory: target env: GH_TOKEN: ${{ secrets.SYNC_TOKEN }} + TARGET_REPO: agent-sh/${{ matrix.repo }} run: | - BRANCH="chore/sync-core-$(date +%Y%m%d-%H%M%S)" + BRANCH="chore/sync-core-${{ matrix.repo }}-$(date +%Y%m%d-%H%M%S)" git config user.name "agent-core-bot" git config user.email "noreply@agent-sh.github.io" git checkout -b "$BRANCH" @@ -57,7 +61,8 @@ jobs: git commit -m "chore: sync core lib and CLAUDE.md from agent-core" git push origin "$BRANCH" gh pr create \ - --repo agent-sh/${{ matrix.repo }} \ + --repo "$TARGET_REPO" \ + --base main \ --title "chore: sync core lib and CLAUDE.md from agent-core" \ --body "Automated sync of lib/ and CLAUDE.md from [agent-core](https://github.com/agent-sh/agent-core)." \ --head "$BRANCH" diff --git a/scripts/generate-claudemd.js b/scripts/generate-claudemd.js index d6db18a..db8be26 100644 --- a/scripts/generate-claudemd.js +++ b/scripts/generate-claudemd.js @@ -7,7 +7,15 @@ const path = require('node:path'); function parseArgs(argv) { const args = {}; for (let i = 2; i < argv.length; i += 2) { + if (!argv[i].startsWith('--')) { + console.error(`[ERROR] Expected flag starting with --, got: ${argv[i]}`); + process.exit(1); + } const key = argv[i].replace(/^--/, ''); + if (!argv[i + 1] || argv[i + 1].startsWith('--')) { + console.error(`[ERROR] Missing value for flag: ${argv[i]}`); + process.exit(1); + } args[key] = argv[i + 1]; } return args; @@ -17,14 +25,13 @@ function renderTemplate(template, vars) { let result = template; // Process conditional sections: {{#key}}...{{/key}} - result = result.replace(/\{\{#(\w+)\}\}\n([\s\S]*?)\{\{\/\1\}\}\n/g, (_, key, block) => { + result = result.replace(/\{\{#(\w+)\}\}\n([\s\S]*?)\{\{\/\1\}\}\n?/g, (_, key, block) => { const section = vars[key]; if (!section || !section.items || section.items.length === 0) { return ''; } - // Replace {{items}} inside the block with bullet list - const itemList = section.items.map(item => `- ${item}`).join('\n'); - return block.replace('{{items}}', itemList); + const itemList = section.items.map(item => `- ${String(item)}`).join('\n'); + return block.replace(/\{\{items\}\}/g, itemList); }); // Replace simple variables @@ -75,10 +82,10 @@ function main() { // Read components.json (optional) let components = { agents: [], skills: [], commands: [] }; const componentsPath = path.join(targetDir, 'components.json'); - if (fs.existsSync(componentsPath)) { - try { - components = JSON.parse(fs.readFileSync(componentsPath, 'utf8')); - } catch (err) { + try { + components = JSON.parse(fs.readFileSync(componentsPath, 'utf8')); + } catch (err) { + if (err.code !== 'ENOENT') { console.error(`[WARN] Cannot parse components.json: ${err.message}`); } } @@ -95,7 +102,12 @@ function main() { // Write CLAUDE.md const outputPath = path.join(targetDir, 'CLAUDE.md'); - fs.writeFileSync(outputPath, output, 'utf8'); + try { + fs.writeFileSync(outputPath, output, 'utf8'); + } catch (err) { + console.error(`[ERROR] Cannot write CLAUDE.md: ${err.message}`); + process.exit(1); + } console.log(`[OK] Generated ${outputPath}`); } diff --git a/scripts/generate-claudemd.test.js b/scripts/generate-claudemd.test.js index aec6c84..aa953d0 100644 --- a/scripts/generate-claudemd.test.js +++ b/scripts/generate-claudemd.test.js @@ -6,7 +6,7 @@ const assert = require('node:assert/strict'); const fs = require('node:fs'); const path = require('node:path'); const os = require('node:os'); -const { execFileSync } = require('node:child_process'); +const { execFileSync, spawnSync } = require('node:child_process'); const SCRIPT = path.join(__dirname, 'generate-claudemd.js'); const TEMPLATE = path.join(__dirname, '..', 'templates', 'CLAUDE.md.tmpl'); @@ -22,6 +22,13 @@ function run(targetDir) { }); } +function runRaw(...args) { + return spawnSync('node', [SCRIPT, ...args], { + encoding: 'utf8', + stdio: ['pipe', 'pipe', 'pipe'], + }); +} + function writeJson(dir, filename, data) { fs.writeFileSync(path.join(dir, filename), JSON.stringify(data, null, 2)); } @@ -148,4 +155,40 @@ describe('generate-claudemd', () => { assert.match(output, /^# plain-name/m); }); + + it('exits with error when --target is missing', () => { + const result = runRaw('--template', TEMPLATE); + assert.strictEqual(result.status, 1); + assert.match(result.stderr, /\[ERROR\].*--target/); + }); + + it('exits with error when package.json is missing', () => { + const result = runRaw('--target', tmpDir, '--template', TEMPLATE); + assert.strictEqual(result.status, 1); + assert.match(result.stderr, /\[ERROR\].*package\.json/); + }); + + it('warns on malformed components.json and still generates output', () => { + writeJson(tmpDir, 'package.json', { + name: '@agentsys/bad-components', + description: 'Bad components', + }); + fs.writeFileSync(path.join(tmpDir, 'components.json'), '{bad json'); + + run(tmpDir); + const output = fs.readFileSync(path.join(tmpDir, 'CLAUDE.md'), 'utf8'); + + assert.match(output, /^# bad-components/m); + assert.doesNotMatch(output, /## Agents/); + }); + + it('verifies stdout contains OK message', () => { + writeJson(tmpDir, 'package.json', { + name: 'stdout-test', + description: 'Test', + }); + + const stdout = run(tmpDir); + assert.match(stdout, /\[OK\] Generated/); + }); }); diff --git a/templates/CLAUDE.md.tmpl b/templates/CLAUDE.md.tmpl index 8a7574c..03574fd 100644 --- a/templates/CLAUDE.md.tmpl +++ b/templates/CLAUDE.md.tmpl @@ -1,3 +1,4 @@ + # {{pluginName}} > {{description}} From 38af8cbeeb900f878ecb3caa4f4c14ddc74acf8f Mon Sep 17 00:00:00 2001 From: Avi Fenesh Date: Sun, 22 Feb 2026 02:18:49 +0200 Subject: [PATCH 3/4] fix: address review iteration 2 - template injection, arg bounds, shell safety - Escape {{ and }} in component items to prevent template injection - Add explicit bounds check in parseArgs for odd arg count - Use env vars instead of inline matrix interpolation in shell --- .github/workflows/sync.yml | 3 ++- scripts/generate-claudemd.js | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/sync.yml b/.github/workflows/sync.yml index d1a2bab..45e4aa5 100644 --- a/.github/workflows/sync.yml +++ b/.github/workflows/sync.yml @@ -52,8 +52,9 @@ jobs: env: GH_TOKEN: ${{ secrets.SYNC_TOKEN }} TARGET_REPO: agent-sh/${{ matrix.repo }} + REPO_NAME: ${{ matrix.repo }} run: | - BRANCH="chore/sync-core-${{ matrix.repo }}-$(date +%Y%m%d-%H%M%S)" + BRANCH="chore/sync-core-${REPO_NAME}-$(date +%Y%m%d-%H%M%S)" git config user.name "agent-core-bot" git config user.email "noreply@agent-sh.github.io" git checkout -b "$BRANCH" diff --git a/scripts/generate-claudemd.js b/scripts/generate-claudemd.js index db8be26..321c4ac 100644 --- a/scripts/generate-claudemd.js +++ b/scripts/generate-claudemd.js @@ -12,7 +12,7 @@ function parseArgs(argv) { process.exit(1); } const key = argv[i].replace(/^--/, ''); - if (!argv[i + 1] || argv[i + 1].startsWith('--')) { + if (i + 1 >= argv.length || argv[i + 1].startsWith('--')) { console.error(`[ERROR] Missing value for flag: ${argv[i]}`); process.exit(1); } @@ -30,7 +30,9 @@ function renderTemplate(template, vars) { if (!section || !section.items || section.items.length === 0) { return ''; } - const itemList = section.items.map(item => `- ${String(item)}`).join('\n'); + const itemList = section.items + .map(item => `- ${String(item).replace(/\{\{/g, '{ {').replace(/\}\}/g, '} }')}`) + .join('\n'); return block.replace(/\{\{items\}\}/g, itemList); }); From 1b7a5a2b176ad478f90e3eeb00214e20779012f9 Mon Sep 17 00:00:00 2001 From: Avi Fenesh Date: Sun, 22 Feb 2026 02:21:02 +0200 Subject: [PATCH 4/4] docs: update README with CLAUDE.md generation docs and full consumer list --- README.md | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 90f1aaa..df49c0b 100644 --- a/README.md +++ b/README.md @@ -4,19 +4,45 @@ Shared core libraries for all agent-sh plugins. Changes here are automatically s ## Consumers -| Repo | How it receives lib/ | -|------|---------------------| +| Repo | How it receives lib/ and CLAUDE.md | +|------|------------------------------------| | agentsys | PR → merge → `sync-lib` propagates to 13 bundled plugins | -| agnix | PR → merge (plugin uses lib/ directly) | +| next-task | PR → merge (plugin uses lib/ directly) | +| ship | PR → merge (plugin uses lib/ directly) | +| enhance | PR → merge (plugin uses lib/ directly) | +| deslop | PR → merge (plugin uses lib/ directly) | +| learn | PR → merge (plugin uses lib/ directly) | +| consult | PR → merge (plugin uses lib/ directly) | +| debate | PR → merge (plugin uses lib/ directly) | +| drift-detect | PR → merge (plugin uses lib/ directly) | +| repo-map | PR → merge (plugin uses lib/ directly) | +| sync-docs | PR → merge (plugin uses lib/ directly) | +| audit-project | PR → merge (plugin uses lib/ directly) | +| perf | PR → merge (plugin uses lib/ directly) | | web-ctl | PR → merge (plugin uses lib/ directly) | ## How sync works -On merge to `main`, the `sync-core` workflow opens PRs in all consumer repos with the updated `lib/` directory. Consumer repos review and merge at their own pace. +On merge to `main`, the `sync` workflow opens PRs in all consumer repos with the updated `lib/` directory and a freshly generated `CLAUDE.md` (rendered from `templates/CLAUDE.md.tmpl`). Consumer repos review and merge at their own pace. + +## CLAUDE.md generation + +Each consumer repo receives a generated `CLAUDE.md` rendered from `templates/CLAUDE.md.tmpl`. The generator reads `package.json` and optionally `components.json` from the target repo. + +Available template variables: +- `{{pluginName}}` - package name with `@agentsys/` prefix stripped +- `{{description}}` - package.json description +- `{{#agents}}` / `{{#skills}}` / `{{#commands}}` - conditional sections from components.json + +To test generation locally: + +```bash +node scripts/generate-claudemd.js --target ../some-plugin --template templates/CLAUDE.md.tmpl +``` ## Developing -Edit files in `lib/`. On merge, changes propagate automatically. To test locally before merging: +Edit files in `lib/` for library changes. Edit `templates/CLAUDE.md.tmpl` to change the CLAUDE.md generated for all consumer plugins. On merge, changes propagate automatically. To test locally before merging: ```bash # Copy to a consumer repo for testing