claude-code-generator 0.5.6__tar.gz → 0.5.8__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 (200) hide show
  1. {claude_code_generator-0.5.6/src/claude_code_generator.egg-info → claude_code_generator-0.5.8}/PKG-INFO +1 -1
  2. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/pyproject.toml +2 -2
  3. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8/src/claude_code_generator.egg-info}/PKG-INFO +1 -1
  4. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/claude_code_generator.egg-info/SOURCES.txt +2 -0
  5. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/__init__.py +1 -1
  6. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/agents.py +45 -15
  7. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/commands/_dispatch.py +99 -78
  8. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/commands/generate.py +34 -2
  9. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/env.py +55 -0
  10. claude_code_generator-0.5.8/src/code_generator/exceptions.py +26 -0
  11. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/orchestrator/cycle_loop.py +43 -0
  12. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/orchestrator/phase1_plan.py +43 -41
  13. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/orchestrator/phase5_closure.py +13 -18
  14. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/state.py +10 -1
  15. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_agents.py +139 -0
  16. claude_code_generator-0.5.8/tests/test_dispatch_graph_report.py +507 -0
  17. claude_code_generator-0.5.8/tests/test_ollama_hardening.py +366 -0
  18. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_phase1.py +15 -62
  19. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_phase5.py +10 -27
  20. claude_code_generator-0.5.6/tests/test_dispatch_graph_report.py +0 -227
  21. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/LICENSE +0 -0
  22. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/README.md +0 -0
  23. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/setup.cfg +0 -0
  24. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/claude_code_generator.egg-info/dependency_links.txt +0 -0
  25. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/claude_code_generator.egg-info/entry_points.txt +0 -0
  26. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/claude_code_generator.egg-info/requires.txt +0 -0
  27. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/claude_code_generator.egg-info/top_level.txt +0 -0
  28. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/checklist.py +0 -0
  29. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/cli.py +0 -0
  30. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/commands/__init__.py +0 -0
  31. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/commands/_bench_io.py +0 -0
  32. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/commands/_crash_recovery.py +0 -0
  33. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/commands/_detect.py +0 -0
  34. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/commands/_resume.py +0 -0
  35. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/commands/_validators.py +0 -0
  36. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/commands/bench.py +0 -0
  37. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/commands/bench_compare.py +0 -0
  38. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/commands/bench_export.py +0 -0
  39. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/commands/init.py +0 -0
  40. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/commands/optimize.py +0 -0
  41. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/commands/review.py +0 -0
  42. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/commands/status.py +0 -0
  43. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/effort.py +0 -0
  44. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/gh/__init__.py +0 -0
  45. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/gh/core.py +0 -0
  46. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/gh/issues.py +0 -0
  47. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/gh/labels.py +0 -0
  48. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/gh/milestones.py +0 -0
  49. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/git_ops.py +0 -0
  50. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/logging_setup.py +0 -0
  51. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/memory.py +0 -0
  52. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/orchestrator/__init__.py +0 -0
  53. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/orchestrator/_cache_warmup.py +0 -0
  54. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/orchestrator/_client_lifecycle.py +0 -0
  55. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/orchestrator/_comments.py +0 -0
  56. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/orchestrator/_memory_writers.py +0 -0
  57. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/orchestrator/_phase5_precommit.py +0 -0
  58. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/orchestrator/cycle_prompts.py +0 -0
  59. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/orchestrator/ollama_budget.py +0 -0
  60. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/orchestrator/phase0_complexity.py +0 -0
  61. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/orchestrator/phase2_review.py +0 -0
  62. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/orchestrator/phase3_4_implement.py +0 -0
  63. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/orchestrator/phase6_test.py +0 -0
  64. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/orchestrator/phase7_commit.py +0 -0
  65. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/preflight.py +0 -0
  66. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/prompts/__init__.py +0 -0
  67. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/prompts/hashes.py +0 -0
  68. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/prompts/prompt-cycle-specializer.md +0 -0
  69. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/prompts/prompt-optimize-requirements.md +0 -0
  70. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/prompts/prompt-phase-0-complexity.md +0 -0
  71. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/prompts/prompt-phase-1-planning.md +0 -0
  72. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/prompts/prompt-phase-2-batch-review.md +0 -0
  73. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/prompts/prompt-phase-3-implementation.md +0 -0
  74. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/prompts/prompt-phase-5-final-review.md +0 -0
  75. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/prompts/prompt-phase-6-test.md +0 -0
  76. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/prompts/prompt-phase-7-commit.md +0 -0
  77. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/prompts/prompt-review.md +0 -0
  78. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/repo_info.py +0 -0
  79. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/requirements_structure.py +0 -0
  80. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/runner/__init__.py +0 -0
  81. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/runner/_telemetry.py +0 -0
  82. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/runner/batch.py +0 -0
  83. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/runner/cache_breakpoints.py +0 -0
  84. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/runner/compaction_pause.py +0 -0
  85. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/runner/fake_runner.py +0 -0
  86. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/runner/mcp.py +0 -0
  87. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/runner/memory_tool.py +0 -0
  88. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/runner/message_parsing.py +0 -0
  89. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/runner/options.py +0 -0
  90. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/runner/phase_telemetry.py +0 -0
  91. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/runner/protocol.py +0 -0
  92. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/runner/rate_limit.py +0 -0
  93. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/runner/retry.py +0 -0
  94. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/runner/sdk_runner.py +0 -0
  95. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/runner/soft_reset.py +0 -0
  96. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/runner/state_guard.py +0 -0
  97. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/runner/subprocess_runner.py +0 -0
  98. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/runner/types.py +0 -0
  99. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/runner/utils.py +0 -0
  100. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/state_retention.py +0 -0
  101. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/templates/__init__.py +0 -0
  102. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/templates/angular.md +0 -0
  103. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/templates/base.md +0 -0
  104. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/templates/fastapi.md +0 -0
  105. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/templates/finance.md +0 -0
  106. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/templates/fullstack.md +0 -0
  107. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/templates/nestjs.md +0 -0
  108. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/src/code_generator/templates/python-cli.md +0 -0
  109. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_bench.py +0 -0
  110. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_bench_compare.py +0 -0
  111. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_bench_export.py +0 -0
  112. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_bench_fixture.py +0 -0
  113. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_bench_regression.py +0 -0
  114. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_cache_breakpoints.py +0 -0
  115. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_cache_ttl_ordering.py +0 -0
  116. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_cache_warmup.py +0 -0
  117. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_changelog.py +0 -0
  118. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_checklist.py +0 -0
  119. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_claude_md.py +0 -0
  120. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_cli_io_logging.py +0 -0
  121. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_client_lifecycle.py +0 -0
  122. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_comments.py +0 -0
  123. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_commit_message.py +0 -0
  124. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_compaction_pause_handler.py +0 -0
  125. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_crash_recovery.py +0 -0
  126. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_cycle_loop.py +0 -0
  127. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_cycle_loop_multicycle.py +0 -0
  128. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_cycle_ollama_model.py +0 -0
  129. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_cycle_prompts.py +0 -0
  130. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_delta_planning.py +0 -0
  131. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_dependencies.py +0 -0
  132. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_detect.py +0 -0
  133. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_docs_no_default_max_turns.py +0 -0
  134. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_docs_ollama_model_guide.py +0 -0
  135. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_docs_ollama_pro.py +0 -0
  136. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_effective_model_routing.py +0 -0
  137. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_effort.py +0 -0
  138. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_effort_routing_consistency.py +0 -0
  139. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_env.py +0 -0
  140. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_generate.py +0 -0
  141. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_generate_ollama.py +0 -0
  142. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_generate_resume.py +0 -0
  143. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_gh.py +0 -0
  144. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_gh_labels.py +0 -0
  145. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_gh_milestones.py +0 -0
  146. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_gh_repo_threading.py +0 -0
  147. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_gh_submodules.py +0 -0
  148. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_git_ops.py +0 -0
  149. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_init.py +0 -0
  150. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_logging_setup.py +0 -0
  151. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_max_turns_cli_flag.py +0 -0
  152. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_mcp.py +0 -0
  153. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_memory.py +0 -0
  154. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_memory_tool.py +0 -0
  155. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_memory_writers.py +0 -0
  156. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_message_parsing.py +0 -0
  157. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_no_max_turns_in_call_sites.py +0 -0
  158. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_no_max_turns_literal.py +0 -0
  159. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_non_goals_grep_guard.py +0 -0
  160. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_ollama_budget.py +0 -0
  161. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_ollama_rate_limit.py +0 -0
  162. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_optimize.py +0 -0
  163. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_options.py +0 -0
  164. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_phase0.py +0 -0
  165. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_phase2.py +0 -0
  166. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_phase2_cache_regression.py +0 -0
  167. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_phase2_multicycle_token_reduction.py +0 -0
  168. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_phase2_token_reduction.py +0 -0
  169. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_phase3_4.py +0 -0
  170. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_phase5_precommit.py +0 -0
  171. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_phase6.py +0 -0
  172. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_phase7.py +0 -0
  173. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_phase_mcp_regression.py +0 -0
  174. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_phase_telemetry.py +0 -0
  175. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_phase_token_logging.py +0 -0
  176. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_preflight.py +0 -0
  177. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_preflight_ollama.py +0 -0
  178. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_prompt_drift.py +0 -0
  179. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_prompt_prefix_snapshots.py +0 -0
  180. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_prompt_prefix_stability.py +0 -0
  181. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_prompts.py +0 -0
  182. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_rate_limit.py +0 -0
  183. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_repo_info.py +0 -0
  184. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_requirements_structure.py +0 -0
  185. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_retry.py +0 -0
  186. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_review.py +0 -0
  187. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_runner_protocol.py +0 -0
  188. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_runner_protocol_annotations.py +0 -0
  189. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_runner_types.py +0 -0
  190. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_runner_utils.py +0 -0
  191. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_sdk_runner.py +0 -0
  192. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_sdk_runner_shared.py +0 -0
  193. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_session_mode.py +0 -0
  194. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_state.py +0 -0
  195. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_state_guard.py +0 -0
  196. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_state_retention.py +0 -0
  197. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_status.py +0 -0
  198. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_subprocess_runner.py +0 -0
  199. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/tests/test_telemetry.py +0 -0
  200. {claude_code_generator-0.5.6 → claude_code_generator-0.5.8}/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.6
