Skip to content
Draft
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
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,13 @@
"lint:fix": "eslint . --ext .js,.cjs,.mjs,.yaml --fix",
"lint:md": "markdownlint-cli2 \"**/*.md\"",
"prepare": "command -v husky >/dev/null 2>&1 && husky || exit 0",
"quality": "npm run format:check && npm run lint && npm run lint:md && npm run docs:build && npm run test:install && npm run test:urls && npm run validate:refs && npm run validate:skills",
"quality": "npm run format:check && npm run lint && npm run lint:md && npm run docs:build && npm run test:install && npm run test:urls && npm run test:renderer && npm run validate:refs && npm run validate:skills",
"rebundle": "node tools/installer/bundlers/bundle-web.js rebundle",
"test": "npm run test:refs && npm run test:install && npm run test:urls && npm run test:channels && npm run lint && npm run lint:md && npm run format:check",
"test": "npm run test:refs && npm run test:install && npm run test:urls && npm run test:channels && npm run test:renderer && npm run lint && npm run lint:md && npm run format:check",
"test:channels": "node test/test-installer-channels.js",
"test:install": "node test/test-installation-components.js",
"test:refs": "node test/test-file-refs-csv.js",
"test:renderer": "node test/test-quick-dev-renderer.js",
"test:urls": "node test/test-parse-source-urls.js",
"validate:refs": "node tools/validate-file-refs.js --strict",
"validate:skills": "node tools/validate-skills.js --strict"
Expand Down
109 changes: 4 additions & 105 deletions src/bmm-skills/4-implementation/bmad-quick-dev/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,109 +3,8 @@ name: bmad-quick-dev
description: 'Implements any user intent, requirement, story, bug fix or change request by producing clean working code artifacts that follow the project''s existing architecture, patterns and conventions. Use when the user wants to build, fix, tweak, refactor, add or modify any code, component or feature.'
---

# Quick Dev New Preview Workflow
```
python render.py
```

**Goal:** Turn user intent into a hardened, reviewable artifact.

**CRITICAL:** If a step says "read fully and follow step-XX", you read and follow step-XX. No exceptions.

## READY FOR DEVELOPMENT STANDARD

A specification is "Ready for Development" when:

- **Actionable**: Every task has a file path and specific action.
- **Logical**: Tasks ordered by dependency.
- **Testable**: All ACs use Given/When/Then.
- **Complete**: No placeholders or TBDs.

## SCOPE STANDARD

A specification should target a **single user-facing goal** within **900–1600 tokens**:

- **Single goal**: One cohesive feature, even if it spans multiple layers/files. Multi-goal means >=2 **top-level independent shippable deliverables** — each could be reviewed, tested, and merged as a separate PR without breaking the others. Never count surface verbs, "and" conjunctions, or noun phrases. Never split cross-layer implementation details inside one user goal.
- Split: "add dark mode toggle AND refactor auth to JWT AND build admin dashboard"
- Don't split: "add validation and display errors" / "support drag-and-drop AND paste AND retry"
- **900–1600 tokens**: Optimal range for LLM consumption. Below 900 risks ambiguity; above 1600 risks context-rot in implementation agents.
- **Neither limit is a gate.** Both are proposals with user override.

## Conventions

- Bare paths (e.g. `step-01-clarify-and-route.md`) resolve from the skill root.
- `{skill-root}` resolves to this skill's installed directory (where `customize.toml` lives).
- `{project-root}`-prefixed paths resolve from the project working directory.
- `{skill-name}` resolves to the skill directory's basename.

## On Activation

### Step 1: Resolve the Workflow Block

