Python · 49187 bytes Raw Blame History
1 """Tests for Sprint 04 workflow routing and artifact persistence."""
2
3 from __future__ import annotations
4
5 from pathlib import Path
6
7 from loader.llm.base import ToolCall
8 from loader.runtime.clarify_grounding import ClarifyGrounding, ClarifyRepoFact
9 from loader.runtime.dod import DefinitionOfDoneStore, create_definition_of_done
10 from loader.runtime.workflow import (
11 ClarifyBrief,
12 ModeRouter,
13 PlanningArtifacts,
14 WorkflowArtifactStore,
15 WorkflowMode,
16 advance_todos_from_tool_call,
17 build_execute_bridge,
18 effective_pending_todo_items,
19 enrich_clarify_brief_with_grounding,
20 extract_verification_commands_from_markdown,
21 merge_refreshed_todos_with_existing_scope,
22 preserve_task_grounded_acceptance_criteria,
23 reconcile_aggregate_completion_steps,
24 sync_todos_to_definition_of_done,
25 )
26
27
28 def test_mode_router_routes_ambiguous_prompt_to_clarify() -> None:
29 router = ModeRouter()
30
31 decision = router.route("Improve Loader so it feels more like claw-code.")
32
33 assert decision.mode == WorkflowMode.CLARIFY
34 assert decision.reason_code == "task_is_ambiguous"
35 assert decision.route_score >= decision.runner_up_score
36
37
38 def test_mode_router_routes_complex_prompt_to_plan() -> None:
39 router = ModeRouter()
40
41 decision = router.route(
42 "Implement a persistent workflow mode router with clarify artifacts, "
43 "planning artifacts, and verification-plan wiring in the runtime."
44 )
45
46 assert decision.mode == WorkflowMode.PLAN
47 assert decision.complexity_score >= router.plan_threshold
48
49
50 def test_mode_router_routes_simple_prompt_to_execute() -> None:
51 router = ModeRouter()
52
53 decision = router.route("Read pyproject.toml and tell me the package name.")
54
55 assert decision.mode == WorkflowMode.EXECUTE
56
57
58 def test_clarify_brief_round_trips_and_seeds_acceptance_criteria() -> None:
59 brief = ClarifyBrief.fallback(
60 task_statement="Clarify the authentication change.",
61 question="What outcome matters most?",
62 answer="Add login without touching the signup flow.",
63 )
64 markdown = brief.to_markdown()
65
66 loaded = ClarifyBrief.from_markdown(
67 markdown,
68 task_statement=brief.task_statement,
69 question=brief.question,
70 answer=brief.answer,
71 )
72
73 assert "single-question clarify brief" in markdown
74 assert "return control to `execute` mode" in markdown
75 assert loaded.task_statement == brief.task_statement
76 assert "Add login" in loaded.acceptance_criteria[0]
77 assert loaded.non_goals
78
79
80 def test_enrich_clarify_brief_with_grounding_replaces_generic_sections() -> None:
81 brief = ClarifyBrief.fallback(
82 task_statement="Tighten Loader runtime clarify behavior.",
83 question="What part should change most?",
84 answer="Keep the work narrow.",
85 )
86 grounding = ClarifyGrounding(
87 existing_references=["src/loader/runtime/workflow_lanes.py"],
88 candidate_touchpoints=["src/loader/runtime/clarify_strategy.py"],
89 repo_facts=[
90 ClarifyRepoFact(
91 path="src/loader/runtime/workflow_lanes.py",
92 summary="class WorkflowLaneRunner:",
93 ),
94 ClarifyRepoFact(
95 path="src/loader/runtime/clarify_strategy.py",
96 summary="Intent-aware clarify strategy for runtime follow-up.",
97 ),
98 ],
99 )
100
101 enriched = enrich_clarify_brief_with_grounding(brief, grounding)
102
103 assert enriched.likely_touchpoints[:2] == [
104 "src/loader/runtime/workflow_lanes.py",
105 "src/loader/runtime/clarify_strategy.py",
106 ]
107 assert any("clarify_strategy.py" in item for item in enriched.constraints)
108 assert any("WorkflowLaneRunner" in item for item in enriched.assumptions)
109 assert any("Primary work stays scoped" in item for item in enriched.acceptance_criteria)
110 assert "likely_touchpoints" in enriched.explicit_sections
111
112
113 def test_planning_artifacts_round_trip_and_extract_commands() -> None:
114 artifacts = PlanningArtifacts.from_model_output(
115 "\n".join(
116 [
117 "# Implementation Plan",
118 "",
119 "## Execution Order",
120 "1. Inspect auth files.",
121 "2. Implement the change.",
122 "",
123 "## Risks",
124 "- Regression in signup.",
125 "",
126 "<<<VERIFICATION>>>",
127 "",
128 "# Verification Plan",
129 "",
130 "## Acceptance Criteria",
131 "- Login works without changing signup.",
132 "",
133 "## Verification Commands",
134 "- `uv run pytest tests/test_auth.py -q`",
135 "- `uv run mypy src/loader`",
136 ]
137 ),
138 task_statement="Clarify and implement the auth change.",
139 )
140
141 assert "single-pass planning artifact generation" in artifacts.implementation_markdown
142 assert "planner/critic consensus loop" in artifacts.implementation_markdown
143 assert "single-pass planning artifact generation" in artifacts.verification_markdown
144 assert artifacts.implementation_steps[:2] == [
145 "Inspect auth files.",
146 "Implement the change.",
147 ]
148 assert artifacts.acceptance_criteria == ["Login works without changing signup."]
149 assert artifacts.verification_commands == [
150 "uv run pytest tests/test_auth.py -q",
151 "uv run mypy src/loader",
152 ]
153 assert extract_verification_commands_from_markdown(artifacts.verification_markdown) == [
154 "uv run pytest tests/test_auth.py -q",
155 "uv run mypy src/loader",
156 ]
157
158
159 def test_planning_artifacts_recover_embedded_verification_from_legacy_separator() -> None:
160 artifacts = PlanningArtifacts.from_model_output(
161 "\n".join(
162 [
163 "# Implementation Plan",
164 "",
165 "## Execution Order",
166 "1. Inspect index.html.",
167 "2. Fix the chapter links.",
168 "",
169 "# Verification Plan",
170 "",
171 "## Acceptance Criteria",
172 "- All chapter links point to real files.",
173 "",
174 "## Verification Commands",
175 "- `grep -o 'href=\"[^\"]*\"' index.html`",
176 "- `ls chapters`",
177 "",
178 "<<VERIFICATION>>",
179 ]
180 ),
181 task_statement="Fix the broken chapter links in index.html.",
182 )
183
184 assert "## Verification Commands" not in artifacts.implementation_markdown
185 assert "## Verification Commands" in artifacts.verification_markdown
186 assert artifacts.verification_commands == [
187 "grep -o 'href=\"[^\"]*\"' index.html",
188 "ls chapters",
189 ]
190
191
192 def test_extract_verification_commands_from_markdown_splits_code_blocks() -> None:
193 markdown = "\n".join(
194 [
195 "# Verification Plan",
196 "",
197 "## Verification Commands",
198 "```bash",
199 "# Check chapter files",
200 "ls chapters",
201 "grep -n \"href=\" index.html",
202 "```",
203 ]
204 )
205
206 assert extract_verification_commands_from_markdown(markdown) == [
207 "ls chapters",
208 'grep -n "href=" index.html',
209 ]
210
211
212 def test_extract_verification_commands_from_markdown_ignores_prose_only_bullets() -> None:
213 markdown = "\n".join(
214 [
215 "# Verification Plan",
216 "",
217 "## Verification Commands",
218 "- Check that all chapter links in index.html resolve to existing files",
219 "- Validate chapter titles with `python3 scripts/check_titles.py`",
220 "- `test -f index.html`",
221 ]
222 )
223
224 assert extract_verification_commands_from_markdown(markdown) == [
225 "python3 scripts/check_titles.py",
226 "test -f index.html",
227 ]
228
229
230 def test_extract_verification_commands_from_markdown_trims_inline_command_explanations() -> None:
231 markdown = "\n".join(
232 [
233 "# Verification Plan",
234 "",
235 "## Verification Commands",
236 (
237 '- `grep -n "href" ~/Loader/guides/fortran/index.html` '
238 "- to identify all href attributes"
239 ),
240 ]
241 )
242
243 assert extract_verification_commands_from_markdown(markdown) == [
244 'grep -n "href" ~/Loader/guides/fortran/index.html',
245 ]
246
247
248 def test_extract_verification_commands_keeps_shell_pipelines_intact() -> None:
249 markdown = "\n".join(
250 [
251 "# Verification Plan",
252 "",
253 "## Verification Commands",
254 "```bash",
255 "ls -la chapters/",
256 "cat index.html | head -20",
257 "```",
258 ]
259 )
260
261 assert extract_verification_commands_from_markdown(markdown) == [
262 "ls -la chapters/",
263 "cat index.html | head -20",
264 ]
265
266
267 def test_preserve_task_grounded_acceptance_criteria_keeps_original_scope_on_refresh() -> None:
268 task = (
269 "Create an equally thorough nginx guide with index.html plus chapter files "
270 "covering getting started, installation, first website setup, configs, and "
271 "advanced topics."
272 )
273
274 preserved = preserve_task_grounded_acceptance_criteria(
275 task,
276 existing_acceptance_criteria=[
277 "All files are created in the correct locations with proper directory structure",
278 "Content covers all required topics: getting started, installation, first website, configuration basics, advanced configurations, and troubleshooting",
279 ],
280 refreshed_acceptance_criteria=[
281 "At least one chapter file exists in ~/Loader/guides/nginx/chapters/",
282 "~/Loader/guides/nginx/index.html exists and contains proper table of contents",
283 ],
284 )
285
286 assert (
287 "All files are created in the correct locations with proper directory structure"
288 in preserved
289 )
290 assert (
291 "Content covers all required topics: getting started, installation, first website, configuration basics, advanced configurations, and troubleshooting"
292 in preserved
293 )
294 assert "At least one chapter file exists in ~/Loader/guides/nginx/chapters/" in preserved
295
296
297 def test_preserve_task_grounded_acceptance_criteria_drops_stale_plan_specific_scope() -> None:
298 task = (
299 "Implement a persistent workflow artifact with planning artifacts, "
300 "verification commands, and plan refresh discipline."
301 )
302
303 preserved = preserve_task_grounded_acceptance_criteria(
304 task,
305 existing_acceptance_criteria=["planned.txt exists in the workspace root."],
306 refreshed_acceptance_criteria=["notes.txt exists in the workspace root."],
307 )
308
309 assert preserved == ["notes.txt exists in the workspace root."]
310
311
312 def test_planning_artifacts_with_acceptance_criteria_rewrites_verification_markdown() -> None:
313 artifacts = PlanningArtifacts.from_model_output(
314 "\n".join(
315 [
316 "# Implementation Plan",
317 "",
318 "## Execution Order",
319 "1. Create the guide files.",
320 "",
321 "<<<VERIFICATION>>>",
322 "",
323 "# Verification Plan",
324 "",
325 "## Acceptance Criteria",
326 "- At least one chapter file exists.",
327 "",
328 "## Verification Commands",
329 "- `find chapters -name \"*.html\" | wc -l`",
330 ]
331 ),
332 task_statement="Create a thorough nginx guide.",
333 )
334
335 updated = artifacts.with_acceptance_criteria(
336 [
337 "All files are created in the correct locations.",
338 "Content covers getting started, installation, and advanced topics.",
339 ]
340 )
341
342 assert "At least one chapter file exists." not in updated.verification_markdown
343 assert "All files are created in the correct locations." in updated.verification_markdown
344 assert (
345 "Content covers getting started, installation, and advanced topics."
346 in updated.verification_markdown
347 )
348
349
350 def test_planning_artifacts_with_progress_context_records_touched_and_completed_work() -> None:
351 artifacts = PlanningArtifacts.from_model_output(
352 "\n".join(
353 [
354 "# Implementation Plan",
355 "",
356 "## Execution Order",
357 "1. Create the guide files.",
358 "",
359 "<<<VERIFICATION>>>",
360 "",
361 "# Verification Plan",
362 "",
363 "## Acceptance Criteria",
364 "- At least one chapter file exists.",
365 "",
366 "## Verification Commands",
367 "- `find chapters -name \"*.html\" | wc -l`",
368 ]
369 ),
370 task_statement="Create a thorough nginx guide.",
371 )
372
373 updated = artifacts.with_progress_context(
374 touched_files=["/tmp/nginx/index.html"],
375 completed_items=[
376 "Create the guide scaffold",
377 "Collect verification evidence",
378 ],
379 )
380
381 assert "## Confirmed Progress" in updated.implementation_markdown
382 assert "Already touched during execution: `/tmp/nginx/index.html`." in (
383 updated.implementation_markdown
384 )
385 assert "Already completed during execution: Create the guide scaffold." in (
386 updated.implementation_markdown
387 )
388 assert "Collect verification evidence" not in updated.implementation_markdown
389
390
391 def test_merge_refreshed_todos_with_existing_scope_keeps_grounded_progress() -> None:
392 task = (
393 "Create an equally thorough nginx guide with index.html plus chapter files "
394 "covering getting started, installation, first website setup, configs, and "
395 "advanced topics."
396 )
397
398 todos = merge_refreshed_todos_with_existing_scope(
399 task,
400 existing_pending_items=[
401 "Create each chapter file in sequence, following the established pattern",
402 "Collect verification evidence",
403 ],
404 existing_completed_items=["Create directory structure for the new nginx guide"],
405 refreshed_steps=["Create sample chapter file to verify the structure works"],
406 )
407
408 assert todos[0]["content"] == "Create directory structure for the new nginx guide"
409 assert todos[0]["status"] == "completed"
410 assert any(
411 item["content"] == "Create each chapter file in sequence, following the established pattern"
412 and item["status"] == "pending"
413 for item in todos
414 )
415
416
417 def test_merge_refreshed_todos_with_existing_scope_filters_retro_refresh_noise() -> None:
418 task = (
419 "Create an equally thorough nginx guide with index.html plus chapter files "
420 "covering getting started, installation, first website setup, configs, and "
421 "advanced topics."
422 )
423
424 todos = merge_refreshed_todos_with_existing_scope(
425 task,
426 existing_pending_items=[
427 "Create each chapter file in sequence, following the same structure as the Fortran guide",
428 "Ensure all files are properly linked and formatted consistently",
429 ],
430 existing_completed_items=[
431 "First, examine the existing Fortran guide structure to understand the format and cadence",
432 "Create the directory structure for the new nginx guide",
433 "Create the main index.html file",
434 ],
435 refreshed_steps=[
436 "First examined the existing Fortran guide structure to understand format and cadence",
437 "Created the main index.html file with navigation",
438 "Created chapter files in sequence:",
439 "01-getting-started.html",
440 "02-installation.html",
441 "03-first-website.html",
442 "04-configuring.html",
443 "All files properly linked with navigation between chapters",
444 "Verify the final navigation links across the guide",
445 ],
446 )
447
448 labels = {item["content"]: item["status"] for item in todos}
449 assert (
450 labels["Create each chapter file in sequence, following the same structure as the Fortran guide"]
451 == "pending"
452 )
453 assert labels["Ensure all files are properly linked and formatted consistently"] == "pending"
454 assert labels["Verify the final navigation links across the guide"] == "pending"
455 assert "Created chapter files in sequence:" not in labels
456 assert "04-configuring.html" not in labels
457
458
459 def test_merge_refreshed_todos_with_existing_scope_drops_unplanned_filename_expansion() -> None:
460 task = (
461 "Create an equally thorough nginx guide with index.html plus chapter files "
462 "covering getting started, installation, configuration, usage, and troubleshooting."
463 )
464
465 todos = merge_refreshed_todos_with_existing_scope(
466 task,
467 existing_pending_items=[
468 "Create chapter files with appropriate content structure",
469 ],
470 existing_completed_items=[
471 "Create the nginx guide directory structure",
472 "Create introduction.html",
473 ],
474 refreshed_steps=[
475 "Create optimization.html",
476 "Create security.html",
477 "Ensure consistent chapter navigation",
478 ],
479 planned_files={
480 "index.html",
481 "introduction.html",
482 "installation.html",
483 "configuration.html",
484 "usage.html",
485 "troubleshooting.html",
486 },
487 )
488
489 labels = {item["content"]: item["status"] for item in todos}
490 assert "Create chapter files with appropriate content structure" in labels
491 assert "Ensure consistent chapter navigation" in labels
492 assert "Create optimization.html" not in labels
493 assert "Create security.html" not in labels
494
495
496 def test_planning_artifacts_with_file_changes_replaces_file_change_section() -> None:
497 artifacts = PlanningArtifacts(
498 implementation_markdown="\n".join(
499 [
500 "# Implementation Plan",
501 "",
502 "## File Changes",
503 "- `old.txt`",
504 "",
505 "## Execution Order",
506 "- Do the work",
507 "",
508 ]
509 )
510 + "\n",
511 verification_markdown="# Verification Plan\n",
512 verification_commands=[],
513 acceptance_criteria=["task"],
514 implementation_steps=["Do the work"],
515 )
516
517 updated = artifacts.with_file_changes(
518 ["`guides/nginx/index.html`", "`guides/nginx/chapters/`"]
519 )
520
521 assert "`old.txt`" not in updated.implementation_markdown
522 assert "`guides/nginx/index.html`" in updated.implementation_markdown
523 assert "`guides/nginx/chapters/`" in updated.implementation_markdown
524
525
526 def test_effective_pending_todo_items_filters_stale_discovery_after_artifacts_exist(
527 temp_dir: Path,
528 ) -> None:
529 guide_root = temp_dir / "guides" / "nginx"
530 chapters = guide_root / "chapters"
531 guide_root.mkdir(parents=True)
532 chapters.mkdir()
533 index_path = guide_root / "index.html"
534 chapter_one = chapters / "01-getting-started.html"
535 chapter_two = chapters / "02-installation.html"
536 index_path.write_text("<html></html>\n")
537 chapter_one.write_text("<h1>One</h1>\n")
538 chapter_two.write_text("<h1>Two</h1>\n")
539
540 implementation_plan = temp_dir / "implementation.md"
541 implementation_plan.write_text(
542 "\n".join(
543 [
544 "# Implementation Plan",
545 "",
546 "## File Changes",
547 f"- `{guide_root}/`",
548 f"- `{chapters}/`",
549 f"- `{index_path}`",
550 f"- `{chapter_one}`",
551 f"- `{chapter_two}`",
552 "",
553 ]
554 )
555 )
556
557 dod = create_definition_of_done("Create a multi-file nginx guide.")
558 dod.implementation_plan = str(implementation_plan)
559 dod.pending_items = [
560 "First, examine the existing Fortran guide structure to understand the format and content organization",
561 "Verify all guide files are linked and complete",
562 "Complete the requested work",
563 ]
564
565 pending = effective_pending_todo_items(dod, project_root=temp_dir)
566
567 assert "Verify all guide files are linked and complete" in pending
568 assert "Complete the requested work" in pending
569 assert not any("Fortran guide structure" in item for item in pending)
570
571
572 def test_effective_pending_todo_items_filters_completed_setup_before_build_finishes(
573 temp_dir: Path,
574 ) -> None:
575 guide_root = temp_dir / "guides" / "nginx"
576 chapters = guide_root / "chapters"
577 chapters.mkdir(parents=True)
578 index_path = guide_root / "index.html"
579 chapter_one = chapters / "01-introduction.html"
580 index_path.write_text("<html></html>\n")
581 chapter_one.write_text("<h1>One</h1>\n")
582
583 implementation_plan = temp_dir / "implementation.md"
584 implementation_plan.write_text(
585 "\n".join(
586 [
587 "# Implementation Plan",
588 "",
589 "## File Changes",
590 f"- `{guide_root}/`",
591 f"- `{chapters}/`",
592 f"- `{index_path}`",
593 f"- `{chapter_one}`",
594 f"- `{chapters / '02-installation.html'}`",
595 "",
596 ]
597 )
598 )
599
600 dod = create_definition_of_done("Create a multi-file nginx guide.")
601 dod.implementation_plan = str(implementation_plan)
602 dod.pending_items = [
603 "Create the nginx directory structure",
604 "Create each chapter file with appropriate content",
605 "Complete the requested work",
606 ]
607
608 pending = effective_pending_todo_items(dod, project_root=temp_dir)
609
610 assert "Create the nginx directory structure" not in pending
611 assert "Create each chapter file with appropriate content" in pending
612 assert "Complete the requested work" in pending
613
614
615 def test_effective_pending_todo_items_filters_stale_creation_steps_after_artifacts_exist(
616 temp_dir: Path,
617 ) -> None:
618 guide_root = temp_dir / "guides" / "nginx"
619 chapters = guide_root / "chapters"
620 guide_root.mkdir(parents=True)
621 chapters.mkdir()
622 index_path = guide_root / "index.html"
623 chapter_one = chapters / "01-getting-started.html"
624 chapter_two = chapters / "02-installation.html"
625 index_path.write_text("<html></html>\n")
626 chapter_one.write_text("<h1>One</h1>\n")
627 chapter_two.write_text("<h1>Two</h1>\n")
628
629 implementation_plan = temp_dir / "implementation.md"
630 implementation_plan.write_text(
631 "\n".join(
632 [
633 "# Implementation Plan",
634 "",
635 "## File Changes",
636 f"- `{guide_root}/`",
637 f"- `{chapters}/`",
638 f"- `{index_path}`",
639 f"- `{chapter_one}`",
640 f"- `{chapter_two}`",
641 "",
642 ]
643 )
644 )
645
646 dod = create_definition_of_done("Create a multi-file nginx guide.")
647 dod.implementation_plan = str(implementation_plan)
648 dod.pending_items = [
649 "Create 01-getting-started.html",
650 "Creating 02-installation.html",
651 "Verify all guide files are linked and complete",
652 "Complete the requested work",
653 ]
654
655 pending = effective_pending_todo_items(dod, project_root=temp_dir)
656
657 assert "Verify all guide files are linked and complete" in pending
658 assert "Complete the requested work" in pending
659 assert "Create 01-getting-started.html" not in pending
660 assert "Creating 02-installation.html" not in pending
661
662
663 def test_workflow_artifact_store_and_bridge_round_trip(tmp_path: Path) -> None:
664 store = WorkflowArtifactStore(tmp_path)
665 brief = ClarifyBrief.fallback(
666 task_statement="Clarify the runtime changes.",
667 question="What matters most?",
668 answer="Close the tool-use gap first.",
669 )
670 artifacts = PlanningArtifacts.fallback(task_statement=brief.task_statement)
671
672 brief_path = store.write_brief(brief.task_statement, brief)
673 implementation_path, verification_path = store.write_plan(
674 brief.task_statement,
675 artifacts,
676 )
677 bridge = build_execute_bridge(brief_path, implementation_path, verification_path)
678
679 assert brief_path.exists()
680 assert implementation_path.exists()
681 assert verification_path.exists()
682 assert bridge is not None
683 assert "Task Brief" in bridge
684 assert "Implementation Plan" in bridge
685 assert "Verification Plan" in bridge
686
687
688 def test_definition_of_done_round_trip_preserves_workflow_links(tmp_path: Path) -> None:
689 store = DefinitionOfDoneStore(tmp_path)
690 dod = create_definition_of_done("Implement Loader workflow routing.")
691 dod.current_mode = "plan"
692 dod.mode_history = ["clarify", "plan"]
693 dod.clarify_brief = str(tmp_path / ".loader" / "briefs" / "brief.md")
694 dod.implementation_plan = str(tmp_path / ".loader" / "plans" / "impl.md")
695 dod.verification_plan = str(tmp_path / ".loader" / "plans" / "verify.md")
696
697 saved_path = store.save(dod)
698 reloaded = store.load(saved_path)
699
700 assert reloaded.current_mode == "plan"
701 assert reloaded.mode_history == ["clarify", "plan"]
702 assert reloaded.clarify_brief == dod.clarify_brief
703 assert reloaded.implementation_plan == dod.implementation_plan
704 assert reloaded.verification_plan == dod.verification_plan
705
706
707 def test_sync_todos_to_definition_of_done_preserves_runtime_items() -> None:
708 dod = create_definition_of_done("Implement Loader workflow routing.")
709 dod.pending_items.append("Collect verification evidence")
710
711 sync_todos_to_definition_of_done(
712 dod,
713 [
714 {
715 "content": "Write router",
716 "active_form": "Writing router",
717 "status": "in_progress",
718 },
719 {
720 "content": "Update tests",
721 "active_form": "Updating tests",
722 "status": "completed",
723 },
724 ],
725 )
726
727 assert "Writing router" in dod.pending_items
728 assert "Collect verification evidence" in dod.pending_items
729 assert "Update tests" in dod.completed_items
730
731
732 def test_sync_todos_to_definition_of_done_keeps_completed_items_monotonic() -> None:
733 dod = create_definition_of_done("Create a multi-file nginx guide.")
734 sync_todos_to_definition_of_done(
735 dod,
736 [
737 {
738 "content": "Create 03-first-website.html",
739 "active_form": "Creating 03-first-website.html",
740 "status": "pending",
741 },
742 {
743 "content": "Create 04-configuration-basics.html",
744 "active_form": "Creating 04-configuration-basics.html",
745 "status": "pending",
746 },
747 ],
748 )
749
750 assert advance_todos_from_tool_call(
751 dod,
752 ToolCall(
753 id="write-third-chapter",
754 name="write",
755 arguments={
756 "file_path": "/tmp/nginx/chapters/03-first-website.html",
757 "content": "<html></html>",
758 },
759 ),
760 )
761 assert "Create 03-first-website.html" in dod.completed_items
762
763 sync_todos_to_definition_of_done(
764 dod,
765 [
766 {
767 "content": "Create 03-first-website.html",
768 "active_form": "Creating 03-first-website.html",
769 "status": "pending",
770 },
771 {
772 "content": "Create 04-configuration-basics.html",
773 "active_form": "Creating 04-configuration-basics.html",
774 "status": "pending",
775 },
776 ],
777 )
778
779 assert "Create 03-first-website.html" in dod.completed_items
780 assert "Create 03-first-website.html" not in dod.pending_items
781 assert "Create 04-configuration-basics.html" in dod.pending_items
782
783
784 def test_advance_todos_from_tool_call_tracks_plan_progress() -> None:
785 dod = create_definition_of_done("Fix the chapter links in index.html.")
786 sync_todos_to_definition_of_done(
787 dod,
788 [
789 {
790 "content": "First, examine the current index.html file to understand its structure",
791 "active_form": "Working on: First, examine the current index.html file to understand its structure",
792 "status": "pending",
793 },
794 {
795 "content": "List and read all HTML files in the chapters directory to extract chapter information",
796 "active_form": "Working on: List and read all HTML files in the chapters directory to extract chapter information",
797 "status": "pending",
798 },
799 {
800 "content": "Parse chapter titles from each HTML file",
801 "active_form": "Working on: Parse chapter titles from each HTML file",
802 "status": "pending",
803 },
804 {
805 "content": "Update index.html with correct chapter links and titles",
806 "active_form": "Working on: Update index.html with correct chapter links and titles",
807 "status": "pending",
808 },
809 {
810 "content": "Verify the updated index.html file is properly formatted",
811 "active_form": "Working on: Verify the updated index.html file is properly formatted",
812 "status": "pending",
813 },
814 ],
815 )
816
817 assert advance_todos_from_tool_call(
818 dod,
819 ToolCall(
820 id="read-index",
821 name="read",
822 arguments={"file_path": "/tmp/fortran/index.html"},
823 ),
824 )
825 assert (
826 "First, examine the current index.html file to understand its structure"
827 in dod.completed_items
828 )
829
830 assert advance_todos_from_tool_call(
831 dod,
832 ToolCall(
833 id="glob-chapters",
834 name="glob",
835 arguments={"path": "/tmp/fortran/chapters", "pattern": "*.html"},
836 ),
837 )
838 assert (
839 "List and read all HTML files in the chapters directory to extract chapter information"
840 in dod.completed_items
841 )
842
843 assert advance_todos_from_tool_call(
844 dod,
845 ToolCall(
846 id="read-chapter",
847 name="read",
848 arguments={"file_path": "/tmp/fortran/chapters/01-introduction.html"},
849 ),
850 )
851 assert "Parse chapter titles from each HTML file" in dod.completed_items
852
853 assert advance_todos_from_tool_call(
854 dod,
855 ToolCall(
856 id="patch-index",
857 name="patch",
858 arguments={"file_path": "/tmp/fortran/index.html", "hunks": []},
859 ),
860 )
861 assert "Update index.html with correct chapter links and titles" in dod.completed_items
862
863 assert advance_todos_from_tool_call(
864 dod,
865 ToolCall(
866 id="verify-index",
867 name="bash",
868 arguments={"command": "grep -o 'href=\"[^\"]*\"' /tmp/fortran/index.html"},
869 ),
870 )
871 assert "Verify the updated index.html file is properly formatted" in dod.completed_items
872
873
874 def test_advance_todos_from_tool_call_keeps_aggregate_mutation_steps_pending() -> None:
875 dod = create_definition_of_done("Create a multi-file nginx guide.")
876 sync_todos_to_definition_of_done(
877 dod,
878 [
879 {
880 "content": "Create each chapter file in sequence, following the same structure as the Fortran guide",
881 "active_form": "Working on: Create each chapter file in sequence, following the same structure as the Fortran guide",
882 "status": "pending",
883 },
884 {
885 "content": "Ensure all files are properly linked and formatted consistently",
886 "active_form": "Working on: Ensure all files are properly linked and formatted consistently",
887 "status": "pending",
888 },
889 ],
890 )
891
892 assert (
893 advance_todos_from_tool_call(
894 dod,
895 ToolCall(
896 id="write-one-chapter",
897 name="write",
898 arguments={
899 "file_path": "/tmp/nginx/chapters/01-getting-started.html",
900 "content": "<html></html>",
901 },
902 ),
903 )
904 is False
905 )
906 assert (
907 "Create each chapter file in sequence, following the same structure as the Fortran guide"
908 in dod.pending_items
909 )
910
911
912 def test_advance_todos_from_tool_call_keeps_plural_chapter_creation_step_pending() -> None:
913 dod = create_definition_of_done("Create a multi-file nginx guide.")
914 sync_todos_to_definition_of_done(
915 dod,
916 [
917 {
918 "content": "Create chapter files following the established pattern",
919 "active_form": "Working on: Create chapter files following the established pattern",
920 "status": "pending",
921 },
922 {
923 "content": "Ensure consistency with existing guide formatting and content style",
924 "active_form": "Working on: Ensure consistency with existing guide formatting and content style",
925 "status": "pending",
926 },
927 ],
928 )
929
930 assert (
931 advance_todos_from_tool_call(
932 dod,
933 ToolCall(
934 id="write-one-chapter",
935 name="write",
936 arguments={
937 "file_path": "/tmp/nginx/chapters/01-overview.html",
938 "content": "<html></html>",
939 },
940 ),
941 )
942 is False
943 )
944 assert "Create chapter files following the established pattern" in dod.pending_items
945
946
947 def test_advance_todos_from_tool_call_tracks_bash_directory_creation_progress() -> None:
948 dod = create_definition_of_done("Create a multi-file nginx guide.")
949 sync_todos_to_definition_of_done(
950 dod,
951 [
952 {
953 "content": "Create the nginx directory structure",
954 "active_form": "Working on: Create the nginx directory structure",
955 "status": "pending",
956 },
957 {
958 "content": "Create index.html for nginx guide",
959 "active_form": "Working on: Create index.html for nginx guide",
960 "status": "pending",
961 },
962 ],
963 )
964
965 assert advance_todos_from_tool_call(
966 dod,
967 ToolCall(
968 id="mkdir-nginx",
969 name="bash",
970 arguments={"command": "mkdir -p ~/Loader/guides/nginx/chapters"},
971 ),
972 )
973 assert "Create the nginx directory structure" in dod.completed_items
974 assert "Create index.html for nginx guide" in dod.pending_items
975
976
977 def test_advance_todos_from_tool_call_does_not_complete_develop_step_from_reference_read() -> None:
978 dod = create_definition_of_done("Create a multi-file nginx guide.")
979 sync_todos_to_definition_of_done(
980 dod,
981 [
982 {
983 "content": "First, examine the existing fortran guide structure and content",
984 "active_form": "Working on: First, examine the existing fortran guide structure and content",
985 "status": "pending",
986 },
987 {
988 "content": "Develop the main index.html file for the nginx guide",
989 "active_form": "Working on: Develop the main index.html file for the nginx guide",
990 "status": "pending",
991 },
992 ],
993 )
994
995 assert advance_todos_from_tool_call(
996 dod,
997 ToolCall(
998 id="read-reference-index",
999 name="read",
1000 arguments={"file_path": "~/Loader/guides/fortran/index.html"},
1001 ),
1002 )
1003 assert (
1004 "First, examine the existing fortran guide structure and content"
1005 in dod.completed_items
1006 )
1007 assert "Develop the main index.html file for the nginx guide" in dod.pending_items
1008
1009
1010 def test_advance_todos_from_tool_call_does_not_complete_content_examination_from_shallow_glob() -> None:
1011 dod = create_definition_of_done("Create a multi-file nginx guide.")
1012 sync_todos_to_definition_of_done(
1013 dod,
1014 [
1015 {
1016 "content": "First, examine the existing fortran guide structure and content",
1017 "active_form": "Working on: First, examine the existing fortran guide structure and content",
1018 "status": "pending",
1019 },
1020 {
1021 "content": "Develop the main index.html file for the nginx guide",
1022 "active_form": "Working on: Develop the main index.html file for the nginx guide",
1023 "status": "pending",
1024 },
1025 ],
1026 )
1027
1028 assert (
1029 advance_todos_from_tool_call(
1030 dod,
1031 ToolCall(
1032 id="glob-reference-root",
1033 name="glob",
1034 arguments={"path": "~/Loader/guides/fortran", "pattern": "**"},
1035 ),
1036 )
1037 is False
1038 )
1039 assert (
1040 "First, examine the existing fortran guide structure and content"
1041 in dod.pending_items
1042 )
1043 assert "Develop the main index.html file for the nginx guide" in dod.pending_items
1044
1045
1046 def test_advance_todos_from_tool_call_does_not_complete_populate_step_from_reference_read() -> None:
1047 dod = create_definition_of_done("Create a multi-file nginx guide.")
1048 sync_todos_to_definition_of_done(
1049 dod,
1050 [
1051 {
1052 "content": "First, examine the existing fortran guide structure and content",
1053 "active_form": "Working on: First, examine the existing fortran guide structure and content",
1054 "status": "pending",
1055 },
1056 {
1057 "content": "Populate content for each chapter",
1058 "active_form": "Working on: Populate content for each chapter",
1059 "status": "pending",
1060 },
1061 ],
1062 )
1063
1064 assert advance_todos_from_tool_call(
1065 dod,
1066 ToolCall(
1067 id="read-reference-chapter",
1068 name="read",
1069 arguments={"file_path": "~/Loader/guides/fortran/chapters/01-introduction.html"},
1070 ),
1071 )
1072 assert (
1073 "First, examine the existing fortran guide structure and content"
1074 in dod.completed_items
1075 )
1076 assert "Populate content for each chapter" in dod.pending_items
1077
1078
1079 def test_advance_todos_from_tool_call_does_not_complete_linking_step_from_glob() -> None:
1080 dod = create_definition_of_done("Create a multi-file nginx guide.")
1081 sync_todos_to_definition_of_done(
1082 dod,
1083 [
1084 {
1085 "content": "Link all chapters together properly in the index file",
1086 "active_form": "Working on: Link all chapters together properly in the index file",
1087 "status": "pending",
1088 },
1089 ],
1090 )
1091
1092 assert (
1093 advance_todos_from_tool_call(
1094 dod,
1095 ToolCall(
1096 id="glob-reference-chapters",
1097 name="glob",
1098 arguments={"path": "~/Loader", "pattern": "**/fortran/chapters/*"},
1099 ),
1100 )
1101 is False
1102 )
1103 assert "Link all chapters together properly in the index file" in dod.pending_items
1104
1105
1106 def test_advance_todos_from_tool_call_does_not_complete_aggregate_style_step_from_reference_read() -> None:
1107 dod = create_definition_of_done("Create a multi-file nginx guide.")
1108 sync_todos_to_definition_of_done(
1109 dod,
1110 [
1111 {
1112 "content": "Create each chapter file with appropriate content",
1113 "active_form": "Working on: Create each chapter file with appropriate content",
1114 "status": "pending",
1115 },
1116 {
1117 "content": "Ensure all files follow the same structure and style as the Fortran guide",
1118 "active_form": "Working on: Ensure all files follow the same structure and style as the Fortran guide",
1119 "status": "pending",
1120 },
1121 ],
1122 )
1123
1124 assert (
1125 advance_todos_from_tool_call(
1126 dod,
1127 ToolCall(
1128 id="read-reference-index",
1129 name="read",
1130 arguments={"file_path": "~/Loader/guides/fortran/index.html"},
1131 ),
1132 )
1133 is False
1134 )
1135 assert "Create each chapter file with appropriate content" in dod.pending_items
1136 assert (
1137 "Ensure all files follow the same structure and style as the Fortran guide"
1138 in dod.pending_items
1139 )
1140
1141
1142 def test_sync_todos_to_definition_of_done_keeps_linking_step_pending_while_artifacts_missing(
1143 temp_dir: Path,
1144 ) -> None:
1145 guide_root = temp_dir / "guides" / "nginx"
1146 chapters = guide_root / "chapters"
1147 guide_root.mkdir(parents=True)
1148 chapters.mkdir()
1149 index_path = guide_root / "index.html"
1150 chapter_one = chapters / "01-getting-started.html"
1151 chapter_two = chapters / "02-installation.html"
1152 index_path.write_text("<html></html>\n")
1153 chapter_one.write_text("<h1>One</h1>\n")
1154
1155 implementation_plan = temp_dir / "implementation.md"
1156 implementation_plan.write_text(
1157 "\n".join(
1158 [
1159 "# Implementation Plan",
1160 "",
1161 "## File Changes",
1162 f"- `{guide_root}/`",
1163 f"- `{chapters}/`",
1164 f"- `{index_path}`",
1165 f"- `{chapter_one}`",
1166 f"- `{chapter_two}`",
1167 "",
1168 ]
1169 )
1170 )
1171
1172 dod = create_definition_of_done("Create a multi-file nginx guide.")
1173 dod.implementation_plan = str(implementation_plan)
1174 sync_todos_to_definition_of_done(
1175 dod,
1176 [
1177 {
1178 "content": "Create 01-getting-started.html chapter file",
1179 "active_form": "Creating 01-getting-started.html chapter file",
1180 "status": "completed",
1181 },
1182 {
1183 "content": "Link all chapters together properly in the index file",
1184 "active_form": "Linking chapters in the index file",
1185 "status": "completed",
1186 },
1187 {
1188 "content": "Create 02-installation.html chapter file",
1189 "active_form": "Creating 02-installation.html chapter file",
1190 "status": "pending",
1191 },
1192 ],
1193 project_root=temp_dir,
1194 )
1195
1196 assert "Link all chapters together properly in the index file" in dod.pending_items
1197 assert "Link all chapters together properly in the index file" not in dod.completed_items
1198
1199
1200 def test_sync_todos_to_definition_of_done_allows_linking_step_when_artifacts_exist(
1201 temp_dir: Path,
1202 ) -> None:
1203 guide_root = temp_dir / "guides" / "nginx"
1204 chapters = guide_root / "chapters"
1205 guide_root.mkdir(parents=True)
1206 chapters.mkdir()
1207 index_path = guide_root / "index.html"
1208 chapter_one = chapters / "01-getting-started.html"
1209 chapter_two = chapters / "02-installation.html"
1210 index_path.write_text("<html></html>\n")
1211 chapter_one.write_text("<h1>One</h1>\n")
1212 chapter_two.write_text("<h1>Two</h1>\n")
1213
1214 implementation_plan = temp_dir / "implementation.md"
1215 implementation_plan.write_text(
1216 "\n".join(
1217 [
1218 "# Implementation Plan",
1219 "",
1220 "## File Changes",
1221 f"- `{guide_root}/`",
1222 f"- `{chapters}/`",
1223 f"- `{index_path}`",
1224 f"- `{chapter_one}`",
1225 f"- `{chapter_two}`",
1226 "",
1227 ]
1228 )
1229 )
1230
1231 dod = create_definition_of_done("Create a multi-file nginx guide.")
1232 dod.implementation_plan = str(implementation_plan)
1233 sync_todos_to_definition_of_done(
1234 dod,
1235 [
1236 {
1237 "content": "Link all chapters together properly in the index file",
1238 "active_form": "Linking chapters in the index file",
1239 "status": "completed",
1240 },
1241 ],
1242 project_root=temp_dir,
1243 )
1244
1245 assert "Link all chapters together properly in the index file" in dod.completed_items
1246
1247
1248 def test_sync_todos_to_definition_of_done_reopens_directory_content_step_when_output_dir_is_empty(
1249 temp_dir: Path,
1250 ) -> None:
1251 guide_root = temp_dir / "guides" / "nginx"
1252 chapters = guide_root / "chapters"
1253 guide_root.mkdir(parents=True)
1254 chapters.mkdir()
1255 index_path = guide_root / "index.html"
1256 index_path.write_text("<html></html>\n")
1257
1258 implementation_plan = temp_dir / "implementation.md"
1259 implementation_plan.write_text(
1260 "\n".join(
1261 [
1262 "# Implementation Plan",
1263 "",
1264 "## File Changes",
1265 f"- `{guide_root / 'index.html'}`",
1266 f"- `{chapters}/` (directory for chapter files)",
1267 "",
1268 "## Execution Order",
1269 "- Create chapter files with appropriate content",
1270 ]
1271 )
1272 )
1273
1274 dod = create_definition_of_done("Create an equally thorough nginx guide with chapters.")
1275 dod.implementation_plan = str(implementation_plan)
1276 sync_todos_to_definition_of_done(
1277 dod,
1278 [
1279 {
1280 "content": "Create chapter files with appropriate content",
1281 "active_form": "Creating chapter files with appropriate content",
1282 "status": "completed",
1283 },
1284 ],
1285 project_root=temp_dir,
1286 )
1287
1288 assert "Create chapter files with appropriate content" in dod.pending_items
1289 assert "Create chapter files with appropriate content" not in dod.completed_items
1290
1291
1292 def test_reconcile_aggregate_completion_steps_reopens_linking_step_when_artifacts_missing(
1293 temp_dir: Path,
1294 ) -> None:
1295 guide_root = temp_dir / "guides" / "nginx"
1296 chapters = guide_root / "chapters"
1297 guide_root.mkdir(parents=True)
1298 chapters.mkdir()
1299 index_path = guide_root / "index.html"
1300 chapter_one = chapters / "01-getting-started.html"
1301 chapter_two = chapters / "02-installation.html"
1302 chapter_three = chapters / "03-first-website.html"
1303 index_path.write_text("<html></html>\n")
1304 chapter_one.write_text("<h1>One</h1>\n")
1305 chapter_two.write_text("<h1>Two</h1>\n")
1306
1307 implementation_plan = temp_dir / "implementation.md"
1308 implementation_plan.write_text(
1309 "\n".join(
1310 [
1311 "# Implementation Plan",
1312 "",
1313 "## File Changes",
1314 f"- `{guide_root}/`",
1315 f"- `{chapters}/`",
1316 f"- `{index_path}`",
1317 f"- `{chapter_one}`",
1318 f"- `{chapter_two}`",
1319 f"- `{chapter_three}`",
1320 "",
1321 ]
1322 )
1323 )
1324
1325 dod = create_definition_of_done("Create a multi-file nginx guide.")
1326 dod.implementation_plan = str(implementation_plan)
1327 dod.completed_items.append("Link all chapters together properly")
1328
1329 reconcile_aggregate_completion_steps(dod, project_root=temp_dir)
1330
1331 assert "Link all chapters together properly" not in dod.completed_items
1332 assert "Link all chapters together properly" in dod.pending_items
1333
1334
1335 def test_sync_todos_to_definition_of_done_drops_unplanned_artifact_expansion_after_plan_complete(
1336 temp_dir: Path,
1337 ) -> None:
1338 guide_root = temp_dir / "guides" / "nginx"
1339 chapters = guide_root / "chapters"
1340 guide_root.mkdir(parents=True)
1341 chapters.mkdir()
1342 index_path = guide_root / "index.html"
1343 chapter_one = chapters / "01-getting-started.html"
1344 chapter_two = chapters / "02-installation.html"
1345 index_path.write_text("<html></html>\n")
1346 chapter_one.write_text("<h1>One</h1>\n")
1347 chapter_two.write_text("<h1>Two</h1>\n")
1348
1349 implementation_plan = temp_dir / "implementation.md"
1350 implementation_plan.write_text(
1351 "\n".join(
1352 [
1353 "# Implementation Plan",
1354 "",
1355 "## File Changes",
1356 f"- `{guide_root}/`",
1357 f"- `{chapters}/`",
1358 f"- `{index_path}`",
1359 f"- `{chapter_one}`",
1360 f"- `{chapter_two}`",
1361 "",
1362 ]
1363 )
1364 )
1365
1366 dod = create_definition_of_done("Create a multi-file nginx guide.")
1367 dod.implementation_plan = str(implementation_plan)
1368 sync_todos_to_definition_of_done(
1369 dod,
1370 [
1371 {
1372 "content": "Create 01-getting-started.html",
1373 "active_form": "Creating 01-getting-started.html",
1374 "status": "completed",
1375 },
1376 {
1377 "content": "Create 02-installation.html",
1378 "active_form": "Creating 02-installation.html",
1379 "status": "completed",
1380 },
1381 {
1382 "content": "Create 07-performance-tuning.html",
1383 "active_form": "Creating 07-performance-tuning.html",
1384 "status": "in_progress",
1385 },
1386 ],
1387 project_root=temp_dir,
1388 )
1389
1390 assert "Creating 07-performance-tuning.html" not in dod.pending_items
1391 assert "Create 01-getting-started.html" in dod.completed_items
1392 assert "Create 02-installation.html" in dod.completed_items