3
+ Version: 0.5.8
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.6"
7
+ version = "0.5.8"
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.6
3
+ Version: 0.5.8
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.8"
@@ -74,6 +74,38 @@ _PYTHON_CLI = AgentSelection(
74
74
  )
75
75
 
76
76
 
77
+ # ---------------------------------------------------------------------------
78
+ # Extra-skill detection
79
+ # ---------------------------------------------------------------------------
80
+
81
+ _EXTRA_SKILL_KEYWORDS: tuple[tuple[tuple[str, ...], tuple[str, ...]], ...] = (
82
+ (("supabase",), ("supabase", "supabase-postgres-best-practices")),
83
+ (
84
+ ("skfolio", "portfolio optim", "efficient frontier", "mean-variance", "risk parity"),
85
+ ("skfolio",),
86
+ ),
87
+ (("yfinance", "yahoo finance", "ohlcv", "stock data", "ticker data"), ("yfinance",)),
88
+ (("xlsx", ".xlsx", " excel", "spreadsheet"), ("xlsx",)),
89
+ (("docx", ".docx", "word document"), ("docx",)),
90
+ )
91
+
92
+
93
+ def _detect_extra_skills(block: str) -> tuple[str, ...]:
94
+ extras: list[str] = []
95
+ for keywords, skills in _EXTRA_SKILL_KEYWORDS:
96
+ if any(kw in block for kw in keywords):
97
+ extras.extend(skills)
98
+ return tuple(extras)
99
+
100
+
101
+ def _merge(base: AgentSelection, extras: tuple[str, ...]) -> AgentSelection:
102
+ if not extras:
103
+ return base
104
+ existing = set(base.skills)
105
+ new_skills = base.skills + tuple(s for s in extras if s not in existing)
106
+ return AgentSelection(primary=base.primary, support=base.support, skills=new_skills)
107
+
108
+
77
109
  # ---------------------------------------------------------------------------
