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