tenseleyflow/loader / ef7bc99

Browse files

Align TodoWrite resume targets

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
ef7bc9931a573c7591682b1dc9d236a95f68cae5
Parents
3fb23b2
Tree
47f02cb

2 changed files

StatusFile+-
M src/loader/runtime/tool_batches.py 12 0
M tests/test_tool_batches.py 139 2
src/loader/runtime/tool_batches.pymodified
@@ -1214,6 +1214,12 @@ class ToolBatchRunner:
12141214
             project_root=self.context.project_root,
12151215
             missing_artifact=missing_artifact,
12161216
         )
1217
+        missing_artifact = _prefer_missing_artifact_for_pending_item(
1218
+            dod,
1219
+            missing_artifact=missing_artifact,
1220
+            next_pending=next_pending,
1221
+            project_root=self.context.project_root,
1222
+        )
12171223
         if missing_artifact is None:
12181224
             if next_pending and _todo_is_mutation_step(next_pending):
12191225
                 pending_target = infer_pending_todo_output_target(
@@ -1351,6 +1357,12 @@ class ToolBatchRunner:
13511357
             project_root=self.context.project_root,
13521358
             missing_artifact=missing_artifact,
13531359
         )
1360
+        missing_artifact = _prefer_missing_artifact_for_pending_item(
1361
+            dod,
1362
+            missing_artifact=missing_artifact,
1363
+            next_pending=next_pending,
1364
+            project_root=self.context.project_root,
1365
+        )
13541366
         todo_refresh = _todo_refresh_guidance(
13551367
             dod,
13561368
             project_root=self.context.project_root,
tests/test_tool_batches.pymodified
@@ -3506,12 +3506,150 @@ async def test_tool_batch_runner_todowrite_with_existing_output_roots_requeues_n
35063506
     assert "Todo tracking is updated. A declared output artifact is still missing." in message
35073507
     assert "Continue with the next pending item: `Write the introduction chapter`." in message
35083508
     assert "Resume by creating `01-introduction.html` now." in message
3509
-    assert "It is the next missing declared output under `chapters/`." in message
35103509
     assert "Prefer one `write` call for `" in message
35113510
     assert "01-introduction.html` instead of more rereads." in message
35123511
     assert "Do not spend the next turn on TodoWrite alone" in message
35133512
 
35143513
 
3514
+@pytest.mark.asyncio
3515
+async def test_tool_batch_runner_todowrite_prefers_pending_index_over_empty_output_directory(
3516
+    temp_dir: Path,
3517
+) -> None:
3518
+    async def assess_confidence(
3519
+        tool_name: str,
3520
+        tool_args: dict,
3521
+        context: str,
3522
+    ) -> ConfidenceAssessment:
3523
+        raise AssertionError("Confidence scoring should not run in this scenario")
3524
+
3525
+    async def verify_action(
3526
+        tool_name: str,
3527
+        tool_args: dict,
3528
+        result: str,
3529
+        expected: str = "",
3530
+    ) -> ActionVerification:
3531
+        raise AssertionError("Verification should not run in this scenario")
3532
+
3533
+    guide_root = temp_dir / "Loader" / "guides" / "nginx"
3534
+    chapters = guide_root / "chapters"
3535
+    chapters.mkdir(parents=True)
3536
+    index_path = guide_root / "index.html"
3537
+    implementation_plan = temp_dir / "implementation.md"
3538
+    implementation_plan.write_text(
3539
+        "\n".join(
3540
+            [
3541
+                "# Implementation Plan",
3542
+                "",
3543
+                "## File Changes",
3544
+                f"- `{chapters}/`",
3545
+                f"- `{index_path}`",
3546
+                "",
3547
+            ]
3548
+        )
3549
+    )
3550
+
3551
+    dod = create_definition_of_done("Create a multi-file nginx guide.")
3552
+    dod.implementation_plan = str(implementation_plan)
3553
+    sync_todos_to_definition_of_done(
3554
+        dod,
3555
+        [
3556
+            {
3557
+                "content": "Examine the existing Fortran guide structure to understand the format and depth",
3558
+                "active_form": "Examining the existing Fortran guide structure",
3559
+                "status": "completed",
3560
+            },
3561
+            {
3562
+                "content": "Create the new nginx guide directory structure",
3563
+                "active_form": "Creating the new nginx guide directory structure",
3564
+                "status": "completed",
3565
+            },
3566
+            {
3567
+                "content": "Create a new index.html for the nginx guide",
3568
+                "active_form": "Creating a new index.html for the nginx guide",
3569
+                "status": "pending",
3570
+            },
3571
+            {
3572
+                "content": "Create the first chapter for the nginx guide",
3573
+                "active_form": "Creating the first chapter for the nginx guide",
3574
+                "status": "pending",
3575
+            },
3576
+        ],
3577
+        project_root=temp_dir,
3578
+    )
3579
+
3580
+    queued_messages: list[str] = []
3581
+    context = build_context(
3582
+        temp_dir=temp_dir,
3583
+        messages=[],
3584
+        safeguards=FakeSafeguards(),
3585
+        assess_confidence=assess_confidence,
3586
+        verify_action=verify_action,
3587
+        auto_recover=False,
3588
+    )
3589
+    context.queue_steering_message_callback = queued_messages.append
3590
+    runner = ToolBatchRunner(context, DefinitionOfDoneStore(temp_dir))
3591
+
3592
+    todos = [
3593
+        {
3594
+            "content": "Examine the existing Fortran guide structure to understand the format and depth",
3595
+            "active_form": "Examining the existing Fortran guide structure",
3596
+            "status": "completed",
3597
+        },
3598
+        {
3599
+            "content": "Create the new nginx guide directory structure",
3600
+            "active_form": "Creating the new nginx guide directory structure",
3601
+            "status": "completed",
3602
+        },
3603
+        {
3604
+            "content": "Create a new index.html for the nginx guide",
3605
+            "active_form": "Creating a new index.html for the nginx guide",
3606
+            "status": "pending",
3607
+        },
3608
+        {
3609
+            "content": "Create the first chapter for the nginx guide",
3610
+            "active_form": "Creating the first chapter for the nginx guide",
3611
+            "status": "pending",
3612
+        },
3613
+    ]
3614
+    tool_call = ToolCall(
3615
+        id="todo-index-before-chapter",
3616
+        name="TodoWrite",
3617
+        arguments={"todos": todos},
3618
+    )
3619
+    executor = FakeExecutor(
3620
+        [
3621
+            tool_outcome(
3622
+                tool_call=tool_call,
3623
+                output="Todos updated",
3624
+                is_error=False,
3625
+                metadata={"new_todos": todos},
3626
+            )
3627
+        ]
3628
+    )
3629
+
3630
+    summary = TurnSummary(final_response="")
3631
+    await runner.execute_batch(
3632
+        tool_calls=[tool_call],
3633
+        tool_source="assistant",
3634
+        pending_tool_calls_seen=set(),
3635
+        emit=_noop_emit,
3636
+        summary=summary,
3637
+        dod=dod,
3638
+        executor=executor,  # type: ignore[arg-type]
3639
+        on_confirmation=None,
3640
+        on_user_question=None,
3641
+        emit_confirmation=None,
3642
+        consecutive_errors=0,
3643
+    )
3644
+
3645
+    assert queued_messages
3646
+    message = queued_messages[-1]
3647
+    assert "Continue with the next pending item: `Create a new index.html for the nginx guide`." in message
3648
+    assert "Resume by creating `index.html` now." in message
3649
+    assert f"Prefer one `write` call for `{index_path.resolve(strict=False)}`" in message
3650
+    assert "01-introduction.html" not in message
3651
+
3652
+
35153653
 @pytest.mark.asyncio
35163654
 async def test_tool_batch_runner_todowrite_with_declared_child_targets_names_next_missing_file(
35173655
     temp_dir: Path,
@@ -3635,7 +3773,6 @@ async def test_tool_batch_runner_todowrite_with_declared_child_targets_names_nex
36353773
     assert "Todo tracking is updated. A declared output artifact is still missing." in message
36363774
     assert "Continue with the next pending item: `Write the introduction chapter`." in message
36373775
     assert "Resume by creating `introduction.html` now." in message
3638
-    assert "It is the next missing declared output under `chapters/`." in message
36393776
     assert "Prefer one `write` call for `" in message
36403777
     assert "introduction.html` instead of more rereads." in message
36413778
     assert "Do not spend the next turn on TodoWrite alone" in message