78
110
  # Public API
79
111
  # ---------------------------------------------------------------------------
@@ -131,18 +163,16 @@ def detect(requirements_path: Path) -> AgentSelection:
131
163
  has_fastapi = "fastapi" in block or "python api" in block
132
164
 
133
165
  if has_angular and has_fastapi:
134
- return _ANGULAR_FASTAPI
135
-
136
- if has_angular:
137
- return _ANGULAR_ONLY
138
-
139
- if has_fastapi:
140
- return _FASTAPI
141
-
142
- if "baml" in block or "llm function" in block:
143
- return _BAML
144
-
145
- if "typer" in block or "click" in block or "python cli" in block:
146
- return _PYTHON_CLI
147
-
148
- return _GENERIC_FALLBACK
166
+ base = _ANGULAR_FASTAPI
167
+ elif has_angular:
168
+ base = _ANGULAR_ONLY
169
+ elif has_fastapi:
170
+ base = _FASTAPI
171
+ elif "baml" in block or "llm function" in block:
172
+ base = _BAML
173
+ elif "typer" in block or "click" in block or "python cli" in block:
174
+ base = _PYTHON_CLI
175
+ else:
176
+ base = _GENERIC_FALLBACK
177
+
178
+ return _merge(base, _detect_extra_skills(block))
@@ -37,14 +37,51 @@ __all__ = ["dispatch_async", "dispatch_orchestrator"]
37
37
 
