cafe-engine 0.1.2__tar.gz → 0.1.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (205) hide show
  1. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/CHANGELOG.md +11 -0
  2. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/PKG-INFO +1 -1
  3. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/pyproject.toml +1 -1
  4. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/agents/cli/copilot.py +5 -5
  5. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/core/phase.py +9 -2
  6. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/phases/pr_phase.py +75 -23
  7. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/ui/cli.py +27 -1
  8. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/utils/checklist_generator.py +8 -1
  9. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/utils/checklist_templates.py +2 -2
  10. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_cli_pr_command.py +87 -1
  11. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_cli_prepare.py +186 -0
  12. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_copilot_cli.py +43 -0
  13. cafe_engine-0.1.3/tests/unit/test_pr_phase_output_todo_only.py +194 -0
  14. cafe_engine-0.1.3/tests/unit/test_pr_phase_post_todo_comment.py +317 -0
  15. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/uv.lock +1 -1
  16. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/.claude/settings.local.json +0 -0
  17. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/.geminiignore +0 -0
  18. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/.githooks/pre-commit +0 -0
  19. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/.gitignore +0 -0
  20. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/CONTRIBUTING.md +0 -0
  21. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/LICENSE +0 -0
  22. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/README.md +0 -0
  23. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/images/cafe-flow.png +0 -0
  24. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/scripts/simulate_agent_test.sh +0 -0
  25. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/setup-hooks.sh +0 -0
  26. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/__init__.py +0 -0
  27. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/agents/__init__.py +0 -0
  28. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/agents/cli/__init__.py +0 -0
  29. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/agents/cli/abstract.py +0 -0
  30. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/agents/cli/claude.py +0 -0
  31. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/agents/cli/cursor.py +0 -0
  32. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/agents/cli/gemini.py +0 -0
  33. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/agents/executor.py +0 -0
  34. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/agents/manager.py +0 -0
  35. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/agents/mock_executor.py +0 -0
  36. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/core/__init__.py +0 -0
  37. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/core/git.py +0 -0
  38. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/core/permission.py +0 -0
  39. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/core/phase_cache.py +0 -0
  40. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/core/questions_schema.py +0 -0
  41. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/core/session.py +0 -0
  42. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/core/status_codes.py +0 -0
  43. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/core/types.py +0 -0
  44. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/data/agents/developer/David.md +0 -0
  45. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/data/agents/developer/Nick.md +0 -0
  46. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/data/agents/developer//347/276/205/345/215/232/351/253/230.md" +0 -0
  47. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/data/agents/developer//351/255/257/346/237/224/345/207/241.md" +0 -0
  48. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/data/agents/pm/Roger.md +0 -0
  49. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/data/agents/pm//350/214/203/346/233/231/347/207/201.md" +0 -0
  50. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/data/agents/reviewer/Richard.md +0 -0
  51. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/data/agents/reviewer//346/236/227/350/220/214/350/212/235.md" +0 -0
  52. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/data/templates/plan/bug.md +0 -0
  53. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/data/templates/plan/default.md +0 -0
  54. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/data/templates/plan/simple.md +0 -0
  55. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/data/templates/spec/default.md +0 -0
  56. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/data/templates/spec/detailed.md +0 -0
  57. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/data/templates/spec/simple.md +0 -0
  58. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/phases/__init__.py +0 -0
  59. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/phases/develop_phase.py +0 -0
  60. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/phases/plan_phase.py +0 -0
  61. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/phases/review_phase.py +0 -0
  62. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/phases/spec_phase.py +0 -0
  63. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/services/__init__.py +0 -0
  64. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/services/delta_display.py +0 -0
  65. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/services/summary_display.py +0 -0
  66. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/services/summary_service.py +0 -0
  67. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/services/time_formatter.py +0 -0
  68. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/services/timeline_builder.py +0 -0
  69. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/templates/__init__.py +0 -0
  70. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/templates/manager.py +0 -0
  71. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/ui/__init__.py +0 -0
  72. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/ui/display.py +0 -0
  73. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/ui/init_helpers.py +0 -0
  74. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/ui/inquirer_prompts.py +0 -0
  75. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/ui/interactive_qa.py +0 -0
  76. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/ui/phase_prompts.py +0 -0
  77. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/ui/template_selector.py +0 -0
  78. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/utils/__init__.py +0 -0
  79. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/utils/checklist_utils.py +0 -0
  80. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/utils/checklist_validator.py +0 -0
  81. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/utils/config.py +0 -0
  82. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/utils/git_utils.py +0 -0
  83. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/utils/github.py +0 -0
  84. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/src/cafe/utils/prompt_utils.py +0 -0
  85. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/.gitignore +0 -0
  86. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/__init__.py +0 -0
  87. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/conftest.py +0 -0
  88. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/fixtures/minimal_config.yaml +0 -0
  89. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/integration/__init__.py +0 -0
  90. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/integration/conftest.py +0 -0
  91. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/integration/test_gemini_session_integration.py +0 -0
  92. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/integration/test_global_config_integration.py +0 -0
  93. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/integration/test_pr_command_non_interactive.py +0 -0
  94. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/integration/test_pr_e2e_with_mock.py +0 -0
  95. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/integration/test_prepare_non_github.py +0 -0
  96. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/integration/test_spec_interactive_qa_e2e.py +0 -0
  97. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/__init__.py +0 -0
  98. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_abstract_cli.py +0 -0
  99. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_agent_edit_auto_sync.py +0 -0
  100. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_agent_executor.py +0 -0
  101. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_agent_executor_path_formats.py +0 -0
  102. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_agent_loading.py +0 -0
  103. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_agent_manager.py +0 -0
  104. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_agent_manager_mock_mode.py +0 -0
  105. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_check_precommit_env.py +0 -0
  106. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_checklist_generator.py +0 -0
  107. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_checklist_utils.py +0 -0
  108. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_claude_cli.py +0 -0
  109. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_cli.py +0 -0
  110. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_cli_agent_commands.py +0 -0
  111. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_cli_agent_defaults.py +0 -0
  112. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_cli_agent_sync.py +0 -0
  113. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_cli_auto_mode.py +0 -0
  114. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_cli_close.py +0 -0
  115. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_cli_close_pr_mode.py +0 -0
  116. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_cli_init.py +0 -0
  117. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_cli_init_phase_config.py +0 -0
  118. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_cli_invoke_command.py +0 -0
  119. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_cli_ls_rm.py +0 -0
  120. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_cli_make.py +0 -0
  121. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_cli_module_init.py +0 -0
  122. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_cli_phase_commands.py +0 -0
  123. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_cli_prepare_init.py +0 -0
  124. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_cli_restore.py +0 -0
  125. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_cli_setup_agents.py +0 -0
  126. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_cli_show.py +0 -0
  127. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_cli_spec_auto.py +0 -0
  128. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_cli_spec_output.py +0 -0
  129. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_cli_template.py +0 -0
  130. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_cli_template_sync.py +0 -0
  131. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_config.py +0 -0
  132. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_context_endtime.py +0 -0
  133. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_context_session_id_update.py +0 -0
  134. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_copilot_parse_response.py +0 -0
  135. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_cursor_cli.py +0 -0
  136. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_delta_display.py +0 -0
  137. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_develop_clarification.py +0 -0
  138. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_develop_no_changes_needed.py +0 -0
  139. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_develop_phase_prompt.py +0 -0
  140. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_directory_structure.py +0 -0
  141. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_display.py +0 -0
  142. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_ensure_agent_file_exists.py +0 -0
  143. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_ensure_default_content.py +0 -0
  144. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_executor_session_recovery_generic.py +0 -0
  145. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_gemini_cli.py +0 -0
  146. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_gemini_content_extractor.py +0 -0
  147. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_git.py +0 -0
  148. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_git_utils.py +0 -0
  149. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_git_utils_ceiling.py +0 -0
  150. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_git_utils_repo_root.py +0 -0
  151. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_git_worktree.py +0 -0
  152. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_github.py +0 -0
  153. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_github_pr_comments.py +0 -0
  154. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_github_utils.py +0 -0
  155. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_global_config.py +0 -0
  156. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_image_download.py +0 -0
  157. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_init_helpers.py +0 -0
  158. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_inquirer_prompts.py +0 -0
  159. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_interactive_qa.py +0 -0
  160. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_mock_agent_executor.py +0 -0
  161. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_permission.py +0 -0
  162. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_phase_cache.py +0 -0
  163. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_phase_get_issue_config_value.py +0 -0
  164. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_phase_interactive.py +0 -0
  165. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_phase_iteration_structure.py +0 -0
  166. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_phase_non_interactive_behavior.py +0 -0
  167. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_phase_progress.py +0 -0
  168. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_phase_prompts.py +0 -0
  169. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_phase_status_analysis_with_tools.py +0 -0
  170. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_plan_phase_github_sync.py +0 -0
  171. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_plan_phase_status_codes.py +0 -0
  172. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_plan_phase_sync_github_config.py +0 -0
  173. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_plan_phase_template_mode.py +0 -0
  174. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_pr_comment_images.py +0 -0
  175. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_pr_phase_context.py +0 -0
  176. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_pr_phase_generate_content.py +0 -0
  177. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_pr_phase_github_mode.py +0 -0
  178. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_pr_phase_issue_comment.py +0 -0
  179. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_pr_phase_iteration_logic.py +0 -0
  180. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_pr_phase_parsing.py +0 -0
  181. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_pr_phase_prepare_content.py +0 -0
  182. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_pr_phase_status_analysis.py +0 -0
  183. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_prepare_auto_sync.py +0 -0
  184. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_prompt_utils.py +0 -0
  185. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_questions_schema.py +0 -0
  186. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_reset_command.py +0 -0
  187. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_resolve_sync_github.py +0 -0
  188. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_session.py +0 -0
  189. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_spec_github_sync.py +0 -0
  190. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_spec_image_download.py +0 -0
  191. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_spec_phase_qa.py +0 -0
  192. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_spec_phase_sync_github_config.py +0 -0
  193. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_spec_prompt_with_images.py +0 -0
  194. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_status_codes.py +0 -0
  195. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_summary_display.py +0 -0
  196. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_summary_service.py +0 -0
  197. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_sync_helpers.py +0 -0
  198. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_template_edit_auto_sync.py +0 -0
  199. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_template_manager.py +0 -0
  200. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_template_manager_global.py +0 -0
  201. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_template_selector_auto.py +0 -0
  202. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_time_formatter.py +0 -0
  203. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_timeline_builder.py +0 -0
  204. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/test_types.py +0 -0
  205. {cafe_engine-0.1.2 → cafe_engine-0.1.3}/tests/unit/utils/test_checklist_validator.py +0 -0
