#!/usr/bin/env python3 import shutil, os from pathlib import Path ROOT = Path(__file__).resolve().parents[1] OUT = ROOT / "install" VER = (ROOT / "VERSION").read_text().strip() if (ROOT / "VERSION").exists() else "0.1.0" BUNDLE = OUT / f"cascadingdev-{VER}" def main(): if BUNDLE.exists(): shutil.rmtree(BUNDLE) # copy essentials (BUNDLE / "assets" / "hooks").mkdir(parents=True, exist_ok=True) (BUNDLE / "assets" / "templates").mkdir(parents=True, exist_ok=True) shutil.copy2(ROOT / "DESIGN.md", BUNDLE / "DESIGN.md") shutil.copy2(ROOT / "assets" / "runtime" / "ramble.py", BUNDLE / "ramble.py") shutil.copy2(ROOT / "assets" / "hooks" / "pre-commit", BUNDLE / "assets" / "hooks" / "pre-commit") for t in ["feature_request.md","discussion.md","design_doc.md"]: shutil.copy2(ROOT / "assets" / "templates" / t, BUNDLE / "assets" / "templates" / t) # write installer entrypoint (BUNDLE / "setup_cascadingdev.py").write_text(INSTALLER_PY, encoding="utf-8") (BUNDLE / "INSTALL.md").write_text("Unzip, then run:\n\n python3 setup_cascadingdev.py\n", encoding="utf-8") (BUNDLE / "VERSION").write_text(VER, encoding="utf-8") print(f"[✓] Built installer → {BUNDLE}") INSTALLER_PY = r'''#!/usr/bin/env python3 import argparse, json, os, shutil, subprocess, sys, datetime from pathlib import Path HERE = Path(__file__).resolve().parent def sh(cmd, cwd=None): return subprocess.run(cmd, check=True, text=True, capture_output=True, cwd=cwd) def say(x): print(x, flush=True) def write_if_missing(p: Path, content: str): p.parent.mkdir(parents=True, exist_ok=True) if not p.exists(): p.write_text(content, encoding="utf-8") def copytree(src: Path, dst: Path): dst.parent.mkdir(parents=True, exist_ok=True) if src.is_file(): shutil.copy2(src, dst) else: shutil.copytree(src, dst, dirs_exist_ok=True) def ensure_git_repo(target: Path): if not (target / ".git").exists(): sh(["git", "init", "-b", "main"], cwd=target) write_if_missing(target / ".gitignore", ".env\n.env.*\nsecrets/\n__pycache__/\n*.pyc\n.pytest_cache/\n.DS_Store\n") def install_hook(target: Path): hooks = target / ".git" / "hooks"; hooks.mkdir(parents=True, exist_ok=True) src = HERE / "assets" / "hooks" / "pre-commit" dst = hooks / "pre-commit" dst.write_text(src.read_text(encoding="utf-8"), encoding="utf-8") dst.chmod(0o755) def run_ramble(target: Path, provider="mock"): ramble = target / "ramble.py" if not ramble.exists(): return None args = [sys.executable, str(ramble), "--provider", provider, "--prompt", "Describe your initial feature request for this repository", "--fields", "Summary", "Title", "Intent", "ProblemItSolves", "BriefOverview", "--criteria", '{"Summary":"<= 2 sentences","Title":"camelCase, <= 24 chars"}'] say("[•] Launching Ramble…") p = subprocess.run(args, text=True, capture_output=True, cwd=target) try: return json.loads((p.stdout or "").strip()) except Exception: say("[-] Could not parse Ramble output; using template defaults.") return None def seed_rules_and_templates(target: Path): write_if_missing(target / ".ai-rules.yml", "version: 1\nfile_associations:\n \"*.md\": \"md-file\"\n\nrules:\n md-file:\n description: \"Normalize Markdown\"\n instruction: |\n Keep markdown tidy (headings, lists, spacing). No content churn.\nsettings:\n model: \"local-mock\"\n temperature: 0.1\n") # copy templates copytree(HERE / "assets" / "templates", target / "process" / "templates") def seed_first_feature(target: Path, req): today = datetime.date.today().isoformat() fr = target / "Docs" / "features" / f"FR_{today}_initial-feature-request" disc = fr / "discussions"; disc.mkdir(parents=True, exist_ok=True) if req: fields = req.get("fields", {}) or {} title = (fields.get("Title") or "initialProjectDesign").strip() intent = (fields.get("Intent") or "—").strip() problem = (fields.get("ProblemItSolves") or "—").strip() brief = (fields.get("BriefOverview") or "—").strip() summary = (req.get("summary") or "").strip() body = f"# Feature Request: {title}\n\n**Intent**: {intent}\n**Motivation / Problem**: {problem}\n**Brief Overview**: {brief}\n\n**Summary**: {summary}\n**Meta**: Created: {today}\n" else: body = (target / "process" / "templates" / "feature_request.md").read_text(encoding="utf-8") (fr / "request.md").write_text(body, encoding="utf-8") (disc / "feature.discussion.md").write_text( f"---\ntype: discussion\nstage: feature\nstatus: OPEN\nfeature_id: FR_{today}_initial-feature-request\ncreated: {today}\n---\n## Summary\nKickoff discussion. Append comments below.\n\n## Participation\n- Maintainer: Kickoff. VOTE: READY\n", encoding="utf-8") (disc / "feature.discussion.sum.md").write_text( "# Summary — Feature\n\n\n## Decisions (ADR-style)\n- (none yet)\n\n\n\n## Open Questions\n- (none yet)\n\n\n\n## Awaiting Replies\n- (none yet)\n\n\n\n## Action Items\n- (none yet)\n\n\n\n## Votes (latest per participant)\nREADY: 1 • CHANGES: 0 • REJECT: 0\n- Maintainer\n\n\n\n## Timeline (most recent first)\n- {today} Maintainer: Kickoff\n\n\n\n## Links\n- Design/Plan: ../design/design.md\n\n".replace("{today}", today), encoding="utf-8") def main(): ap = argparse.ArgumentParser() ap.add_argument("--target", help="Destination path for the user's project") ap.add_argument("--no-ramble", action="store_true") ap.add_argument("--provider", default="mock") args = ap.parse_args() target = Path(args.target or input("User's project folder: ").strip()).expanduser().resolve() target.mkdir(parents=True, exist_ok=True) say(f"[=] Installing into: {target}") # copy top-level assets shutil.copy2(HERE / "DESIGN.md", target / "DESIGN.md") shutil.copy2(HERE / "ramble.py", target / "ramble.py") # basic tree for p in [target / "Docs" / "features", target / "Docs" / "discussions" / "reviews", target / "Docs" / "diagrams" / "file_diagrams", target / "scripts" / "hooks", target / "src", target / "tests", target / "process"]: p.mkdir(parents=True, exist_ok=True) # rules / templates seed_rules_and_templates(target) # git + hook ensure_git_repo(target) install_hook(target) # ramble req = None if args.no_ramble else run_ramble(target, provider=args.provider) # seed FR seed_first_feature(target, req) try: sh(["git", "add", "-A"], cwd=target) sh(["git", "commit", "-m", "chore: bootstrap Cascading Development scaffolding"], cwd=target) except Exception: pass say("[✓] Done. Next:\n cd " + str(target) + "\n git status") if __name__ == "__main__": main() ''' if __name__ == "__main__": main()