tenseleyflow/loader / 44ab5d8

Browse files

Point HTML growth back to the root

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
44ab5d87150bca8a7002c48e3fe51e78f9559534
Parents
484a89a
Tree
b89190a

4 changed files

StatusFile+-
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:
920920
 
921921
         declared_preview = ", ".join(sorted(declared_targets)[:3])
922922
         if authoritative_root_graph:
923
+            root_index = (root / "index.html").resolve(strict=False)
923924
             suggestion = (
924925
                 "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}"
926928
             )
927929
         else:
928930
             suggestion = (
src/loader/runtime/tool_batches.pymodified
@@ -303,6 +303,10 @@ class ToolBatchRunner:
303303
                     outcome.event_content,
304304
                     dod=dod,
305305
                 )
306
+                self._queue_blocked_html_declared_file_creation_nudge(
307
+                    tool_call,
308
+                    outcome.event_content,
309
+                )
306310
                 self._queue_blocked_html_declared_target_nudge(
307311
                     tool_call,
308312
                     outcome.event_content,
@@ -798,6 +802,42 @@ class ToolBatchRunner:
798802
         )
799803
         self.context.queue_steering_message(guidance)
800804
 
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
+
801841
     def _queue_blocked_invalid_mutation_nudge(
802842
         self,
803843
         tool_call: ToolCall,
tests/test_safeguard_services.pymodified
@@ -488,6 +488,7 @@ def test_pre_action_validator_blocks_undeclared_chapter_file_creation_after_root
488488
     assert result.valid is False
489489
     assert result.reason == "HTML file creation falls outside the current declared artifact set"
490490
     assert "09-monitoring.html" in result.suggestion
491
+    assert str((guide / "index.html").resolve(strict=False)) in result.suggestion
491492
 
492493
 
493494
 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
56215621
     assert "closest declared target(s)" not in queued[0]
56225622
 
56235623
 
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
+
56245677
 @pytest.mark.asyncio
56255678
 async def test_tool_batch_runner_blocked_empty_file_path_nudges_concrete_next_artifact(
56265679
     temp_dir: Path,