@@ -2,6 +2,17 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.1.3]
6
+
7
+ ### Added
8
+ - Added `--post-todo-list` option to `cafe pr` command to post PR comment todo list as GitHub PR comment
9
+ - Added `--post-pr-todo-list` option to `cafe prepare` command to set post-PR-todo-list mode for prepare phase
10
+ - Added token usage summary display after PR comment organization completes
11
+
12
+ ### Fixed
13
+ - Fixed copilot session_id detection to recognize both file-based (.jsonl) and directory-based session formats
14
+ - Fixed `_print_token_usage_summary` to gracefully handle mocked agent_manager in tests
15
+
5
16
  ## [0.1.2]
6
17
 
7
18
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cafe-engine
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: CLI Agent Flow Engine - Automated development workflow with AI agents
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.10
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cafe-engine"
3
- version = "0.1.2"
3
+ version = "0.1.3"
4
4
  description = "CLI Agent Flow Engine - Automated development workflow with AI agents"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -221,7 +221,7 @@ class CopilotCLI(AbstractCLI):
221
221
 
222
222
  if copilot_session_dir.exists():
223
223
  self._existing_sessions = {
224
- f.name for f in copilot_session_dir.iterdir() if f.is_file()
224
+ f.name for f in copilot_session_dir.iterdir() if f.is_file() or f.is_dir()
225
225
  }
