From 53981a3ae9328843b5218f1ae329694605891ff3 Mon Sep 17 00:00:00 2001 From: Zeca Figueiredo Date: Thu, 17 Sep 2026 13:44:46 -0300 Subject: [PATCH] support existing-project mode in guide_me.py Adds project_mode (new/existing) to BlueprintInputs, with a new first-prompt selector (prompt_project_mode). existing mode requires the target directory to already exist (validate_existing_target_dir) instead of auto-creating it; copy logic still never overwrites existing files. Mode-aware output: generated Markdown inputs, blue_print_used_on_creation.md title/wording, and CLI messaging all reflect the selected mode. Updates README.md, python3/README.md, and bootstrap_checklist.md to document the new mode. Fixes a pre-existing make pre-bootstrap-audit gap: adds missing embed-aihero-radioactive upstream workflow reference to README/guidance/creation-template docs. Adds/updates tests in test_helpers.py and test_guide_me.py; 100% coverage maintained (35 tests passing). Manually verified end-to-end in existing-project mode against a real target directory (no files overwritten, bundle verification PASS). --- README.md | 5 +- agentic_workflow_blueprint_guidance.md | 2 +- blue_print_used_on_creation.md | 2 +- bootstrap_checklist.md | 1 + python3/README.md | 2 + python3/guide_me.py | 39 ++++++++--- python3/helpers.py | 30 ++++++++- python3/tests/test_guide_me.py | 91 +++++++++++++++++++++++++- python3/tests/test_helpers.py | 80 +++++++++++++++++++++- 9 files changed, 237 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 3f26bd7..ef3a5e2 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ Run these commands from the repository root. - This repository supports only Python 3 bootstrap workflows. - Command entrypoint: `python3 python3/guide_me.py`. - No additional language bootstrap layers are planned in this repository. +- `python3 python3/guide_me.py` supports two project modes: `new` (bootstrap a brand new repository) and `existing` (retrofit AWB docs/workflow files into a repository that already exists). Existing-project mode never overwrites files already present in the target repository and requires the target directory to already exist. ## Responsibility Model @@ -56,7 +57,7 @@ Run these commands from the repository root. - workflows: `document`, `review`, `changelog`, `linear`, `mcp-linear-planner`, `mcp-linear-sync`, `plan-to-blueprint` - skills: review the official AWB skills catalog and capture the skill IDs or directories that match the project scope and selected workflows - runbooks: `document-review-changelog.md`, `linear-mcp.md`, `mcp-linear-sync.md`, `plan-to-blueprint.md` -- additional workflows observed upstream: `analyzing-kubernetes-audit-logs`, `brainstorming`, `c4-architecture`, `changelog-generator`, `html-manual`, `iac`, `implementing-devsecops-security-scanning`, `implementing-network-policies-for-kubernetes`, `implementing-pod-security-admission-controller`, `implementing-rbac-hardening-for-kubernetes`, `implementing-syslog-centralization-with-rsyslog`, `infra-operations`, `network-engineering`, `os-platform`, `performing-container-image-hardening`, `performing-container-security-scanning-with-trivy`, `performing-kubernetes-cis-benchmark-with-kube-bench`, `performing-vulnerability-scanning-with-nessus`, `plan-writing`, `radioactive`, `remediating-s3-bucket-misconfiguration`, `remotion-video-motion`, `scanning-containers-with-trivy-in-cicd`, `scanning-docker-images-with-trivy`, `scanning-kubernetes-manifests-with-kubesec`, `securing-aws-iam-permissions`, `securing-container-registry-images`, `securing-github-actions-workflows`, `securing-kubernetes-on-cloud`, `thermo-fix`, `thermo-nuclear-code-quality-review`, `triaging-vulnerabilities-with-ssvc-framework`, `ui-ux-pro-max` +- additional workflows observed upstream: `analyzing-kubernetes-audit-logs`, `brainstorming`, `c4-architecture`, `changelog-generator`, `embed-aihero-radioactive`, `html-manual`, `iac`, `implementing-devsecops-security-scanning`, `implementing-network-policies-for-kubernetes`, `implementing-pod-security-admission-controller`, `implementing-rbac-hardening-for-kubernetes`, `implementing-syslog-centralization-with-rsyslog`, `infra-operations`, `network-engineering`, `os-platform`, `performing-container-image-hardening`, `performing-container-security-scanning-with-trivy`, `performing-kubernetes-cis-benchmark-with-kube-bench`, `performing-vulnerability-scanning-with-nessus`, `plan-writing`, `radioactive`, `remediating-s3-bucket-misconfiguration`, `remotion-video-motion`, `scanning-containers-with-trivy-in-cicd`, `scanning-docker-images-with-trivy`, `scanning-kubernetes-manifests-with-kubesec`, `securing-aws-iam-permissions`, `securing-container-registry-images`, `securing-github-actions-workflows`, `securing-kubernetes-on-cloud`, `thermo-fix`, `thermo-nuclear-code-quality-review`, `triaging-vulnerabilities-with-ssvc-framework`, `ui-ux-pro-max` - additional runbooks observed upstream: `iac-delivery.md`, `network-change.md`, `os-hardening-patching.md` If upstream names/contracts change, update this guide repo before the next bootstrap run. @@ -69,7 +70,7 @@ Any agent must run the full protocol in `agentic_workflow_blueprint_guidance.md` 1. Update and review the official blueprint repository as source of truth. 2. Read `python3/README.md` before running anything. -3. Run the entrypoint: `python3 python3/guide_me.py`. +3. Run the entrypoint: `python3 python3/guide_me.py` and choose the project mode (`new` for a brand new repository, `existing` to retrofit AWB docs into a repository you already have). 4. If the report shows `[FAIL]`/`[WARN]`, stop, fix those items, and rerun `python3 python3/guide_me.py` until the audit passes. 5. Open the target project repository in VS Code. 6. Review the generated input outputs from `python3/guide_me.py` and refine your answers by rerunning `python3 python3/guide_me.py` until the inputs are correct. diff --git a/agentic_workflow_blueprint_guidance.md b/agentic_workflow_blueprint_guidance.md index c8aa1b4..bfe8a29 100644 --- a/agentic_workflow_blueprint_guidance.md +++ b/agentic_workflow_blueprint_guidance.md @@ -128,7 +128,7 @@ Track these official examples and keep naming aligned: - workflows: `document`, `review`, `changelog`, `linear`, `mcp-linear-planner`, `mcp-linear-sync`, `plan-to-blueprint` - skills: review the official AWB skills catalog and capture the skill IDs or directories that match the project scope and selected workflows - runbooks: `document-review-changelog.md`, `linear-mcp.md`, `mcp-linear-sync.md`, `plan-to-blueprint.md` -- additional workflows observed upstream: `analyzing-kubernetes-audit-logs`, `brainstorming`, `c4-architecture`, `changelog-generator`, `html-manual`, `iac`, `implementing-devsecops-security-scanning`, `implementing-network-policies-for-kubernetes`, `implementing-pod-security-admission-controller`, `implementing-rbac-hardening-for-kubernetes`, `implementing-syslog-centralization-with-rsyslog`, `infra-operations`, `network-engineering`, `os-platform`, `performing-container-image-hardening`, `performing-container-security-scanning-with-trivy`, `performing-kubernetes-cis-benchmark-with-kube-bench`, `performing-vulnerability-scanning-with-nessus`, `plan-writing`, `radioactive`, `remediating-s3-bucket-misconfiguration`, `remotion-video-motion`, `scanning-containers-with-trivy-in-cicd`, `scanning-docker-images-with-trivy`, `scanning-kubernetes-manifests-with-kubesec`, `securing-aws-iam-permissions`, `securing-container-registry-images`, `securing-github-actions-workflows`, `securing-kubernetes-on-cloud`, `thermo-fix`, `thermo-nuclear-code-quality-review`, `triaging-vulnerabilities-with-ssvc-framework`, `ui-ux-pro-max` +- additional workflows observed upstream: `analyzing-kubernetes-audit-logs`, `brainstorming`, `c4-architecture`, `changelog-generator`, `embed-aihero-radioactive`, `html-manual`, `iac`, `implementing-devsecops-security-scanning`, `implementing-network-policies-for-kubernetes`, `implementing-pod-security-admission-controller`, `implementing-rbac-hardening-for-kubernetes`, `implementing-syslog-centralization-with-rsyslog`, `infra-operations`, `network-engineering`, `os-platform`, `performing-container-image-hardening`, `performing-container-security-scanning-with-trivy`, `performing-kubernetes-cis-benchmark-with-kube-bench`, `performing-vulnerability-scanning-with-nessus`, `plan-writing`, `radioactive`, `remediating-s3-bucket-misconfiguration`, `remotion-video-motion`, `scanning-containers-with-trivy-in-cicd`, `scanning-docker-images-with-trivy`, `scanning-kubernetes-manifests-with-kubesec`, `securing-aws-iam-permissions`, `securing-container-registry-images`, `securing-github-actions-workflows`, `securing-kubernetes-on-cloud`, `thermo-fix`, `thermo-nuclear-code-quality-review`, `triaging-vulnerabilities-with-ssvc-framework`, `ui-ux-pro-max` - additional runbooks observed upstream: `iac-delivery.md`, `network-change.md`, `os-hardening-patching.md` If upstream naming or contracts change, update this guide before the next bootstrap. diff --git a/blue_print_used_on_creation.md b/blue_print_used_on_creation.md index d71a413..ce9ed46 100644 --- a/blue_print_used_on_creation.md +++ b/blue_print_used_on_creation.md @@ -31,7 +31,7 @@ Record the values used for: - workflows: `document`, `review`, `changelog`, `linear`, `mcp-linear-planner`, `mcp-linear-sync`, `plan-to-blueprint` - skills: record the official skill names or directories selected for this project - runbooks: `document-review-changelog.md`, `linear-mcp.md`, `mcp-linear-sync.md`, `plan-to-blueprint.md` -- additional workflows observed upstream: `analyzing-kubernetes-audit-logs`, `brainstorming`, `c4-architecture`, `changelog-generator`, `html-manual`, `iac`, `implementing-devsecops-security-scanning`, `implementing-network-policies-for-kubernetes`, `implementing-pod-security-admission-controller`, `implementing-rbac-hardening-for-kubernetes`, `implementing-syslog-centralization-with-rsyslog`, `infra-operations`, `network-engineering`, `os-platform`, `performing-container-image-hardening`, `performing-container-security-scanning-with-trivy`, `performing-kubernetes-cis-benchmark-with-kube-bench`, `performing-vulnerability-scanning-with-nessus`, `plan-writing`, `radioactive`, `remediating-s3-bucket-misconfiguration`, `remotion-video-motion`, `scanning-containers-with-trivy-in-cicd`, `scanning-docker-images-with-trivy`, `scanning-kubernetes-manifests-with-kubesec`, `securing-aws-iam-permissions`, `securing-container-registry-images`, `securing-github-actions-workflows`, `securing-kubernetes-on-cloud`, `thermo-fix`, `thermo-nuclear-code-quality-review`, `triaging-vulnerabilities-with-ssvc-framework`, `ui-ux-pro-max` +- additional workflows observed upstream: `analyzing-kubernetes-audit-logs`, `brainstorming`, `c4-architecture`, `changelog-generator`, `embed-aihero-radioactive`, `html-manual`, `iac`, `implementing-devsecops-security-scanning`, `implementing-network-policies-for-kubernetes`, `implementing-pod-security-admission-controller`, `implementing-rbac-hardening-for-kubernetes`, `implementing-syslog-centralization-with-rsyslog`, `infra-operations`, `network-engineering`, `os-platform`, `performing-container-image-hardening`, `performing-container-security-scanning-with-trivy`, `performing-kubernetes-cis-benchmark-with-kube-bench`, `performing-vulnerability-scanning-with-nessus`, `plan-writing`, `radioactive`, `remediating-s3-bucket-misconfiguration`, `remotion-video-motion`, `scanning-containers-with-trivy-in-cicd`, `scanning-docker-images-with-trivy`, `scanning-kubernetes-manifests-with-kubesec`, `securing-aws-iam-permissions`, `securing-container-registry-images`, `securing-github-actions-workflows`, `securing-kubernetes-on-cloud`, `thermo-fix`, `thermo-nuclear-code-quality-review`, `triaging-vulnerabilities-with-ssvc-framework`, `ui-ux-pro-max` - additional runbooks observed upstream: `iac-delivery.md`, `network-change.md`, `os-hardening-patching.md` If official naming/contracts change, this snapshot must be updated. diff --git a/bootstrap_checklist.md b/bootstrap_checklist.md index 1c450a8..2359cf9 100644 --- a/bootstrap_checklist.md +++ b/bootstrap_checklist.md @@ -18,6 +18,7 @@ Use this checklist at the start of every new project to reduce setup drift. ## B. Input Capture - [ ] I ran the Python 3 entrypoint (`python3 python3/guide_me.py`). +- [ ] I confirmed the correct project mode (`new` or `existing`) for this run. - [ ] I reviewed generated input outputs and reran the Python 3 entrypoint to refine answers until inputs were correct. - [ ] I confirmed the latest generated inputs are the source that will feed AWB scaffolding decisions. - [ ] I reviewed and validated core constraints. diff --git a/python3/README.md b/python3/README.md index 56f655e..30fe96e 100644 --- a/python3/README.md +++ b/python3/README.md @@ -25,6 +25,8 @@ pip install -r requirements.txt python3 guide_me.py ``` +The first prompt selects the project mode: `new` (bootstrap a brand new repository) or `existing` (apply this guide's docs/workflow files to a repository that already exists). Existing-project mode never overwrites files already present in the target repository and requires the target directory to already exist before copying. + ## Tests ```bash diff --git a/python3/guide_me.py b/python3/guide_me.py index 78cc360..fd503ae 100644 --- a/python3/guide_me.py +++ b/python3/guide_me.py @@ -21,11 +21,13 @@ DEFAULT_WORKFLOWS, GENERATED_BLUEPRINTS_DIR, KNOWN_WORKFLOWS, + PROJECT_MODE_EXISTING, copy_bootstrap_assets, extract_audit_findings, prompt, prompt_csv_list, prompt_multiline, + prompt_project_mode, prompt_yes_no, recommended_runbooks, resolve_official_awb_dir, @@ -38,6 +40,7 @@ def print_input_guidance() -> None: print("Input guidance (what you are about to provide):") print("- Official AWB: agentic-workflow-blueprint") print("- Official AWB repo: https://github.com/devton/agentic-workflow-blueprint") + print("- projectMode: new (bootstrap a new repo) or existing (retrofit docs into a current repo)") print("- projectSlug: short repo identifier in kebab-case (example: billing-api)") print("- baseBranch: integration branch name (example: main or develop)") print("- existingRootDoc: root instruction file used by the project (example: AGENTS.md)") @@ -65,6 +68,7 @@ def bullet(lines: List[str]) -> str: f"- Collected at (UTC): {data.collected_at_utc}", "", "## Required Inputs", + f"- projectMode: {data.project_mode}", f"- projectSlug: {data.project_slug}", f"- baseBranch: {data.base_branch}", f"- existingRootDoc: {data.existing_root_doc}", @@ -102,10 +106,15 @@ def bullet(lines: List[str]) -> str: def to_creation_template(data: BlueprintInputs) -> str: workflow_list = ", ".join(data.workflows_wanted) runbooks = recommended_runbooks(data.workflows_wanted) + is_existing = data.project_mode == PROJECT_MODE_EXISTING + title = "# Blueprint Applied to Existing Project" if is_existing else "# Blueprint Used on Creation" + target_repo_label = "existing project repository" if is_existing else "target project repository" return "\n".join( [ - "# Blueprint Used on Creation", + title, + "", + f"Project mode: `{data.project_mode}`", "", "Official AWB source: `https://github.com/devton/agentic-workflow-blueprint`", "", @@ -114,7 +123,7 @@ def to_creation_template(data: BlueprintInputs) -> str: "- Before start, update `agentic-workflow-blueprint` repo.", "- Run `make pre-bootstrap-audit` in this guide repository and proceed only if it passes.", "- Review official blueprint files in `agentic-workflow-blueprint`.", - "- Open the target project repository in VS Code.", + f"- Open the {target_repo_label} in VS Code.", "- Ask the LLM to review the current blueprint changes and this guidance repo.", "", "## Initial Input Snapshot", @@ -145,7 +154,7 @@ def to_creation_template(data: BlueprintInputs) -> str: "", "## Migration Checklist to New Repo", "", - "- Create/open the target project repository directory.", + f"- Create/open the {target_repo_label} directory.", "- Move generated input snapshot files (`blueprint_inputs_*.json` and `blueprint_inputs_*.md`) into `bootstrap/inputs/` in the target repo.", "- Keep this file as `blue_print_used_on_creation.md` at the target repo root.", "- Copy files created during the initial inputs phase.", @@ -190,6 +199,10 @@ def default_target_project_dir(project_slug: str) -> str: return str((Path("~/Documents/GitHub").expanduser() / project_slug).resolve()) +def default_existing_project_dir() -> str: + return str(Path.cwd()) + + def run_pre_bootstrap_audit() -> bool: guide_dir = Path(__file__).resolve().parent.parent cmd = ["make", "pre-bootstrap-audit"] @@ -256,6 +269,8 @@ def run() -> int: print() print_input_guidance() + project_mode = prompt_project_mode() + while True: try: project_slug = validate_slug( @@ -316,6 +331,7 @@ def run() -> int: stack_specific_rules=stack_specific_rules, notes=notes, collected_at_utc=collected_at_utc, + project_mode=project_mode, ) json_path, md_path, creation_template_path = write_outputs(data) @@ -328,6 +344,7 @@ def run() -> int: print("- (all files are written under generated_blueprints/)") print() print("What to do now (recommended):") + print(f"- Project mode: {data.project_mode}") print(f"1. Create/open target repo directory: ../{data.project_slug}") print("2. In target repo, create: bootstrap/inputs/") print(f"3. Move snapshots to target repo: {json_path.name}, {md_path.name}") @@ -343,11 +360,17 @@ def run() -> int: print("6. Continue implementation only in the target repository") print() - if prompt_yes_no( - "I can copy everything needed from this guide and from official AWB to a new project directory. Do it now", - default_yes=False, - ): - default_target = default_target_project_dir(data.project_slug) + copy_prompt = ( + "I can copy everything needed from this guide and from official AWB into this existing project. Do it now" + if data.project_mode == PROJECT_MODE_EXISTING + else "I can copy everything needed from this guide and from official AWB to a new project directory. Do it now" + ) + if prompt_yes_no(copy_prompt, default_yes=False): + default_target = ( + default_existing_project_dir() + if data.project_mode == PROJECT_MODE_EXISTING + else default_target_project_dir(data.project_slug) + ) target_dir_text = prompt("Target project directory", default_target) resolved_target = str(Path(target_dir_text).expanduser().resolve()) print(f"Resolved target directory: {resolved_target}") diff --git a/python3/helpers.py b/python3/helpers.py index 65d9ec5..7183641 100644 --- a/python3/helpers.py +++ b/python3/helpers.py @@ -10,6 +10,9 @@ DEFAULT_WORKFLOWS = ["document", "review", "changelog"] GENERATED_BLUEPRINTS_DIR = "generated_blueprints" +PROJECT_MODE_NEW = "new" +PROJECT_MODE_EXISTING = "existing" +PROJECT_MODES = {PROJECT_MODE_NEW, PROJECT_MODE_EXISTING} DOC_CHAIN_WORKFLOWS = {"document", "review", "changelog"} MCP_SYNC_WORKFLOWS = {"mcp-linear-planner", "mcp-linear-sync"} KNOWN_WORKFLOWS = { @@ -57,6 +60,7 @@ class BlueprintInputs: stack_specific_rules: List[str] notes: List[str] collected_at_utc: str + project_mode: str = PROJECT_MODE_NEW def prompt(text: str, default: str | None = None) -> str: @@ -67,6 +71,18 @@ def prompt(text: str, default: str | None = None) -> str: return default or "" +def prompt_project_mode(default_mode: str = PROJECT_MODE_NEW) -> str: + print("Project mode:") + print(" 1) new - bootstrap a brand new project repository") + print(" 2) existing - apply this guide's docs/workflow files to an existing project") + raw = prompt("Select project mode (new/existing)", default_mode).strip().lower() + if raw in {"1", PROJECT_MODE_NEW}: + return PROJECT_MODE_NEW + if raw in {"2", PROJECT_MODE_EXISTING}: + return PROJECT_MODE_EXISTING + return default_mode + + def prompt_yes_no(text: str, default_yes: bool = True) -> bool: default = "Y/n" if default_yes else "y/N" answer = input(f"{text} [{default}]: ").strip().lower() @@ -149,6 +165,15 @@ def validate_target_dir(guide_dir: Path, target_dir: Path) -> None: ) +def validate_existing_target_dir(target_dir: Path) -> None: + if not target_dir.is_dir(): + raise ValueError( + "Existing project mode requires the target directory to already exist: " + f"{target_dir}. Create or open the existing project repository first, " + "then rerun this guide." + ) + + def copy_file_if_missing( src: Path, dst: Path, copied: List[str], skipped: List[str], warnings: List[str] ) -> None: @@ -197,7 +222,10 @@ def copy_bootstrap_assets( skipped: List[str] = [] warnings: List[str] = [] - target_dir.mkdir(parents=True, exist_ok=True) + if data.project_mode == PROJECT_MODE_EXISTING: + validate_existing_target_dir(target_dir) + else: + target_dir.mkdir(parents=True, exist_ok=True) inputs_dir = target_dir / "bootstrap" / "inputs" inputs_dir.mkdir(parents=True, exist_ok=True) diff --git a/python3/tests/test_guide_me.py b/python3/tests/test_guide_me.py index f726fc7..d62d0c1 100644 --- a/python3/tests/test_guide_me.py +++ b/python3/tests/test_guide_me.py @@ -11,7 +11,11 @@ from helpers import BlueprintInputs -def _data(notes: list[str] | None = None, workflows: list[str] | None = None) -> BlueprintInputs: +def _data( + notes: list[str] | None = None, + workflows: list[str] | None = None, + project_mode: str = "new", +) -> BlueprintInputs: return BlueprintInputs( project_slug="proj-x", base_branch="main", @@ -22,6 +26,7 @@ def _data(notes: list[str] | None = None, workflows: list[str] | None = None) -> stack_specific_rules=["stack"], notes=notes if notes is not None else ["note"], collected_at_utc="2026-05-22T00:00:00+00:00", + project_mode=project_mode, ) @@ -49,6 +54,18 @@ def test_markdown_and_creation_template_variants() -> None: assert "Copy runbook from official AWB" in creation +def test_markdown_and_creation_template_existing_mode() -> None: + data = _data(workflows=["document"], project_mode="existing") + + md = guide_me.to_markdown(data) + assert "- projectMode: existing" in md + + creation = guide_me.to_creation_template(data) + assert "# Blueprint Applied to Existing Project" in creation + assert "Project mode: `existing`" in creation + assert "existing project repository" in creation + + def test_write_outputs_creates_expected_files(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: fake_file = tmp_path / "python3" / "guide_me.py" fake_file.parent.mkdir(parents=True) @@ -79,6 +96,10 @@ def test_default_target_project_dir_uses_fixed_base(monkeypatch: pytest.MonkeyPa assert target.endswith("/Documents/GitHub/proj-x") +def test_default_existing_project_dir_uses_cwd() -> None: + assert guide_me.default_existing_project_dir() == str(Path.cwd()) + + def test_ensure_official_awb_dir_exists_reports_missing( monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str] ) -> None: @@ -157,6 +178,7 @@ def test_run_happy_path_without_copy(monkeypatch: pytest.MonkeyPatch, tmp_path: monkeypatch.setattr(guide_me, "ensure_official_awb_dir_exists", lambda: True) monkeypatch.setattr(guide_me, "run_pre_bootstrap_audit", lambda: True) monkeypatch.setattr(guide_me, "print_input_guidance", lambda: None) + monkeypatch.setattr(guide_me, "prompt_project_mode", lambda: "new") prompt_values = iter(["Bad Slug", "good-slug", "main", "AGENTS.md"]) monkeypatch.setattr(guide_me, "prompt", lambda *_a, **_k: next(prompt_values)) @@ -194,6 +216,7 @@ def test_run_copy_bundle_pass_and_fail(monkeypatch: pytest.MonkeyPatch, tmp_path monkeypatch.setattr(guide_me, "ensure_official_awb_dir_exists", lambda: True) monkeypatch.setattr(guide_me, "run_pre_bootstrap_audit", lambda: True) monkeypatch.setattr(guide_me, "print_input_guidance", lambda: None) + monkeypatch.setattr(guide_me, "prompt_project_mode", lambda: "new") prompt_values = iter(["proj-y", "main", "AGENTS.md", str(tmp_path / "target")]) monkeypatch.setattr(guide_me, "prompt", lambda *_a, **_k: next(prompt_values)) @@ -237,10 +260,75 @@ def test_run_copy_bundle_pass_and_fail(monkeypatch: pytest.MonkeyPatch, tmp_path assert guide_me.run() == 0 +def test_run_existing_project_mode_uses_cwd_default_and_requires_existing_dir( + monkeypatch: pytest.MonkeyPatch, tmp_path: Path +) -> None: + monkeypatch.setattr(guide_me, "ensure_official_awb_dir_exists", lambda: True) + monkeypatch.setattr(guide_me, "run_pre_bootstrap_audit", lambda: True) + monkeypatch.setattr(guide_me, "print_input_guidance", lambda: None) + monkeypatch.setattr(guide_me, "prompt_project_mode", lambda: "existing") + + prompt_values = iter(["proj-y", "main", "AGENTS.md"]) + monkeypatch.setattr(guide_me, "prompt", lambda *_a, **_k: next(prompt_values)) + monkeypatch.setattr(guide_me, "prompt_csv_list", lambda *_a, **_k: ["document"]) + + multiline_values = iter([["Python"], ["notes"]]) + monkeypatch.setattr(guide_me, "prompt_multiline", lambda *_a, **_k: next(multiline_values)) + + # Decline the copy step so we only exercise the mode-aware messaging/defaults. + yes_no_values = iter([True, True, False]) + monkeypatch.setattr(guide_me, "prompt_yes_no", lambda *_a, **_k: next(yes_no_values)) + + out_json = tmp_path / "in3.json" + out_md = tmp_path / "in3.md" + out_creation = tmp_path / "blue_print_used_on_creation.md" + out_json.write_text("{}", encoding="utf-8") + out_md.write_text("# md", encoding="utf-8") + out_creation.write_text("# creation", encoding="utf-8") + monkeypatch.setattr(guide_me, "write_outputs", lambda _data: (out_json, out_md, out_creation)) + + assert guide_me.run() == 0 + + +def test_run_existing_project_mode_copy_requires_existing_dir( + monkeypatch: pytest.MonkeyPatch, tmp_path: Path +) -> None: + monkeypatch.setattr(guide_me, "ensure_official_awb_dir_exists", lambda: True) + monkeypatch.setattr(guide_me, "run_pre_bootstrap_audit", lambda: True) + monkeypatch.setattr(guide_me, "print_input_guidance", lambda: None) + monkeypatch.setattr(guide_me, "prompt_project_mode", lambda: "existing") + + missing_target = tmp_path / "not-created-yet" + prompt_values = iter(["proj-y", "main", "AGENTS.md", str(missing_target)]) + monkeypatch.setattr(guide_me, "prompt", lambda *_a, **_k: next(prompt_values)) + monkeypatch.setattr(guide_me, "prompt_csv_list", lambda *_a, **_k: ["document"]) + + multiline_values = iter([["Python"], ["notes"]]) + monkeypatch.setattr(guide_me, "prompt_multiline", lambda *_a, **_k: next(multiline_values)) + + yes_no_values = iter([True, True, True, True]) + monkeypatch.setattr(guide_me, "prompt_yes_no", lambda *_a, **_k: next(yes_no_values)) + + out_json = tmp_path / "in4.json" + out_md = tmp_path / "in4.md" + out_creation = tmp_path / "blue_print_used_on_creation.md" + out_json.write_text("{}", encoding="utf-8") + out_md.write_text("# md", encoding="utf-8") + out_creation.write_text("# creation", encoding="utf-8") + monkeypatch.setattr(guide_me, "write_outputs", lambda _data: (out_json, out_md, out_creation)) + + guide_dir = tmp_path / "guide" + guide_dir.mkdir() + monkeypatch.setattr(guide_me, "__file__", str(guide_dir / "python3" / "guide_me.py")) + + assert guide_me.run() == 1 + + def test_run_copy_bundle_invalid_target_dir(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: monkeypatch.setattr(guide_me, "ensure_official_awb_dir_exists", lambda: True) monkeypatch.setattr(guide_me, "run_pre_bootstrap_audit", lambda: True) monkeypatch.setattr(guide_me, "print_input_guidance", lambda: None) + monkeypatch.setattr(guide_me, "prompt_project_mode", lambda: "new") prompt_values = iter(["proj-y", "main", "AGENTS.md", str(tmp_path / "guide" / "loto")]) monkeypatch.setattr(guide_me, "prompt", lambda *_a, **_k: next(prompt_values)) @@ -273,6 +361,7 @@ def test_run_copy_bundle_user_cancels_after_resolution( monkeypatch.setattr(guide_me, "ensure_official_awb_dir_exists", lambda: True) monkeypatch.setattr(guide_me, "run_pre_bootstrap_audit", lambda: True) monkeypatch.setattr(guide_me, "print_input_guidance", lambda: None) + monkeypatch.setattr(guide_me, "prompt_project_mode", lambda: "new") prompt_values = iter(["proj-y", "main", "AGENTS.md", str(tmp_path / "target")]) monkeypatch.setattr(guide_me, "prompt", lambda *_a, **_k: next(prompt_values)) diff --git a/python3/tests/test_helpers.py b/python3/tests/test_helpers.py index 44df9b4..2288278 100644 --- a/python3/tests/test_helpers.py +++ b/python3/tests/test_helpers.py @@ -10,7 +10,7 @@ from helpers import BlueprintInputs -def _sample_inputs(workflows: list[str] | None = None) -> BlueprintInputs: +def _sample_inputs(workflows: list[str] | None = None, project_mode: str = "new") -> BlueprintInputs: return BlueprintInputs( project_slug="sample-project", base_branch="main", @@ -21,6 +21,7 @@ def _sample_inputs(workflows: list[str] | None = None) -> BlueprintInputs: stack_specific_rules=["stack"], notes=["note"], collected_at_utc="2026-05-22T00:00:00+00:00", + project_mode=project_mode, ) @@ -47,6 +48,25 @@ def test_prompt_yes_no_variants(monkeypatch: pytest.MonkeyPatch) -> None: assert helpers.prompt_yes_no("Q", default_yes=True) is False +def test_prompt_project_mode_variants(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setattr("builtins.input", lambda _: "") + assert helpers.prompt_project_mode() == helpers.PROJECT_MODE_NEW + + monkeypatch.setattr("builtins.input", lambda _: "2") + assert helpers.prompt_project_mode() == helpers.PROJECT_MODE_EXISTING + + monkeypatch.setattr("builtins.input", lambda _: "existing") + assert helpers.prompt_project_mode() == helpers.PROJECT_MODE_EXISTING + + monkeypatch.setattr("builtins.input", lambda _: "1") + assert helpers.prompt_project_mode() == helpers.PROJECT_MODE_NEW + + monkeypatch.setattr("builtins.input", lambda _: "bogus") + assert helpers.prompt_project_mode(default_mode=helpers.PROJECT_MODE_EXISTING) == ( + helpers.PROJECT_MODE_EXISTING + ) + + def test_prompt_csv_list(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(helpers, "prompt", lambda _text, _default: " A, b , ,C ") assert helpers.prompt_csv_list("Q", ["x"]) == ["a", "b", "c"] @@ -108,6 +128,16 @@ def test_validate_target_dir_accepts_external_path(tmp_path: Path) -> None: helpers.validate_target_dir(guide_dir=guide_dir, target_dir=target_dir) +def test_validate_existing_target_dir(tmp_path: Path) -> None: + missing_dir = tmp_path / "missing" + with pytest.raises(ValueError): + helpers.validate_existing_target_dir(missing_dir) + + existing_dir = tmp_path / "existing" + existing_dir.mkdir() + helpers.validate_existing_target_dir(existing_dir) + + def test_copy_file_if_missing_and_copy_tree_if_missing(tmp_path: Path) -> None: copied: list[str] = [] skipped: list[str] = [] @@ -248,6 +278,54 @@ def test_copy_bootstrap_assets_success_and_failure(tmp_path: Path) -> None: assert any("Bundle verification failed" in item for item in warnings2) +def test_copy_bootstrap_assets_existing_mode_requires_pre_existing_dir(tmp_path: Path) -> None: + data = _sample_inputs(["document"], project_mode=helpers.PROJECT_MODE_EXISTING) + guide_dir = tmp_path / "guide" + official = tmp_path / "agentic-workflow-blueprint" + + guide_dir.mkdir(parents=True) + (guide_dir / "bootstrap_checklist.md").write_text("check", encoding="utf-8") + (guide_dir / "agentic_workflow_blueprint_guidance.md").write_text("guide", encoding="utf-8") + + (official / "workflows" / "document").mkdir(parents=True) + (official / "workflows" / "document" / "README.md").write_text("wf", encoding="utf-8") + (official / "runbooks").mkdir(parents=True) + (official / "runbooks" / "document-review-changelog.md").write_text("rb", encoding="utf-8") + (official / "AGENTS.md").write_text("agents", encoding="utf-8") + + json_path = tmp_path / "inputs.json" + md_path = tmp_path / "inputs.md" + creation_path = tmp_path / "blue_print_used_on_creation.md" + json_path.write_text("{}", encoding="utf-8") + md_path.write_text("# md", encoding="utf-8") + creation_path.write_text("# creation", encoding="utf-8") + + missing_target = tmp_path / "existing-project-not-created" + with pytest.raises(ValueError): + helpers.copy_bootstrap_assets( + guide_dir=guide_dir, + data=data, + json_path=json_path, + md_path=md_path, + creation_template_path=creation_path, + target_dir_text=str(missing_target), + ) + + existing_target = tmp_path / "existing-project" + existing_target.mkdir() + copied, skipped, warnings = helpers.copy_bootstrap_assets( + guide_dir=guide_dir, + data=data, + json_path=json_path, + md_path=md_path, + creation_template_path=creation_path, + target_dir_text=str(existing_target), + ) + assert not skipped + assert not warnings + assert "BUNDLE_VERIFICATION:PASS" in copied + + def test_copy_bootstrap_assets_rejects_target_inside_guide(tmp_path: Path) -> None: data = _sample_inputs(["document"]) guide_dir = tmp_path / "guide"