38
38
  _GRAPHIFY_TIMEOUT = int(os.environ.get("CODE_GENERATOR_GRAPHIFY_TIMEOUT", "600"))
39
39
 
40
+ _GRAPHIFY_EXTRACT_CMD = ["graphify", "extract", "."]
41
+ _GRAPHIFY_UPDATE_CMD = ["graphify", "update", "."]
40
42
 
41
43
  _SETUP_HINT = (
42
- "graph-report: graphify-out/graph.json not found run `/graphify .` once "
43
- "inside Claude Code (or another graphify-compatible assistant) to seed the "
44
- "knowledge graph. After that, code-generator will keep it fresh "
45
- "automatically with `graphify update .`. Using fallback for now."
44
+ "graph-report: headless extraction via `graphify extract .` was attempted "
45
+ "automatically but failed ensure graphify >= 0.7.3 is installed "
46
+ "(`pipx install graphifyy`) or run `/graphify .` once inside Claude Code "
47
+ "to seed the knowledge graph manually. Using fallback for now."
46
48
  )
47
49
 
50
+ _GRAPHIFY_MIN_VERSION = (0, 7, 3)
51
+ _GRAPHIFY_UPGRADE_HINT = (
52
+ "upgrade for headless seeding and v0.5.5 workaround removal: pip install -U graphifyy"
53
+ )
54
+
55
+
56
+ def _parse_version_line(stdout: str) -> str | None:
57
+ """Return 'X.Y.Z' from pip-show stdout, or None when the line is absent."""
58
+ for line in stdout.splitlines():
59
+ if line.startswith("Version:"):
60
+ return line.split(":", 1)[1].strip()
61
+ return None
62
+
63
+
64
+ def _get_graphify_version_str() -> str | None:
65
+ """Probe installed graphifyy version via pip show; None on any failure."""
66
+ try:
67
+ result = subprocess.run(
68
+ ["pip", "show", "graphifyy"], capture_output=True, text=True, check=False
69
+ )
70
+ return _parse_version_line(result.stdout or "")
71
+ except (OSError, subprocess.SubprocessError):
72
+ return None
73
+
74
+
75
+ def _probe_graphify_version(log: logging.Logger) -> None:
76
+ """Warn when graphify is below _GRAPHIFY_MIN_VERSION or undetectable."""
77
+ version_str = _get_graphify_version_str()
78
+ if version_str is None:
79
+ log.warning("graph-report: graphify version unknown — %s", _GRAPHIFY_UPGRADE_HINT)
80
+ return
81
+ version = tuple(int(p) for p in version_str.split(".") if p.isdigit())
82
+ if version < _GRAPHIFY_MIN_VERSION:
83
+ log.warning("graph-report: graphify %s detected — %s", version_str, _GRAPHIFY_UPGRADE_HINT)
84
+
48
85
 
49
86
  def _decode_stderr(stderr: bytes | str | None) -> str:
50
87
  """Decode subprocess stderr to a short single-line string for logging."""
@@ -55,42 +92,65 @@ def _decode_stderr(stderr: bytes | str | None) -> str:
55
92
  return text[:500]
56
93
 
57
94
 
