test_gs_paste_next_regen.py
python
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1
docs: MuseHub-first before ISR #74 — staging solidify NEXT
Human
13 hours ago
| 1 | """Unit tests for GS-PASTE NEXT / paste regeneration (§GSP.10 unit).""" |
| 2 | |
| 3 | from __future__ import annotations |
| 4 | |
| 5 | from pathlib import Path |
| 6 | |
| 7 | from adapters.config import load_config |
| 8 | from tools.governance_hygiene.next_regen import ( |
| 9 | compact_step_id, |
| 10 | decide_split_emission, |
| 11 | plan_next_regen, |
| 12 | render_next_session, |
| 13 | render_paste_ready, |
| 14 | select_unambiguous_next_row, |
| 15 | ) |
| 16 | from tools.governance_hygiene.patch import _render_next_step_glance |
| 17 | from tools.governance_hygiene.types import QueueRow |
| 18 | |
| 19 | |
| 20 | def _roadmap(*rows: str) -> str: |
| 21 | header = ( |
| 22 | "# Roadmap\n\n## Build queue\n\n" |
| 23 | "| Phase | Model | Status | Deliverable |\n" |
| 24 | "| --- | --- | --- | --- |\n" |
| 25 | ) |
| 26 | return header + "\n".join(rows) + "\n" |
| 27 | |
| 28 | |
| 29 | def test_select_exactly_one_open_row() -> None: |
| 30 | text = _roadmap( |
| 31 | "| **Done** | Auto | **DONE** | x |", |
| 32 | "| **Open** | Auto | **NEXT** | y |", |
| 33 | ) |
| 34 | row, reason = select_unambiguous_next_row(text) |
| 35 | assert reason is None |
| 36 | assert row is not None |
| 37 | assert "Open" in row.phase_label |
| 38 | |
| 39 | |
| 40 | def test_select_zero_open_rows() -> None: |
| 41 | text = _roadmap("| **Done** | Auto | **DONE** | x |") |
| 42 | row, reason = select_unambiguous_next_row(text) |
| 43 | assert row is None |
| 44 | assert reason == "zero_open_rows" |
| 45 | |
| 46 | |
| 47 | def test_select_multiple_open_rows() -> None: |
| 48 | text = _roadmap( |
| 49 | "| **A** | Auto | **TODO** | a |", |
| 50 | "| **B** | Auto | **WIP** | b |", |
| 51 | ) |
| 52 | row, reason = select_unambiguous_next_row(text) |
| 53 | assert row is None |
| 54 | assert reason == "multiple_open_rows" |
| 55 | |
| 56 | |
| 57 | def test_select_invalid_model_ambiguous() -> None: |
| 58 | text = _roadmap("| **Open** | Operator choice | **NEXT** | y |") |
| 59 | row, reason = select_unambiguous_next_row(text) |
| 60 | assert row is None |
| 61 | assert reason == "invalid_model_label" |
| 62 | |
| 63 | |
| 64 | def test_thinking_to_auto_emit_b_when_freeze_pass(tmp_path: Path) -> None: |
| 65 | docs = tmp_path / "docs" |
| 66 | docs.mkdir() |
| 67 | (docs / "PHASE-GS-PASTE-READY-REGEN.md").write_text( |
| 68 | "# Freeze\n\n```yaml\nphase: GS-PASTE\nreview_stamp:\n verdict: pass\n```\n", |
| 69 | encoding="utf-8", |
| 70 | ) |
| 71 | row = QueueRow( |
| 72 | phase_label="**GS-PASTE**", |
| 73 | model="Thinking → Auto", |
| 74 | status="**NEXT**", |
| 75 | deliverable="docs/archive/phases/PHASE-GS-PASTE-READY-REGEN.md", |
| 76 | raw_line="", |
| 77 | ) |
| 78 | emit_model, reason, is_b = decide_split_emission(row, tmp_path) |
| 79 | assert reason is None |
| 80 | assert emit_model == "Auto" |
| 81 | assert is_b is True |
| 82 | |
| 83 | |
| 84 | def test_thinking_to_auto_emit_a_without_freeze_pass(tmp_path: Path) -> None: |
| 85 | docs = tmp_path / "docs" |
| 86 | docs.mkdir() |
| 87 | row = QueueRow( |
| 88 | phase_label="**GS-PASTE**", |
| 89 | model="Thinking → Auto", |
| 90 | status="**NEXT**", |
| 91 | deliverable="freeze something", |
| 92 | raw_line="", |
| 93 | ) |
| 94 | emit_model, reason, is_b = decide_split_emission(row, tmp_path) |
| 95 | assert reason is None |
| 96 | assert emit_model == "Thinking" |
| 97 | assert is_b is False |
| 98 | |
| 99 | |
| 100 | def test_rendered_next_contains_h3_h5_h6_substrings(tmp_path: Path) -> None: |
| 101 | config = load_config( |
| 102 | Path(__file__).resolve().parents[1] / "fixtures" / "config-git-only.yaml" |
| 103 | ) |
| 104 | roadmap = _roadmap( |
| 105 | "| **Prior** | Auto | **DONE** | prior work |", |
| 106 | "| **GS-PASTE-b** | Auto | **NEXT** | regen paste |", |
| 107 | ) |
| 108 | decision = plan_next_regen( |
| 109 | roadmap_text=roadmap, |
| 110 | handover_text="## NEXT SESSION — x\n", |
| 111 | config=config, |
| 112 | repo_root=tmp_path, |
| 113 | ) |
| 114 | assert decision.row is not None |
| 115 | body = render_next_session( |
| 116 | decision=decision, |
| 117 | roadmap_text=roadmap, |
| 118 | config=config, |
| 119 | sync_date="2026-07-30", |
| 120 | ) |
| 121 | assert "**Date:**" in body |
| 122 | assert "**Current position:**" in body |
| 123 | assert "**Model:**" in body |
| 124 | assert "### THE ONE NEXT STEP — **Model:" in body |
| 125 | assert "| **ID** |" in body |
| 126 | assert "| **Branch** |" in body |
| 127 | assert "| **Repo** |" in body |
| 128 | assert "| **Read first** |" in body |
| 129 | assert "| **Hard stops** |" in body |
| 130 | assert "### Paste-ready prompt" not in body |
| 131 | |
| 132 | paste = render_paste_ready(decision=decision, config=config) |
| 133 | assert "### Paste-ready prompt —" in paste |
| 134 | assert "Model: Auto" in paste |
| 135 | assert "Repo:" in paste |
| 136 | assert "Step:" in paste |
| 137 | assert "Authority:" in paste |
| 138 | assert "build-verification-review" in paste |
| 139 | |
| 140 | |
| 141 | def test_glance_fail_closed_when_open_count_not_one() -> None: |
| 142 | multi = _roadmap( |
| 143 | "| **A** | Auto | **TODO** | a |", |
| 144 | "| **B** | Auto | **WIP** | b |", |
| 145 | ) |
| 146 | glance = _render_next_step_glance(multi) |
| 147 | assert "No unambiguous NEXT row — operator authorship required." in glance |
| 148 | |
| 149 | zero = _roadmap("| **A** | Auto | **DONE** | a |") |
| 150 | assert "No unambiguous NEXT row" in _render_next_step_glance(zero) |
| 151 | |
| 152 | |
| 153 | def test_compact_step_id_uses_first_segment() -> None: |
| 154 | assert compact_step_id("**GS-PASTE-b Paste regen**") == "GS-PASTE-b" |
File History
1 commit
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1
docs: MuseHub-first before ISR #74 — staging solidify NEXT
Human
13 hours ago