tenseleyflow/loader / 3cbd1bb

Browse files

Keep setup before missing files

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
3cbd1bb06a2eee323b318aa4ccc414a8c47acd87
Parents
89ffea4
Tree
b776be4

3 changed files

StatusFile+-
M src/loader/runtime/workflow.py 1 1
M tests/test_repair.py 5 5
M tests/test_workflow.py 37 0
src/loader/runtime/workflow.pymodified
@@ -1515,7 +1515,7 @@ def _todo_is_subsumed_by_concrete_missing_artifact(
1515
     target, expect_directory = missing_artifact
1515
     target, expect_directory = missing_artifact
1516
     if _contains_any(text, _BROAD_SETUP_HINTS):
1516
     if _contains_any(text, _BROAD_SETUP_HINTS):
1517
         if not expect_directory:
1517
         if not expect_directory:
1518
-            return True
1518
+            return target.parent.exists()
1519
         return target.is_dir()
1519
         return target.is_dir()
1520
     return False
1520
     return False
1521
 
1521
 
tests/test_repair.pymodified
@@ -308,22 +308,22 @@ def test_empty_response_retry_mentions_write_can_create_missing_parent_directori
308
     assert decision.should_continue is True
308
     assert decision.should_continue is True
309
     assert decision.retry_message is not None
309
     assert decision.retry_message is not None
310
     assert (
310
     assert (
311
-        "Resume with this exact next step: continue `Write main index.html for nginx guide` "
311
+        "Resume with this exact next step: create `index.html`."
312
-        "by creating `index.html`."
313
         in decision.retry_message
312
         in decision.retry_message
314
     )
313
     )
315
     assert (
314
     assert (
316
-        f"Prefer one `write(content=...)` call for `{index_path}` before more research."
315
+        f"Prefer one `write` call for `{index_path}` before any more reference reads."
317
         in decision.retry_message
316
         in decision.retry_message
318
     )
317
     )
319
     assert (
318
     assert (
320
-        f'Emit this tool shape now: `write(file_path="{index_path.resolve(strict=False)}", content="...")`.'
319
+        "The `write` tool can create that file's parent directories automatically, so do the write in one step instead of stopping for a separate mkdir."
321
         in decision.retry_message
320
         in decision.retry_message
322
     )
321
     )
323
     assert (
322
     assert (
324
-        "Do not restart discovery unless one specific missing fact blocks that file write."
323
+        f'Emit this tool shape now: `write(file_path="{index_path.resolve(strict=False)}", content="...")`.'
325
         in decision.retry_message
324
         in decision.retry_message
326
     )
325
     )
326
+    assert "Do not restart discovery unless one specific missing fact blocks this step." in decision.retry_message
327
 
327
 
328
 
328
 
329
 def test_empty_response_retry_recovers_blocked_empty_file_path_to_concrete_target(
329
 def test_empty_response_retry_recovers_blocked_empty_file_path_to_concrete_target(
tests/test_workflow.pymodified
@@ -20,6 +20,7 @@ from loader.runtime.workflow import (
20
     extract_verification_commands_from_markdown,
20
     extract_verification_commands_from_markdown,
21
     infer_pending_todo_output_target,
21
     infer_pending_todo_output_target,
22
     merge_refreshed_todos_with_existing_scope,
22
     merge_refreshed_todos_with_existing_scope,
23
+    preferred_pending_todo_item,
23
     preserve_task_grounded_acceptance_criteria,
24
     preserve_task_grounded_acceptance_criteria,
24
     reconcile_aggregate_completion_steps,
25
     reconcile_aggregate_completion_steps,
25
     sync_todos_to_definition_of_done,
26
     sync_todos_to_definition_of_done,
@@ -1018,6 +1019,42 @@ def test_infer_pending_todo_output_target_maps_broad_setup_to_planned_directory(
1018
     assert target == chapters.resolve(strict=False)
1019
     assert target == chapters.resolve(strict=False)
1019
 
1020
 
1020
 
1021
 
1022
+def test_preferred_pending_todo_item_keeps_setup_step_when_missing_file_parent_absent(
1023
+    tmp_path: Path,
1024
+) -> None:
1025
+    dod = create_definition_of_done("Create a multi-file nginx guide.")
1026
+    nginx_root = tmp_path / "Loader" / "guides" / "nginx"
1027
+    chapters = nginx_root / "chapters"
1028
+    index_path = nginx_root / "index.html"
1029
+    implementation_plan = tmp_path / "implementation.md"
1030
+    implementation_plan.write_text(
1031
+        "\n".join(
1032
+            [
1033
+                "# Implementation Plan",
1034
+                "",
1035
+                "## File Changes",
1036
+                f"- `{chapters}/`",
1037
+                f"- `{index_path}`",
1038
+                "",
1039
+            ]
1040
+        )
1041
+    )
1042
+    dod.implementation_plan = str(implementation_plan)
1043
+    dod.pending_items = [
1044
+        "Create the nginx directory structure",
1045
+        "Create the main index.html file for nginx guide",
1046
+        "Complete the requested work",
1047
+    ]
1048
+
1049
+    preferred = preferred_pending_todo_item(
1050
+        dod,
1051
+        project_root=tmp_path,
1052
+        missing_artifact=(index_path.resolve(strict=False), False),
1053
+    )
1054
+
1055
+    assert preferred == "Create the nginx directory structure"
1056
+
1057
+
1021
 def test_advance_todos_from_tool_call_does_not_complete_content_study_from_root_index_read() -> None:
1058
 def test_advance_todos_from_tool_call_does_not_complete_content_study_from_root_index_read() -> None:
1022
     dod = create_definition_of_done("Create a multi-file nginx guide.")
1059
     dod = create_definition_of_done("Create a multi-file nginx guide.")
1023
     sync_todos_to_definition_of_done(
1060
     sync_todos_to_definition_of_done(