Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"name": "bmad",
"source": "./plugins/bmad",
"description": "BMAD Method - Breakthrough Method for Agile AI-Driven Development",
"version": "6.2.0.0"
"version": "6.2.0.1"
}
]
}
1 change: 1 addition & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
set -e
bun run lint:staged
bun run typecheck
1 change: 1 addition & 0 deletions .husky/pre-push
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
set -e
bun run typecheck
bun run lint
bun run validate
2 changes: 1 addition & 1 deletion .plugin-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v6.2.0.0
v6.2.0.1
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<!-- upstream-badges-end -->

<!-- upstream-version-start -->
**Plugin version:** v6.2.0.0
**Plugin version:** v6.2.0.1

| Module | Version | Released | Last Synced |
|---|---|---|---|
Expand Down
247 changes: 247 additions & 0 deletions docs/script-pipeline.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
# Script Pipeline

How the sync, generation, validation, and release scripts work together.

## Data Flow

```text
.upstream/<repo>/ (pinned to version tag)
[sync-upstream-content.ts] ── copies files + rewrites paths
├──▶ plugins/bmad/skills/<name>/{instructions, steps, data}
├──▶ plugins/bmad/_shared/{tasks/, tea-index.csv}
└──▶ .upstream-versions/<id>.json (updated)
[generate-agents.ts] ── converts .agent.yaml → markdown
└──▶ plugins/bmad/agents/<slug>.md
[generate-skills.ts] ── generates SKILL.md from workflow metadata
└──▶ plugins/bmad/skills/<name>/SKILL.md
[generate-agent-manifest.ts] ── builds agent registry
└──▶ plugins/bmad/_shared/agent-manifest.csv
[validate-upstream-coverage.ts] ── checks everything matches
└──▶ exit 0 (pass) or exit 1 (fail)
```

## Upstream Sources

Five repositories contribute content, each pinned to a version tag in
`.upstream-versions/<id>.json`:

```csv
ID,Repository,Layout,Description
core,bmadcode/BMAD-METHOD,categorized,Main framework — agents + skills + tasks
tea,bmad-code-org/bmad-method-test-architecture-enterprise,flat,Testing and architecture skills
bmb,bmad-code-org/bmad-builder,flat,Builder skills
cis,bmad-code-org/bmad-module-creative-intelligence-suite,flat,Creative intelligence skills
gds,bmad-code-org/bmad-module-game-dev-studio,categorized,Game development skills
```

**Layout** determines directory structure:

- **Categorized** (core, gds): `contentRoot/<category>/<workflow>/`
- **Flat** (tea, bmb, cis): `contentRoot/<workflow>/`

The `workflow-iterator.ts` library handles both transparently.

## Scripts

### sync-upstream-content.ts

Copies supporting files from upstream into the plugin, applying path rewrites.

- **Reads:** upstream repos (checked out to pinned version), `.upstream-versions/`
- **Writes:** `plugins/bmad/skills/`, `plugins/bmad/_shared/`, `.upstream-versions/`
- **Skips:** `workflow.md`, `workflow.yaml` (plugin generates its own SKILL.md)
- **Path rewrites:** transforms `{project-root}/_bmad/...` references to
`${CLAUDE_PLUGIN_ROOT}/skills/...` via `path-rewriter.ts`
- **CLI:** `bun run sync [--source <id>] [--dry-run]`

Without `--source`, runs all sources and bumps plugin version to `<core>.0`.

### generate-agents.ts

Converts upstream agent definitions to plugin agent markdown files.

- **Reads:** `.agent.yaml` files (or `SKILL.md` for newer format) from upstream
- **Writes:** `plugins/bmad/agents/<slug>.md`
- **Process:** parses YAML metadata (title, role, identity, principles, menu) →
generates markdown with activation section. SKILL.md agents are copied directly.
- **CLI:** `bun run generate:agents [--source <id>] [--dry-run]`

### generate-skills.ts

Generates `SKILL.md` files from workflow metadata.

- **Reads:** `workflow.yaml`/`workflow.md` frontmatter from upstream skill dirs
- **Writes:** `plugins/bmad/skills/<name>/SKILL.md`
- **Skips:** generation when upstream already provides `SKILL.md`
- **Process:** extracts name/description from workflow metadata, detects
sub-workflows, looks up corresponding agent, generates SKILL.md
- **CLI:** `bun run generate:skills [--source <id>] [--dry-run]`

### generate-agent-manifest.ts

Builds the agent registry CSV used by party-mode and advanced-elicitation.

