From 5425ce9fbb702043cfe0b3fee1e034f44db50568 Mon Sep 17 00:00:00 2001 From: rob Date: Sat, 25 Oct 2025 02:30:38 -0300 Subject: [PATCH] 1st commit --- .gitignore | 4 + README.md | 0 assets/runtime/.gitignore.seed | 0 assets/templates/design_doc.md | 0 assets/templates/discussion.md | 0 assets/templates/feature_request.md | 0 pyproject.toml | 0 src/cascadingdev/__init__.py | 0 src/cascadingdev/cli.py | 0 src/cascadingdev/feature_seed.py | 0 src/cascadingdev/fs_scaffold.py | 0 src/cascadingdev/ramble_integration.py | 0 src/cascadingdev/rules_seed.py | 0 src/cascadingdev/utils.py | 0 tools/build_installer.py | 155 +++++++++++++++++++++++++ tools/smoke_test.py | 0 16 files changed, 159 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 assets/runtime/.gitignore.seed create mode 100644 assets/templates/design_doc.md create mode 100644 assets/templates/discussion.md create mode 100644 assets/templates/feature_request.md create mode 100644 pyproject.toml create mode 100644 src/cascadingdev/__init__.py create mode 100644 src/cascadingdev/cli.py create mode 100644 src/cascadingdev/feature_seed.py create mode 100644 src/cascadingdev/fs_scaffold.py create mode 100644 src/cascadingdev/ramble_integration.py create mode 100644 src/cascadingdev/rules_seed.py create mode 100644 src/cascadingdev/utils.py create mode 100644 tools/build_installer.py create mode 100644 tools/smoke_test.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c94d1eb --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.venv/ +.idea/ +__pycache__/ +*.pyc diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/assets/runtime/.gitignore.seed b/assets/runtime/.gitignore.seed new file mode 100644 index 0000000..e69de29 diff --git a/assets/templates/design_doc.md b/assets/templates/design_doc.md new file mode 100644 index 0000000..e69de29 diff --git a/assets/templates/discussion.md b/assets/templates/discussion.md new file mode 100644 index 0000000..e69de29 diff --git a/assets/templates/feature_request.md b/assets/templates/feature_request.md new file mode 100644 index 0000000..e69de29 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..e69de29 diff --git a/src/cascadingdev/__init__.py b/src/cascadingdev/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/cascadingdev/cli.py b/src/cascadingdev/cli.py new file mode 100644 index 0000000..e69de29 diff --git a/src/cascadingdev/feature_seed.py b/src/cascadingdev/feature_seed.py new file mode 100644 index 0000000..e69de29 diff --git a/src/cascadingdev/fs_scaffold.py b/src/cascadingdev/fs_scaffold.py new file mode 100644 index 0000000..e69de29 diff --git a/src/cascadingdev/ramble_integration.py b/src/cascadingdev/ramble_integration.py new file mode 100644 index 0000000..e69de29 diff --git a/src/cascadingdev/rules_seed.py b/src/cascadingdev/rules_seed.py new file mode 100644 index 0000000..e69de29 diff --git a/src/cascadingdev/utils.py b/src/cascadingdev/utils.py new file mode 100644 index 0000000..e69de29 diff --git a/tools/build_installer.py b/tools/build_installer.py new file mode 100644 index 0000000..ee7e071 --- /dev/null +++ b/tools/build_installer.py @@ -0,0 +1,155 @@ +#!/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") + 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) + + # 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() diff --git a/tools/smoke_test.py b/tools/smoke_test.py new file mode 100644 index 0000000..e69de29