226
226
  else:
227
227
  self._existing_sessions = set()
@@ -229,7 +229,7 @@ class CopilotCLI(AbstractCLI):
229
229
  def extract_session_id(self, output_lines: List[str]) -> Optional[str]:
230
230
  """Extract newly created session ID from file system.
231
231
 
232
- Copilot automatically creates session files, need to detect from file system.
232
+ Copilot automatically creates session directories, need to detect from file system.
233
233
 
234
234
  Args:
235
235
  output_lines: List of lines from CLI output (not used here)
@@ -245,9 +245,9 @@ class CopilotCLI(AbstractCLI):
245
245
  # Wait for file system to update
246
246
  time.sleep(0.1)
247
247
 
248
- # Get current session files
248
+ # Get current session files and directories
249
249
  current_sessions = {
250
- f.name for f in copilot_session_dir.iterdir() if f.is_file()
250
+ f.name for f in copilot_session_dir.iterdir() if f.is_file() or f.is_dir()
251
251
  }
252
252
 
253
253
  # Find newly created sessions
@@ -256,7 +256,7 @@ class CopilotCLI(AbstractCLI):
256
256
  if new_sessions:
257
257
  # Get newest session (sorted by name)
258
258
  newest_session = sorted(new_sessions)[-1]
259
- # Remove .jsonl extension
259
+ # Remove .jsonl extension if present (for file-based sessions)
260
260
  session_id = newest_session.replace(".jsonl", "")
261
261
  return session_id
262
262
 
@@ -1136,8 +1136,15 @@ class Phase(ABC):
1136
1136
  if not hasattr(self, "agent_manager"):
1137
1137
  return
1138
1138
 
1139
- token_usage = self.agent_manager.get_total_token_usage()
1140
- model = self.agent_manager.get_last_model()
1139
+ try:
1140
+ token_usage = self.agent_manager.get_total_token_usage()
1141
+ model = self.agent_manager.get_last_model()
1142
+
1143
+ # Verify we have real token usage data (not mocks)
1144
+ if not isinstance(token_usage.input_tokens, int):
1145
+ return
1146
+ except (AttributeError, TypeError):
1147
+ return
1141
1148
 
1142
1149
  print()
1143
1150
  print("=" * 60)
@@ -38,6 +38,7 @@ class PRPhase(Phase):
38
38
  force_push: bool = False,
39
39
  interactive: bool = True,
40
40
  base_branch: Optional[str] = None,
41
+ post_todo_list: Optional[bool] = None,
41
42
  ) -> None:
42
43
  """Initialize PR phase.
43
44
 
@@ -57,6 +58,7 @@ class PRPhase(Phase):
57
58
  force_push: Force push to remote (default: False)
58
59
  interactive: Enable interactive mode (default: True)
59
60
  base_branch: Target base branch (None for auto-detection from config)
61
+ post_todo_list: Post organized todo list as PR comment (None for auto-detect from config)
60
62
  """
61
63
  super().__init__(interactive=interactive)
62
64
 
@@ -93,6 +95,19 @@ class PRPhase(Phase):
93
95
  config_base = self._get_issue_config_value(config_file, "base_branch")
94
96
  self.base_branch = config_base if config_base else "main"
95
97
 
98
+ # Resolve post_todo_list: CLI value > config file value > default (True)
99
+ if post_todo_list is not None:
100
+ # CLI parameter takes precedence
101
+ self.post_todo_list = post_todo_list
102
+ else:
103
+ config_file = self.issue_dir / "issue.yaml"
104
+ config_value = self._get_issue_config_value(config_file, "pr.post_todo_list")
105
+ if config_value is not None:
106
+ self.post_todo_list = bool(config_value)
107
+ else:
108
+ # Default to True to maintain backward compatibility
109
+ self.post_todo_list = True
110
+
96
111
  # Set up history tracking (like other phases)
97
112
  self.phase_dir = self.issue_dir / "pr"
98
113
  self.history_dir = self.phase_dir / "history"
@@ -1078,6 +1093,21 @@ class PRPhase(Phase):
1078
1093
  console.print(f"[yellow]⚠️ Warning: Failed to fetch/save PR comments: {e}[/yellow]")
1079
1094
  return None
1080
1095
 
1096
+ @staticmethod
1097
+ def _build_todo_list_comment(todo_content: str, user_input_path: str) -> str:
1098
+ """Build PR comment body with todo list and user_input.md reference.
1099
+
1100
+ Args:
1101
+ todo_content: The organized todo list content
1102
+ user_input_path: File path to user_input.md for reference
1103
+
1104
+ Returns:
1105
+ Formatted comment body with file path reference and todo list
1106
+ """
1107
+ return f"""> 📋 Original review comments: `{user_input_path}`
1108
+
1109
+ {todo_content}"""
1110
+
1081
1111
  def _organize_comments_to_todo_list(self, pr_number: int, pr_url: str, branch_name: str) -> PhaseResult:
1082
1112
  """Organize PR comments into actionable todo list format.