Run: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`

**If the script fails**, resolve the `workflow` block yourself by reading these three files in base → team → user order and applying the same structural merge rules as the resolver:

1. `{skill-root}/customize.toml` — defaults
2. `{project-root}/_bmad/custom/{skill-name}.toml` — team overrides
3. `{project-root}/_bmad/custom/{skill-name}.user.toml` — personal overrides

Any missing file is skipped. Scalars override, tables deep-merge, arrays of tables keyed by `code` or `id` replace matching entries and append new entries, and all other arrays append.

### Step 2: Execute Prepend Steps

Execute each entry in `{workflow.activation_steps_prepend}` in order before proceeding.

### Step 3: Load Persistent Facts

Treat every entry in `{workflow.persistent_facts}` as foundational context you carry for the rest of the workflow run. Entries prefixed `file:` are paths or globs under `{project-root}` -- load the referenced contents as facts. All other entries are facts verbatim.

### Step 4: Load Config

Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve:

- `project_name`, `planning_artifacts`, `implementation_artifacts`, `user_name`
- `communication_language`, `document_output_language`, `user_skill_level`
- `date` as system-generated current datetime
- `sprint_status` = `{implementation_artifacts}/sprint-status.yaml`
- `project_context` = `**/project-context.md` (load if exists)
- CLAUDE.md / memory files (load if exist)
- YOU MUST ALWAYS SPEAK OUTPUT in your Agent communication style with the config `{communication_language}`
- Language MUST be tailored to `{user_skill_level}`
- Generate all documents in `{document_output_language}`

### Step 5: Greet the User

Greet `{user_name}`, speaking in `{communication_language}`.

### Step 6: Execute Append Steps

Execute each entry in `{workflow.activation_steps_append}` in order.

Activation is complete. Begin the workflow below.

## WORKFLOW ARCHITECTURE

This uses **step-file architecture** for disciplined execution:

- **Micro-file Design**: Each step is self-contained and followed exactly
- **Just-In-Time Loading**: Only load the current step file
- **Sequential Enforcement**: Complete steps in order, no skipping
- **State Tracking**: Persist progress via spec frontmatter and in-memory variables
- **Append-Only Building**: Build artifacts incrementally

### Step Processing Rules

1. **READ COMPLETELY**: Read the entire step file before acting
2. **FOLLOW SEQUENCE**: Execute sections in order
3. **WAIT FOR INPUT**: Halt at checkpoints and wait for human
4. **LOAD NEXT**: When directed, read fully and follow the next step file

### Critical Rules (NO EXCEPTIONS)

- **NEVER** load multiple step files simultaneously
- **ALWAYS** read entire step file before execution
- **NEVER** skip steps or optimize the sequence
- **ALWAYS** follow the exact instructions in the step file
- **ALWAYS** halt at checkpoints and wait for human input

## FIRST STEP

Read fully and follow: `./step-01-clarify-and-route.md` to begin the workflow.
Then follow the instruction it prints to stdout.
156 changes: 156 additions & 0 deletions src/bmm-skills/4-implementation/bmad-quick-dev/render.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
#!/usr/bin/env python3
"""render.py — bmad-quick-dev template renderer.

Resolves compile-time {{.variable}} placeholders from BMad's central config,
bakes absolute paths for {project-root} into derived values, and writes
rendered .md files to {project-root}/_bmad/render/bmad-quick-dev/.

Config: four-layer merge of _bmad/config.toml + config.user.toml +
custom/config.toml + custom/config.user.toml (post-#2285 installs).
Keys surface from [core] and [modules.bmm]. Missing config.toml → HALT.

Runtime {variable} placeholders (single curly) pass through untouched for
the LLM to resolve during workflow execution.

Every invocation rebuilds from scratch — no hash, no cache.
Python 3.11+ stdlib only. UTF-8 I/O.
"""

import os
import posixpath
import re
import sys
import tomllib


def find_project_root():
"""Walk up from cwd until a _bmad/ directory is found. On failure, print a
HALT instruction to stdout and exit non-zero."""
current = os.path.abspath(os.getcwd())
while True:
candidate = os.path.join(current, "_bmad")
if os.path.isdir(candidate):
return current
parent = os.path.dirname(current)
if parent == current:
print(
f"HALT and report to the user: no _bmad/ directory found walking up from {os.getcwd()}"
)
sys.exit(1)
current = parent


def _deep_merge(base, override):
"""Dict-aware deep merge. Lists and scalars: override wins (we don't need
the full keyed-merge semantics of resolve_config.py — quick-dev only reads
flat scalars out of [core] and [modules.bmm])."""
if isinstance(base, dict) and isinstance(override, dict):
result = dict(base)
for key, value in override.items():
result[key] = _deep_merge(result[key], value) if key in result else value
return result
return override


def load_central_config(root):
"""Four-layer merge of _bmad/config.toml and its peers. HALTs if the base
_bmad/config.toml is absent."""
bmad_dir = posixpath.join(root, "_bmad")
base = posixpath.join(bmad_dir, "config.toml")
if not os.path.isfile(base):
print(
f"HALT and report to the user: central config not found at {base} — "
"ensure this is a post-#2285 BMAD install"
)
sys.exit(1)

layers = [
base,
posixpath.join(bmad_dir, "config.user.toml"),
posixpath.join(bmad_dir, "custom", "config.toml"),
posixpath.join(bmad_dir, "custom", "config.user.toml"),
]
merged = {}
for path in layers:
if not os.path.isfile(path):
continue
try:
with open(path, "rb") as fh:
data = tomllib.load(fh)
except (tomllib.TOMLDecodeError, OSError) as error:
print(f"render.py: skipping {path}: {error}", file=sys.stderr)
continue
if isinstance(data, dict):
merged = _deep_merge(merged, data)
return merged


def flatten_central_config(merged):
"""Lift scalar keys from [core] and [modules.bmm] into a single namespace.
Module keys take precedence on collision (installer strips core keys from
module buckets, so collisions shouldn't happen in practice)."""
flat = {}
for section in (merged.get("core"), merged.get("modules", {}).get("bmm")):
if not isinstance(section, dict):
continue
for key, value in section.items():
if isinstance(value, bool):
flat[key] = "true" if value else "false"
elif isinstance(value, (str, int, float)):
flat[key] = str(value)
return flat


def render_template(content, vars_):
"""Resolve {{.var}} substitutions. Unresolved references emit an empty string
(Go's missingkey=zero semantics)."""
return re.sub(r"\{\{\.(\w+)\}\}", lambda m: vars_.get(m.group(1), ""), content)


def main():
script_dir = os.path.dirname(os.path.abspath(__file__))
skill_name = os.path.basename(script_dir)
root = find_project_root()
root = root.replace(os.sep, "/")
bmad_dir = posixpath.join(root, "_bmad")

vars_ = flatten_central_config(load_central_config(root))

for key in list(vars_.keys()):
vars_[key] = vars_[key].replace("{project-root}", root)

vars_["project_root"] = root
vars_["main_config"] = posixpath.join(bmad_dir, "config.toml")
vars_["sprint_status"] = posixpath.join(
vars_["implementation_artifacts"], "sprint-status.yaml"
)
vars_["deferred_work_file"] = posixpath.join(
vars_["implementation_artifacts"], "deferred-work.md"
)

out_dir = posixpath.join(root, "_bmad", "render", skill_name)
os.makedirs(out_dir, exist_ok=True)

for fname in os.listdir(out_dir):
if fname.endswith(".md"):
os.remove(posixpath.join(out_dir, fname))

count = 0
for fname in sorted(os.listdir(script_dir)):
if not fname.endswith(".md") or fname == "SKILL.md":
continue
src = posixpath.join(script_dir, fname)
dst = posixpath.join(out_dir, fname)
with open(src, "r", encoding="utf-8", newline="") as fh:
content = fh.read()
with open(dst, "w", encoding="utf-8", newline="") as fh:
fh.write(render_template(content, vars_))
count += 1

print(f"render.py: rendered {count} files -> {out_dir}", file=sys.stderr)
workflow_md = posixpath.join(out_dir, "workflow.md")
print(f"read and follow {workflow_md}")


if __name__ == "__main__":
main()
Loading
Loading