58
- def _compute_and_persist_graph_report(project_dir: Path, log: logging.Logger) -> None:
59
- """Refresh the codebase graph via graphify and persist the report.
95
+ def _extract_argv(ollama_model: str | None) -> list[str]:
96
+ """Build graphify extract argv, appending --backend ollama on the Ollama path."""
97
+ if ollama_model is not None:
98
+ return [*_GRAPHIFY_EXTRACT_CMD, "--backend", "ollama"]
99
+ return list(_GRAPHIFY_EXTRACT_CMD)
100
+
101
+
102
+ def _run_graphify(
103
+ cmd: list[str],
104
+ project_dir: Path,
105
+ log: logging.Logger,
106
+ memories_dir: Path,
107
+ ) -> bool:
108
+ """Run a graphify subprocess. Write fallback and return False on any error."""
109
+ try:
110
+ log.info("graph-report: running %s", " ".join(cmd))
111
+ subprocess.run( # noqa: S603
112
+ cmd,
113
+ cwd=project_dir,
114
+ timeout=_GRAPHIFY_TIMEOUT,
115
+ capture_output=True,
116
+ check=True,
117
+ )
118
+ return True
119
+ except subprocess.CalledProcessError as exc:
120
+ stderr = _decode_stderr(exc.stderr)
121
+ log.warning(
122
+ "graph-report: graphify exited %d; using fallback. stderr: %s",
123
+ exc.returncode,
124
+ stderr or "<empty>",
125
+ )
126
+ except subprocess.TimeoutExpired as exc:
127
+ log.warning("graph-report: graphify timed out after %ss; using fallback", exc.timeout)
128
+ except (FileNotFoundError, OSError) as exc:
129
+ log.warning("graph-report: could not invoke graphify (%s); using fallback", exc)
130
+ _memory.write_memory_file(memories_dir, "cycle-repo-map.md", _memory.REPOMAP_FALLBACK)
131
+ return False
60
132
 
61
- Graphify's shell CLI does **not** support a "build" subcommand — the full
62
- LLM-driven build only happens through the ``/graphify`` slash-command
63
- inside an AI assistant (Claude Code, Codex, Cursor, etc.). The shell
64
- binary only supports maintenance subcommands like ``update``, ``watch``,
65
- ``query``.
133
+
134
+ def _compute_and_persist_graph_report(
135
+ project_dir: Path, log: logging.Logger, *, ollama_model: str | None = None
136
+ ) -> None:
137
+ """Refresh the codebase graph via graphify and persist the report.
66
138
 
67
139
  Strategy:
68
140
 
69
- 1. If ``graphify-out/graph.json`` does **not** exist, the user has never
70
- seeded the graph. Log a one-time setup hint, write the fallback
71
- sentinel, and return do not call subprocess. The user must run
72
- ``/graphify .`` in their assistant once to seed it.
141
+ 1. If ``graphify-out/graph.json`` does **not** exist, run
142
+ ``graphify extract .`` (headless LLM-driven seeding, requires >= 0.7.3).
143
+ On success, read ``GRAPH_REPORT.md`` and persist. On failure, write the
144
+ fallback sentinel and emit :data:`_SETUP_HINT`.
73
145
 
74
- 2. If it exists, run ``graphify update .`` (cwd=project_dir). This is
75
- AST-only re-extraction, no LLM cost, fast on cached files. It
76
- refreshes ``graph.json`` and ``GRAPH_REPORT.md``. Doc / paper /
77
- image changes are *not* picked up — for those the user must run
78
- ``/graphify .`` again in their assistant.
146
+ 2. If it exists, run ``graphify update .`` (AST-only refresh, no LLM cost).
147
+ Refreshes ``graph.json`` and ``GRAPH_REPORT.md``.
79
148
 
80
149
  Falls back to :data:`code_generator.memory.REPOMAP_FALLBACK` on any
81
150
  failure (binary missing, timeout, non-zero exit, missing report file).
