tenseleyflow/loader / 48742fd

Browse files

Filter post-build todo expansion

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
48742fd064489e2e0962138db224235471d3df9d
Parents
37acacf
Tree
8af6027

3 changed files

StatusFile+-
M src/loader/runtime/workflow.py 6 1
M tests/test_tool_batches.py 156 0
M tests/test_workflow.py 55 0
src/loader/runtime/workflow.pymodified
@@ -11,6 +11,7 @@ from typing import ClassVar
1111
 from ..llm.base import ToolCall
1212
 from .clarify_grounding import ClarifyGrounding
1313
 from .dod import (
14
+    all_planned_artifact_outputs_exist,
1415
     all_planned_artifacts_exist,
1516
     collect_planned_artifact_targets,
1617
     infer_next_output_file,
@@ -840,7 +841,11 @@ def effective_pending_todo_items(
840841
             project_root=project_root,
841842
         )
842843
     ]
843
-    if not all_planned_artifacts_exist(dod, project_root=project_root, max_paths=24):
844
+    if not all_planned_artifact_outputs_exist(
845
+        dod,
846
+        project_root=project_root,
847
+        max_paths=24,
848
+    ):
844849
         return pending_items
845850
 
846851
     planned_files = {
tests/test_tool_batches.pymodified
@@ -3945,6 +3945,162 @@ async def test_tool_batch_runner_todowrite_after_outputs_exist_but_links_missing
39453945
     assert "Move to verification or final confirmation using the files already on disk." in message
39463946
 
39473947
 
3948
+@pytest.mark.asyncio
3949
+async def test_tool_batch_runner_todowrite_drops_unplanned_expansion_after_outputs_exist(
3950
+    temp_dir: Path,
3951
+) -> None:
3952
+    async def assess_confidence(
3953
+        tool_name: str,
3954
+        tool_args: dict,
3955
+        context: str,
3956
+    ) -> ConfidenceAssessment:
3957
+        raise AssertionError("Confidence scoring should not run for this scenario")
3958
+
3959
+    async def verify_action(
3960
+        tool_name: str,
3961
+        tool_args: dict,
3962
+        result: str,
3963
+        expected: str = "",
3964
+    ) -> ActionVerification:
3965
+        raise AssertionError("Verification should not run for this scenario")
3966
+
3967
+    guide_root = temp_dir / "guides" / "nginx"
3968
+    chapters = guide_root / "chapters"
3969
+    guide_root.mkdir(parents=True)
3970
+    chapters.mkdir()
3971
+    index_path = guide_root / "index.html"
3972
+    chapter_one = chapters / "01-introduction.html"
3973
+    chapter_two = chapters / "02-installation.html"
3974
+    index_path.write_text(
3975
+        "\n".join(
3976
+            [
3977
+                '<a href="chapters/01-introduction.html">Intro</a>',
3978
+                '<a href="chapters/02-installation.html">Install</a>',
3979
+                '<a href="../index.html">Back</a>',
3980
+                "",
3981
+            ]
3982
+        )
3983
+    )
3984
+    chapter_one.write_text("<html></html>\n")
3985
+    chapter_two.write_text("<html></html>\n")
3986
+
3987
+    implementation_plan = temp_dir / "implementation.md"
3988
+    implementation_plan.write_text(
3989
+        "\n".join(
3990
+            [
3991
+                "# Implementation Plan",
3992
+                "",
3993
+                "## File Changes",
3994
+                f"- `{guide_root}/`",
3995
+                f"- `{chapters}/`",
3996
+                f"- `{index_path}`",
3997
+                f"- `{chapter_one}`",
3998
+                f"- `{chapter_two}`",
3999
+                "",
4000
+            ]
4001
+        )
4002
+    )
4003
+
4004
+    context = build_context(
4005
+        temp_dir=temp_dir,
4006
+        messages=[],
4007
+        safeguards=FakeSafeguards(),
4008
+        assess_confidence=assess_confidence,
4009
+        verify_action=verify_action,
4010
+        auto_recover=False,
4011
+    )
4012
+    queued_messages: list[str] = []
4013
+    context.queue_steering_message_callback = queued_messages.append
4014
+    runner = ToolBatchRunner(context, DefinitionOfDoneStore(temp_dir))
4015
+    dod = create_definition_of_done("Create a multi-file nginx guide.")
4016
+    dod.implementation_plan = str(implementation_plan)
4017
+    dod.verification_commands = [f"ls -la {guide_root}"]
4018
+
4019
+    tool_call = ToolCall(
4020
+        id="todo-post-build-expansion",
4021
+        name="TodoWrite",
4022
+        arguments={
4023
+            "todos": [
4024
+                {
4025
+                    "content": "Create index.html for nginx guide",
4026
+                    "activeForm": "Creating index.html",
4027
+                    "status": "in_progress",
4028
+                },
4029
+                {
4030
+                    "content": "Create chapter 01-introduction.html",
4031
+                    "activeForm": "Creating chapter 01-introduction.html",
4032
+                    "status": "completed",
4033
+                },
4034
+                {
4035
+                    "content": "Create chapter 02-installation.html",
4036
+                    "activeForm": "Creating chapter 02-installation.html",
4037
+                    "status": "completed",
4038
+                },
4039
+                {
4040
+                    "content": "Create chapter 08-troubleshooting.html",
4041
+                    "activeForm": "Creating chapter 08-troubleshooting.html",
4042
+                    "status": "pending",
4043
+                },
4044
+            ]
4045
+        },
4046
+    )
4047
+    executor = FakeExecutor(
4048
+        [
4049
+            tool_outcome(
4050
+                tool_call=tool_call,
4051
+                output="Todos updated",
4052
+                is_error=False,
4053
+                metadata={
4054
+                    "new_todos": [
4055
+                        {
4056
+                            "content": "Create index.html for nginx guide",
4057
+                            "active_form": "Creating index.html",
4058
+                            "status": "in_progress",
4059
+                        },
4060
+                        {
4061
+                            "content": "Create chapter 01-introduction.html",
4062
+                            "active_form": "Creating chapter 01-introduction.html",
4063
+                            "status": "completed",
4064
+                        },
4065
+                        {
4066
+                            "content": "Create chapter 02-installation.html",
4067
+                            "active_form": "Creating chapter 02-installation.html",
4068
+                            "status": "completed",
4069
+                        },
4070
+                        {
4071
+                            "content": "Create chapter 08-troubleshooting.html",
4072
+                            "active_form": "Creating chapter 08-troubleshooting.html",
4073
+                            "status": "pending",
4074
+                        },
4075
+                    ]
4076
+                },
4077
+            )
4078
+        ]
4079
+    )
4080
+
4081
+    summary = TurnSummary(final_response="")
4082
+    await runner.execute_batch(
4083
+        tool_calls=[tool_call],
4084
+        tool_source="assistant",
4085
+        pending_tool_calls_seen=set(),
4086
+        emit=_noop_emit,
4087
+        summary=summary,
4088
+        dod=dod,
4089
+        executor=executor,  # type: ignore[arg-type]
4090
+        on_confirmation=None,
4091
+        on_user_question=None,
4092
+        emit_confirmation=None,
4093
+        consecutive_errors=0,
4094
+    )
4095
+
4096
+    assert queued_messages
4097
+    message = queued_messages[-1]
4098
+    assert "Todo tracking is updated. All explicitly planned artifacts now exist on disk." in message
4099
+    assert "Repair or verify the current files instead of expanding the artifact set." in message
4100
+    assert "Move to verification or final confirmation using the files already on disk." in message
4101
+    assert "08-troubleshooting.html" not in message
4102
+
4103
+
39484104
 @pytest.mark.asyncio
39494105
 async def test_tool_batch_runner_todowrite_with_existing_output_roots_requeues_next_mutation(
39504106
     temp_dir: Path,
tests/test_workflow.pymodified
@@ -662,6 +662,61 @@ def test_effective_pending_todo_items_filters_stale_creation_steps_after_artifac
662662
     assert "Creating 02-installation.html" not in pending
663663
 
664664
 
665
+def test_effective_pending_todo_items_filters_unplanned_expansion_after_outputs_exist(
666
+    temp_dir: Path,
667
+) -> None:
668
+    guide_root = temp_dir / "guides" / "nginx"
669
+    chapters = guide_root / "chapters"
670
+    guide_root.mkdir(parents=True)
671
+    chapters.mkdir()
672
+    index_path = guide_root / "index.html"
673
+    chapter_one = chapters / "01-introduction.html"
674
+    chapter_two = chapters / "02-installation.html"
675
+    index_path.write_text(
676
+        "\n".join(
677
+            [
678
+                '<a href="chapters/01-introduction.html">Intro</a>',
679
+                '<a href="chapters/02-installation.html">Install</a>',
680
+                '<a href="../index.html">Back</a>',
681
+                "",
682
+            ]
683
+        )
684
+    )
685
+    chapter_one.write_text("<h1>One</h1>\n")
686
+    chapter_two.write_text("<h1>Two</h1>\n")
687
+
688
+    implementation_plan = temp_dir / "implementation.md"
689
+    implementation_plan.write_text(
690
+        "\n".join(
691
+            [
692
+                "# Implementation Plan",
693
+                "",
694
+                "## File Changes",
695
+                f"- `{guide_root}/`",
696
+                f"- `{chapters}/`",
697
+                f"- `{index_path}`",
698
+                f"- `{chapter_one}`",
699
+                f"- `{chapter_two}`",
700
+                "",
701
+            ]
702
+        )
703
+    )
704
+
705
+    dod = create_definition_of_done("Create a multi-file nginx guide.")
706
+    dod.implementation_plan = str(implementation_plan)
707
+    dod.pending_items = [
708
+        "Creating chapter 08-troubleshooting.html",
709
+        "Verify all guide files are linked and complete",
710
+        "Complete the requested work",
711
+    ]
712
+
713
+    pending = effective_pending_todo_items(dod, project_root=temp_dir)
714
+
715
+    assert "Verify all guide files are linked and complete" in pending
716
+    assert "Complete the requested work" in pending
717
+    assert "Creating chapter 08-troubleshooting.html" not in pending
718
+
719
+
665720
 def test_workflow_artifact_store_and_bridge_round_trip(tmp_path: Path) -> None:
666721
     store = WorkflowArtifactStore(tmp_path)
667722
     brief = ClarifyBrief.fallback(