claude-code-generator 0.5.7__tar.gz → 0.5.9__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 (199) hide show
  1. {claude_code_generator-0.5.7/src/claude_code_generator.egg-info → claude_code_generator-0.5.9}/PKG-INFO +1 -1
  2. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/pyproject.toml +2 -2
  3. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9/src/claude_code_generator.egg-info}/PKG-INFO +1 -1
  4. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/claude_code_generator.egg-info/SOURCES.txt +2 -0
  5. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/__init__.py +1 -1
  6. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/commands/_dispatch.py +1 -3
  7. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/commands/generate.py +3 -0
  8. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/env.py +28 -0
  9. claude_code_generator-0.5.9/src/code_generator/exceptions.py +26 -0
  10. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/orchestrator/cycle_loop.py +43 -0
  11. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/runner/sdk_runner.py +12 -9
  12. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/runner/subprocess_runner.py +3 -1
  13. claude_code_generator-0.5.9/tests/test_ollama_hardening.py +366 -0
  14. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_phase1.py +0 -2
  15. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/LICENSE +0 -0
  16. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/README.md +0 -0
  17. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/setup.cfg +0 -0
  18. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/claude_code_generator.egg-info/dependency_links.txt +0 -0
  19. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/claude_code_generator.egg-info/entry_points.txt +0 -0
  20. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/claude_code_generator.egg-info/requires.txt +0 -0
  21. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/claude_code_generator.egg-info/top_level.txt +0 -0
  22. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/agents.py +0 -0
  23. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/checklist.py +0 -0
  24. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/cli.py +0 -0
  25. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/commands/__init__.py +0 -0
  26. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/commands/_bench_io.py +0 -0
  27. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/commands/_crash_recovery.py +0 -0
  28. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/commands/_detect.py +0 -0
  29. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/commands/_resume.py +0 -0
  30. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/commands/_validators.py +0 -0
  31. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/commands/bench.py +0 -0
  32. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/commands/bench_compare.py +0 -0
  33. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/commands/bench_export.py +0 -0
  34. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/commands/init.py +0 -0
  35. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/commands/optimize.py +0 -0
  36. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/commands/review.py +0 -0
  37. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/commands/status.py +0 -0
  38. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/effort.py +0 -0
  39. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/gh/__init__.py +0 -0
  40. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/gh/core.py +0 -0
  41. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/gh/issues.py +0 -0
  42. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/gh/labels.py +0 -0
  43. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/gh/milestones.py +0 -0
  44. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/git_ops.py +0 -0
  45. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/logging_setup.py +0 -0
  46. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/memory.py +0 -0
  47. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/orchestrator/__init__.py +0 -0
  48. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/orchestrator/_cache_warmup.py +0 -0
  49. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/orchestrator/_client_lifecycle.py +0 -0
  50. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/orchestrator/_comments.py +0 -0
  51. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/orchestrator/_memory_writers.py +0 -0
  52. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/orchestrator/_phase5_precommit.py +0 -0
  53. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/orchestrator/cycle_prompts.py +0 -0
  54. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/orchestrator/ollama_budget.py +0 -0
  55. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/orchestrator/phase0_complexity.py +0 -0
  56. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/orchestrator/phase1_plan.py +0 -0
  57. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/orchestrator/phase2_review.py +0 -0
  58. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/orchestrator/phase3_4_implement.py +0 -0
  59. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/orchestrator/phase5_closure.py +0 -0
  60. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/orchestrator/phase6_test.py +0 -0
  61. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/orchestrator/phase7_commit.py +0 -0
  62. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/preflight.py +0 -0
  63. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/prompts/__init__.py +0 -0
  64. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/prompts/hashes.py +0 -0
  65. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/prompts/prompt-cycle-specializer.md +0 -0
  66. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/prompts/prompt-optimize-requirements.md +0 -0
  67. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/prompts/prompt-phase-0-complexity.md +0 -0
  68. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/prompts/prompt-phase-1-planning.md +0 -0
  69. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/prompts/prompt-phase-2-batch-review.md +0 -0
  70. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/prompts/prompt-phase-3-implementation.md +0 -0
  71. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/prompts/prompt-phase-5-final-review.md +0 -0
  72. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/prompts/prompt-phase-6-test.md +0 -0
  73. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/prompts/prompt-phase-7-commit.md +0 -0
  74. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/prompts/prompt-review.md +0 -0
  75. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/repo_info.py +0 -0
  76. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/requirements_structure.py +0 -0
  77. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/runner/__init__.py +0 -0
  78. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/runner/_telemetry.py +0 -0
  79. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/runner/batch.py +0 -0
  80. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/runner/cache_breakpoints.py +0 -0
  81. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/runner/compaction_pause.py +0 -0
  82. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/runner/fake_runner.py +0 -0
  83. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/runner/mcp.py +0 -0
  84. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/runner/memory_tool.py +0 -0
  85. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/runner/message_parsing.py +0 -0
  86. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/runner/options.py +0 -0
  87. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/runner/phase_telemetry.py +0 -0
  88. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/runner/protocol.py +0 -0
  89. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/runner/rate_limit.py +0 -0
  90. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/runner/retry.py +0 -0
  91. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/runner/soft_reset.py +0 -0
  92. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/runner/state_guard.py +0 -0
  93. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/runner/types.py +0 -0
  94. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/runner/utils.py +0 -0
  95. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/state.py +0 -0
  96. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/state_retention.py +0 -0
  97. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/templates/__init__.py +0 -0
  98. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/templates/angular.md +0 -0
  99. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/templates/base.md +0 -0
  100. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/templates/fastapi.md +0 -0
  101. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/templates/finance.md +0 -0
  102. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/templates/fullstack.md +0 -0
  103. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/templates/nestjs.md +0 -0
  104. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/templates/python-cli.md +0 -0
  105. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_agents.py +0 -0
  106. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_bench.py +0 -0
  107. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_bench_compare.py +0 -0
  108. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_bench_export.py +0 -0
  109. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_bench_fixture.py +0 -0
  110. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_bench_regression.py +0 -0
  111. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_cache_breakpoints.py +0 -0
  112. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_cache_ttl_ordering.py +0 -0
  113. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_cache_warmup.py +0 -0
  114. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_changelog.py +0 -0
  115. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_checklist.py +0 -0
  116. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_claude_md.py +0 -0
  117. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_cli_io_logging.py +0 -0
  118. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_client_lifecycle.py +0 -0
  119. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_comments.py +0 -0
  120. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_commit_message.py +0 -0
  121. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_compaction_pause_handler.py +0 -0
  122. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_crash_recovery.py +0 -0
  123. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_cycle_loop.py +0 -0
  124. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_cycle_loop_multicycle.py +0 -0
  125. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_cycle_ollama_model.py +0 -0
  126. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_cycle_prompts.py +0 -0
  127. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_delta_planning.py +0 -0
  128. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_dependencies.py +0 -0
  129. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_detect.py +0 -0
  130. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_dispatch_graph_report.py +0 -0
  131. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_docs_no_default_max_turns.py +0 -0
  132. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_docs_ollama_model_guide.py +0 -0
  133. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_docs_ollama_pro.py +0 -0
  134. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_effective_model_routing.py +0 -0
  135. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_effort.py +0 -0
  136. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_effort_routing_consistency.py +0 -0
  137. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_env.py +0 -0
  138. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_generate.py +0 -0
  139. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_generate_ollama.py +0 -0
  140. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_generate_resume.py +0 -0
  141. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_gh.py +0 -0
  142. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_gh_labels.py +0 -0
  143. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_gh_milestones.py +0 -0
  144. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_gh_repo_threading.py +0 -0
  145. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_gh_submodules.py +0 -0
  146. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_git_ops.py +0 -0
  147. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_init.py +0 -0
  148. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_logging_setup.py +0 -0
  149. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_max_turns_cli_flag.py +0 -0
  150. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_mcp.py +0 -0
  151. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_memory.py +0 -0
  152. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_memory_tool.py +0 -0
  153. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_memory_writers.py +0 -0
  154. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_message_parsing.py +0 -0
  155. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_no_max_turns_in_call_sites.py +0 -0
  156. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_no_max_turns_literal.py +0 -0
  157. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_non_goals_grep_guard.py +0 -0
  158. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_ollama_budget.py +0 -0
  159. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_ollama_rate_limit.py +0 -0
  160. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_optimize.py +0 -0
  161. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_options.py +0 -0
  162. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_phase0.py +0 -0
  163. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_phase2.py +0 -0
  164. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_phase2_cache_regression.py +0 -0
  165. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_phase2_multicycle_token_reduction.py +0 -0
  166. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_phase2_token_reduction.py +0 -0
  167. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_phase3_4.py +0 -0
  168. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_phase5.py +0 -0
  169. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_phase5_precommit.py +0 -0
  170. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_phase6.py +0 -0
  171. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_phase7.py +0 -0
  172. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_phase_mcp_regression.py +0 -0
  173. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_phase_telemetry.py +0 -0
  174. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_phase_token_logging.py +0 -0
  175. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_preflight.py +0 -0
  176. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_preflight_ollama.py +0 -0
  177. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_prompt_drift.py +0 -0
  178. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_prompt_prefix_snapshots.py +0 -0
  179. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_prompt_prefix_stability.py +0 -0
  180. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_prompts.py +0 -0
  181. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_rate_limit.py +0 -0
  182. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_repo_info.py +0 -0
  183. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_requirements_structure.py +0 -0
  184. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_retry.py +0 -0
  185. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_review.py +0 -0
  186. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_runner_protocol.py +0 -0
  187. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_runner_protocol_annotations.py +0 -0
  188. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_runner_types.py +0 -0
  189. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_runner_utils.py +0 -0
  190. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_sdk_runner.py +0 -0
  191. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_sdk_runner_shared.py +0 -0
  192. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_session_mode.py +0 -0
  193. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_state.py +0 -0
  194. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_state_guard.py +0 -0
  195. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_state_retention.py +0 -0
  196. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_status.py +0 -0
  197. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_subprocess_runner.py +0 -0
  198. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_telemetry.py +0 -0
  199. {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: claude-code-generator
3
- Version: 0.5.7
3
+ Version: 0.5.9
4
4
  Summary: Orchestrator CLI that drives Claude Code end-to-end to generate whole projects from a requirements.md file.
5
5
  Author: Silvio Baratto
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "claude-code-generator"
7
- version = "0.5.7"
7
+ version = "0.5.9"
8
8
  description = "Orchestrator CLI that drives Claude Code end-to-end to generate whole projects from a requirements.md file."
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -96,4 +96,4 @@ asyncio_mode = "auto"
96
96
 
97
97
  [tool.pyright]
98
98
  # Test mocks intentionally use unused parameters to match the production interface signature.
99
- reportUnusedParameter = "none"
99
+ reportUnusedParameter = false
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: claude-code-generator
3
- Version: 0.5.7
3
+ Version: 0.5.9
4
4
  Summary: Orchestrator CLI that drives Claude Code end-to-end to generate whole projects from a requirements.md file.
5
5
  Author: Silvio Baratto
6
6
  License: MIT
@@ -13,6 +13,7 @@ src/code_generator/checklist.py
13
13
  src/code_generator/cli.py
14
14
  src/code_generator/effort.py
15
15
  src/code_generator/env.py
16
+ src/code_generator/exceptions.py
16
17
  src/code_generator/git_ops.py
17
18
  src/code_generator/logging_setup.py
18
19
  src/code_generator/memory.py
@@ -151,6 +152,7 @@ tests/test_no_max_turns_in_call_sites.py
151
152
  tests/test_no_max_turns_literal.py
152
153
  tests/test_non_goals_grep_guard.py
153
154
  tests/test_ollama_budget.py
155
+ tests/test_ollama_hardening.py
154
156
  tests/test_ollama_rate_limit.py
155
157
  tests/test_optimize.py
156
158
  tests/test_options.py
@@ -1,3 +1,3 @@
1
1
  """code-generator: orchestrator CLI for end-to-end project generation."""
2
2
 
3
- __version__ = "0.5.6"
3
+ __version__ = "0.5.9"
@@ -310,9 +310,7 @@ async def _apply_delta_plan(
310
310
  "Phase 0 failed during delta planning; aborting to preserve cycle history."
311
311
  )
312
312
 
313
- new_cycles = state_module.append_new_cycles(
314
- st, raw_cycles, ollama_model=effective_model
315
- )
313
+ new_cycles = state_module.append_new_cycles(st, raw_cycles, ollama_model=effective_model)
316
314
 
317
315
  if not new_cycles:
318
316
  logger.info("Delta planning: no new cycles detected.")
@@ -460,6 +460,9 @@ def generate_ollama_command(
460
460
  )
461
461
  raise typer.Exit(code=2)
462
462
 
463
+ # #268: advisory warning when the Ollama path carries a claude-* model tag.
464
+ env.warn_on_claude_model_for_ollama(effective_model)
465
+
463
466
  # (6) Persist the resolved model on existing cycles before dispatch so
464
467
  # --continue resume stays consistent across sessions. New cycles
465
468
  # created by phase 0/1 inherit the tag via the orchestrator thread
@@ -286,6 +286,34 @@ def assert_safe_environment() -> None:
286
286
  strip_dangerous_env()
287
287
 
288
288
 
289
+ def warn_on_claude_model_for_ollama(model: str | None) -> None:
290
+ """Emit a WARNING when an Ollama-localhost env carries a ``claude-*`` model tag.
291
+
292
+ The Ollama codepath (#218) routes through a local daemon, so a
293
+ ``claude-opus-4-7`` / ``claude-sonnet-4-6`` / ``claude-haiku-*`` tag
294
+ almost always indicates a misconfiguration — the operator intended a
295
+ local model but typed an Anthropic model name. The warning is purely
296
+ advisory; the upstream guarantee lives in the orchestrator's
297
+ ``_run_phases`` guard, which raises
298
+ :class:`~code_generator.exceptions.OllamaModelRequiredError` for the
299
+ same condition.
300
+
301
+ Args:
302
+ model: The resolved effective model tag, or ``None`` (no-op).
303
+ """
304
+ if model is not None and model.startswith("claude-"):
305
+ _logger.warning(
306
+ "Ollama daemon routing is active (ANTHROPIC_BASE_URL=%s) "
307
+ "but the model tag %r starts with 'claude-'. "
308
+ "This likely indicates a misconfiguration — did you mean "
309
+ "to use an Ollama model tag (e.g. 'qwen3-coder:480b:cloud')? "
310
+ "Use `code-generator generate ollama --model <tag>` for "
311
+ "single-model Ollama runs.",
312
+ OLLAMA_BASE_URL,
313
+ model,
314
+ )
315
+
316
+
289
317
  def assert_safe_environment_custom_model() -> None:
290
318
  """Strip Anthropic API creds but preserve routing for custom model endpoints.
291
319
 
@@ -0,0 +1,26 @@
1
+ """Shared exception classes for the code-generator pipeline.
2
+
3
+ Single-responsibility: domain exception types only — no runner errors
4
+ (those live in runner/types.py), no preflight errors (preflight.py).
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+
10
+ class OllamaModelRequiredError(RuntimeError):
11
+ """Raised when the Ollama codepath has no resolvable model tag.
12
+
13
+ On the Ollama single-model path (#218), every phase requires a concrete
14
+ model tag (e.g. ``qwen3-coder:480b:cloud``). This error fires when the
15
+ orchestrator detects that no model is available — either because
16
+ ``--model`` wasn't passed on a fresh run, or because the stored
17
+ ``CycleState.ollama_model`` is ``None`` on ``--continue``.
18
+
19
+ The ``fix_action`` string is a ready-to-paste remediation for the
20
+ operator; it is included in the error message automatically.
21
+ """
22
+
23
+ def __init__(self, reason: str, fix_action: str) -> None:
24
+ self.reason = reason
25
+ self.fix_action = fix_action
26
+ super().__init__(f"{reason} {fix_action}")
@@ -14,11 +14,14 @@ Topological iteration:
14
14
  from __future__ import annotations
15
15
 
16
16
  import logging
17
+ import os
17
18
  import shutil
18
19
  from typing import TYPE_CHECKING, Any, Literal
19
20
 
21
+ from code_generator import env as _env
20
22
  from code_generator import gh, git_ops
21
23
  from code_generator import state as _state
24
+ from code_generator.exceptions import OllamaModelRequiredError
22
25
  from code_generator.logging_setup import setup_phase_logger
23
26
  from code_generator.orchestrator import (
24
27
  phase1_plan,
@@ -171,6 +174,12 @@ def _resolve_effective_model(state: State, cycle: CycleState | None) -> str | No
171
174
  Priority: cycle.ollama_model (multi-cycle) > state.cycles[-1].ollama_model
172
175
  (single-mode fallback when ``cycle`` is not yet bound). The function never
173
176
  reads ``state.ollama_model`` because the field is cycle-level per #217.
177
+
178
+ Raises:
179
+ OllamaModelRequiredError: When the Ollama codepath is detected
180
+ (``ANTHROPIC_BASE_URL`` is localhost) and no model tag can be
181
+ resolved from state/cycle — this prevents a silent fallback
182
+ to an Anthropic model name.
174
183
  """
175
184
  if cycle is not None and cycle.ollama_model:
176
185
  return cycle.ollama_model
@@ -178,6 +187,21 @@ def _resolve_effective_model(state: State, cycle: CycleState | None) -> str | No
178
187
  last = state.cycles[-1]
179
188
  if isinstance(last, CycleState) and last.ollama_model:
180
189
  return last.ollama_model
190
+
191
+ if _env._is_localhost_base_url(os.environ.get("ANTHROPIC_BASE_URL")): # noqa: SLF001
192
+ raise OllamaModelRequiredError(
193
+ reason=(
194
+ "Ollama daemon routing is active but no model tag is resolvable "
195
+ "from state.json. This happens when --continue is used on a "
196
+ "legacy state file that predates the ollama_model field, or "
197
+ "when --model was omitted on a fresh run."
198
+ ),
199
+ fix_action=(
200
+ "Re-run with --model <tag> to supply the model explicitly, e.g.: "
201
+ "code-generator generate ollama --model qwen3-coder:480b:cloud"
202
+ ),
203
+ )
204
+
181
205
  return None
182
206
 
183
207
 
@@ -231,6 +255,25 @@ async def _run_phases(
231
255
  if effective_model is None:
232
256
  effective_model = _resolve_effective_model(state, cycle)
233
257
 
258
+ # #268: belt-and-suspenders — when the Ollama codepath is active
259
+ # (effective_model is set) but the tag starts with "claude-", the operator
260
+ # almost certainly intended a local model. Refuse before any phase runs
261
+ # so a mistyped tag never reaches the daemon (which would emit a cryptic
262
+ # "model may not exist").
263
+ if effective_model is not None and effective_model.startswith("claude-"):
264
+ raise OllamaModelRequiredError(
265
+ reason=(
266
+ f"Ollama single-model routing is active but the model tag "
267
+ f"{effective_model!r} starts with 'claude-'. "
268
+ "This likely indicates a misconfiguration — did you mean "
269
+ "to use an Ollama model tag?"
270
+ ),
271
+ fix_action=(
272
+ "Pass a valid Ollama model tag instead, e.g.: "
273
+ "code-generator generate ollama --model qwen3-coder:480b:cloud"
274
+ ),
275
+ )
276
+
234
277
  # #220: Ollama per-cycle budget tracker. ``effective_model is not None``
235
278
  # uniquely identifies the Ollama codepath inside the orchestrator, so we
236
279
  # key activation off it rather than threading an extra ``provider`` flag.
@@ -26,6 +26,7 @@ from code_generator.runner.message_parsing import (
26
26
  render_prompt,
27
27
  render_system_message,
28
28
  )
29
+ from code_generator.runner.state_guard import protect_state_file
29
30
  from code_generator.runner.types import (
30
31
  ApiUpstreamError,
31
32
  MaxTurnsExceeded,
@@ -332,11 +333,12 @@ async def run(
332
333
  )
333
334
 
334
335
  t0 = time.monotonic()
335
- async with ClaudeSDKClient(options=options) as client:
336
- await client.query(prompt)
337
- text, session_id, usage, compaction_events, clear_events = await _drain_messages(
338
- client, logger, state_path
339
- )
336
+ with protect_state_file(state_path):
337
+ async with ClaudeSDKClient(options=options) as client:
338
+ await client.query(prompt)
339
+ text, session_id, usage, compaction_events, clear_events = await _drain_messages(
340
+ client, logger, state_path
341
+ )
340
342
  wall_seconds = time.monotonic() - t0
341
343
 
342
344
  logger.info("SDK session complete.")
@@ -412,10 +414,11 @@ async def run_with_shared_client(
412
414
  prompt = pause_handler.consume(prompt, issue_number=consume_issue)
413
415
 
414
416
  t0 = time.monotonic()
415
- await client.query(prompt)
416
- text, session_id, usage, compaction_events, clear_events = await _drain_messages(
417
- client, logger, state_path, pause_handler=pause_handler
418
- )
417
+ with protect_state_file(state_path):
418
+ await client.query(prompt)
419
+ text, session_id, usage, compaction_events, clear_events = await _drain_messages(
420
+ client, logger, state_path, pause_handler=pause_handler
421
+ )
419
422
  wall_seconds = time.monotonic() - t0
420
423
 
421
424
  logger.info("SDK shared-client session complete.")
@@ -20,6 +20,7 @@ from typing import TYPE_CHECKING, Any
20
20
 
21
21
  from code_generator import env as _env
22
22
  from code_generator import state as _state
23
+ from code_generator.runner.state_guard import protect_state_file
23
24
 
24
25
  # Import shared types from the canonical module (DIP: depend on abstraction).
25
26
  # Re-exported here so callers that previously imported from subprocess_runner
@@ -350,7 +351,8 @@ async def run(
350
351
  raise ApiUpstreamError(f"claude CLI exited with code {proc.returncode}")
351
352
 
352
353
  t0 = time.monotonic()
353
- await asyncio.to_thread(_run_subprocess)
354
+ with protect_state_file(state_path):
355
+ await asyncio.to_thread(_run_subprocess)
354
356
  wall_seconds = time.monotonic() - t0
355
357
 
356
358
  return RunResult(
@@ -0,0 +1,366 @@
1
+ """Tests for Ollama single-model hardening (issue #268).
2
+
3
+ AC coverage:
4
+ - OllamaModelRequiredError exists with fix-action message
5
+ - _resolve_effective_model raises when on Ollama path and no model resolvable
6
+ - _run_phases raises when effective_model starts with claude-
7
+ - env warns when model tag starts with claude-* on localhost routing
8
+ - Legacy state.json without ollama_model raises clear error on --continue
9
+ - Anthropic Max path unchanged (effective_model=None)
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import logging
15
+ from typing import TYPE_CHECKING
16
+ from unittest.mock import AsyncMock, MagicMock, patch
17
+
18
+ import pytest
19
+
20
+ from code_generator import state as _state
21
+ from code_generator.exceptions import OllamaModelRequiredError
22
+ from code_generator.runner.types import RunResult, TokenUsage
23
+
24
+ if TYPE_CHECKING:
25
+ from pathlib import Path
26
+
27
+
28
+ _OLLAMA_TAG = "qwen3-coder:480b:cloud"
29
+ _CLAUDE_TAG = "claude-opus-4-7"
30
+
31
+
32
+ # ---------------------------------------------------------------------------
33
+ # Helpers
34
+ # ---------------------------------------------------------------------------
35
+
36
+
37
+ def _make_runner() -> MagicMock:
38
+ runner = MagicMock()
39
+ runner.run = AsyncMock(
40
+ return_value=RunResult(
41
+ text='{"mode":"single","cycles":[]}', session_id=None, usage=TokenUsage()
42
+ ),
43
+ )
44
+ return runner
45
+
46
+
47
+ def _make_state(tmp_path: Path) -> tuple[_state.State, Path]:
48
+ st = _state.load_state(tmp_path / "missing.json")
49
+ cg = tmp_path / ".code-generator"
50
+ cg.mkdir(parents=True, exist_ok=True)
51
+ (cg / "memories").mkdir(exist_ok=True)
52
+ state_path = cg / "state.json"
53
+ _state.save_state(state_path, st)
54
+ return st, state_path
55
+
56
+
57
+ def _make_cycle(ollama_model: str | None) -> _state.CycleState:
58
+ return _state.CycleState(
59
+ id=1,
60
+ name="c1",
61
+ milestone_number=1,
62
+ milestone_title="c1",
63
+ status="open",
64
+ phase=None,
65
+ issues=[],
66
+ commit_sha=None,
67
+ ollama_model=ollama_model,
68
+ )
69
+
70
+
71
+ def _make_cycle_no_ollama_field() -> _state.CycleState:
72
+ """Create a CycleState that has no ollama_model attribute set."""
73
+ cycle = _state.CycleState(
74
+ id=1,
75
+ name="c1",
76
+ milestone_number=1,
77
+ milestone_title="c1",
78
+ status="open",
79
+ phase=None,
80
+ issues=[],
81
+ commit_sha=None,
82
+ )
83
+ # Simulate legacy state.json: remove the ollama_model attr
84
+ object.__setattr__(cycle, "ollama_model", None)
85
+ return cycle
86
+
87
+
88
+ # ---------------------------------------------------------------------------
89
+ # OllamaModelRequiredError
90
+ # ---------------------------------------------------------------------------
91
+
92
+
93
+ class TestOllamaModelRequiredError:
94
+ def test_exception_includes_reason_and_fix_action(self) -> None:
95
+ err = OllamaModelRequiredError("no model found", "pass --model <tag>")
96
+ assert "no model found" in str(err)
97
+ assert "pass --model <tag>" in str(err)
98
+
99
+ def test_exception_is_runtime_error(self) -> None:
100
+ err = OllamaModelRequiredError("x", "y")
101
+ assert isinstance(err, RuntimeError)
102
+
103
+ def test_reason_and_fix_action_accessible_as_attrs(self) -> None:
104
+ err = OllamaModelRequiredError("reason text", "fix text")
105
+ assert err.reason == "reason text"
106
+ assert err.fix_action == "fix text"
107
+
108
+
109
+ # ---------------------------------------------------------------------------
110
+ # _resolve_effective_model — raises when on Ollama path, no model resolvable
111
+ # ---------------------------------------------------------------------------
112
+
113
+
114
+ class TestResolveEffectiveModelRaisesOnMissingModel:
115
+ def test_raises_when_cycle_has_ollama_model_none_and_state_has_none(
116
+ self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
117
+ ) -> None:
118
+ from code_generator.orchestrator import cycle_loop
119
+
120
+ monkeypatch.setenv("ANTHROPIC_BASE_URL", "http://localhost:11434")
121
+ state, _ = _make_state(tmp_path)
122
+ cycle = _make_cycle(None)
123
+ state.cycles = [cycle]
124
+
125
+ with pytest.raises(OllamaModelRequiredError, match="no model tag"):
126
+ cycle_loop._resolve_effective_model(state, cycle)
127
+
128
+ def test_raises_when_cycle_is_none_and_state_cycles_have_none_model(
129
+ self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
130
+ ) -> None:
131
+ from code_generator.orchestrator import cycle_loop
132
+
133
+ monkeypatch.setenv("ANTHROPIC_BASE_URL", "http://localhost:11434")
134
+ state, _ = _make_state(tmp_path)
135
+ cycle = _make_cycle(None)
136
+ state.cycles = [cycle]
137
+
138
+ with pytest.raises(OllamaModelRequiredError, match="no model tag"):
139
+ cycle_loop._resolve_effective_model(state, None)
140
+
141
+ def test_returns_model_when_cycle_has_valid_tag(self, tmp_path: Path) -> None:
142
+ from code_generator.orchestrator import cycle_loop
143
+
144
+ state, _ = _make_state(tmp_path)
145
+ cycle = _make_cycle(_OLLAMA_TAG)
146
+ state.cycles = [cycle]
147
+
148
+ result = cycle_loop._resolve_effective_model(state, cycle)
149
+ assert result == _OLLAMA_TAG
150
+
151
+ def test_returns_none_on_anthropic_max_path(
152
+ self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
153
+ ) -> None:
154
+ """When no cycle has ollama_model set (None), returns None — Anthropic Max."""
155
+ from code_generator.orchestrator import cycle_loop
156
+
157
+ monkeypatch.delenv("ANTHROPIC_BASE_URL", raising=False)
158
+ state, _ = _make_state(tmp_path)
159
+ # No cycles in state → anthropic max path
160
+ result = cycle_loop._resolve_effective_model(state, None)
161
+ assert result is None
162
+
163
+ def test_returns_none_when_cycle_none_and_no_cycles_in_state(
164
+ self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
165
+ ) -> None:
166
+ from code_generator.orchestrator import cycle_loop
167
+
168
+ monkeypatch.delenv("ANTHROPIC_BASE_URL", raising=False)
169
+ state, _ = _make_state(tmp_path)
170
+ state.cycles = []
171
+
172
+ result = cycle_loop._resolve_effective_model(state, None)
173
+ assert result is None
174
+
175
+
176
+ # ---------------------------------------------------------------------------
177
+ # _run_phases — raises when effective_model starts with "claude-"
178
+ # ---------------------------------------------------------------------------
179
+
180
+
181
+ class TestRunPhasesRejectsClaudeModelOnOllamaPath:
182
+ @pytest.mark.asyncio
183
+ async def test_raises_when_effective_model_starts_with_claude_prefix(
184
+ self, tmp_path: Path
185
+ ) -> None:
186
+ from code_generator.orchestrator import cycle_loop
187
+
188
+ state, _ = _make_state(tmp_path)
189
+ cycle = _make_cycle(_CLAUDE_TAG)
190
+ state.cycles = [cycle]
191
+
192
+ with pytest.raises(OllamaModelRequiredError, match="claude-"):
193
+ await cycle_loop._run_phases(
194
+ state=state,
195
+ cycle=cycle,
196
+ project_dir=tmp_path,
197
+ runner_module=_make_runner(),
198
+ log_prefix="",
199
+ start_phase=1,
200
+ effective_model=_CLAUDE_TAG,
201
+ )
202
+
203
+ @pytest.mark.asyncio
204
+ async def test_does_not_raise_when_effective_model_is_none(
205
+ self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
206
+ ) -> None:
207
+ from code_generator.orchestrator import cycle_loop
208
+
209
+ monkeypatch.delenv("ANTHROPIC_BASE_URL", raising=False)
210
+ state, _ = _make_state(tmp_path)
211
+ cycle = _make_cycle(None)
212
+ state.cycles = [cycle]
213
+
214
+ with (
215
+ patch.object(cycle_loop.phase1_plan, "run", new=AsyncMock()),
216
+ patch.object(cycle_loop.phase2_review, "run", new=AsyncMock()),
217
+ patch.object(cycle_loop.phase3_4_implement, "run", new=AsyncMock()),
218
+ patch.object(
219
+ cycle_loop.phase5_closure,
220
+ "run",
221
+ new=AsyncMock(return_value=MagicMock(new_issues=[])),
222
+ ),
223
+ patch.object(cycle_loop.phase6_test, "run", new=AsyncMock()),
224
+ patch.object(cycle_loop.phase7_commit, "run", new=AsyncMock()),
225
+ patch.object(cycle_loop, "reset_cycle_summary", lambda *a, **k: None),
226
+ patch.object(cycle_loop, "write_project_plan", lambda *a, **k: None),
227
+ patch.object(cycle_loop, "append_cycle_summaries", lambda *a, **k: None),
228
+ patch.object(cycle_loop, "populate_cycle_start_fields", lambda *a, **k: None),
229
+ patch.object(_state, "get_issues", lambda *a, **k: []),
230
+ patch.object(cycle_loop, "_capture_base_commit", lambda *a, **k: None),
231
+ ):
232
+ await cycle_loop._run_phases(
233
+ state=state,
234
+ cycle=cycle,
235
+ project_dir=tmp_path,
236
+ runner_module=_make_runner(),
237
+ log_prefix="",
238
+ start_phase=1,
239
+ effective_model=None,
240
+ )
241
+
242
+ @pytest.mark.asyncio
243
+ async def test_does_not_raise_when_effective_model_is_valid_ollama_tag(
244
+ self, tmp_path: Path
245
+ ) -> None:
246
+ from code_generator.orchestrator import cycle_loop
247
+
248
+ state, _ = _make_state(tmp_path)
249
+ cycle = _make_cycle(_OLLAMA_TAG)
250
+ state.cycles = [cycle]
251
+
252
+ with (
253
+ patch.object(cycle_loop.phase1_plan, "run", new=AsyncMock()),
254
+ patch.object(cycle_loop.phase2_review, "run", new=AsyncMock()),
255
+ patch.object(cycle_loop.phase3_4_implement, "run", new=AsyncMock()),
256
+ patch.object(
257
+ cycle_loop.phase5_closure,
258
+ "run",
259
+ new=AsyncMock(return_value=MagicMock(new_issues=[])),
260
+ ),
261
+ patch.object(cycle_loop.phase6_test, "run", new=AsyncMock()),
262
+ patch.object(cycle_loop.phase7_commit, "run", new=AsyncMock()),
263
+ patch.object(cycle_loop, "reset_cycle_summary", lambda *a, **k: None),
264
+ patch.object(cycle_loop, "write_project_plan", lambda *a, **k: None),
265
+ patch.object(cycle_loop, "append_cycle_summaries", lambda *a, **k: None),
266
+ patch.object(cycle_loop, "populate_cycle_start_fields", lambda *a, **k: None),
267
+ patch.object(_state, "get_issues", lambda *a, **k: []),
268
+ patch.object(cycle_loop, "_capture_base_commit", lambda *a, **k: None),
269
+ ):
270
+ await cycle_loop._run_phases(
271
+ state=state,
272
+ cycle=cycle,
273
+ project_dir=tmp_path,
274
+ runner_module=_make_runner(),
275
+ log_prefix="",
276
+ start_phase=1,
277
+ effective_model=_OLLAMA_TAG,
278
+ )
279
+
280
+
281
+ # ---------------------------------------------------------------------------
282
+ # env.warn_on_claude_model_for_ollama — WARNING for claude-* + localhost
283
+ # ---------------------------------------------------------------------------
284
+
285
+
286
+ class TestEnvWarnsOnClaudeModelWithLocalhost:
287
+ def test_warns_when_model_starts_with_claude_prefix(self, caplog) -> None:
288
+ from code_generator import env
289
+
290
+ with caplog.at_level(logging.WARNING, logger="code_generator.env"):
291
+ env.warn_on_claude_model_for_ollama("claude-opus-4-7")
292
+
293
+ assert any("claude-" in record.message for record in caplog.records), (
294
+ f"expected WARNING with 'claude-', got: {[r.message for r in caplog.records]}"
295
+ )
296
+
297
+ def test_does_not_warn_when_model_is_valid_ollama_tag(self, caplog) -> None:
298
+ from code_generator import env
299
+
300
+ with caplog.at_level(logging.WARNING, logger="code_generator.env"):
301
+ env.warn_on_claude_model_for_ollama("qwen3-coder:480b:cloud")
302
+
303
+ assert not caplog.records, (
304
+ f"expected no WARNING, got: {[r.message for r in caplog.records]}"
305
+ )
306
+
307
+ def test_does_not_raise_or_warn_when_model_is_none(self, caplog) -> None:
308
+ from code_generator import env
309
+
310
+ with caplog.at_level(logging.WARNING, logger="code_generator.env"):
311
+ env.warn_on_claude_model_for_ollama(None)
312
+
313
+ assert not caplog.records
314
+
315
+
316
+ # ---------------------------------------------------------------------------
317
+ # Legacy state.json without ollama_model → clear error on --continue
318
+ # ---------------------------------------------------------------------------
319
+
320
+
321
+ class TestLegacyStateWithoutOllamaModel:
322
+ @pytest.mark.asyncio
323
+ async def test_resolve_effective_model_raises_for_legacy_cycle_no_model(
324
+ self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
325
+ ) -> None:
326
+ from code_generator.orchestrator import cycle_loop
327
+
328
+ monkeypatch.setenv("ANTHROPIC_BASE_URL", "http://localhost:11434")
329
+ state, _ = _make_state(tmp_path)
330
+ cycle = _make_cycle_no_ollama_field()
331
+ state.cycles = [cycle]
332
+
333
+ with pytest.raises(OllamaModelRequiredError, match="--continue"):
334
+ cycle_loop._resolve_effective_model(state, cycle)
335
+
336
+
337
+ # ---------------------------------------------------------------------------
338
+ # Anthropic Max path regression guard
339
+ # ---------------------------------------------------------------------------
340
+
341
+
342
+ class TestAnthropicMaxPathUnchanged:
343
+ def test_resolve_effective_model_returns_none_when_no_ollama_model_anywhere(
344
+ self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
345
+ ) -> None:
346
+ from code_generator.orchestrator import cycle_loop
347
+
348
+ monkeypatch.delenv("ANTHROPIC_BASE_URL", raising=False)
349
+ state, _ = _make_state(tmp_path)
350
+ cycle = _make_cycle(None)
351
+ state.cycles = [cycle]
352
+
353
+ result = cycle_loop._resolve_effective_model(state, cycle)
354
+ assert result is None, f"Anthropic Max path broken: {result=}, expected None"
355
+
356
+ def test_resolve_effective_model_returns_none_empty_cycles_single_mode(
357
+ self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
358
+ ) -> None:
359
+ from code_generator.orchestrator import cycle_loop
360
+
361
+ monkeypatch.delenv("ANTHROPIC_BASE_URL", raising=False)
362
+ state, _ = _make_state(tmp_path)
363
+ state.cycles = []
364
+
365
+ result = cycle_loop._resolve_effective_model(state, None)
366
+ assert result is None
@@ -1001,5 +1001,3 @@ class TestLoadSpecializedPrompt:
1001
1001
  result = phase1_plan._load_specialized_prompt(tmp_path, state, cycle)
1002
1002
 
1003
1003
  assert "Cycle scope here" in result
1004
-
1005
-