feat(web-bundles): release packager + manifest for bmadcode.com/web-bundles/ (#2424)
* feat(web-bundles): add release packager + bundle manifest
Adds the infrastructure for shipping web bundles as downloadable ZIPs
attached to a GitHub Release, consumed by the upcoming
bmadcode.com/web-bundles/ page.
- web-bundles/bundles.json — manifest with persona, tagline, description,
accent color, motif key, knowledge files, and feature flags
(web-browsing, deep-research, stitch integration) for each of the 6
bundles. Top-level releaseTag and downloadUrlPattern so the
consuming page can construct download URLs without hardcoding.
- tools/bundle-web-bundles.js — packager that zips each bundle dir into
dist/web-bundles/{slug}.zip and prints the gh release create command.
Zero dependencies; uses system zip.
- .gitignore — exclude dist/web-bundles/ build artifacts.
The web-bundles-v1.0.0 release on GitHub is currently in draft state
with the 6 zips attached; it'll be published in coordination with the
Ghost site page going live.
* fix(web-bundles): single-source release tag, sharper bundle copy
- Remove downloadUrlPattern from bundles.json — the consuming page
derives the URL from releaseTag, so version bumps now touch one
field instead of two.
- product-brief-coach: drop "one-page" (briefs are whatever length
the product earns).
- brainstorming-coach: real numbers — 60 techniques across 10
categories — with concrete examples (SCAMPER, Drunk History
Retelling, Nature's Solutions, Six Thinking Hats, etc.) so the
card actually communicates the surprising breadth.
* fix(web-bundles): harden release script per PR review
- Verify the zip CLI is on PATH up front with a clear install
hint, instead of crashing mid-zip with an opaque execSync error.
- Wrap JSON.parse in try/catch; validate the manifest shape (bundles
array non-empty, releaseTag present, slug present per entry) before
trying to package, so config errors fail with a targeted message.
- Catch zip failures per-bundle and surface the failing slug.
- Refuse to print the gh release command when zero bundles were
packaged (would otherwise mislead the user into creating an empty
release).
- Derive --title from manifest.releaseTag so the printed command can
never drift from the actual tag (was previously hardcoded
"Web Bundles v1" while the tag had moved to v1.0.0).
- Remove the stale `web-bundles-v1` example from the file header.
Addresses augmentcode bot review comments on PR #2424.
* docs(web-bundles): rewrite copy to actually sell what each bundle does
The JSON drives the bmadcode.com/web-bundles/ page; previous copy
was generic and undersold the actual capabilities. Rewrote each
tagline + description to lead with concrete, differentiating facts
pulled directly from each bundle's SKILL.md:
- Brainstorming Coach: 60 techniques across 10 categories with
specific names (SCAMPER, Drunk History Retelling, Nature's
Solutions, Shadow Work Mining, Superposition Collapse); calls
out the 4 routes (browse, recommend, random, progressive) and
the ~100-idea quantity-unlocks-quality target.
- Product Brief Coach: names the three intent modes (Create /
Update / Validate) and the two working paths (Fast / Coaching);
surfaces the [ASSUMPTION] tag system and the Addendum.
- PRFAQ Coach: details the 4 stages (Ignition / Press Release /
Customer FAQ / Internal FAQ + Verdict), the 9 press release
sections, the weasel-word list ("best-in-class", "seamless"),
and that it adapts for commercial, internal, OSS, community.
- PRD Coach: spells out the two entry points (Vision+Features
vs Journey-led), named-protagonist journeys, glossary
discipline, stable ID system (FR-1..N, SM-C1..N), and the
7-dimension validation rubric.
- UX Coach: leads with the two-spine contract (DESIGN.md +
EXPERIENCE.md), Don Norman framing, named-protagonist
journeys, surface closure as the test, and Stitch integration.
- Market & Industry Research: leads with Deep Research as the
engine, names Porter and Christensen as anchors, lists the 6
deliverable sections, and frames the deliverable as synthesis
not a research dump.
* fix(web-bundles): security hardening + strict bundle validation
Two issues raised by coderabbit on the latest commit:
1. Shell injection surface: execSync was building the zip command
with a template literal that interpolated bundle.slug from JSON.
Even with our controlled inputs, a slug with shell metacharacters
would break quoting. Switched to execFileSync with an argument
array (no shell) and added a strict ^[a-z0-9][a-z0-9-]*$ slug
regex enforced before any FS or zip call.
2. Missing bundle directories were [SKIP]-warned but the script
still printed the release command, allowing an incomplete release
to ship cleanly. Now treated as fatal: any missing or invalid slug
blocks the printed gh command and exits non-zero with the offending
slugs listed.
This commit is contained in:
parent
cede485217
commit
2b76d03316
|
|
@ -81,3 +81,6 @@ _bmad/custom/*.user.toml
|
|||
website/.astro/
|
||||
website/dist/
|
||||
build/
|
||||
|
||||
# Web bundle release artifacts
|
||||
dist/web-bundles/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,117 @@
|
|||
/**
|
||||
* Web Bundle Release Packager
|
||||
*
|
||||
* Zips each bundle under web-bundles/ into dist/web-bundles/{slug}.zip
|
||||
* for attachment to a GitHub Release.
|
||||
*
|
||||
* Usage:
|
||||
* node tools/bundle-web-bundles.js
|
||||
*
|
||||
* After running, the script prints the exact `gh release create` command
|
||||
* (with the correct tag from bundles.json) for you to copy.
|
||||
*/
|
||||
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const { execSync, execFileSync } = require('node:child_process');
|
||||
|
||||
const REPO_ROOT = path.resolve(__dirname, '..');
|
||||
const BUNDLES_DIR = path.join(REPO_ROOT, 'web-bundles');
|
||||
const DIST_DIR = path.join(REPO_ROOT, 'dist', 'web-bundles');
|
||||
const MANIFEST = path.join(BUNDLES_DIR, 'bundles.json');
|
||||
const SLUG_RE = /^[a-z0-9][a-z0-9-]*$/;
|
||||
|
||||
function fail(msg) {
|
||||
console.error(`[ERROR] ${msg}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function requireZipCli() {
|
||||
try {
|
||||
execSync('zip -v', { stdio: 'ignore' });
|
||||
} catch {
|
||||
fail("'zip' CLI not found on PATH. Install zip (macOS: preinstalled; Debian/Ubuntu: apt install zip; Alpine: apk add zip) and re-run.");
|
||||
}
|
||||
}
|
||||
|
||||
function loadManifest() {
|
||||
if (!fs.existsSync(MANIFEST)) {
|
||||
fail(`bundles.json not found at ${MANIFEST}`);
|
||||
}
|
||||
let manifest;
|
||||
try {
|
||||
manifest = JSON.parse(fs.readFileSync(MANIFEST, 'utf-8'));
|
||||
} catch (error) {
|
||||
fail(`bundles.json is not valid JSON: ${error.message}`);
|
||||
}
|
||||
if (!Array.isArray(manifest.bundles) || manifest.bundles.length === 0) {
|
||||
fail('bundles.json is missing a non-empty "bundles" array.');
|
||||
}
|
||||
if (typeof manifest.releaseTag !== 'string' || !manifest.releaseTag) {
|
||||
fail('bundles.json is missing "releaseTag".');
|
||||
}
|
||||
return manifest;
|
||||
}
|
||||
|
||||
function main() {
|
||||
requireZipCli();
|
||||
const manifest = loadManifest();
|
||||
const releaseTag = manifest.releaseTag;
|
||||
|
||||
fs.mkdirSync(DIST_DIR, { recursive: true });
|
||||
|
||||
console.log(`Packaging ${manifest.bundles.length} bundles for release ${releaseTag}\n`);
|
||||
|
||||
const zipped = [];
|
||||
const missing = [];
|
||||
const invalid = [];
|
||||
for (const bundle of manifest.bundles) {
|
||||
if (!bundle.slug || !SLUG_RE.test(bundle.slug)) {
|
||||
invalid.push(bundle.slug || '(no slug)');
|
||||
console.error(` [INVALID] slug must match ${SLUG_RE} — got: ${bundle.slug}`);
|
||||
continue;
|
||||
}
|
||||
const src = path.join(BUNDLES_DIR, bundle.slug);
|
||||
if (!fs.existsSync(src)) {
|
||||
missing.push(bundle.slug);
|
||||
console.error(` [MISSING] ${bundle.slug} — directory not found`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const out = path.join(DIST_DIR, `${bundle.slug}.zip`);
|
||||
if (fs.existsSync(out)) fs.unlinkSync(out);
|
||||
|
||||
try {
|
||||
execFileSync('zip', ['-r', '-X', '-q', out, bundle.slug, '-x', '*.DS_Store'], {
|
||||
cwd: BUNDLES_DIR,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
} catch (error) {
|
||||
fail(`zip failed for ${bundle.slug}: ${error.message}`);
|
||||
}
|
||||
|
||||
const size = (fs.statSync(out).size / 1024).toFixed(1);
|
||||
console.log(` [OK] ${bundle.slug}.zip (${size} KB)`);
|
||||
zipped.push(bundle.slug);
|
||||
}
|
||||
|
||||
if (invalid.length > 0) {
|
||||
fail(`Refusing to publish: ${invalid.length} bundle(s) have invalid slugs: ${invalid.join(', ')}`);
|
||||
}
|
||||
if (missing.length > 0) {
|
||||
fail(`Refusing to publish an incomplete release: missing directories for ${missing.join(', ')}`);
|
||||
}
|
||||
if (zipped.length === 0) {
|
||||
fail('No bundles were packaged. Check bundles.json against web-bundles/ subdirectories.');
|
||||
}
|
||||
|
||||
console.log(`\nWrote ${zipped.length} bundles to ${path.relative(REPO_ROOT, DIST_DIR)}/`);
|
||||
console.log('\nNext step — create or update the GitHub Release:\n');
|
||||
console.log(` gh release create ${releaseTag} dist/web-bundles/*.zip \\`);
|
||||
console.log(` --title "${releaseTag}" \\`);
|
||||
console.log(` --notes "BMad web bundles for Gemini Gems and ChatGPT Custom GPTs. See https://bmadcode.com/web-bundles/"\n`);
|
||||
console.log('Or, to refresh an existing release:\n');
|
||||
console.log(` gh release upload ${releaseTag} dist/web-bundles/*.zip --clobber\n`);
|
||||
}
|
||||
|
||||
main();
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
{
|
||||
"schemaVersion": "1.0",
|
||||
"releaseTag": "web-bundles-v1.0.0",
|
||||
"releasedAt": "2026-05-25",
|
||||
"bundles": [
|
||||
{
|
||||
"slug": "brainstorming-coach",
|
||||
"name": "Brainstorming Coach",
|
||||
"tagline": "60 ideation techniques across 10 categories — from SCAMPER to Zombie Apocalypse Planning",
|
||||
"description": "Sixty proven brainstorming techniques spanning structured frameworks (SCAMPER, Five Whys, First Principles), wild plays (Drunk History Retelling, Zombie Apocalypse Planning), biomimetic prompts (Nature's Solutions), introspective work (Shadow Work Mining, Values Archaeology), and quantum-flavored framings (Superposition Collapse). Pick a route — browse the library, get a recommendation, take a random surprise, or run a progressive divergence-to-action flow. The coach asks; you generate every idea. Targets ~100 ideas before organizing (the breakthroughs live past idea 20), then promotes the session into themes, a 2×2 prioritization, and a breakthrough collage in Canvas.",
|
||||
"defaultPersona": {
|
||||
"name": "Carson",
|
||||
"title": "Elite Brainstorming Specialist",
|
||||
"lineage": "Osborn lineage"
|
||||
},
|
||||
"swapPersona": {
|
||||
"name": "Mary",
|
||||
"title": "Strategic Business Analyst",
|
||||
"lineage": "BMad analyst — research-first rigor"
|
||||
},
|
||||
"accentColor": "#3b82f6",
|
||||
"motif": "constellation",
|
||||
"knowledgeFiles": ["SKILL.md", "brain-methods.csv"],
|
||||
"needsWebBrowsing": true,
|
||||
"needsDeepResearch": false,
|
||||
"stitchIntegration": false
|
||||
},
|
||||
{
|
||||
"slug": "product-brief-coach",
|
||||
"name": "Product Brief Coach",
|
||||
"tagline": "Three modes (Create / Update / Validate), two paths (Fast / Coaching), one brief you're proud of",
|
||||
"description": "Create a brief drawn out through real conversation. Update an existing brief against a change signal. Or Validate — honest critique against the brief's own purpose. Two working modes: Fast path batches the gaps into one or two consolidated questions and tags assumptions for you to fix in review (best for pitching tomorrow); Coaching path walks section by section, mirrors before pushing, names the assumption hiding under your confident sentence (best for the brief you want to be proud of). The coach refuses to invent moats, traction, or differentiation you didn't give it. Depth that doesn't fit the brief lands in an Addendum so nothing valuable gets lost. Length is whatever the product earns.",
|
||||
"defaultPersona": {
|
||||
"name": "Mary",
|
||||
"title": "Strategic Business Analyst",
|
||||
"lineage": "BMad analyst"
|
||||
},
|
||||
"swapPersona": {
|
||||
"name": "Iris",
|
||||
"title": "Senior Product Strategist",
|
||||
"lineage": "Mirror-then-push, unhurried thinking-partner voice"
|
||||
},
|
||||
"accentColor": "#d4a853",
|
||||
"motif": "single-page",
|
||||
"knowledgeFiles": ["SKILL.md"],
|
||||
"needsWebBrowsing": true,
|
||||
"needsDeepResearch": false,
|
||||
"stitchIntegration": false
|
||||
},
|
||||
{
|
||||
"slug": "prfaq-coach",
|
||||
"name": "PRFAQ Coach",
|
||||
"tagline": "Amazon's Working Backwards as a forcing function — write the press release before you build",
|
||||
"description": "Four stages of customer-first pressure: Ignition (specific customer, concrete problem, real stakes), Press Release (9 sections, each forcing a different clarity — headline, problem paragraph, solution paragraph, leader quote, customer quote, getting started), Customer FAQ (validate the value proposition from outside in), and Internal FAQ + Verdict (feasibility, trade-offs, what survived). Hardcore mode: weasel words like 'best-in-class', 'seamless', and 'revolutionary' get challenged on sight; if you lead with technology, the coach redirects to the customer's actual problem. Generates a hero image for the press release. Works for commercial products, internal tools, open source, and community initiatives — the structure stays, the language adapts.",
|
||||
"defaultPersona": {
|
||||
"name": "Mary",
|
||||
"title": "Strategic Business Analyst",
|
||||
"lineage": "BMad analyst"
|
||||
},
|
||||
"swapPersona": {
|
||||
"name": "Bezos",
|
||||
"title": "Working Backwards Coach",
|
||||
"lineage": "Amazon discipline — direct, dry, customer-first"
|
||||
},
|
||||
"accentColor": "#dc2626",
|
||||
"motif": "document-ribbon",
|
||||
"knowledgeFiles": ["SKILL.md"],
|
||||
"needsWebBrowsing": true,
|
||||
"needsDeepResearch": false,
|
||||
"stitchIntegration": false
|
||||
},
|
||||
{
|
||||
"slug": "prd-coach",
|
||||
"name": "PRD Coach",
|
||||
"tagline": "PRD coaching with built-in validation — capabilities in the PRD, mechanism in the Addendum",
|
||||
"description": "Three modes (Create / Update / Validate), two entry points (Vision + Features for enterprise and dev products, Journey-led for consumer and UX-heavy products). Captures named-protagonist user journeys ('Mary, mom of three, kids finally asleep') instead of abstract personas. Enforces glossary discipline: every domain noun defined once, used verbatim across FRs, UJs, and SMs. Maintains stable IDs (FR-1..N globally, success metrics paired with counter-metrics SM-C1..N). The 7-dimension validation rubric grades decision-readiness, substance over theater, strategic coherence, done-ness clarity, scope honesty, downstream usability, and shape fit, with each finding cited to a specific PRD location. Length scales with stakes — hobby PRDs hit two pages; launch PRDs run as long as the FRs require.",
|
||||
"defaultPersona": {
|
||||
"name": "John",
|
||||
"title": "Product Manager",
|
||||
"lineage": "BMad PM — Cagan lineage"
|
||||
},
|
||||
"swapPersona": {
|
||||
"name": "Ezra",
|
||||
"title": "Principal Product Manager",
|
||||
"lineage": "Calmer, slower-tempo coaching"
|
||||
},
|
||||
"accentColor": "#6366f1",
|
||||
"motif": "section-stack",
|
||||
"knowledgeFiles": ["SKILL.md", "prd-template.md", "prd-validation-checklist.md"],
|
||||
"needsWebBrowsing": true,
|
||||
"needsDeepResearch": false,
|
||||
"stitchIntegration": false
|
||||
},
|
||||
{
|
||||
"slug": "ux-coach",
|
||||
"name": "UX Coach",
|
||||
"tagline": "Two spines engineering can build from — DESIGN.md + EXPERIENCE.md as the contract",
|
||||
"description": "Don Norman's human-centered design as the operating method. The coach elicits your vision and never imposes its own — no colors, fonts, or layouts you didn't put on the table. Captures named-protagonist journeys ('Mary on her couch after the kids are asleep') as the unit of design thinking. Produces two spines: DESIGN.md for visual identity (tokens in YAML frontmatter), EXPERIENCE.md for information architecture, behavior, states, and accessibility. Spines win on conflict with any mock, wireframe, or Stitch output. Surface closure is the test: every stated need has a surface, every surface has a journey. Pairs with Google Stitch — the handoff produces a Stitch prompt you copy straight from Canvas to generate editable mockups.",
|
||||
"defaultPersona": {
|
||||
"name": "Sally",
|
||||
"title": "UX Designer",
|
||||
"lineage": "BMad UX designer"
|
||||
},
|
||||
"swapPersona": {
|
||||
"name": "Kenji",
|
||||
"title": "Principal Product Designer",
|
||||
"lineage": "Rams restraint + Zhuo systems discipline"
|
||||
},
|
||||
"accentColor": "#10b981",
|
||||
"motif": "nested-layers",
|
||||
"knowledgeFiles": ["SKILL.md", "ux-validation.md"],
|
||||
"needsWebBrowsing": true,
|
||||
"needsDeepResearch": false,
|
||||
"stitchIntegration": true
|
||||
},
|
||||
{
|
||||
"slug": "market-and-industry-research",
|
||||
"name": "Market & Industry Research",
|
||||
"tagline": "Deep Research wrapped in a sharp brief — research as input to a decision, not a deliverable",
|
||||
"description": "Built around platform Deep Research mode. The coach handles the conversation: scopes what you actually need, drafts a sharp Deep Research brief you copy directly into Gemini or ChatGPT, then ingests the report and shapes the deliverable around your decision or learning goal. Methodology anchors when they help: Michael Porter for competitive structure, Clayton Christensen for Jobs-to-be-Done. Six sections to mix and match — market dynamics, customer insights, competitive landscape, regulatory & compliance, technical & technology trends, strategic synthesis. Validates every numeric, regulatory, or competitive claim against a source and a date. Generic findings ('the market is growing') get pushed back to specifics ('which segment, what rate, citing whom'). The deliverable is the synthesis against your decision, not the research dump.",
|
||||
"defaultPersona": {
|
||||
"name": "Mary",
|
||||
"title": "Strategic Business Analyst",
|
||||
"lineage": "BMad analyst"
|
||||
},
|
||||
"swapPersona": {
|
||||
"name": "Geoff",
|
||||
"title": "Market Strategist",
|
||||
"lineage": "Geoffrey Moore + April Dunford lineage"
|
||||
},
|
||||
"accentColor": "#f59e0b",
|
||||
"motif": "positioning-rings",
|
||||
"knowledgeFiles": ["SKILL.md"],
|
||||
"needsWebBrowsing": true,
|
||||
"needsDeepResearch": true,
|
||||
"stitchIntegration": false
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
Reference in New Issue