Add BMAD planning artifacts: brief, PRD, architecture, UX, spec, tickets
Includes the BMAD install config, the initiative ticket tree, the portal context-resources scripts, and the .gitignore change that tracks BMAD files (generated _bmad/render/ is ignored). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sk72AxLwQnYttF6VFSxQLt
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
97b2a95062
commit
24de5878ed
@@ -0,0 +1,143 @@
|
||||
#!/usr/bin/env python3
|
||||
# /// script
|
||||
# requires-python = ">=3.11"
|
||||
# ///
|
||||
"""Release version stamper for module repositories.
|
||||
|
||||
Writes a human-supplied SemVer version into every module record: the
|
||||
`version` line inside the `[bmod]` table of each skills/*/bmod.toml that has
|
||||
one. Member skills carry no version and are not written. Each module
|
||||
repository's release runbook uses it to stamp releases and the next
|
||||
placeholder on `dev`.
|
||||
|
||||
Before writing anything it runs the repository checks in
|
||||
validate_manifests.py beside it, the same ones pre-commit and CI run.
|
||||
`--check` runs only those checks and writes nothing.
|
||||
|
||||
A file may carry keys and tables this script does not know. The runtime
|
||||
ignores them, so a release must not refuse them; they are left exactly as
|
||||
written. The version line is rewritten textually, so nothing else in a file
|
||||
moves, and each new file is parsed and compared before anything is written.
|
||||
|
||||
After writing, the script re-reads every file and fails naming the offending
|
||||
path if anything is off.
|
||||
|
||||
Usage:
|
||||
uv run --python 3.11 skills/bmad/scripts/stamp_release.py 1.2.0 [--project-root <path>]
|
||||
uv run --python 3.11 skills/bmad/scripts/stamp_release.py --check [--project-root <path>]
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import importlib.util
|
||||
import sys
|
||||
import tomllib
|
||||
from pathlib import Path
|
||||
|
||||
sys.dont_write_bytecode = True
|
||||
|
||||
|
||||
class StampError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def load_validator():
|
||||
"""The repository checks, so a release and a commit can never be held to different rules."""
|
||||
path = Path(__file__).resolve().parent / "validate_manifests.py"
|
||||
spec = importlib.util.spec_from_file_location("bmad_validate_for_stamp", path)
|
||||
if spec is None or spec.loader is None:
|
||||
raise StampError(f"cannot load the repository checks at {path}")
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
validator = load_validator()
|
||||
setup = validator.setup
|
||||
|
||||
|
||||
def validate_version(version: str) -> None:
|
||||
problem = validator.version_problem(version)
|
||||
if problem is not None:
|
||||
raise StampError(problem)
|
||||
|
||||
|
||||
def collect_records(project_root: Path) -> list[Path]:
|
||||
report = validator.check_repo(project_root)
|
||||
if report.problems:
|
||||
raise StampError("\n ".join(report.problems))
|
||||
return list(report.records)
|
||||
|
||||
|
||||
def read_toml(path: Path, rel: str) -> tuple[str, dict[str, object]]:
|
||||
try:
|
||||
text = path.read_bytes().decode("utf-8")
|
||||
return text, tomllib.loads(text)
|
||||
except (OSError, UnicodeError, tomllib.TOMLDecodeError) as error:
|
||||
raise StampError(f"{rel}: cannot read bmod file: {error}") from error
|
||||
|
||||
|
||||
def verify_stamp(root: Path, records: list[Path], expected: dict[str, dict[str, object]]) -> None:
|
||||
for record in records:
|
||||
rel = record.relative_to(root).as_posix()
|
||||
_, data = read_toml(record, rel)
|
||||
if data != expected[rel]:
|
||||
raise StampError(f"{rel}: stamping changed something other than the version")
|
||||
collect_records(root)
|
||||
|
||||
|
||||
def run(project_root: Path, version: str) -> int:
|
||||
try:
|
||||
validate_version(version)
|
||||
records = collect_records(project_root)
|
||||
|
||||
# Nothing is written if any file fails.
|
||||
planned: list[tuple[Path, str]] = []
|
||||
expected: dict[str, dict[str, object]] = {}
|
||||
for record in records:
|
||||
rel = record.relative_to(project_root).as_posix()
|
||||
original, data = read_toml(record, rel)
|
||||
try:
|
||||
planned.append((record, validator.stamp_text(original, version)))
|
||||
except ValueError as error:
|
||||
raise StampError(f"{rel}: {error}") from error
|
||||
expected[rel] = validator.with_version(data, version)
|
||||
|
||||
for path, content in planned:
|
||||
try:
|
||||
path.write_bytes(content.encode("utf-8"))
|
||||
except OSError as error:
|
||||
raise StampError(f"{path.relative_to(project_root).as_posix()}: cannot write: {error}") from error
|
||||
verify_stamp(project_root, records, expected)
|
||||
except StampError as error:
|
||||
print(f"Error: {error}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
print(f"Stamped version {version} into {len(planned)} files:")
|
||||
for path, _ in planned:
|
||||
print(f" {path.relative_to(project_root).as_posix()}")
|
||||
return 0
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description="Stamp a version into every module record.")
|
||||
parser.add_argument("version", nargs="?", help='SemVer release version, e.g. "6.12.0"')
|
||||
parser.add_argument("--check", action="store_true", help="run the repository checks and write nothing")
|
||||
parser.add_argument(
|
||||
"--project-root", type=Path, default=Path.cwd(), help="repository to stamp (default: the current directory)"
|
||||
)
|
||||
args = parser.parse_args(argv)
|
||||
if args.check == (args.version is not None):
|
||||
parser.error("give a version to stamp, or --check, but not both")
|
||||
if args.check:
|
||||
return validator.main(["--project-root", str(args.project_root)])
|
||||
return run(args.project_root.resolve(), args.version)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if sys.platform == "win32":
|
||||
# Piped output on Windows defaults to a legacy code page, not UTF-8.
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
sys.stderr.reconfigure(encoding="utf-8")
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user