82
- Stderr from graphify is captured and logged at WARN level on failure so
83
- the user can diagnose without re-running by hand.
84
-
85
- Auth: graphify inherits the parent process env. ``graphify update`` is
86
- AST-only and makes no LLM calls, so OAuth context is irrelevant on this
87
- codepath — but we still don't strip ``ANTHROPIC_*`` because the startup
88
- ``env.assert_safe_environment()`` check (CLAUDE.md non-negotiable #1)
89
- already guarantees those vars are absent on the Anthropic Max path.
90
151
 
91
152
  Args:
92
- project_dir: Project root directory (graphify writes ``graphify-out/``
93
- into this directory).
153
+ project_dir: Project root directory.
94
154
  log: Phase logger.
95
155
  """
96
156
  memories_dir = project_dir / ".code-generator" / "memories"
@@ -106,53 +166,14 @@ def _compute_and_persist_graph_report(project_dir: Path, log: logging.Logger) ->
106
166
  return
107
167
 
108
168
  if not graph_json.exists():
109
- log.info(_SETUP_HINT)
110
- _memory.write_memory_file(memories_dir, "cycle-repo-map.md", _memory.REPOMAP_FALLBACK)
111
- return
112
-
113
- cmd = ["graphify", "update", "."]
114
- # GRAPHIFY_NO_TIPS=1 suppresses the tip-printer that has a NameError in
115
- # graphify v0.5.5 (_os not in scope outside the install block).
116
- os.environ.setdefault("GRAPHIFY_NO_TIPS", "1")
117
- try:
118
- log.info("graph-report: refreshing (cmd=%s)", " ".join(cmd))
119
- subprocess.run( # noqa: S603
120
- cmd,
121
- cwd=project_dir,
122
- timeout=_GRAPHIFY_TIMEOUT,
123
- capture_output=True,
124
- check=True,
125
- )
126
- except subprocess.CalledProcessError as exc:
127
- stderr = _decode_stderr(exc.stderr)
128
- # graphify v0.5.5 has a NameError in its tip-printer (_os not in scope)
129
- # that fires after a successful update — treat it as success so the
130
- # real GRAPH_REPORT.md is used instead of the fallback sentinel.
131
- if "NameError: name '_os' is not defined" in (stderr or ""):
132
- log.info(
133
- "graph-report: graphify tip-printer bug (v0.5.5) — graph updated; ignoring exit 1"
134
- )
135
- else:
136
- log.warning(
137
- "graph-report: graphify exited %d; using fallback. stderr: %s",
138
- exc.returncode,
139
- stderr or "<empty>",
140
- )
141
- _memory.write_memory_file(memories_dir, "cycle-repo-map.md", _memory.REPOMAP_FALLBACK)
169
+ log.info("graph-report: graph.json absent — attempting headless extraction")
170
+ if not _run_graphify(_extract_argv(ollama_model), project_dir, log, memories_dir):
171
+ log.info(_SETUP_HINT)
172
+ return
173
+ else:
174
+ _probe_graphify_version(log)
175
+ if not _run_graphify(_GRAPHIFY_UPDATE_CMD, project_dir, log, memories_dir):
142
176
  return
143
- except subprocess.TimeoutExpired as exc:
144
- log.warning(
145
- "graph-report: graphify timed out after %ss; using fallback",
146
- exc.timeout,
147
- )
148
- _memory.write_memory_file(memories_dir, "cycle-repo-map.md", _memory.REPOMAP_FALLBACK)
149
- return
150
- except (FileNotFoundError, OSError) as exc:
151
- # `FileNotFoundError` here means the `graphify` binary disappeared
152
- # between `shutil.which` and `subprocess.run`. Extremely unlikely.
153
- log.warning("graph-report: could not invoke graphify (%s); using fallback", exc)
154
- _memory.write_memory_file(memories_dir, "cycle-repo-map.md", _memory.REPOMAP_FALLBACK)
155
- return
156
177
 
157
178
  try:
158
179
  content = report_path.read_text(encoding="utf-8")
@@ -289,7 +310,7 @@ async def _apply_delta_plan(
289
310
  "Phase 0 failed during delta planning; aborting to preserve cycle history."
290
311
  )
291
312
 
292
- new_cycles = state_module.append_new_cycles(st, raw_cycles)
313
+ new_cycles = state_module.append_new_cycles(st, raw_cycles, ollama_model=effective_model)
293
314
 
294
315
  if not new_cycles:
295
316
  logger.info("Delta planning: no new cycles detected.")
@@ -344,7 +365,7 @@ async def dispatch_async(
344
365
  # Build the codebase graph once at the top of dispatch so Phase 0
345
366
  # (complexity analysis) sees a fresh report alongside Phases 1 and 2.
346
367
  graph_logger = setup_phase_logger("graph-report", project_dir)
347
- _compute_and_persist_graph_report(project_dir, graph_logger)
368
+ _compute_and_persist_graph_report(project_dir, graph_logger, ollama_model=ollama_model)
348
369
 
349
370
  # Phase 0: determine mode when not already known.
350
371
  if mode == "auto":
@@ -147,6 +147,20 @@ def _persist_repo_info(
147
147
  @generate_app.callback(invoke_without_command=True)
148
148
  def generate_command(
149
149
  ctx: typer.Context,
150
+ model: Annotated[
151
+ str | None,
152
+ typer.Option(
153
+ "--model",
154
+ help=(
155
+ "Model tag to use for ALL phases (single-model routing). "
156
+ "When set, overrides the per-phase Opus/Sonnet/Haiku defaults "
157
+ "and routes every phase through the operator's configured endpoint. "
158
+ "Preserves existing ANTHROPIC_AUTH_TOKEN + ANTHROPIC_BASE_URL from "
159
+ "the parent environment."
160
+ ),
161
+ show_default=False,
162
+ ),
163
+ ] = None,
150
164
  dry_run: Annotated[
151
165
  bool,
152
166
  typer.Option("--dry-run/--no-dry-run", help="Print what would happen without doing it."),
@@ -192,12 +206,21 @@ def generate_command(
192
206
  ),
193
207
  ] = None,
194
208
  ) -> None:
195
- """Run the full code-generation pipeline (default: Anthropic Max)."""
209
+ """Run the full code-generation pipeline (default: Anthropic Max).
210
+
211
+ Pass --model to route all phases through a custom endpoint (e.g. a local
212
+ Ollama daemon or a cloud proxy) instead of Anthropic Max. When --model is
213
+ set, the existing ANTHROPIC_AUTH_TOKEN and ANTHROPIC_BASE_URL from the
214
+ parent environment are preserved.
215
+ """
196
216
  # A subcommand like `generate ollama` has its own body; skip the default flow.
197
217
  if ctx.invoked_subcommand is not None:
198
218
  return
199
219
 
200
- env.assert_safe_environment()
220
+ if model is not None:
221
+ env.assert_safe_environment_custom_model()
222
+ else:
223
+ env.assert_safe_environment()
201
224
  env.assert_single_workspace()
202
225
 
203
226
  if phase is not None and continue_:
@@ -235,6 +258,11 @@ def generate_command(
235
258
  # Persist the fresh RepoInfo atomically; warns when it differs from stored value.
236
259
  _persist_repo_info(st, repo_info, state_path)
237
260
 
261
+ # When --model is set, stamp the tag on existing cycles so --continue
262
+ # resolves the correct model without the operator re-typing --model.
263
+ if model is not None:
264
+ _stamp_model_on_existing_cycles(st, model, state_path)
265
+
238
266
  # Rate-limit pause guard: inform user and exit cleanly.
239
267
  if _check_paused(st):
240
268
  raise typer.Exit(code=0)
@@ -262,6 +290,7 @@ def generate_command(
262
290
  session_mode=effective_session_mode,
263
291
  session_mode_explicit=session_mode_explicit,
264
292
  max_turns=max_turns,
293
+ ollama_model=model,
265
294
  )
266
295
  except typer.Exit:
267
296
  raise
@@ -431,6 +460,9 @@ def generate_ollama_command(
431
460
  )
432
461
  raise typer.Exit(code=2)
433
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
+
434
466
  # (6) Persist the resolved model on existing cycles before dispatch so
435
467
  # --continue resume stays consistent across sessions. New cycles
436
468
  # created by phase 0/1 inherit the tag via the orchestrator thread
@@ -286,6 +286,61 @@ 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
+
317
+ def assert_safe_environment_custom_model() -> None:
318
+ """Strip Anthropic API creds but preserve routing for custom model endpoints.
319
+
320
+ When ``--model`` is passed to the default ``generate`` command (not the
321
+ ``ollama`` subcommand), the operator's existing ``ANTHROPIC_AUTH_TOKEN``
322
+ and ``ANTHROPIC_BASE_URL`` are preserved so the custom endpoint routing
323
+ survives. Only real Anthropic credit keys are stripped.
324
+ """
325
+ offenders = [var for var in DANGEROUS_VARS if var in os.environ]
326
+ if offenders:
327
+ listed = ", ".join(offenders)
328
+ print(
329
+ f"Using custom model (preserving routing, ignoring {listed} for this process).",
330
+ file=sys.stderr,
331
+ )
332
+ _strip_dangerous_env_custom()
333
+
334
+
335
+ def _strip_dangerous_env_custom() -> None:
336
+ """Strip DANGEROUS_VARS except ANTHROPIC_AUTH_TOKEN (preserve custom routing)."""
337
+ protected = {"ANTHROPIC_AUTH_TOKEN"}
338
+ for var in DANGEROUS_VARS:
339
+ if var in protected:
340
+ continue
341
+ os.environ.pop(var, None)
342
+
343
+
289
344
  def assert_safe_environment_ollama() -> None:
290
345
  """Strip Anthropic env vars and install the scoped Ollama routing in place.
291
346
 
@@ -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.