diff --git a/.github/workflows/ci-pipeline.yml b/.github/workflows/ci-pipeline.yml index c88f6f42..66ff8c4f 100644 --- a/.github/workflows/ci-pipeline.yml +++ b/.github/workflows/ci-pipeline.yml @@ -118,12 +118,12 @@ jobs: run: | export PYTHONPATH="${PYTHONPATH}:${PWD}" python -c " - from medharness.workflows.init import _scaffold_dhf + from medharness.scaffold import scaffold_dhf from pathlib import Path import tempfile with tempfile.TemporaryDirectory() as tmp: dhf_dir = Path(tmp) / 'test-dhf' - _scaffold_dhf(dhf_dir) + scaffold_dhf(dhf_dir) assert (dhf_dir / 'DHF' / 'config' / 'global.yaml').exists() assert not (dhf_dir / 'DHF' / 'config' / 'doc_types').exists() # defaults are inherited assert (dhf_dir / 'DHF' / 'items').exists() @@ -152,13 +152,13 @@ jobs: run: | export PYTHONPATH="${PYTHONPATH}:${PWD}" python -c " - from medharness.workflows.init import _scaffold_dhf, _replace_placeholders + from medharness.scaffold import scaffold_dhf, replace_placeholders from pathlib import Path import tempfile, subprocess, sys with tempfile.TemporaryDirectory() as tmp: dhf_dir = Path(tmp) / 'starter-dhf' - _scaffold_dhf(dhf_dir) - _replace_placeholders(dhf_dir, 'Test Project') + scaffold_dhf(dhf_dir) + replace_placeholders(dhf_dir, 'Test Project') dhf = str(dhf_dir / 'DHF') subprocess.run([sys.executable, '-m', 'medharness', '--dhf', dhf, 'verify', 'dhf'], check=True) # The starter items say 'Replace with your own': strict refuses them, so they cannot ship. diff --git a/CHANGELOG.md b/CHANGELOG.md index 1349ec00..002725ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,21 @@ MedHarness follows [Semantic Versioning](https://semver.org/): ## [Unreleased] +## [0.54.1] — 2026-10-04 + +### Fixed + +- **The code review's fix pass now carries the issues it must fix.** The prompt said "the review above", + which only a model resuming the session could see; a model from another provider (no sessions) was + told to fix nothing in particular. The issues are in the prompt. +- The design review's "already checked" note no longer mentions a spec file that no stage writes. + +### Changed + +- `medharness/workflows/` is gone: `init.py` is `medharness/scaffold.py` (its test helpers lose the + underscore: `scaffold_dhf`, `replace_placeholders`) and `cr_state.py` is `medharness/services/cr_state.py`. + Nothing in it was a workflow. No behaviour change. + ## [0.54.0] — 2026-10-04 ### Changed @@ -2976,7 +2991,7 @@ a compliance record is the defect class that matters most. ### Internal -- `dhfkit/tests/test_document_generation.py` imported `medharness.workflows.init`, +- `dhfkit/tests/test_document_generation.py` imported `medharness.scaffold`, breaking the `dhfkit` → `medharness` boundary and the ability to run dhfkit's suite standalone. It now builds its fixture from the bundled templates directly. A check over the whole package confirms no such import remains. diff --git a/dhfkit/tests/test_cr_closure_criteria.py b/dhfkit/tests/test_cr_closure_criteria.py index 6d73a9d4..7fb324ea 100644 --- a/dhfkit/tests/test_cr_closure_criteria.py +++ b/dhfkit/tests/test_cr_closure_criteria.py @@ -19,7 +19,7 @@ import pytest import dhfkit.api as api -from medharness.workflows.init import _replace_placeholders, _scaffold_dhf +from medharness.scaffold import replace_placeholders, scaffold_dhf CLOSURE = { "implementation_notes": "Toolbar updated.", @@ -31,8 +31,8 @@ @pytest.fixture def cr(tmp_path: Path) -> tuple[Path, str]: - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "Closure") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "Closure") dhf = tmp_path / "DHF" for state in ("design", "develop"): api.transition_item(dhf, "CR-001", state) diff --git a/dhfkit/tests/test_dangling_covers_the_schema.py b/dhfkit/tests/test_dangling_covers_the_schema.py index 505f1d57..65b9f920 100644 --- a/dhfkit/tests/test_dangling_covers_the_schema.py +++ b/dhfkit/tests/test_dangling_covers_the_schema.py @@ -26,7 +26,7 @@ from dhfkit.item_store import ItemStore from dhfkit.traceability import find_dangling_links from dhfkit.models.config import ProjectConfig -from medharness.workflows.init import _replace_placeholders, _scaffold_dhf +from medharness.scaffold import replace_placeholders, scaffold_dhf TEMPLATES = Path(__file__).resolve().parents[1] / "templates" / "config" / "doc_types" @@ -51,8 +51,8 @@ def test_the_scan_found_fields() -> None: @pytest.mark.parametrize("field,code", FIELDS, ids=[f for f, _ in FIELDS]) def test_a_dangling_link_is_detected(field: str, code: str, tmp_path: Path) -> None: - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "Dangling") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "Dangling") dhf = tmp_path / "DHF" target = next(((dhf / "items").rglob(f"{code}-*.yaml")), None) @@ -78,15 +78,15 @@ def test_a_dangling_link_is_detected(field: str, code: str, tmp_path: Path) -> N class TestTheFieldSetComesFromTheSchema: def test_config_reports_every_declared_relationship(self, tmp_path: Path) -> None: - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "Derived") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "Derived") config = ProjectConfig.load(tmp_path / "DHF" / "config") assert config.relationship_fields() == {f for f, _ in FIELDS} def test_a_project_defined_field_is_picked_up(self, tmp_path: Path) -> None: """The point of deriving: a field this package has never heard of.""" - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "Custom") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "Custom") dhf = tmp_path / "DHF" from dhfkit.paths import DEFAULT_CONFIG_DIR diff --git a/dhfkit/tests/test_duplicate_item_ids.py b/dhfkit/tests/test_duplicate_item_ids.py index 7ced01d0..9601920b 100644 --- a/dhfkit/tests/test_duplicate_item_ids.py +++ b/dhfkit/tests/test_duplicate_item_ids.py @@ -22,13 +22,13 @@ import dhfkit.api as api from dhfkit.item_store import ItemStore -from medharness.workflows.init import _replace_placeholders, _scaffold_dhf +from medharness.scaffold import replace_placeholders, scaffold_dhf @pytest.fixture def dhf(tmp_path: Path) -> Path: - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "Duplicates") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "Duplicates") return tmp_path / "DHF" diff --git a/dhfkit/tests/test_ids_are_never_reused.py b/dhfkit/tests/test_ids_are_never_reused.py index 7d977615..015e1611 100644 --- a/dhfkit/tests/test_ids_are_never_reused.py +++ b/dhfkit/tests/test_ids_are_never_reused.py @@ -17,7 +17,7 @@ import pytest import dhfkit.api as api -from medharness.workflows.init import _replace_placeholders, _scaffold_dhf +from medharness.scaffold import replace_placeholders, scaffold_dhf def _git(root: Path, *args: str) -> None: @@ -31,8 +31,8 @@ def _commit(root: Path, message: str) -> None: @pytest.fixture def repo(tmp_path: Path) -> Path: - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "Reuse") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "Reuse") _git(tmp_path, "init", "-q") _commit(tmp_path, "scaffold") return tmp_path @@ -81,8 +81,8 @@ def test_ids_are_still_sequential_without_deletions(self, repo: Path) -> None: class TestOutsideGit: def test_a_dhf_with_no_repository_still_creates_items(self, tmp_path: Path) -> None: """History is unavailable there; behave as before rather than fail.""" - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "NoGit") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "NoGit") dhf = tmp_path / "DHF" assert _new_srs(dhf, "a").startswith("SRS-") assert _new_srs(dhf, "b").startswith("SRS-") diff --git a/dhfkit/tests/test_item_writes_are_atomic.py b/dhfkit/tests/test_item_writes_are_atomic.py index 27749196..15304870 100644 --- a/dhfkit/tests/test_item_writes_are_atomic.py +++ b/dhfkit/tests/test_item_writes_are_atomic.py @@ -22,13 +22,13 @@ import dhfkit.api as api from dhfkit.item_store import ItemStore -from medharness.workflows.init import _replace_placeholders, _scaffold_dhf +from medharness.scaffold import replace_placeholders, scaffold_dhf @pytest.fixture def dhf(tmp_path: Path) -> Path: - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "Atomic") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "Atomic") return tmp_path / "DHF" @@ -86,8 +86,8 @@ class TestDhfkitDoesNotCommit: """ def _repo(self, tmp_path: Path) -> Path: - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "Labels") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "Labels") for args in (["init", "-q"], ["add", "-A"], ["-c", "user.email=t@e", "-c", "user.name=t", "commit", "-qm", "scaffold"]): diff --git a/dhfkit/tests/test_lifecycle_states_resolve.py b/dhfkit/tests/test_lifecycle_states_resolve.py index 44a6ef91..98c087c3 100644 --- a/dhfkit/tests/test_lifecycle_states_resolve.py +++ b/dhfkit/tests/test_lifecycle_states_resolve.py @@ -17,7 +17,7 @@ import yaml import dhfkit.api as api -from medharness.workflows.init import _replace_placeholders, _scaffold_dhf +from medharness.scaffold import replace_placeholders, scaffold_dhf TEMPLATES = Path(__file__).resolve().parents[1] / "templates" / "config" @@ -59,8 +59,8 @@ class TestTheScaffoldedCRCanReachCompleted: def test_the_full_path(self, tmp_path: Path) -> None: """Closing needs the record that closure is supposed to mean.""" - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "Lifecycle") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "Lifecycle") dhf = tmp_path / "DHF" for state in ("design", "develop"): api.transition_item(dhf, "CR-001", state) @@ -83,8 +83,8 @@ def test_updating_a_field_does_not_undo_the_lifecycle(self, tmp_path: Path) -> N Filling in a CR's required fields sent it back to `new`, so the criteria above could never be satisfied — the act of qualifying disqualified it. """ - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "Lifecycle") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "Lifecycle") dhf = tmp_path / "DHF" api.transition_item(dhf, "CR-001", "design") api.update_item(dhf, "CR-001", {"implementation_notes": "x"}) @@ -92,13 +92,13 @@ def test_updating_a_field_does_not_undo_the_lifecycle(self, tmp_path: Path) -> N def test_an_undefined_state_is_still_refused(self, tmp_path: Path) -> None: """Widening the state list must not make the machine permissive.""" - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "Lifecycle") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "Lifecycle") with pytest.raises(ValueError): api.transition_item(tmp_path / "DHF", "CR-001", "nonexistent") def test_skipping_a_state_is_still_refused(self, tmp_path: Path) -> None: - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "Lifecycle") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "Lifecycle") with pytest.raises(ValueError): api.transition_item(tmp_path / "DHF", "CR-001", "completed") diff --git a/dhfkit/tests/test_network_calls_have_timeouts.py b/dhfkit/tests/test_network_calls_have_timeouts.py index e3a28f8d..4a263124 100644 --- a/dhfkit/tests/test_network_calls_have_timeouts.py +++ b/dhfkit/tests/test_network_calls_have_timeouts.py @@ -104,7 +104,7 @@ def _text_io_without_encoding() -> list[tuple[str, int, str]]: def test_the_encoding_scan_sees_real_calls() -> None: - text = (ROOT / "medharness" / "workflows" / "init.py").read_text(encoding="utf-8") + text = (ROOT / "medharness" / "scaffold.py").read_text(encoding="utf-8") assert "read_text(" in text, "the probe file no longer does text IO" diff --git a/dhfkit/tests/test_stale_global_config.py b/dhfkit/tests/test_stale_global_config.py index 006885ab..f1436f29 100644 --- a/dhfkit/tests/test_stale_global_config.py +++ b/dhfkit/tests/test_stale_global_config.py @@ -24,7 +24,7 @@ import yaml import dhfkit.api as api -from medharness.workflows.init import _replace_placeholders, _scaffold_dhf +from medharness.scaffold import replace_placeholders, scaffold_dhf @pytest.fixture @@ -32,8 +32,8 @@ def aged(tmp_path: Path) -> Path: """A DHF whose own lifecycle override predates the states its doc types use.""" from dhfkit.paths import DEFAULT_CONFIG_DIR - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "Aged") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "Aged") dhf = tmp_path / "DHF" config = dhf / "config" / "global.yaml" data = yaml.safe_load(config.read_text()) diff --git a/medharness/cli/build.py b/medharness/cli/build.py index 865e6e94..9551333e 100644 --- a/medharness/cli/build.py +++ b/medharness/cli/build.py @@ -109,7 +109,7 @@ def change_plan(ctx: click.Context, cr_id: str, pr_number: int | None, since_ref pushes to that PR, revising when a reviewer asked for changes. """ from medharness.services.cr_generation import generate_dhf # noqa: PLC0415 - from medharness.workflows.cr_state import assert_cr_active # noqa: PLC0415 + from medharness.services.cr_state import assert_cr_active # noqa: PLC0415 dhf: Path = ctx.obj["dhf"] try: store = open_store(ctx.obj["dhf"]) @@ -167,7 +167,7 @@ def change_implement(ctx: click.Context, cr_id: str, pr_number: int | None, pushes to that PR, revising when a reviewer asked for changes. """ from medharness.services.cr_generation import generate_code # noqa: PLC0415 - from medharness.workflows.cr_state import assert_cr_active # noqa: PLC0415 + from medharness.services.cr_state import assert_cr_active # noqa: PLC0415 dhf: Path = ctx.obj["dhf"] try: store = open_store(ctx.obj["dhf"]) diff --git a/medharness/cli/init.py b/medharness/cli/init.py index de881371..5b40e6cb 100644 --- a/medharness/cli/init.py +++ b/medharness/cli/init.py @@ -12,5 +12,5 @@ def init_cmd() -> None: Takes no prompts — the project name is derived from the directory name. Existing files are not overwritten. """ - from medharness.workflows.init import run_init + from medharness.scaffold import run_init click.echo(json.dumps(run_init())) diff --git a/medharness/workflows/init.py b/medharness/scaffold.py similarity index 97% rename from medharness/workflows/init.py rename to medharness/scaffold.py index fe0c027e..d5c685f2 100644 --- a/medharness/workflows/init.py +++ b/medharness/scaffold.py @@ -43,7 +43,7 @@ # DHF scaffold # --------------------------------------------------------------------------- -def _scaffold_dhf(project_dir: Path) -> None: +def scaffold_dhf(project_dir: Path) -> None: """Create DHF structure inside project_dir from bundled templates.""" project_dir.mkdir(parents=True, exist_ok=True) @@ -93,7 +93,7 @@ def _substitutable_files(project_dir: Path): yield Path(dirpath) / filename -def _replace_placeholders(project_dir: Path, project_name: str) -> None: +def replace_placeholders(project_dir: Path, project_name: str) -> None: """Substitute template placeholders in scaffolded content.""" try: medharness_version = pkg_version("medharness") @@ -252,8 +252,8 @@ def say(text: str = "", **style) -> None: before = {f for f in project_dir.rglob("*") if f.is_file()} steps = [ - ("Scaffold DHF structure", lambda: (_scaffold_dhf(project_dir), - _replace_placeholders(project_dir, project_name))), + ("Scaffold DHF structure", lambda: (scaffold_dhf(project_dir), + replace_placeholders(project_dir, project_name))), ("Write AGENTS.md and CLAUDE.md", lambda: _write_agent_files(project_dir, project_name)), ("Write .gitignore", lambda: _write_gitignore(project_dir)), ] diff --git a/medharness/services/cr_generation.py b/medharness/services/cr_generation.py index 80b5c58a..d4ec0af3 100644 --- a/medharness/services/cr_generation.py +++ b/medharness/services/cr_generation.py @@ -345,11 +345,11 @@ def generate_code( if code_review_verdict != "needs_revision" or review_cycle >= _MAX_CODE_REVIEW_CYCLES: break + issues = "\n".join(f"- {issue}" for issue in review_data["issues"]) run.call( f"run_code_fix_{review_cycle}", - f"The code review for {cr_id} found issues. " - f"Fix each issue flagged in the review above — modify only the affected files. " - f"Do not make unrelated changes.", + f"The code review for {cr_id} found issues:\n{issues}\n\n" + f"Fix each one — modify only the affected files. Do not make unrelated changes.", develop_llm, critical=False, ) diff --git a/medharness/workflows/cr_state.py b/medharness/services/cr_state.py similarity index 100% rename from medharness/workflows/cr_state.py rename to medharness/services/cr_state.py diff --git a/medharness/services/stage_run.py b/medharness/services/stage_run.py index bfff440f..9b96ece6 100644 --- a/medharness/services/stage_run.py +++ b/medharness/services/stage_run.py @@ -241,9 +241,8 @@ def _augment_review_prompt(base: str, errors: list[dict]) -> str: if not errors: return base + ( "\n\n## Deterministic Checks (already passed)\n\n" - "Schema, traceability, and the presence of all spec `affected_items` " - "(or required `@links:` test annotations) have been verified " - "mechanically. Do not re-derive them — focus on judgment questions " + "Schema, traceability, and the CR's recorded `affected_items` " + "have been verified mechanically. Do not re-derive them — focus on judgment questions " "that a script cannot answer." ) residual = "\n".join(f"- {e.get('field', '?')}: {e.get('issue', '')}" for e in errors) diff --git a/medharness/workflows/__init__.py b/medharness/workflows/__init__.py deleted file mode 100644 index 66b7ed14..00000000 --- a/medharness/workflows/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# workflows package diff --git a/pyproject.toml b/pyproject.toml index 96734830..a3178366 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "medharness" -version = "0.54.0" +version = "0.54.1" description = "Open-source design-controlled development infrastructure for medical device and SaMD teams — includes dhfkit" readme = "README.md" license = { text = "MIT" } diff --git a/scripts/audit_oss_delivery.sh b/scripts/audit_oss_delivery.sh index a83a40e7..dbde7427 100755 --- a/scripts/audit_oss_delivery.sh +++ b/scripts/audit_oss_delivery.sh @@ -198,7 +198,7 @@ else fi # Init no longer does git clone or pip install -e dhf/ -if file_has_pattern "pip install -e dhf/" medharness/workflows/init.py; then +if file_has_pattern "pip install -e dhf/" medharness/scaffold.py; then fail "init still references pip install -e dhf/ (no longer needed)" else pass "init does not reference separate dhfkit install" @@ -227,28 +227,28 @@ fi echo "=== 4. SCAFFOLD-CONTRACT AUDIT ===" # init must scaffold from local bundled templates, not remote fetch -if file_has_pattern "_scaffold_dhf|scaffolds from" medharness/workflows/init.py; then +if file_has_pattern "scaffold_dhf|scaffolds from" medharness/scaffold.py; then pass "init scaffolds DHF from local templates" else fail "init missing local scaffold logic" fi # init must NOT reference git clone or remote fetch -if file_has_pattern "git clone|DHF_TEMPLATE_REPO|_fetch_dhf_template" medharness/workflows/init.py; then +if file_has_pattern "git clone|DHF_TEMPLATE_REPO|_fetch_dhf_template" medharness/scaffold.py; then fail "init still references remote DHF fetch" else pass "init has no remote DHF fetch logic" fi # Generated workflows: no compliance-check -if file_has_pattern 'ci compliance-check' medharness/workflows/init.py; then +if file_has_pattern 'ci compliance-check' medharness/scaffold.py; then fail "generated workflow references ci compliance-check" else pass "generated workflows: no compliance-check references" fi # CLAUDE.md template points to existing docs -if file_has_pattern 'README.md' medharness/workflows/init.py; then +if file_has_pattern 'README.md' medharness/scaffold.py; then pass "CLAUDE.md template references README.md" fi diff --git a/tests/contract/conftest.py b/tests/contract/conftest.py index 14ad1dff..005d8f14 100644 --- a/tests/contract/conftest.py +++ b/tests/contract/conftest.py @@ -5,7 +5,7 @@ import pytest -from medharness.workflows.init import _scaffold_dhf, _replace_placeholders +from medharness.scaffold import scaffold_dhf, replace_placeholders @pytest.fixture(scope="module") @@ -13,6 +13,6 @@ def scaffolded_dhf(): """Scaffold a temp DHF repo from templates — the integration test target.""" with tempfile.TemporaryDirectory() as tmp: dhf_dir = Path(tmp) / "starter-dhf" - _scaffold_dhf(dhf_dir) - _replace_placeholders(dhf_dir, "Test Project") + scaffold_dhf(dhf_dir) + replace_placeholders(dhf_dir, "Test Project") yield dhf_dir diff --git a/tests/contract/test_reference_project.py b/tests/contract/test_reference_project.py index d17598ae..41f2e536 100644 --- a/tests/contract/test_reference_project.py +++ b/tests/contract/test_reference_project.py @@ -12,7 +12,7 @@ import pytest -from medharness.workflows.init import _scaffold_dhf, _replace_placeholders +from medharness.scaffold import scaffold_dhf, replace_placeholders REPO_ROOT = Path(__file__).resolve().parent.parent.parent @@ -23,8 +23,8 @@ def dhf(): """Scaffold a fresh DHF from templates for each test.""" with tempfile.TemporaryDirectory() as tmp: dhf_dir = Path(tmp) / "starter-dhf" - _scaffold_dhf(dhf_dir) - _replace_placeholders(dhf_dir, "Test Project") + scaffold_dhf(dhf_dir) + replace_placeholders(dhf_dir, "Test Project") yield dhf_dir diff --git a/tests/contract/test_scaffold_snapshot.py b/tests/contract/test_scaffold_snapshot.py index e25425a9..ba22de9b 100644 --- a/tests/contract/test_scaffold_snapshot.py +++ b/tests/contract/test_scaffold_snapshot.py @@ -10,7 +10,7 @@ import pytest -from medharness.workflows.init import _scaffold_dhf, _replace_placeholders +from medharness.scaffold import scaffold_dhf, replace_placeholders class TestScaffoldStructure: @@ -48,8 +48,8 @@ class TestScaffoldStructure: def scaffolded(self): with tempfile.TemporaryDirectory() as tmp: dhf_dir = Path(tmp) / "test-dhf" - _scaffold_dhf(dhf_dir) - _replace_placeholders(dhf_dir, "Test Project") + scaffold_dhf(dhf_dir) + replace_placeholders(dhf_dir, "Test Project") yield dhf_dir def test_core_directories_exist(self, scaffolded): @@ -136,9 +136,9 @@ def test_double_scaffold_does_not_crash(self): """ with tempfile.TemporaryDirectory() as tmp: dhf_dir = Path(tmp) / "test-dhf" - _scaffold_dhf(dhf_dir) + scaffold_dhf(dhf_dir) first = sorted(p.relative_to(dhf_dir) for p in dhf_dir.rglob("*") if p.is_file()) - _scaffold_dhf(dhf_dir) # should not raise + scaffold_dhf(dhf_dir) # should not raise second = sorted(p.relative_to(dhf_dir) for p in dhf_dir.rglob("*") if p.is_file()) # "does not crash" is not the property that matters: the scaffold diff --git a/tests/guards/test_a_malformed_dhf_is_named_not_crashed_on.py b/tests/guards/test_a_malformed_dhf_is_named_not_crashed_on.py index 4009352d..3a3150e5 100644 --- a/tests/guards/test_a_malformed_dhf_is_named_not_crashed_on.py +++ b/tests/guards/test_a_malformed_dhf_is_named_not_crashed_on.py @@ -25,7 +25,7 @@ import pytest -from medharness.workflows.init import _replace_placeholders, _scaffold_dhf +from medharness.scaffold import replace_placeholders, scaffold_dhf ROOT = Path(__file__).resolve().parents[2] @@ -71,8 +71,8 @@ def _commit_with_origin_main(root: Path) -> None: @pytest.fixture def broken(request, tmp_path: Path) -> tuple[Path, Path]: - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "Broken") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "Broken") _commit_with_origin_main(tmp_path) dhf = tmp_path / "DHF" locate, content = FAULTS[request.param] @@ -129,8 +129,8 @@ def test_a_broken_item_does_not_leave_the_denominator(tmp_path: Path) -> None: Before, `verify tests` still produced an answer — a smaller one. Asserting the command stops is what rules that out; this names why it matters. """ - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "Shrink") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "Shrink") dhf = tmp_path / "DHF" _first_srs(dhf).write_text("- x\n", encoding="utf-8") diff --git a/tests/guards/test_every_command_answers_in_json.py b/tests/guards/test_every_command_answers_in_json.py index f280b08a..8b7c9acd 100644 --- a/tests/guards/test_every_command_answers_in_json.py +++ b/tests/guards/test_every_command_answers_in_json.py @@ -19,7 +19,7 @@ import pytest -from medharness.workflows.init import _replace_placeholders, _scaffold_dhf +from medharness.scaffold import replace_placeholders, scaffold_dhf #: (module, args). Each must exit 0 or 1 and put JSON on stdout. COMMANDS = [ @@ -37,8 +37,8 @@ @pytest.fixture(scope="module") def project(tmp_path_factory) -> Path: root = tmp_path_factory.mktemp("project") - _scaffold_dhf(root) - _replace_placeholders(root, "Json") + scaffold_dhf(root) + replace_placeholders(root, "Json") (root / "test-results").mkdir() for args in (["init", "-q"], ["add", "-A"], ["-c", "user.email=t@e", "-c", "user.name=t", "commit", "-qm", "base"]): diff --git a/tests/guards/test_package_boundary.py b/tests/guards/test_package_boundary.py index 289536d1..9ab04418 100644 --- a/tests/guards/test_package_boundary.py +++ b/tests/guards/test_package_boundary.py @@ -101,9 +101,9 @@ def test_the_adapter_exposes_its_config(self) -> None: def test_the_api_exposes_it_by_path(self, tmp_path: Path) -> None: import dhfkit.api as api - from medharness.workflows.init import _replace_placeholders, _scaffold_dhf + from medharness.scaffold import replace_placeholders, scaffold_dhf - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "Boundary") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "Boundary") config = api.get_config(tmp_path / "DHF") assert config.project_name == "Boundary" diff --git a/tests/guards/test_packaging_templates.py b/tests/guards/test_packaging_templates.py index 9d03de67..96e48b2c 100644 --- a/tests/guards/test_packaging_templates.py +++ b/tests/guards/test_packaging_templates.py @@ -1,7 +1,7 @@ """Guards on what the wheel does and does not carry. The CI workflow is deliberately not part of the release payload — adopters copy -it from `docs/adopting.md` and own it. That policy had drifted: `_scaffold_dhf` +it from `docs/adopting.md` and own it. That policy had drifted: `scaffold_dhf` copied the template, `_UPGRADE_MAP` claimed to manage it, and the README showed it as init output, while `exclude-package-data` kept it out of the wheel. So `init` silently created no workflow and `upgrade` reported "all up to date" diff --git a/tests/guards/test_storage_access_is_bounded.py b/tests/guards/test_storage_access_is_bounded.py index 30a6cb2a..be62b593 100644 --- a/tests/guards/test_storage_access_is_bounded.py +++ b/tests/guards/test_storage_access_is_bounded.py @@ -23,7 +23,7 @@ MEDHARNESS = ROOT / "medharness" #: Scaffolding: creating a DHF and keeping its skeleton current. -SCAFFOLD = {"medharness/workflows/init.py"} +SCAFFOLD = {"medharness/scaffold.py"} #: A path *built* into the DHF: the `/` join operator followed by one of its #: directories. A JSON key, a git argument, a variable named doc_types, and a @@ -71,10 +71,10 @@ class TestTheStoreCanAnswerWhatWasAskedOfTheFilesystem: def _adapter(self, tmp_path: Path): from dhfkit.item_store import ItemStore - from medharness.workflows.init import _replace_placeholders, _scaffold_dhf + from medharness.scaffold import replace_placeholders, scaffold_dhf - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "Bounded") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "Bounded") # The scaffold ships no plans; a project writes its own, and this asks # whether the store can find one once it exists. plans = tmp_path / "DHF" / "documents" / "plans" diff --git a/tests/integration/test_a_link_must_point_at_an_item.py b/tests/integration/test_a_link_must_point_at_an_item.py index ffe270ec..002b861b 100644 --- a/tests/integration/test_a_link_must_point_at_an_item.py +++ b/tests/integration/test_a_link_must_point_at_an_item.py @@ -13,13 +13,13 @@ from click.testing import CliRunner from medharness.cli import main -from medharness.workflows.init import _replace_placeholders, _scaffold_dhf +from medharness.scaffold import replace_placeholders, scaffold_dhf @pytest.fixture def dhf(tmp_path: Path) -> Path: - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "Links") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "Links") return tmp_path / "DHF" diff --git a/tests/integration/test_a_store_that_is_not_files.py b/tests/integration/test_a_store_that_is_not_files.py index 71750d2e..d03bfbde 100644 --- a/tests/integration/test_a_store_that_is_not_files.py +++ b/tests/integration/test_a_store_that_is_not_files.py @@ -18,7 +18,7 @@ import dhfkit.adapter as adapter_module from dhfkit.local_adapter import LocalDHFAdapter from medharness.cli import main -from medharness.workflows.init import _replace_placeholders, _scaffold_dhf +from medharness.scaffold import replace_placeholders, scaffold_dhf class MemoryAdapter: @@ -59,8 +59,8 @@ def load(self): @pytest.fixture def memory_project(tmp_path: Path, monkeypatch: pytest.MonkeyPatch): """A scaffolded project whose items live in memory, none on disk.""" - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "Memory") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "Memory") dhf = tmp_path / "DHF" yaml_adapter = LocalDHFAdapter(dhf / "items", None) items = {i.uid: i for i in yaml_adapter.load_all()} @@ -123,8 +123,8 @@ def test_verify_dhf_gives_the_same_verdict_as_on_files(self, memory_project, tmp dhf, _ = memory_project on_memory = json.loads(_run(dhf, "verify", "dhf").stdout) plain = tmp_path_factory.mktemp("plain") - _scaffold_dhf(plain) - _replace_placeholders(plain, "Memory") + scaffold_dhf(plain) + replace_placeholders(plain, "Memory") on_files = json.loads(_run(plain / "DHF", "verify", "dhf").stdout) assert on_memory["passed"] == on_files["passed"] assert on_memory["summary"] == on_files["summary"] @@ -146,7 +146,7 @@ def test_a_command_that_reads_the_diff_refuses_with_the_reason(self, memory_proj class TestChoosingAStore: def test_an_uninstalled_store_names_what_is_installed(self, tmp_path: Path, monkeypatch) -> None: - _scaffold_dhf(tmp_path) + scaffold_dhf(tmp_path) monkeypatch.setattr(adapter_module, "entry_points", lambda group: []) global_yaml = tmp_path / "DHF" / "config" / "global.yaml" global_yaml.write_text(global_yaml.read_text() + "\nstore:\n type: jira\n") @@ -155,7 +155,7 @@ def test_an_uninstalled_store_names_what_is_installed(self, tmp_path: Path, monk assert "'jira' " in result.stderr and "not installed" in result.stderr and "yaml" in result.stderr def test_no_store_key_means_yaml_files(self, tmp_path: Path) -> None: - _scaffold_dhf(tmp_path) + scaffold_dhf(tmp_path) result = _run(tmp_path / "DHF", "item", "list", "--type", "SRS") assert result.exit_code == 0 and result.stdout.strip() assert yaml.safe_load((tmp_path / "DHF" / "config" / "global.yaml").read_text()).get("store") is None diff --git a/tests/integration/test_a_sys_needs_an_origin.py b/tests/integration/test_a_sys_needs_an_origin.py index 4f4c768a..2b2b34de 100644 --- a/tests/integration/test_a_sys_needs_an_origin.py +++ b/tests/integration/test_a_sys_needs_an_origin.py @@ -16,13 +16,13 @@ from fixtures.starter import keep_the_starter_text from medharness.cli import main -from medharness.workflows.init import _replace_placeholders, _scaffold_dhf +from medharness.scaffold import replace_placeholders, scaffold_dhf @pytest.fixture def dhf(tmp_path: Path) -> Path: - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "Origin") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "Origin") keep_the_starter_text(tmp_path / "DHF") return tmp_path / "DHF" diff --git a/tests/integration/test_ai_stages_end_to_end.py b/tests/integration/test_ai_stages_end_to_end.py index f69ad7bb..3b7e348b 100644 --- a/tests/integration/test_ai_stages_end_to_end.py +++ b/tests/integration/test_ai_stages_end_to_end.py @@ -21,7 +21,7 @@ from medharness.cli import main from medharness.results import CodeReport, PlanReport -from medharness.workflows.init import _replace_placeholders, _scaffold_dhf +from medharness.scaffold import replace_placeholders, scaffold_dhf FAKE = Path(__file__).resolve().parents[1] / "fixtures" / "fake_claude.py" MH = f"{shlex.quote(sys.executable)} -m medharness --dhf DHF" @@ -40,8 +40,8 @@ def project(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path: subprocess.run(["git", "clone", "-q", str(remote), str(work)], check=True, capture_output=True) _git(work, "config", "user.email", "ci@example.com") _git(work, "config", "user.name", "ci") - _scaffold_dhf(work) - _replace_placeholders(work, "Ai") + scaffold_dhf(work) + replace_placeholders(work, "Ai") _git(work, "add", "-A") _git(work, "commit", "-qm", "base") _git(work, "push", "-q", "origin", "HEAD:main") @@ -457,6 +457,8 @@ def test_a_review_that_asks_for_changes_sends_the_model_back_until_it_approves( assert result.exit_code == 0, result.stderr assert [c["stage"] for c in _calls(project)] == ["develop", "code_review", "code_fix", "code_review"] assert "empty report handled" in (project / "src" / "pdf.py").read_text() + fix = next(c for c in _calls(project) if c["stage"] == "code_fix") + assert "no test for an empty report" in fix["prompt"], "a model with no session must still be told the issue" def test_a_review_that_never_approves_stops_after_three_rounds( self, project: Path, tmp_path: Path, diff --git a/tests/integration/test_an_empty_matrix_list_turns_coverage_off.py b/tests/integration/test_an_empty_matrix_list_turns_coverage_off.py index e745c412..ae9639c3 100644 --- a/tests/integration/test_an_empty_matrix_list_turns_coverage_off.py +++ b/tests/integration/test_an_empty_matrix_list_turns_coverage_off.py @@ -14,13 +14,13 @@ from fixtures.starter import keep_the_starter_text from medharness.cli import main -from medharness.workflows.init import _replace_placeholders, _scaffold_dhf +from medharness.scaffold import replace_placeholders, scaffold_dhf @pytest.fixture def dhf(tmp_path: Path) -> Path: - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "Matrices") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "Matrices") keep_the_starter_text(tmp_path / "DHF") next((tmp_path / "DHF" / "items").rglob("SWDD-001.yaml")).unlink() # SRS-001 is now uncovered return tmp_path / "DHF" diff --git a/tests/integration/test_change_impact.py b/tests/integration/test_change_impact.py index 365ba7ed..383bdbab 100644 --- a/tests/integration/test_change_impact.py +++ b/tests/integration/test_change_impact.py @@ -17,7 +17,7 @@ from fixtures.starter import keep_the_starter_text from medharness.cli import main -from medharness.workflows.init import _replace_placeholders, _scaffold_dhf +from medharness.scaffold import replace_placeholders, scaffold_dhf def _git(repo: Path, *args: str) -> None: @@ -26,8 +26,8 @@ def _git(repo: Path, *args: str) -> None: @pytest.fixture def repo(tmp_path: Path) -> Path: - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "Impact") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "Impact") keep_the_starter_text(tmp_path / "DHF") _git(tmp_path, "init", "-q", "-b", "main") _git(tmp_path, "config", "user.email", "t@example.com") diff --git a/tests/integration/test_ci_pipeline_gates.py b/tests/integration/test_ci_pipeline_gates.py index 5f466292..627e9acd 100644 --- a/tests/integration/test_ci_pipeline_gates.py +++ b/tests/integration/test_ci_pipeline_gates.py @@ -19,7 +19,7 @@ import pytest -from medharness.workflows.init import _scaffold_dhf, _replace_placeholders +from medharness.scaffold import scaffold_dhf, replace_placeholders REPO_ROOT = Path(__file__).resolve().parent.parent.parent @@ -33,8 +33,8 @@ def dhf(): """Scaffold a DHF with an initialised git repo for all gate tests.""" with tempfile.TemporaryDirectory() as tmp: dhf_dir = Path(tmp) / "gate-dhf" - _scaffold_dhf(dhf_dir) - _replace_placeholders(dhf_dir, "Gate Test Project") + scaffold_dhf(dhf_dir) + replace_placeholders(dhf_dir, "Gate Test Project") for cmd in [ ["git", "init", "-b", "main", str(dhf_dir)], ["git", "-C", str(dhf_dir), "config", "user.email", "gate@test.local"], diff --git a/tests/integration/test_commands_on_bad_input.py b/tests/integration/test_commands_on_bad_input.py index 37d2a15b..77b4da2e 100644 --- a/tests/integration/test_commands_on_bad_input.py +++ b/tests/integration/test_commands_on_bad_input.py @@ -20,13 +20,13 @@ from medharness.cli import main from fixtures.starter import keep_the_starter_text from medharness.results import GateResult, InitReport, ReleaseReport -from medharness.workflows.init import _replace_placeholders, _scaffold_dhf +from medharness.scaffold import replace_placeholders, scaffold_dhf @pytest.fixture def project(tmp_path: Path) -> Path: - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "Edge") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "Edge") subprocess.run(["git", "init", "-q"], cwd=tmp_path, check=True) return tmp_path diff --git a/tests/integration/test_completion_reads_the_types_from_config.py b/tests/integration/test_completion_reads_the_types_from_config.py index 4d200420..b73430fe 100644 --- a/tests/integration/test_completion_reads_the_types_from_config.py +++ b/tests/integration/test_completion_reads_the_types_from_config.py @@ -14,7 +14,7 @@ from click.testing import CliRunner from medharness.cli import main -from medharness.workflows.init import _replace_placeholders, _scaffold_dhf +from medharness.scaffold import replace_placeholders, scaffold_dhf HWR = { "code": "HWR", "name": "Hardware Requirement", "prefix": "HWR-", "directory": "hwr", @@ -26,8 +26,8 @@ def _project(tmp_path: Path, *, verification_method: list[str] | None) -> Path: - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "Hw") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "Hw") dhf = tmp_path / "DHF" (dhf / "config" / "doc_types").mkdir(exist_ok=True) (dhf / "config" / "doc_types" / "hwr.yaml").write_text(yaml.safe_dump(HWR)) diff --git a/tests/integration/test_full_cr_lifecycle.py b/tests/integration/test_full_cr_lifecycle.py index 1243c36d..7c54390d 100644 --- a/tests/integration/test_full_cr_lifecycle.py +++ b/tests/integration/test_full_cr_lifecycle.py @@ -17,7 +17,7 @@ import pytest -from medharness.workflows.init import _scaffold_dhf, _replace_placeholders +from medharness.scaffold import scaffold_dhf, replace_placeholders REPO_ROOT = Path(__file__).resolve().parent.parent.parent @@ -31,8 +31,8 @@ def dhf(): """ with tempfile.TemporaryDirectory() as tmp: dhf_dir = Path(tmp) / "lifecycle-dhf" - _scaffold_dhf(dhf_dir) - _replace_placeholders(dhf_dir, "Lifecycle Test Project") + scaffold_dhf(dhf_dir) + replace_placeholders(dhf_dir, "Lifecycle Test Project") subprocess.run( ["git", "init", "-b", "main", str(dhf_dir)], capture_output=True, check=True, @@ -202,7 +202,7 @@ class TestCRPhaseEnum: """Unit-level checks for the CRPhase state machine helpers.""" def test_active_phases_are_correct(self): - from medharness.workflows.cr_state import CRPhase, ACTIVE_PHASES, TERMINAL_PHASES + from medharness.services.cr_state import CRPhase, ACTIVE_PHASES, TERMINAL_PHASES assert CRPhase.NEW in ACTIVE_PHASES assert CRPhase.DESIGN in ACTIVE_PHASES assert CRPhase.DEVELOP in ACTIVE_PHASES @@ -210,6 +210,6 @@ def test_active_phases_are_correct(self): assert CRPhase.CANCELLED in TERMINAL_PHASES def test_phase_values_match_dhf_status_strings(self): - from medharness.workflows.cr_state import CRPhase + from medharness.services.cr_state import CRPhase assert CRPhase.NEW.value == "new" assert CRPhase.COMPLETED.value == "completed" diff --git a/tests/integration/test_generate_dhf_e2e.py b/tests/integration/test_generate_dhf_e2e.py index 53f206fc..f297a136 100644 --- a/tests/integration/test_generate_dhf_e2e.py +++ b/tests/integration/test_generate_dhf_e2e.py @@ -20,7 +20,7 @@ import pytest -from medharness.workflows.init import _scaffold_dhf, _replace_placeholders +from medharness.scaffold import scaffold_dhf, replace_placeholders REPO_ROOT = Path(__file__).resolve().parent.parent.parent @@ -55,8 +55,8 @@ def dhf_repo(): """Scaffold a DHF with a git repo initialised at origin/main.""" with tempfile.TemporaryDirectory() as tmp: dhf_dir = Path(tmp) / "e2e-dhf" - _scaffold_dhf(dhf_dir) - _replace_placeholders(dhf_dir, "E2E Test Project") + scaffold_dhf(dhf_dir) + replace_placeholders(dhf_dir, "E2E Test Project") _init_dhf_repo(dhf_dir, "E2E Test", "e2e@test.local") yield dhf_dir @@ -331,8 +331,8 @@ def test_run_review_step_present(self, dhf_repo): def _make_fix_pass_dhf(tmp: str) -> Path: """Scaffold an isolated DHF with origin/main wired for the fix-pass tests.""" dhf_dir = Path(tmp) / "fix-pass-dhf" - _scaffold_dhf(dhf_dir) - _replace_placeholders(dhf_dir, "Fix Pass Test") + scaffold_dhf(dhf_dir) + replace_placeholders(dhf_dir, "Fix Pass Test") _init_dhf_repo(dhf_dir, "Fix Pass Test", "fix@test.local") return dhf_dir diff --git a/tests/integration/test_init_cmd.py b/tests/integration/test_init_cmd.py index 0b5deabf..2d104e8d 100644 --- a/tests/integration/test_init_cmd.py +++ b/tests/integration/test_init_cmd.py @@ -10,9 +10,9 @@ import inspect -from medharness.workflows.init import ( - _scaffold_dhf, - _replace_placeholders, +from medharness.scaffold import ( + scaffold_dhf, + replace_placeholders, _write_agent_files, _write_gitignore, ) @@ -60,29 +60,29 @@ def test_existing_files_are_added_to_not_replaced(self, tmp_path): # ── placeholder substitution ───────────────────────────────────────────── def test_replace_placeholders_substitutes_project_name(self, tmp_path): - """_replace_placeholders substitutes {{project_name}} in DHF template files.""" + """replace_placeholders substitutes {{project_name}} in DHF template files.""" (tmp_path / "DHF").mkdir(parents=True) readme = tmp_path / "README.md" readme.write_text("# {{project_name}} DHF") - _replace_placeholders(tmp_path, "Test Device") + replace_placeholders(tmp_path, "Test Device") assert "Test Device" in readme.read_text() assert "{{project_name}}" not in readme.read_text() def test_replace_placeholders_substitutes_medharness_version(self, tmp_path): - """_replace_placeholders substitutes {{medharness_version}}.""" + """replace_placeholders substitutes {{medharness_version}}.""" (tmp_path / "DHF").mkdir(parents=True) wf = tmp_path / "workflow.yml" wf.write_text("pip install medharness=={{medharness_version}}") - _replace_placeholders(tmp_path, "Device") + replace_placeholders(tmp_path, "Device") assert "{{medharness_version}}" not in wf.read_text() def test_replace_placeholders_handles_missing_dir(self, tmp_path): - """_replace_placeholders handles directories with no substitutable files gracefully.""" + """replace_placeholders handles directories with no substitutable files gracefully.""" (tmp_path / "DHF").mkdir() untouched = tmp_path / "DHF" / "keep.txt" untouched.write_text("no placeholders here") - _replace_placeholders(tmp_path, "Device") + replace_placeholders(tmp_path, "Device") # A no-op would pass on "does not raise" alone; what matters is that it # left a file with nothing to substitute exactly as it was. @@ -109,8 +109,8 @@ def test_write_gitignore_skips_existing(self, tmp_path): # ── DHF scaffold ───────────────────────────────────────────────────────── def test_scaffold_uses_local_templates(self): - """_scaffold_dhf copies from bundled templates, not remote git.""" - src = inspect.getsource(_scaffold_dhf) + """scaffold_dhf copies from bundled templates, not remote git.""" + src = inspect.getsource(scaffold_dhf) assert "shutil.copytree" in src assert "_TEMPLATES_DIR" in src assert "git clone" not in src @@ -119,23 +119,23 @@ def test_scaffold_uses_local_templates(self): def test_scaffold_writes_nothing_that_nothing_reads(self, tmp_path): """`.github/prompts/*.md` and `AI-harness/context.md` were scaffolded and kept current by `upgrade`, and no code or prompt read them.""" - _scaffold_dhf(tmp_path) + scaffold_dhf(tmp_path) assert not (tmp_path / ".github").exists() assert not (tmp_path / "AI-harness").exists() def test_scaffold_omits_github_workflow(self, tmp_path): """CI is not in the release payload, so init must not claim to create it.""" - _scaffold_dhf(tmp_path) + scaffold_dhf(tmp_path) assert not (tmp_path / ".github" / "workflows").exists() def test_scaffold_creates_dhf_readme_inside_dhf(self, tmp_path): - """_scaffold_dhf places README inside DHF/, not at repo root.""" - _scaffold_dhf(tmp_path) + """scaffold_dhf places README inside DHF/, not at repo root.""" + scaffold_dhf(tmp_path) assert (tmp_path / "DHF" / "README.md").exists() def test_scaffold_creates_items_directories(self, tmp_path): - """_scaffold_dhf creates DHF item directories for all doc types.""" - _scaffold_dhf(tmp_path) + """scaffold_dhf creates DHF item directories for all doc types.""" + scaffold_dhf(tmp_path) for d in ("00_uc", "01_crs", "02_sys", "03_srs", "04_modules", "05_swdd", "07_cr"): assert (tmp_path / "DHF" / "items" / d).is_dir(), f"Missing items/{d}" @@ -143,7 +143,7 @@ def test_scaffold_creates_items_directories(self, tmp_path): def test_run_init_no_github_calls(self): """run_init makes no GitHub API or gh CLI calls.""" - from medharness.workflows.init import run_init + from medharness.scaffold import run_init src = inspect.getsource(run_init) assert "gh(" not in src assert "_repo_exists" not in src @@ -153,7 +153,7 @@ def test_run_init_no_github_calls(self): def test_run_init_no_prompts(self): """run_init contains no click.prompt calls — it is zero-prompt.""" - from medharness.workflows.init import run_init + from medharness.scaffold import run_init src = inspect.getsource(run_init) assert "click.prompt" not in src @@ -162,7 +162,7 @@ def test_the_ai_workflow_page_shows_the_section_init_writes(): """A project that predates AGENTS.md copies it from there.""" from pathlib import Path - from medharness.workflows.init import DHF_INSTRUCTIONS + from medharness.scaffold import DHF_INSTRUCTIONS adopting = (Path(__file__).resolve().parents[2] / "docs" / "ai-workflow.md").read_text() assert DHF_INSTRUCTIONS in adopting diff --git a/tests/integration/test_one_verification_model.py b/tests/integration/test_one_verification_model.py index f6523ac3..e8b00fcf 100644 --- a/tests/integration/test_one_verification_model.py +++ b/tests/integration/test_one_verification_model.py @@ -17,13 +17,13 @@ from fixtures.starter import keep_the_starter_text from medharness.cli import main -from medharness.workflows.init import _replace_placeholders, _scaffold_dhf +from medharness.scaffold import replace_placeholders, scaffold_dhf @pytest.fixture def dhf(tmp_path: Path) -> Path: - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "Verify") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "Verify") keep_the_starter_text(tmp_path / "DHF") for name, methods in (("CRS-001.yaml", ["Test"]), ("SYS-001.yaml", ["Test"]), ("SRS-001.yaml", ["Test"])): _set(tmp_path / "DHF", name, verification_method=methods, verification_criteria="c") diff --git a/tests/integration/test_placeholder_floor.py b/tests/integration/test_placeholder_floor.py index 3f188a2d..6b093405 100644 --- a/tests/integration/test_placeholder_floor.py +++ b/tests/integration/test_placeholder_floor.py @@ -13,12 +13,12 @@ from click.testing import CliRunner from medharness.cli import main -from medharness.workflows.init import _replace_placeholders, _scaffold_dhf +from medharness.scaffold import replace_placeholders, scaffold_dhf def _project(tmp_path: Path) -> Path: - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "Floor") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "Floor") return tmp_path / "DHF" diff --git a/tests/integration/test_release_reruns_the_gates.py b/tests/integration/test_release_reruns_the_gates.py index ec9fa62b..a26a21a4 100644 --- a/tests/integration/test_release_reruns_the_gates.py +++ b/tests/integration/test_release_reruns_the_gates.py @@ -17,12 +17,12 @@ from fixtures.starter import keep_the_starter_text from medharness.cli import main from medharness.results import ReleaseReport -from medharness.workflows.init import _replace_placeholders, _scaffold_dhf +from medharness.scaffold import replace_placeholders, scaffold_dhf def _project(tmp_path: Path, **cr_fields) -> Path: - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "Rel") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "Rel") dhf = tmp_path / "DHF" keep_the_starter_text(dhf) cr = next((dhf / "items").rglob("CR-001.yaml")) diff --git a/tests/integration/test_stages_leave_work_uncommitted.py b/tests/integration/test_stages_leave_work_uncommitted.py index 1827b805..95e5a727 100644 --- a/tests/integration/test_stages_leave_work_uncommitted.py +++ b/tests/integration/test_stages_leave_work_uncommitted.py @@ -18,7 +18,7 @@ import pytest -from medharness.workflows.init import _replace_placeholders, _scaffold_dhf +from medharness.scaffold import replace_placeholders, scaffold_dhf def _git(repo: Path, *args: str) -> str: @@ -28,8 +28,8 @@ def _git(repo: Path, *args: str) -> str: @pytest.fixture def repo(tmp_path: Path) -> Path: - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "Stages") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "Stages") _git(tmp_path, "init", "-q", "-b", "main") _git(tmp_path, "-c", "user.email=t@e", "-c", "user.name=t", "add", "-A") _git(tmp_path, "-c", "user.email=t@e", "-c", "user.name=t", "commit", "-qm", "base") diff --git a/tests/integration/test_status_is_a_state.py b/tests/integration/test_status_is_a_state.py index 6ededd97..8f4bf12a 100644 --- a/tests/integration/test_status_is_a_state.py +++ b/tests/integration/test_status_is_a_state.py @@ -17,13 +17,13 @@ from fixtures.starter import keep_the_starter_text from medharness.cli import main -from medharness.workflows.init import _replace_placeholders, _scaffold_dhf +from medharness.scaffold import replace_placeholders, scaffold_dhf @pytest.fixture def dhf(tmp_path: Path) -> Path: - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "States") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "States") keep_the_starter_text(tmp_path / "DHF") return tmp_path / "DHF" diff --git a/tests/integration/test_the_design_stage_reads_the_projects_chains.py b/tests/integration/test_the_design_stage_reads_the_projects_chains.py index 660843f1..6805f737 100644 --- a/tests/integration/test_the_design_stage_reads_the_projects_chains.py +++ b/tests/integration/test_the_design_stage_reads_the_projects_chains.py @@ -19,7 +19,7 @@ from medharness.cli import main from medharness.services.design_validation import _validate_cascade_completeness, cascade_children -from medharness.workflows.init import _replace_placeholders, _scaffold_dhf +from medharness.scaffold import replace_placeholders, scaffold_dhf def _doc_type(code: str, link: str | None = None, target: str | None = None) -> dict: @@ -34,8 +34,8 @@ def _doc_type(code: str, link: str | None = None, target: str | None = None) -> @pytest.fixture def dhf(tmp_path: Path) -> Path: """The starter DHF plus hardware requirements that refine SYS and are tested by TST.""" - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "Hw") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "Hw") dhf = tmp_path / "DHF" config = dhf / "config" (config / "doc_types").mkdir(exist_ok=True) diff --git a/tests/integration/test_typed_links.py b/tests/integration/test_typed_links.py index 7dbae698..5e24eb0a 100644 --- a/tests/integration/test_typed_links.py +++ b/tests/integration/test_typed_links.py @@ -18,13 +18,13 @@ from fixtures.starter import keep_the_starter_text from medharness.cli import main -from medharness.workflows.init import _replace_placeholders, _scaffold_dhf +from medharness.scaffold import replace_placeholders, scaffold_dhf @pytest.fixture def dhf(tmp_path: Path) -> Path: - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "Typed") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "Typed") keep_the_starter_text(tmp_path / "DHF") return tmp_path / "DHF" diff --git a/tests/integration/test_what_belongs_at_each_level.py b/tests/integration/test_what_belongs_at_each_level.py index e994bee3..d2a00863 100644 --- a/tests/integration/test_what_belongs_at_each_level.py +++ b/tests/integration/test_what_belongs_at_each_level.py @@ -16,15 +16,15 @@ import dhfkit from medharness.cli import main -from medharness.workflows.init import _replace_placeholders, _scaffold_dhf +from medharness.scaffold import replace_placeholders, scaffold_dhf DEFAULTS = Path(dhfkit.__file__).parent / "templates" / "config" / "doc_types" SECTION = "### What Belongs at Each Level" def _project(tmp_path: Path) -> Path: - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "Levels") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "Levels") return tmp_path / "DHF" diff --git a/tests/unit/test_a_cr_can_be_cancelled.py b/tests/unit/test_a_cr_can_be_cancelled.py index 5f56cff4..c6ed839f 100644 --- a/tests/unit/test_a_cr_can_be_cancelled.py +++ b/tests/unit/test_a_cr_can_be_cancelled.py @@ -13,14 +13,14 @@ import pytest from dhfkit.item_store import ItemStore -from medharness.workflows.cr_state import assert_cr_active -from medharness.workflows.init import _replace_placeholders, _scaffold_dhf +from medharness.services.cr_state import assert_cr_active +from medharness.scaffold import replace_placeholders, scaffold_dhf @pytest.fixture def adapter(tmp_path: Path) -> ItemStore: - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "Cancel") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "Cancel") return ItemStore(tmp_path / "DHF") diff --git a/tests/unit/test_a_running_agent_gets_the_same_instructions.py b/tests/unit/test_a_running_agent_gets_the_same_instructions.py index 419e844b..86c0b510 100644 --- a/tests/unit/test_a_running_agent_gets_the_same_instructions.py +++ b/tests/unit/test_a_running_agent_gets_the_same_instructions.py @@ -12,13 +12,13 @@ from click.testing import CliRunner from medharness.cli import main -from medharness.workflows.init import _replace_placeholders, _scaffold_dhf +from medharness.scaffold import replace_placeholders, scaffold_dhf @pytest.fixture def dhf(tmp_path: Path, monkeypatch) -> Path: - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "Local") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "Local") def no_model(*a, **k): raise AssertionError("--prompt started a model") diff --git a/tests/unit/test_ci_verification.py b/tests/unit/test_ci_verification.py index e6b2d88c..1e35fc29 100644 --- a/tests/unit/test_ci_verification.py +++ b/tests/unit/test_ci_verification.py @@ -220,10 +220,10 @@ def test_the_structural_gate_says_when_it_could_not_check(self, tmp_path) -> Non from dhfkit.item_store import ItemStore from medharness.services.verify_dhf import ci_structural_gate - from medharness.workflows.init import _replace_placeholders, _scaffold_dhf + from medharness.scaffold import replace_placeholders, scaffold_dhf - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "Flaky") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "Flaky") real = ItemStore.list_items calls = {"n": 0} @@ -243,9 +243,9 @@ def flaky(self, *a, **k): def test_a_healthy_dhf_reports_no_such_error(self, tmp_path) -> None: from medharness.services.verify_dhf import ci_structural_gate - from medharness.workflows.init import _replace_placeholders, _scaffold_dhf + from medharness.scaffold import replace_placeholders, scaffold_dhf - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "Fine") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "Fine") result = ci_structural_gate(tmp_path / "DHF") assert not any("could not be checked" in e for e in result["errors"]) diff --git a/tests/unit/test_cr_does_not_affect_itself.py b/tests/unit/test_cr_does_not_affect_itself.py index c03a9a92..87524f80 100644 --- a/tests/unit/test_cr_does_not_affect_itself.py +++ b/tests/unit/test_cr_does_not_affect_itself.py @@ -23,13 +23,13 @@ import dhfkit.api as api from medharness.services.cr_impact import _record_design_impact_in_cr -from medharness.workflows.init import _replace_placeholders, _scaffold_dhf +from medharness.scaffold import replace_placeholders, scaffold_dhf @pytest.fixture def dhf(tmp_path: Path) -> Path: - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "SelfRef") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "SelfRef") return tmp_path / "DHF" diff --git a/tests/unit/test_cr_phases.py b/tests/unit/test_cr_phases.py index 9975224b..fce295d7 100644 --- a/tests/unit/test_cr_phases.py +++ b/tests/unit/test_cr_phases.py @@ -11,20 +11,20 @@ import pytest from dhfkit.item_store import ItemStore -from medharness.workflows.cr_state import ( +from medharness.services.cr_state import ( ACTIVE_PHASES, TERMINAL_PHASES, CRPhase, assert_cr_active, get_cr_phase, ) -from medharness.workflows.init import _replace_placeholders, _scaffold_dhf +from medharness.scaffold import replace_placeholders, scaffold_dhf @pytest.fixture def dhf(tmp_path: Path) -> Path: - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "Trial") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "Trial") return tmp_path / "DHF" diff --git a/tests/unit/test_gate_envelope.py b/tests/unit/test_gate_envelope.py index 21f46f1f..aaad5cc0 100644 --- a/tests/unit/test_gate_envelope.py +++ b/tests/unit/test_gate_envelope.py @@ -24,7 +24,7 @@ from medharness.services.verify_soup import soup_gate from medharness.services.envelope import GATE_RESULT_KEYS, ENVELOPE_KEYS, gate_result from medharness.services.verify_tests import ci_test_coverage_gate -from medharness.workflows.init import _replace_placeholders, _scaffold_dhf +from medharness.scaffold import replace_placeholders, scaffold_dhf #: Gates that take only a DHF path, so they can be called generically. SIMPLE_GATES = ( @@ -64,8 +64,8 @@ def _run_gate(command: str, dhf) -> dict: @pytest.fixture def dhf(tmp_path: Path) -> Path: - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "Trial") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "Trial") return tmp_path / "DHF" @@ -243,8 +243,8 @@ def failing_dhf(tmp_path: Path) -> Path: """ import subprocess - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "Broken") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "Broken") dhf = tmp_path / "DHF" # A link that resolves to nothing. @@ -454,8 +454,8 @@ class TestGateLevelWarningsReachStderr: @pytest.fixture def inert_dhf(self, tmp_path: Path) -> Path: - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "Inert") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "Inert") dhf = tmp_path / "DHF" results = dhf / "test-results" results.mkdir(exist_ok=True) diff --git a/tests/unit/test_init_isolation.py b/tests/unit/test_init_isolation.py index 4081acf3..9d5b09af 100644 --- a/tests/unit/test_init_isolation.py +++ b/tests/unit/test_init_isolation.py @@ -3,7 +3,7 @@ Most of these pin regressions introduced by the previous review round, plus one older defect that made `medharness init` permanently corrupt the installed package: the documented setup creates `.venv` **inside** the project, and -`_replace_placeholders` walked the whole tree, so it rewrote +`replace_placeholders` walked the whole tree, so it rewrote `site-packages/dhfkit/templates` in place. Every later project scaffolded from that virtualenv then inherited the first project's name. """ @@ -18,10 +18,10 @@ from dhfkit.item_store import ItemStore from medharness.services.traceability_report import verification_evidence -from medharness.workflows.init import ( +from medharness.scaffold import ( _NON_SCAFFOLD_DIRS, - _replace_placeholders, - _scaffold_dhf, + replace_placeholders, + scaffold_dhf, _substitutable_files, _write_gitignore, ) @@ -42,14 +42,14 @@ def test_venv_contents_are_not_rewritten(self, tmp_path: Path) -> None: template = installed / "context.md" template.write_text("# AI Agent Context — {{project_name}}") - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "Trial") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "Trial") assert template.read_text() == "# AI Agent Context — {{project_name}}" def test_project_files_are_still_rewritten(self, tmp_path: Path) -> None: - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "Trial") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "Trial") assert "{{project_name}}" not in (tmp_path / "DHF" / "README.md").read_text() @pytest.mark.parametrize("pruned", sorted(_NON_SCAFFOLD_DIRS)) @@ -69,17 +69,17 @@ def test_walk_still_reaches_nested_project_files(self, tmp_path: Path) -> None: assert "x.md" in found def test_unreadable_file_does_not_abort_the_scaffold(self, tmp_path: Path) -> None: - _scaffold_dhf(tmp_path) + scaffold_dhf(tmp_path) locked = tmp_path / "DHF" / "locked.md" locked.write_text("{{project_name}}") locked.chmod(0o000) try: - _replace_placeholders(tmp_path, "Trial") # must not raise + replace_placeholders(tmp_path, "Trial") # must not raise finally: locked.chmod(0o644) # "does not abort" is a claim about the files after the unreadable one. - # Without this the test passes if _replace_placeholders becomes a no-op. + # Without this the test passes if replace_placeholders becomes a no-op. substituted = [ f for f in (tmp_path / "DHF").rglob("*") if f.is_file() and f != locked and "Trial" in _safe_read(f) @@ -108,8 +108,8 @@ def test_the_release_gate_skips_a_layer_the_project_omits(self, tmp_path: Path) entitled to omit the use-case layer must still be releasable.""" from medharness.services.verify_dhf import ci_structural_gate - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "Trial") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "Trial") dhf = tmp_path / "DHF" config = dhf / "config" / "global.yaml" config.write_text(config.read_text().replace("omit_doc_types: []", "omit_doc_types: [UC]")) @@ -127,8 +127,8 @@ class TestPrefixConsistency: """services/traceability_report.py must agree with dhfkit's Item.prefix.""" def test_core_resolves_multi_segment_prefixes(self, tmp_path: Path) -> None: - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "Trial") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "Trial") dhf = tmp_path / "DHF" # The loader resolves the doc-type code from the ID's first segment, so # a multi-segment prefix must keep that segment as its code: code VER, diff --git a/tests/unit/test_malformed_dhf_is_reported.py b/tests/unit/test_malformed_dhf_is_reported.py index 1e728771..5354cbe2 100644 --- a/tests/unit/test_malformed_dhf_is_reported.py +++ b/tests/unit/test_malformed_dhf_is_reported.py @@ -18,7 +18,7 @@ import pytest from click.testing import CliRunner -from medharness.workflows.init import _replace_placeholders, _scaffold_dhf +from medharness.scaffold import replace_placeholders, scaffold_dhf #: Every command that reads DHF items, with the arguments it needs to get there. COMMANDS = [ @@ -36,8 +36,8 @@ def broken(tmp_path_factory) -> Path: """A DHF with one mistyped field — otherwise a clean scaffold.""" root = tmp_path_factory.mktemp("broken") - _scaffold_dhf(root) - _replace_placeholders(root, "Broken") + scaffold_dhf(root) + replace_placeholders(root, "Broken") risk = next((root / "DHF" / "items").rglob("RISK-*.yaml")) risk.write_text(risk.read_text() + "typo_field: oops\n") return root @@ -110,7 +110,7 @@ class TestAHealthyDHFIsUnaffected: def test_commands_still_work(self, tmp_path: Path) -> None: from medharness.cli import main - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "Fine") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "Fine") r = CliRunner().invoke(main, ["--dhf", str(tmp_path / "DHF"), "item", "list"]) assert r.exit_code == 0, r.output diff --git a/tests/unit/test_release_anomalies.py b/tests/unit/test_release_anomalies.py index f5e0a115..0091239c 100644 --- a/tests/unit/test_release_anomalies.py +++ b/tests/unit/test_release_anomalies.py @@ -98,10 +98,10 @@ def test_unassessed_defect_blocks_the_release(self, tmp_path: Path) -> None: """ import dhfkit.api as api from medharness.services.release_baseline import build_release - from medharness.workflows.init import _replace_placeholders, _scaffold_dhf + from medharness.scaffold import replace_placeholders, scaffold_dhf - _scaffold_dhf(tmp_path / "project") - _replace_placeholders(tmp_path / "project", "Anomalies") + scaffold_dhf(tmp_path / "project") + replace_placeholders(tmp_path / "project", "Anomalies") dhf = tmp_path / "project" / "DHF" _defect(dhf, "DEF-001", "open") @@ -201,10 +201,10 @@ class TestARelIsRecordedOnlyWhenEverythingPassed: """ def _project(self, tmp_path: Path) -> Path: - from medharness.workflows.init import _replace_placeholders, _scaffold_dhf + from medharness.scaffold import replace_placeholders, scaffold_dhf - _scaffold_dhf(tmp_path / "project") - _replace_placeholders(tmp_path / "project", "Gated") + scaffold_dhf(tmp_path / "project") + replace_placeholders(tmp_path / "project", "Gated") keep_the_starter_text(tmp_path / "project" / "DHF") return tmp_path / "project" / "DHF" diff --git a/tests/unit/test_release_leaves_the_dhf_alone.py b/tests/unit/test_release_leaves_the_dhf_alone.py index 8da609ef..216e162e 100644 --- a/tests/unit/test_release_leaves_the_dhf_alone.py +++ b/tests/unit/test_release_leaves_the_dhf_alone.py @@ -15,10 +15,10 @@ def _project(tmp_path: Path) -> Path: - from medharness.workflows.init import _replace_placeholders, _scaffold_dhf + from medharness.scaffold import replace_placeholders, scaffold_dhf - _scaffold_dhf(tmp_path / "project") - _replace_placeholders(tmp_path / "project", "Untouched") + scaffold_dhf(tmp_path / "project") + replace_placeholders(tmp_path / "project", "Untouched") keep_the_starter_text(tmp_path / "project" / "DHF") return tmp_path / "project" diff --git a/tests/unit/test_traceability_cycles.py b/tests/unit/test_traceability_cycles.py index cee27f5a..a846ae1e 100644 --- a/tests/unit/test_traceability_cycles.py +++ b/tests/unit/test_traceability_cycles.py @@ -23,7 +23,7 @@ import yaml from medharness.services.traceability import find_link_cycles -from medharness.workflows.init import _replace_placeholders, _scaffold_dhf +from medharness.scaffold import replace_placeholders, scaffold_dhf class TestFindLinkCycles: @@ -81,8 +81,8 @@ def test_the_ordering_is_stable(self) -> None: class TestTheGateReportsIt: @pytest.fixture def cyclic(self, tmp_path: Path) -> Path: - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "Cycles") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "Cycles") dhf = tmp_path / "DHF" sys_item = next((dhf / "items").rglob("SYS-001.yaml")) data = yaml.safe_load(sys_item.read_text(encoding="utf-8")) @@ -109,8 +109,8 @@ def test_verify_dhf_fails_and_names_the_path(self, cyclic: Path) -> None: def test_the_scaffold_itself_has_none(self, tmp_path: Path) -> None: """The check is only useful if a healthy DHF stays green.""" - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "Clean") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "Clean") code, payload = self._verify(tmp_path / "DHF") assert code == 0, payload["errors"] @@ -130,8 +130,8 @@ def test_no_finding_key_is_left_behind(self, tmp_path: Path) -> None: from medharness.services.verify_dhf import ci_structural_gate from medharness.services.traceability import analyse - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "Copy") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "Copy") dhf = tmp_path / "DHF" produced = set(analyse(ItemStore(dhf))) diff --git a/tests/unit/test_verify_dhf_gate.py b/tests/unit/test_verify_dhf_gate.py index 80898361..a2f37597 100644 --- a/tests/unit/test_verify_dhf_gate.py +++ b/tests/unit/test_verify_dhf_gate.py @@ -17,12 +17,12 @@ from medharness.cli import main from medharness.services.verify_dhf import ci_structural_gate -from medharness.workflows.init import _replace_placeholders, _scaffold_dhf +from medharness.scaffold import replace_placeholders, scaffold_dhf def _scaffold(tmp_path: Path) -> Path: - _scaffold_dhf(tmp_path) - _replace_placeholders(tmp_path, "Trial") + scaffold_dhf(tmp_path) + replace_placeholders(tmp_path, "Trial") return tmp_path / "DHF" @@ -115,5 +115,5 @@ def test_documented_pipeline_passes_the_flag(self) -> None: def test_scaffold_does_not_create_a_workflow(self, tmp_path: Path) -> None: """Scaffolding it would silently do nothing on an installed package.""" - _scaffold_dhf(tmp_path) + scaffold_dhf(tmp_path) assert not (tmp_path / ".github" / "workflows").exists() diff --git a/uv.lock b/uv.lock index 4801f197..2190e88c 100644 --- a/uv.lock +++ b/uv.lock @@ -508,7 +508,7 @@ wheels = [ [[package]] name = "medharness" -version = "0.54.0" +version = "0.54.1" source = { editable = "." } dependencies = [ { name = "click" },