Point HTML growth back to the root
Authored by
mfwolffe <wolffemf@dukes.jmu.edu>
- SHA
44ab5d87150bca8a7002c48e3fe51e78f9559534- Parents
-
484a89a - Tree
b89190a
44ab5d8
44ab5d87150bca8a7002c48e3fe51e78f9559534484a89a
b89190a| Status | File | + | - |
|---|---|---|---|
| M |
src/loader/runtime/safeguard_services.py
|
3 | 1 |
| M |
src/loader/runtime/tool_batches.py
|
40 | 0 |
| M |
tests/test_safeguard_services.py
|
1 | 0 |
| M |
tests/test_tool_batches.py
|
53 | 0 |
src/loader/runtime/safeguard_services.pymodified@@ -920,9 +920,11 @@ class PreActionValidator: | ||
| 920 | 920 | |
| 921 | 921 | declared_preview = ", ".join(sorted(declared_targets)[:3]) |
| 922 | 922 | if authoritative_root_graph: |
| 923 | + root_index = (root / "index.html").resolve(strict=False) | |
| 923 | 924 | suggestion = ( |
| 924 | 925 | "Keep new non-root HTML files within the root-declared artifact set and " |
| 925 | - f"update the guide root before creating undeclared sibling pages, for example: {current_relative}" | |
| 926 | + f"update the guide root `{root_index}` before creating undeclared sibling pages, " | |
| 927 | + f"for example: {current_relative}" | |
| 926 | 928 | ) |
| 927 | 929 | else: |
| 928 | 930 | suggestion = ( |
src/loader/runtime/tool_batches.pymodified@@ -303,6 +303,10 @@ class ToolBatchRunner: | ||
| 303 | 303 | outcome.event_content, |
| 304 | 304 | dod=dod, |
| 305 | 305 | ) |
| 306 | + self._queue_blocked_html_declared_file_creation_nudge( | |
| 307 | + tool_call, | |
| 308 | + outcome.event_content, | |
| 309 | + ) | |
| 306 | 310 | self._queue_blocked_html_declared_target_nudge( |
| 307 | 311 | tool_call, |
| 308 | 312 | outcome.event_content, |
@@ -798,6 +802,42 @@ class ToolBatchRunner: | ||
| 798 | 802 | ) |
| 799 | 803 | self.context.queue_steering_message(guidance) |
| 800 | 804 | |
| 805 | + def _queue_blocked_html_declared_file_creation_nudge( | |
| 806 | + self, | |
| 807 | + tool_call: ToolCall, | |
| 808 | + event_content: str, | |
| 809 | + ) -> None: | |
| 810 | + """Steer blocked undeclared HTML file creation back through the root guide.""" | |
| 811 | + | |
| 812 | + if tool_call.name not in {"write", "edit", "patch"}: | |
| 813 | + return | |
| 814 | + if "HTML file creation falls outside the current declared artifact set" not in event_content: | |
| 815 | + return | |
| 816 | + | |
| 817 | + target = str( | |
| 818 | + tool_call.arguments.get("file_path") | |
| 819 | + or tool_call.arguments.get("path") | |
| 820 | + or "" | |
| 821 | + ).strip() | |
| 822 | + if not target: | |
| 823 | + return | |
| 824 | + | |
| 825 | + target_path = Path(target).expanduser().resolve(strict=False) | |
| 826 | + root_index = (target_path.parent.parent / "index.html").resolve(strict=False) | |
| 827 | + relative_target = None | |
| 828 | + try: | |
| 829 | + relative_target = target_path.relative_to(root_index.parent).as_posix() | |
| 830 | + except ValueError: | |
| 831 | + relative_target = target_path.name | |
| 832 | + | |
| 833 | + guidance = ( | |
| 834 | + "That new HTML file is outside the current root-declared artifact set. " | |
| 835 | + f"Before creating `{relative_target}`, update `{root_index}` so the guide root " | |
| 836 | + "explicitly links to that page, then retry the file creation. " | |
| 837 | + "Stay on the active guide files; do not reopen the earlier reference guide first." | |
| 838 | + ) | |
| 839 | + self.context.queue_steering_message(guidance) | |
| 840 | + | |
| 801 | 841 | def _queue_blocked_invalid_mutation_nudge( |
| 802 | 842 | self, |
| 803 | 843 | tool_call: ToolCall, |
tests/test_safeguard_services.pymodified@@ -488,6 +488,7 @@ def test_pre_action_validator_blocks_undeclared_chapter_file_creation_after_root | ||
| 488 | 488 | assert result.valid is False |
| 489 | 489 | assert result.reason == "HTML file creation falls outside the current declared artifact set" |
| 490 | 490 | assert "09-monitoring.html" in result.suggestion |
| 491 | + assert str((guide / "index.html").resolve(strict=False)) in result.suggestion | |
| 491 | 492 | |
| 492 | 493 | |
| 493 | 494 | def test_pre_action_validator_allows_declared_missing_chapter_file_creation( |
tests/test_tool_batches.pymodified@@ -5621,6 +5621,59 @@ def test_tool_batch_runner_blocked_html_declared_target_nudge_without_close_matc | ||
| 5621 | 5621 | assert "closest declared target(s)" not in queued[0] |
| 5622 | 5622 | |
| 5623 | 5623 | |
| 5624 | +def test_tool_batch_runner_blocked_html_declared_file_creation_nudge_points_to_root( | |
| 5625 | + temp_dir: Path, | |
| 5626 | +) -> None: | |
| 5627 | + async def assess_confidence( | |
| 5628 | + tool_name: str, | |
| 5629 | + tool_args: dict, | |
| 5630 | + context: str, | |
| 5631 | + ) -> ConfidenceAssessment: | |
| 5632 | + raise AssertionError("Confidence scoring should be disabled in this scenario") | |
| 5633 | + | |
| 5634 | + async def verify_action( | |
| 5635 | + tool_name: str, | |
| 5636 | + tool_args: dict, | |
| 5637 | + result: str, | |
| 5638 | + expected: str = "", | |
| 5639 | + ) -> ActionVerification: | |
| 5640 | + raise AssertionError("Verification should not run in this scenario") | |
| 5641 | + | |
| 5642 | + context = build_context( | |
| 5643 | + temp_dir=temp_dir, | |
| 5644 | + messages=[], | |
| 5645 | + safeguards=FakeSafeguards(), | |
| 5646 | + assess_confidence=assess_confidence, | |
| 5647 | + verify_action=verify_action, | |
| 5648 | + ) | |
| 5649 | + queued: list[str] = [] | |
| 5650 | + context.queue_steering_message_callback = queued.append | |
| 5651 | + runner = ToolBatchRunner(context, DefinitionOfDoneStore(temp_dir)) | |
| 5652 | + | |
| 5653 | + target = temp_dir / "guide" / "chapters" / "troubleshooting.html" | |
| 5654 | + runner._queue_blocked_html_declared_file_creation_nudge( | |
| 5655 | + ToolCall( | |
| 5656 | + id="write-troubleshooting", | |
| 5657 | + name="write", | |
| 5658 | + arguments={"file_path": str(target)}, | |
| 5659 | + ), | |
| 5660 | + ( | |
| 5661 | + "[Blocked - HTML file creation falls outside the current declared artifact set] " | |
| 5662 | + "Suggestion: Keep new non-root HTML files within the root-declared artifact set and " | |
| 5663 | + f"update the guide root `{(temp_dir / 'guide' / 'index.html').resolve(strict=False)}` " | |
| 5664 | + "before creating undeclared sibling pages, for example: chapters/troubleshooting.html. " | |
| 5665 | + "Already-declared local targets include: chapters/advanced-topics.html, " | |
| 5666 | + "chapters/basic-usage.html, chapters/configuration.html" | |
| 5667 | + ), | |
| 5668 | + ) | |
| 5669 | + | |
| 5670 | + assert queued | |
| 5671 | + assert "update" in queued[0].lower() | |
| 5672 | + assert str((temp_dir / "guide" / "index.html").resolve(strict=False)) in queued[0] | |
| 5673 | + assert "`chapters/troubleshooting.html`" in queued[0] | |
| 5674 | + assert "retry the file creation" in queued[0] | |
| 5675 | + | |
| 5676 | + | |
| 5624 | 5677 | @pytest.mark.asyncio |
| 5625 | 5678 | async def test_tool_batch_runner_blocked_empty_file_path_nudges_concrete_next_artifact( |
| 5626 | 5679 | temp_dir: Path, |