1083
1113
 
@@ -1139,13 +1169,13 @@ class PRPhase(Phase):
1139
1169
  output_file_path=output_pattern,
1140
1170
  prev_output_file_path=prev_output_pattern,
1141
1171
  checklist_file_path=checklist_file,
1172
+ basic_principles=None,
1142
1173
  )
1143
1174
 
1144
- # Copy user_input.md to output.md as starting point
1145
- # Agent will then append todo list below the original content
1146
- if user_input_file.exists() and not output_file.exists():
1147
- import shutil
1148
- shutil.copy2(user_input_file, output_file)
1175
+ # Create empty output.md file for agent to write todo list only
1176
+ # Original PR comments remain in user_input.md for reference
1177
+ if not output_file.exists():
1178
+ output_file.write_text("", encoding="utf-8")
1149
1179
 
1150
1180
  # Get relative paths for tool permissions
1151
1181
  try:
@@ -1202,16 +1232,11 @@ Do NOT return a status code until ALL checklist items are marked as [x].
1202
1232
  **Task:** Organize PR comments into actionable todo list.
1203
1233
 
1204
1234
  **Context:**
1205
- - Output file: {output_pattern} (already contains PR comments, append todo list below){images_instruction}
1206
-
1207
- **Instructions:**
1208
- 1. Read the output file which already contains the PR comments
1209
- 2. Analyze the comments and organize them into actionable todo items
1210
- 3. Append the todo list to the END of the output file (do not remove the original content)
1235
+ - PR comments source: {user_input_pattern} (original review comments)
1236
+ - Output file: {output_pattern} (write organized todo list here){images_instruction}
1211
1237
 