- **Reads:** all `plugins/bmad/agents/*.md`
- **Writes:** `plugins/bmad/_shared/agent-manifest.csv`
- **CLI:** `bun run generate:manifest [--dry-run]`

No `--source` filter — the manifest is always a complete registry.

### validate-upstream-coverage.ts

Checks that all upstream content is accounted for in the plugin.

Seven validation checks:

```csv
Check,What it validates
checkSync,Upstream repos at tracked versions
checkAgents,Upstream agents ↔ plugin agent files
checkWorkflows,Upstream workflows ↔ plugin skill dirs ↔ plugin.json
checkContent,Supporting files synced correctly
checkVersion,.upstream-versions ↔ upstream package.json
checkAgentSkills,Agent menu workflows ↔ plugin skill dirs
checkPaths,Path rewrites complete and valid
```

- **Output:** pass (exit 0) or fail with gaps (exit 1), plus workaround warnings
- **CLI:** `bun run validate [--verbose]`

### sync-all.ts

Orchestrator that runs the full pipeline sequentially:

```text
sync → generate:agents → generate:skills
```

Passes through `--source <id>` and `--dry-run`. Exits on first failure.

- **CLI:** `bun run sync-all [--source <id>] [--dry-run]`

### clean-orphaned-skills.ts

Detects and removes skill directories that no longer match any upstream source.

- **Reads:** all upstream sources, plugin-only lists, workaround mappings
- **Removes:** orphaned dirs in `plugins/bmad/skills/`
- **CLI:** `bun run clean:orphaned [--dry-run]`

### bump-core.ts

Bumps plugin version to track a new core upstream release.

- Fetches latest tag from `.upstream/BMAD-METHOD`
- Updates `.upstream-versions/core.json`
- Derives plugin version: `<core>.0` (resets patch)
- Updates all 4 version files + README badge
- **CLI:** `bun run bump-core [--tag v6.0.2] [--dry-run] [--yes]`

### bump-module.ts

Bumps plugin version for a module update (non-core).

- Fetches latest tag from the module's upstream repo
- Updates `.upstream-versions/<id>.json`
- Increments plugin patch: `<core>.X` → `<core>.<X+1>`
- Updates all 4 version files + README badge
- **CLI:** `bun run bump-module -- --source <id> [--tag v1.7.1] [--dry-run] [--yes]`

### update-readme-version.ts

Updates the README version table and badge JSON files.

- **Reads:** `.upstream-versions/`, `.plugin-version`, upstream git tags
- **Writes:** `README.md` (version table + badges), `.github/badges/*.json`
- **CLI:** `bun run update-readme`

### release.sh

Two-phase release workflow. See `./docs/releasing.md` for full details.

- **Phase 1 (prepare):** beads sync → release branch → PR to main → wait for CI
- **Phase 2 (finish):** merge PR → tag main → GitHub release → return to dev
- **CLI:** `./scripts/release.sh [version]` or `./scripts/release.sh --after-ci`

## Shared Libraries

```csv
Library,Key exports,Used by
upstream-sources.ts,"UPSTREAM_SOURCES, getSource(), readVersion(), workflowWorkarounds","sync, generate-*, validate, clean, bump-*"
config.ts,"ROOT, PLUGIN, PLUGIN_JSON_PATH",all scripts
path-rewriter.ts,"rewriteFileContent(), buildRewriteMap()",sync
workflow-iterator.ts,"getWorkflowEntries()","generate-skills, validate, clean"
fs-utils.ts,"listFilesRecursive(), normalize()","sync, validate"
git-utils.ts,gitInUpstream(),"sync, bump-*"
output.ts,"pass(), fail(), warn(), section()",validate
bump-utils.ts,"VERSION_FILES, fetchLatestTag(), updateJsonVersionFiles()","bump-core, bump-module"
```

## Git Hooks

```csv
Hook,Commands,Scope
pre-commit,"lint:staged (biome --staged), typecheck",Staged files only
pre-push,"typecheck, lint (biome full repo), validate",Full project
```

**Gap:** The pre-push hook runs all three commands sequentially without `set -e`.
If lint fails, validate still runs and the hook exits with validate's exit code
(which may be 0). This means lint failures don't block pushes.

## Version Files

Four files must stay in sync:

```csv
File,Format,Prefix
.plugin-version,plain text,v (e.g. v6.2.0.0)
package.json,JSON version field,no prefix (6.2.0.0)
plugins/bmad/.claude-plugin/plugin.json,JSON version field,no prefix
.claude-plugin/marketplace.json,JSON version field,no prefix
```