1212
- **Output format to append:**
1238
+ **Output format to write:**
1213
1239
  ```markdown
1214
-
1215
1240
  ## Todo List
1216
1241
 
1217
1242
  ### [Category/Theme]
@@ -1268,18 +1293,24 @@ The system will verify checklist completion. If unchecked items remain, you will
1268
1293
  message=f"Checklist validation failed - {validation_result.unchecked_count} items not marked as complete",
1269
1294
  )
1270
1295
 
1271
- # Validate output.md was updated (should differ from user_input.md)
1272
- pr_dir = self.issue_dir / "pr"
1273
- was_updated = self._check_output_file_updated(
1274
- output_file=output_file,
1275
- iteration=self.iteration,
1276
- phase_dir=pr_dir,
1277
- compare_content=user_input_file.read_text(encoding="utf-8") if user_input_file.exists() else None,
1278
- )
1279
- if not was_updated:
1296
+ # Validate output.md contains todo list content
1297
+ if not output_file.exists():
1298
+ return PhaseResult(
1299
+ status=PhaseStatus.FAILED,
1300
+ message="Agent did not create output.md file",
1301
+ )
1302
+
1303
+ output_content = output_file.read_text(encoding="utf-8")
1304
+ # Check if output.md contains todo list markers (either ## Todo List or checkbox items)
1305
+ has_todo_list = ("## Todo List" in output_content or
1306
+ "## Todo" in output_content or
1307
+ "- [ ]" in output_content or
1308
+ "- [x]" in output_content)
1309
+
1310
+ if not has_todo_list:
1280
1311
  return PhaseResult(
1281
1312
  status=PhaseStatus.FAILED,
1282
- message="Agent did not update output.md with todo list",
1313
+ message="Agent did not write todo list to output.md (missing todo list markers)",
1283
1314
  )
1284
1315
 
1285
1316
  # Additional validation: if agent returned CONFIRMED, verify this is correct
@@ -1332,12 +1363,33 @@ Return ONLY the status code (CAFE_CONFIRMED or CAFE_NEEDS_CHANGES) with no expla
1332
1363
  pr_dir = self.issue_dir / "pr"
1333
1364
  iteration_dir = pr_dir / f"iteration_{self.iteration:03d}"
1334
1365
  output_file = iteration_dir / "output.md"
1366
+ # Post todo list as PR comment in GitHub mode (if enabled)
1367
+ if pr_number > 0 and self.post_todo_list: # Only post in GitHub mode when option is enabled
1368
+ try:
1369
+ todo_list_content = output_file.read_text(encoding="utf-8")
1370
+ from cafe.utils.git_utils import to_cwd_relative_path
1371
+ try:
1372
+ user_input_display = to_cwd_relative_path(user_input_file)
1373
+ except ValueError:
1374
+ user_input_display = str(user_input_file)
1375
+
1376
+ comment_body = self._build_todo_list_comment(todo_list_content, user_input_display)
1377
+ self.github_ops.add_pr_comment(str(pr_number), comment_body)
1378
+ except Exception as e:
1379
+ # Don't fail the workflow if posting comment fails - just warn
1380
+ from rich.console import Console
1381
+ console = Console()
1382
+ console.print(f"[yellow]⚠️ Warning: Failed to post todo list as PR comment: {e}[/yellow]")
1383
+
1335
1384
  from cafe.utils.git_utils import to_cwd_relative_path
1336
1385
  try:
1337
1386
  output_display = to_cwd_relative_path(output_file)
1338
1387
  except ValueError:
1339
1388
  output_display = str(output_file)
1340
1389
 
1390
+ # Display token usage summary before returning
1391
+ self._print_token_usage_summary()
1392
+
1341
1393
  return PhaseResult(
1342
1394
  status=PhaseStatus.COMPLETED,
1343
1395
  message=f"Organized PR comments into todo list ({output_display})",
@@ -816,6 +816,11 @@ def prepare(
816
816
  "--sync-plan-github/--no-sync-plan-github",
817
817
  help="Sync plan to GitHub issue when confirmed (default: auto-detect based on issue_id)",
818
818
  ),
819
+ post_pr_todo_list: Optional[bool] = typer.Option(
820
+ None,
821
+ "--post-pr-todo-list/--no-post-pr-todo-list",
822
+ help="Post organized PR comments as todo list to PR (default: True when auto-create PR is enabled)",
823
+ ),
819
824
  ) -> None:
820
825
  """Prepare issue environment (directory, config, git branch) before running spec phase.
821
826
 
@@ -1045,9 +1050,12 @@ def prepare(
1045
1050
  # Auto create PR depends on whether it's a GitHub repo
1046
1051
  if is_github_repo():
1047
1052
  pr_config["auto_create"] = True
1053
+ # In Quick setup, auto_create is always True for GitHub repos,
1054
+ # so always enable post_todo_list as well
1055
+ pr_config["post_todo_list"] = True
1048
1056
  else:
1049
1057
  pr_config["auto_create"] = False
1050
-
1058
+
1051
1059
  # Display default values summary
1052
1060
  console.print()
1053
1061
  console.print("[green]✓ Quick setup applied with recommended defaults:[/green]")
@@ -1058,6 +1066,7 @@ def prepare(
1058
1066
  if is_github_repo():
1059
1067
  console.print(f" • Sync to GitHub: {spec_config.get('sync_github', False)}")
1060
1068
  console.print(f" • Auto create PR: {pr_config.get('auto_create', False)}")
1069
+ console.print(f" • Post PR todo list: {pr_config.get('post_todo_list', False)}")
1061
1070
  console.print()
1062
1071
  else:
1063
1072
  # Custom configuration: Prompt for remaining settings
@@ -1131,6 +1140,15 @@ def prepare(
1131
1140
  config_file = Path(".cafe") / "issues" / issue_name / "issue.yaml"
1132
1141
  auto_create_pr_result = prompt_and_save_auto_create(config_file, "pr.auto_create")
1133
1142
  pr_config["auto_create"] = auto_create_pr_result
1143
+
1144
+ # Prompt for post_todo_list only when auto_create is enabled
1145
+ if auto_create_pr_result:
1146
+ console.print()
1147
+ post_todo_list_result = prompt_confirm(
1148
+ "Post organized PR comments as todo list to PR?",
1149
+ default=True,
1150
+ )
1151
+ pr_config["post_todo_list"] = post_todo_list_result
1134
1152
  elif not interactive:
1135
1153
  # Explicit non-interactive mode (--no-interactive): use CLI parameters
1136
1154
  from cafe.utils.git_utils import is_github_repo
@@ -1154,6 +1172,8 @@ def prepare(
1154
1172
  # PR config (only for GitHub repos)
1155
1173
  if is_github_repo() and auto_create_pr:
1156
1174
  pr_config["auto_create"] = True
1175
+ if post_pr_todo_list is not None:
1176
+ pr_config["post_todo_list"] = post_pr_todo_list
1157
1177
  # else: issue_name was provided as argument but not --no-interactive
1158
1178
  # Don't save any config (old behavior for backward compatibility)
1159
1179
 
@@ -3397,6 +3417,11 @@ def pr(
3397
3417
  "--interactive/--no-interactive",
3398
3418
  help="Allow interactive prompts (default: True)",
3399
3419
  ),
3420
+ post_todo_list: Optional[bool] = typer.Option(
3421
+ None,
3422
+ "--post-todo-list/--no-post-todo-list",
3423
+ help="Post organized todo list as PR comment (default: auto-detect from config)",
3424
+ ),
3400
3425
  ) -> None:
3401
3426
  """Create pull request for the issue.
3402
3427
 
@@ -3470,6 +3495,7 @@ def pr(
3470
3495
  force_push=force,
3471
3496
  interactive=interactive,
3472
3497
  base_branch=base if base != "main" else None, # Pass base only if not default
3498
+ post_todo_list=post_todo_list,
3473
3499
  )
3474
3500
 
3475
3501
  result = phase.execute()
@@ -371,6 +371,7 @@ def generate_pr_comments_checklist(
371
371
  output_file_path: str,
372
372
  prev_output_file_path: Optional[str],
373
373
  checklist_file_path: Path,
374
+ basic_principles: Optional[str] = None,
374
375
  ) -> None:
375
376
  """Generate checklist file for PR comments organization.
376
377
 
@@ -380,6 +381,7 @@ def generate_pr_comments_checklist(
380
381
  output_file_path: Path to output.md where todo list will be written
381
382
  prev_output_file_path: Path to previous output.md (None if first iteration)
382
383
  checklist_file_path: Path where checklist file should be created
384
+ basic_principles: Basic principles text in "- item" format (optional)
383
385
  """
384
386
  # Get agent file path
385
387
  agent_file = AgentManager.get_agent_file_path(agent_name, "developer")
@@ -390,8 +392,13 @@ def generate_pr_comments_checklist(
390
392
  # Get agent guidelines checklist
391
393
  agent_guidelines = extract_agent_guidelines_checklist(agent_file)
392
394
 
395
+ # Convert basic principles to checklist format
396
+ basic_principles_checklist = ""
397
+ if basic_principles:
398
+ basic_principles_checklist = convert_to_checklist(basic_principles, "Basic Principles")
399
+
393
400
  # Combine all sections
394
- checklist_content = f"{execution_steps}\n{agent_guidelines}"
401
+ checklist_content = f"{execution_steps}\n{basic_principles_checklist}\n{agent_guidelines}"
395
402
 
396
403
  # Build placeholders dict
397
404
  placeholders = {
@@ -236,10 +236,10 @@ PR_EXECUTION_STEPS_ITERATION_N = """## Checklist
236
236
  PR_COMMENTS_ORGANIZATION_STEPS = """## Checklist
237
237
 
238
238
  [ ] Read {agent_file} to understand your role and native language
239
- [ ] Read {output_file} which already contains PR comments
239
+ [ ] Read {user_input_file} which contains the original PR review comments
240
240
  [ ] Check {prev_output_file} (if exists) - skip todo items that are already completed there
241
241
  [ ] Group related comments together
242
242
  [ ] Convert comments into actionable todo list items (markdown checkbox format: - [ ] Item - keep all items UNCHECKED as they represent work to be done)
243
- [ ] Append organized todo list to the END of {output_file} (do not remove original content)
243
+ [ ] Write ONLY the organized todo list to {output_file} (do NOT copy original PR comments)
244
244
  [ ] Return ONLY the status code in your response
245
245
  """
@@ -199,10 +199,96 @@ class TestPRCommandGitHubMode:
199
199
  assert pr_url in result.stdout
200
200
 
201
201
  # Verify subprocess was called with gh pr diff --web
202
- calls = [call for call in mock_subprocess_run.call_args_list
202
+ calls = [call for call in mock_subprocess_run.call_args_list
203
203
  if call[0][0] == ["gh", "pr", "diff", "--web"]]
204
204
  assert len(calls) >= 1, "Expected subprocess to be called with gh pr diff --web"
205
205
 
206
+
207
+ class TestPRCommandPostTodoListOption:
208
+ """Test --post-todo-list / --no-post-todo-list option passing to PRPhase."""
209
+
210
+ def _make_mocks(self, mock_git_cls, mock_config_cls, mock_agent_cls,
211
+ mock_perm_cls, mock_github_ops_cls, mock_pr_phase_cls):
212
+ """Helper to set up common mocks."""
213
+ mock_git = MagicMock()
214
+ mock_git.get_current_branch.return_value = "test-issue"
215
+ mock_git_cls.return_value = mock_git
216
+
217
+ mock_config = MagicMock()
218
+ mock_config.get.return_value = {"name": "David", "cli": "copilot"}
219
+ mock_config_cls.return_value = mock_config
220
+
221
+ mock_agent_cls.return_value = MagicMock()
222
+ mock_perm_cls.return_value = MagicMock()
223
+ mock_github_ops_cls.return_value = MagicMock()
224
+
225
+ mock_pr_phase = MagicMock()
226
+ mock_pr_phase.execute.return_value = PhaseResult(
227
+ status=PhaseStatus.COMPLETED,
228
+ message="Done",
229
+ data={"pr_url": "https://github.com/test/repo/pull/1", "pr_number": 1},
230
+ )
231
+ mock_pr_phase_cls.return_value = mock_pr_phase
232
+ return mock_pr_phase_cls
233
+
234
+ @patch("subprocess.run")
235
+ @patch("cafe.ui.cli.GitHubOps")
236
+ @patch("cafe.ui.cli.PermissionHandler")
237
+ @patch("cafe.ui.cli.AgentManager")
238
+ @patch("cafe.ui.cli.ConfigManager")
239
+ @patch("cafe.ui.cli.GitOperations")
240
+ @patch("cafe.ui.cli.PRPhase")
241
+ def test_post_todo_list_flag_passes_true_to_pr_phase(
242
+ self,
243
+ mock_pr_phase_cls,
244
+ mock_git_cls,
245
+ mock_config_cls,
246
+ mock_agent_cls,
247
+ mock_perm_cls,
248
+ mock_github_ops_cls,
249
+ mock_subprocess_run,
250
+ temp_repo_dir,
251
+ ):
252
+ """Test 2.1: --post-todo-list フラグが PRPhase に post_todo_list=True で渡される。"""
253
+ mock_subprocess_run.return_value = MagicMock(returncode=0)
254
+ self._make_mocks(mock_git_cls, mock_config_cls, mock_agent_cls,
255
+ mock_perm_cls, mock_github_ops_cls, mock_pr_phase_cls)
256
+
257
+ result = runner.invoke(app, ["pr", "--post-todo-list"])
258
+
259
+ assert result.exit_code == 0
260
+ call_kwargs = mock_pr_phase_cls.call_args[1]
261
+ assert call_kwargs.get("post_todo_list") is True
262
+
263
+ @patch("subprocess.run")
264
+ @patch("cafe.ui.cli.GitHubOps")
265
+ @patch("cafe.ui.cli.PermissionHandler")
266
+ @patch("cafe.ui.cli.AgentManager")
267
+ @patch("cafe.ui.cli.ConfigManager")
268
+ @patch("cafe.ui.cli.GitOperations")
269
+ @patch("cafe.ui.cli.PRPhase")
270
+ def test_no_post_todo_list_flag_passes_false_to_pr_phase(
271
+ self,
272
+ mock_pr_phase_cls,
273
+ mock_git_cls,
274
+ mock_config_cls,
275
+ mock_agent_cls,
276
+ mock_perm_cls,
277
+ mock_github_ops_cls,
278
+ mock_subprocess_run,
279
+ temp_repo_dir,
280
+ ):
281
+ """Test 2.2: --no-post-todo-list フラグが PRPhase に post_todo_list=False で渡される。"""
282
+ mock_subprocess_run.return_value = MagicMock(returncode=0)
283
+ self._make_mocks(mock_git_cls, mock_config_cls, mock_agent_cls,
284
+ mock_perm_cls, mock_github_ops_cls, mock_pr_phase_cls)
285
+
286
+ result = runner.invoke(app, ["pr", "--no-post-todo-list"])
287
+
288
+ assert result.exit_code == 0
289
+ call_kwargs = mock_pr_phase_cls.call_args[1]
290
+ assert call_kwargs.get("post_todo_list") is False
291
+
206
292
  @patch("subprocess.run")
207
293
  @patch("cafe.ui.cli.GitHubOps")
208
294
  @patch("cafe.ui.cli.PermissionHandler")
@@ -971,3 +971,189 @@ class TestPrepareCommandSetupMode:
971
971
  # 驗證沒有詢問 templates 或其他設定
972
972
  mock_template_list.assert_not_called()
973
973
  mock_phase_confirm.assert_not_called()
974
+
975
+
976
+ class TestPrepareCommandPostPrTodoList:
977
+ """Test --post-pr-todo-list option in cafe prepare command."""
978
+
979
+ @patch("cafe.ui.phase_prompts.prompt_text")
980
+ @patch("cafe.ui.phase_prompts.GitHubOps")
981
+ @patch("cafe.ui.cli.GitHubOps")
982
+ @patch("cafe.ui.phase_prompts.prompt_confirm")
983
+ @patch("cafe.ui.cli.prompt_confirm")
984
+ @patch("cafe.ui.template_selector.prompt_list")
985
+ @patch("cafe.ui.phase_prompts.prompt_list")
986
+ @patch("cafe.ui.cli.prompt_list")
987
+ @patch("cafe.ui.cli.prompt_text")
988
+ def test_quick_setup_sets_post_todo_list_true(
989
+ self,
990
+ mock_prompt_text_cli,
991
+ mock_cli_list,
992
+ mock_phase_list,
993
+ mock_template_list,
994
+ mock_cli_confirm,
995
+ mock_phase_confirm,
996
+ MockGitHubOps_cli,
997
+ MockGitHubOps_phase,
998
+ mock_prompt_text_phase,
999
+ temp_repo_dir,
1000
+ mock_git_ops,
1001
+ ):
1002
+ """Test 3.1: Quick setup モードでは pr_config.post_todo_list が True に設定される。"""
1003
+ mock_github_ops = MagicMock()
1004
+ MockGitHubOps_cli.return_value = mock_github_ops
1005
+ MockGitHubOps_phase.return_value = mock_github_ops
1006
+ mock_github_ops.extract_issue_number.return_value = "123"
1007
+
1008
+ mock_prompt_text_cli.return_value = "quick-setup-test"
1009
+ mock_prompt_text_phase.return_value = "123"
1010
+ mock_cli_confirm.return_value = False # worktree (n)
1011
+
1012
+ mock_phase_list.return_value = "2. Fetch from GitHub Issue"
1013
+ mock_cli_list.return_value = "Quick setup (use recommended defaults)"
1014
+
1015
+ result = runner.invoke(app, ["prepare"])
1016
+
1017
+ assert result.exit_code == 0
1018
+
1019
+ config_file = temp_repo_dir / ".cafe" / "issues" / "quick-setup-test" / "issue.yaml"
1020
+ with open(config_file) as f:
1021
+ config_data = yaml.safe_load(f)
1022
+ assert config_data["pr"]["auto_create"] is True
1023
+ assert config_data["pr"]["post_todo_list"] is True
1024
+
1025
+ @patch("cafe.ui.phase_prompts.prompt_text")
1026
+ @patch("cafe.ui.phase_prompts.GitHubOps")
1027
+ @patch("cafe.ui.cli.GitHubOps")
1028
+ @patch("cafe.ui.phase_prompts.prompt_confirm")
1029
+ @patch("cafe.ui.cli.prompt_confirm")
1030
+ @patch("cafe.ui.template_selector.prompt_list")
1031
+ @patch("cafe.ui.phase_prompts.prompt_list")
1032
+ @patch("cafe.ui.cli.prompt_list")
1033
+ @patch("cafe.ui.cli.prompt_text")
1034
+ def test_custom_config_auto_create_true_prompts_post_todo_list(
1035
+ self,
1036
+ mock_prompt_text_cli,
1037
+ mock_cli_list,
1038
+ mock_phase_list,
1039
+ mock_template_list,
1040
+ mock_cli_confirm,
1041
+ mock_phase_confirm,
1042
+ MockGitHubOps_cli,
1043
+ MockGitHubOps_phase,
1044
+ mock_prompt_text_phase,
1045
+ temp_repo_dir,
1046
+ mock_git_ops,
1047
+ ):
1048
+ """Test 3.2: Custom 設定モードで auto_create=True の場合、post_todo_list が尋ねられる。"""
1049
+ mock_github_ops = MagicMock()
1050
+ MockGitHubOps_cli.return_value = mock_github_ops
1051
+ MockGitHubOps_phase.return_value = mock_github_ops
1052
+ mock_github_ops.extract_issue_number.return_value = "42"
1053
+
1054
+ mock_prompt_text_cli.return_value = "custom-auto-create"
1055
+ mock_prompt_text_phase.return_value = "42"
1056
+ # worktree=No, sync_spec=True, sync_plan=True, auto_create=True, post_todo_list=False
1057
+ mock_cli_confirm.side_effect = [False, True, True, True, False]
1058
+ mock_phase_confirm.side_effect = [True] # auto_create prompt in phase_prompts
1059
+
1060
+ mock_phase_list.return_value = "2. Fetch from GitHub Issue"
1061
+ mock_cli_list.return_value = "Custom configuration"
1062
+ mock_template_list.return_value = "default (system default)"
1063
+
1064
+ result = runner.invoke(app, ["prepare"])
1065
+
1066
+ assert result.exit_code == 0
1067
+
1068
+ # post_todo_list should have been set (either True or False from prompt)
1069
+ config_file = temp_repo_dir / ".cafe" / "issues" / "custom-auto-create" / "issue.yaml"
1070
+ with open(config_file) as f:
1071
+ config_data = yaml.safe_load(f)
1072
+ # post_todo_list should be present when auto_create is True
1073
+ assert "post_todo_list" in config_data.get("pr", {})
1074
+
1075
+ @patch("cafe.ui.phase_prompts.prompt_text")
1076
+ @patch("cafe.ui.phase_prompts.GitHubOps")
1077
+ @patch("cafe.ui.cli.GitHubOps")
1078
+ @patch("cafe.ui.phase_prompts.prompt_confirm")
1079
+ @patch("cafe.ui.cli.prompt_confirm")
1080
+ @patch("cafe.ui.template_selector.prompt_list")
1081
+ @patch("cafe.ui.phase_prompts.prompt_list")
1082
+ @patch("cafe.ui.cli.prompt_list")
1083
+ @patch("cafe.ui.cli.prompt_text")
1084
+ def test_custom_config_auto_create_false_does_not_prompt_post_todo_list(
1085
+ self,
1086
+ mock_prompt_text_cli,
1087
+ mock_cli_list,
1088
+ mock_phase_list,
1089
+ mock_template_list,
1090
+ mock_cli_confirm,
1091
+ mock_phase_confirm,
1092
+ MockGitHubOps_cli,
1093
+ MockGitHubOps_phase,
1094
+ mock_prompt_text_phase,
1095
+ temp_repo_dir,
1096
+ mock_git_ops,
1097
+ ):
1098
+ """Test 3.3: Custom 設定モードで auto_create=False の場合、post_todo_list は尋ねられない。"""
1099
+ mock_github_ops = MagicMock()
1100
+ MockGitHubOps_cli.return_value = mock_github_ops
1101
+ MockGitHubOps_phase.return_value = mock_github_ops
1102
+ mock_github_ops.extract_issue_number.return_value = "42"
1103
+
1104
+ mock_prompt_text_cli.return_value = "custom-no-auto-create"
1105
+ mock_prompt_text_phase.return_value = "42"
1106
+ # worktree=No, sync_spec=True, sync_plan=True
1107
+ mock_cli_confirm.side_effect = [False, True, True]
1108
+ # auto_create prompt in phase_prompts returns False
1109
+ mock_phase_confirm.side_effect = [False]
1110
+
1111
+ mock_phase_list.return_value = "2. Fetch from GitHub Issue"
1112
+ mock_cli_list.return_value = "Custom configuration"
1113
+ mock_template_list.return_value = "default (system default)"
1114
+
1115
+ result = runner.invoke(app, ["prepare"])
1116
+
1117
+ assert result.exit_code == 0
1118
+
1119
+ # post_todo_list should NOT be set when auto_create is False
1120
+ config_file = temp_repo_dir / ".cafe" / "issues" / "custom-no-auto-create" / "issue.yaml"
1121
+ with open(config_file) as f:
1122
+ config_data = yaml.safe_load(f)
1123
+ assert "post_todo_list" not in config_data.get("pr", {})
1124
+
1125
+ def test_non_interactive_post_pr_todo_list_flag_saves_to_config(self, temp_repo_dir, mock_git_ops):
1126
+ """Test 3.4/3.5: --no-interactive モードで --post-pr-todo-list フラグが issue.yaml に保存される。"""
1127
+ result = runner.invoke(app, [
1128
+ "prepare", "noninteractive-test",
1129
+ "--no-interactive",
1130
+ "--input-method", "manual",
1131
+ "--rigor", "medium",
1132
+ "--auto-create-pr",
1133
+ "--post-pr-todo-list",
1134
+ ])
1135
+
1136
+ assert result.exit_code == 0
1137
+
1138
+ config_file = temp_repo_dir / ".cafe" / "issues" / "noninteractive-test" / "issue.yaml"
1139
+ with open(config_file) as f:
1140
+ config_data = yaml.safe_load(f)
1141
+ assert config_data["pr"]["post_todo_list"] is True
1142
+
1143
+ def test_non_interactive_no_post_pr_todo_list_flag_saves_false_to_config(self, temp_repo_dir, mock_git_ops):
1144
+ """Test 3.4b: --no-interactive モードで --no-post-pr-todo-list フラグが issue.yaml に保存される。"""
1145
+ result = runner.invoke(app, [
1146
+ "prepare", "noninteractive-false-test",
1147
+ "--no-interactive",
1148
+ "--input-method", "manual",
1149
+ "--rigor", "medium",
1150
+ "--auto-create-pr",
1151
+ "--no-post-pr-todo-list",
1152
+ ])
1153
+
1154
+ assert result.exit_code == 0
1155
+
1156
+ config_file = temp_repo_dir / ".cafe" / "issues" / "noninteractive-false-test" / "issue.yaml"
1157
+ with open(config_file) as f:
1158
+ config_data = yaml.safe_load(f)
1159
+ assert config_data["pr"]["post_todo_list"] is False