**Versioning scheme:** `<core-upstream-version>.X` where X is a patch counter.
Core bump resets X to 0. Module bumps increment X.

## Common Command Sequences

Full sync after core bump:

```sh
bun run bump-core
bun run sync-all # sync → generate:agents → generate:skills
bun run generate:manifest
bun run validate
```

Module-only update:

```sh
bun run bump-module -- --source tea
bun run sync-all -- --source tea
bun run validate
```

Release:

```sh
./scripts/release.sh # current version
./scripts/release.sh 6.2.1.0 # bump + release
./scripts/release.sh --after-ci # resume after CI fix
```
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "bmad-plugin",
"version": "6.2.0.0",
"version": "6.2.0.1",
"type": "module",
"scripts": {
"prepare": "husky",
Expand Down
2 changes: 1 addition & 1 deletion plugins/bmad/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "bmad",
"version": "6.2.0.0",
"version": "6.2.0.1",
"description": "BMAD Method - Breakthrough Method for Agile AI-Driven Development",
"author": {
"name": "PabloLION",
Expand Down
2 changes: 1 addition & 1 deletion scripts/clean-orphaned-skills.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const SKILLS_DIR = join(PLUGIN, 'skills');

/**
* Build a set of all valid skill names by iterating upstream sources.
* Includes plugin-only skills and workaround-mapped names.
* Includes plugin-only skills and agent ref mapped names.
*/
async function getValidSkillNames(): Promise<Set<string>> {
const valid = new Set<string>();
Expand Down
6 changes: 4 additions & 2 deletions scripts/generate-agents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ function extractSkillName(
if (!match) return undefined;

const workflowName = match[1]!;
const workarounds = source.workflowWorkarounds ?? {};
const workarounds = source.agentRefMappings ?? {};
return workarounds[workflowName] ?? workflowName;
}

Expand Down Expand Up @@ -266,7 +266,9 @@ async function processSource(source: UpstreamSource): Promise<number> {
if (format === 'skill') {
// SKILL.md-based agent — copy directly
if (DRY_RUN) {
console.log(` [dry-run] would copy: agents/${slug}.md (from SKILL.md)`);
console.log(
` [dry-run] would copy: agents/${slug}.md (from SKILL.md)`,
);
} else {
const content = await Bun.file(path).text();
await Bun.write(outPath, content);
Expand Down
4 changes: 1 addition & 3 deletions scripts/generate-skills.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,9 +262,7 @@ async function processSource(source: UpstreamSource): Promise<number> {
// Skip generation if upstream provides SKILL.md (sync already copied it)
const upstreamSkillMd = join(entry.upstreamDir, 'SKILL.md');
if (await exists(upstreamSkillMd)) {
console.log(
` skip: ${entry.skillName} (upstream provides SKILL.md)`,
);
console.log(` skip: ${entry.skillName} (upstream provides SKILL.md)`);
continue;
}

Expand Down
15 changes: 5 additions & 10 deletions scripts/lib/checks/agent-skills.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,19 +54,14 @@ function checkAgentWorkflows(
agentName: string,
workflows: string[],
skillDirs: Set<string>,
workarounds: Record<string, string>,
mappings: Record<string, string>,
planned: Set<string>,
): void {
for (const wf of workflows) {
const skillName = workarounds[wf] ?? wf;
const skillName = mappings[wf] ?? wf;
if (skillDirs.has(skillName)) {
if (workarounds[wf]) {
warn(
`[${sourceId}] ${agentName} → ${skillName} (workaround — upstream "${wf}" ≠ plugin "${skillName}")`,
);
} else {
pass(`[${sourceId}] ${agentName} → ${skillName}`);
}
// Mapped refs are expected — pass silently like direct matches
pass(`[${sourceId}] ${agentName} → ${skillName}`);
} else if (planned.has(wf)) {
warn(
`[${sourceId}] ${agentName} references planned workflow "${wf}" (not yet implemented upstream)`,
Expand Down Expand Up @@ -96,7 +91,7 @@ export async function checkAgentSkills(): Promise<void> {
if (!(await exists(agentsDir))) continue;

const entries = await readdir(agentsDir, { withFileTypes: true });
const workarounds = source.workflowWorkarounds ?? {};
const workarounds = source.agentRefMappings ?? {};
const planned = source.plannedWorkflows ?? new Set<string>();

for (const entry of entries) {
Expand Down
Loading
Loading