claude-code-generator 0.4.5__tar.gz → 0.4.6__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 (183) hide show
  1. {claude_code_generator-0.4.5/src/claude_code_generator.egg-info → claude_code_generator-0.4.6}/PKG-INFO +1 -1
  2. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/pyproject.toml +1 -1
  3. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6/src/claude_code_generator.egg-info}/PKG-INFO +1 -1
  4. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/claude_code_generator.egg-info/SOURCES.txt +4 -0
  5. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/__init__.py +1 -1
  6. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/commands/generate.py +8 -2
  7. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/env.py +31 -7
  8. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/gh/__init__.py +2 -0
  9. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/gh/issues.py +17 -0
  10. claude_code_generator-0.4.6/src/code_generator/orchestrator/cycle_prompts.py +267 -0
  11. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/orchestrator/phase0_complexity.py +17 -0
  12. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/orchestrator/phase1_plan.py +42 -1
  13. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/orchestrator/phase2_review.py +219 -114
  14. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/prompts/__init__.py +3 -1
  15. claude_code_generator-0.4.6/src/code_generator/prompts/prompt-cycle-specializer.md +62 -0
  16. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/prompts/prompt-phase-1-planning.md +5 -4
  17. claude_code_generator-0.4.6/src/code_generator/prompts/prompt-phase-2-batch-review.md +84 -0
  18. claude_code_generator-0.4.6/tests/test_cycle_prompts.py +249 -0
  19. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_env.py +63 -4
  20. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_gh.py +32 -0
  21. claude_code_generator-0.4.6/tests/test_phase2.py +569 -0
  22. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_prompt_prefix_snapshots.py +4 -1
  23. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_prompt_prefix_stability.py +49 -3
  24. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_prompts.py +39 -11
  25. claude_code_generator-0.4.5/tests/test_phase2.py +0 -975
  26. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/LICENSE +0 -0
  27. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/README.md +0 -0
  28. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/setup.cfg +0 -0
  29. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/claude_code_generator.egg-info/dependency_links.txt +0 -0
  30. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/claude_code_generator.egg-info/entry_points.txt +0 -0
  31. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/claude_code_generator.egg-info/requires.txt +0 -0
  32. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/claude_code_generator.egg-info/top_level.txt +0 -0
  33. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/agents.py +0 -0
  34. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/cli.py +0 -0
  35. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/commands/__init__.py +0 -0
  36. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/commands/_bench_io.py +0 -0
  37. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/commands/_crash_recovery.py +0 -0
  38. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/commands/_detect.py +0 -0
  39. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/commands/_dispatch.py +0 -0
  40. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/commands/_resume.py +0 -0
  41. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/commands/_validators.py +0 -0
  42. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/commands/bench.py +0 -0
  43. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/commands/bench_compare.py +0 -0
  44. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/commands/bench_export.py +0 -0
  45. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/commands/init.py +0 -0
  46. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/commands/optimize.py +0 -0
  47. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/commands/review.py +0 -0
  48. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/commands/status.py +0 -0
  49. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/effort.py +0 -0
  50. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/gh/core.py +0 -0
  51. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/gh/labels.py +0 -0
  52. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/gh/milestones.py +0 -0
  53. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/git_ops.py +0 -0
  54. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/logging_setup.py +0 -0
  55. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/memory.py +0 -0
  56. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/orchestrator/__init__.py +0 -0
  57. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/orchestrator/_client_lifecycle.py +0 -0
  58. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/orchestrator/_comments.py +0 -0
  59. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/orchestrator/_memory_writers.py +0 -0
  60. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/orchestrator/_phase5_precommit.py +0 -0
  61. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/orchestrator/cycle_loop.py +0 -0
  62. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/orchestrator/ollama_budget.py +0 -0
  63. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/orchestrator/phase3_4_implement.py +0 -0
  64. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/orchestrator/phase5_closure.py +0 -0
  65. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/orchestrator/phase6_test.py +0 -0
  66. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/orchestrator/phase7_commit.py +0 -0
  67. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/preflight.py +0 -0
  68. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/prompts/hashes.py +0 -0
  69. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/prompts/prompt-optimize-requirements.md +0 -0
  70. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/prompts/prompt-phase-0-complexity.md +0 -0
  71. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/prompts/prompt-phase-2-issue-review.md +0 -0
  72. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/prompts/prompt-phase-3-implementation.md +0 -0
  73. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/prompts/prompt-phase-5-final-review.md +0 -0
  74. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/prompts/prompt-phase-6-test.md +0 -0
  75. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/prompts/prompt-phase-7-commit.md +0 -0
  76. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/prompts/prompt-review.md +0 -0
  77. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/repo_info.py +0 -0
  78. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/repomap.py +0 -0
  79. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/requirements_structure.py +0 -0
  80. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/runner/__init__.py +0 -0
  81. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/runner/_telemetry.py +0 -0
  82. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/runner/batch.py +0 -0
  83. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/runner/fake_runner.py +0 -0
  84. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/runner/mcp.py +0 -0
  85. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/runner/message_parsing.py +0 -0
  86. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/runner/options.py +0 -0
  87. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/runner/protocol.py +0 -0
  88. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/runner/rate_limit.py +0 -0
  89. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/runner/retry.py +0 -0
  90. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/runner/sdk_runner.py +0 -0
  91. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/runner/soft_reset.py +0 -0
  92. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/runner/state_guard.py +0 -0
  93. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/runner/subprocess_runner.py +0 -0
  94. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/runner/types.py +0 -0
  95. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/runner/utils.py +0 -0
  96. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/state.py +0 -0
  97. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/state_retention.py +0 -0
  98. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/templates/__init__.py +0 -0
  99. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/templates/angular.md +0 -0
  100. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/templates/base.md +0 -0
  101. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/templates/fastapi.md +0 -0
  102. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/templates/finance.md +0 -0
  103. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/templates/fullstack.md +0 -0
  104. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/templates/nestjs.md +0 -0
  105. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/src/code_generator/templates/python-cli.md +0 -0
  106. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_agents.py +0 -0
  107. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_bench.py +0 -0
  108. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_bench_compare.py +0 -0
  109. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_bench_export.py +0 -0
  110. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_bench_fixture.py +0 -0
  111. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_bench_regression.py +0 -0
  112. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_changelog.py +0 -0
  113. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_claude_md.py +0 -0
  114. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_client_lifecycle.py +0 -0
  115. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_comments.py +0 -0
  116. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_commit_message.py +0 -0
  117. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_crash_recovery.py +0 -0
  118. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_cycle_loop.py +0 -0
  119. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_cycle_loop_multicycle.py +0 -0
  120. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_cycle_ollama_model.py +0 -0
  121. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_delta_planning.py +0 -0
  122. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_dependencies.py +0 -0
  123. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_detect.py +0 -0
  124. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_docs_no_default_max_turns.py +0 -0
  125. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_docs_ollama_model_guide.py +0 -0
  126. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_docs_ollama_pro.py +0 -0
  127. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_effective_model_routing.py +0 -0
  128. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_effort.py +0 -0
  129. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_generate.py +0 -0
  130. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_generate_ollama.py +0 -0
  131. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_generate_resume.py +0 -0
  132. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_gh_labels.py +0 -0
  133. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_gh_milestones.py +0 -0
  134. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_gh_repo_threading.py +0 -0
  135. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_gh_submodules.py +0 -0
  136. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_git_ops.py +0 -0
  137. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_init.py +0 -0
  138. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_logging_setup.py +0 -0
  139. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_max_turns_cli_flag.py +0 -0
  140. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_mcp.py +0 -0
  141. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_memory.py +0 -0
  142. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_memory_writers.py +0 -0
  143. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_message_parsing.py +0 -0
  144. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_no_max_turns_in_call_sites.py +0 -0
  145. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_no_max_turns_literal.py +0 -0
  146. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_non_goals_grep_guard.py +0 -0
  147. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_ollama_budget.py +0 -0
  148. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_ollama_rate_limit.py +0 -0
  149. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_optimize.py +0 -0
  150. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_options.py +0 -0
  151. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_phase0.py +0 -0
  152. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_phase1.py +0 -0
  153. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_phase2_batch.py +0 -0
  154. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_phase3_4.py +0 -0
  155. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_phase5.py +0 -0
  156. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_phase5_precommit.py +0 -0
  157. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_phase6.py +0 -0
  158. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_phase7.py +0 -0
  159. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_phase_mcp_regression.py +0 -0
  160. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_phase_token_logging.py +0 -0
  161. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_preflight.py +0 -0
  162. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_preflight_ollama.py +0 -0
  163. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_prompt_drift.py +0 -0
  164. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_rate_limit.py +0 -0
  165. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_repo_info.py +0 -0
  166. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_repomap.py +0 -0
  167. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_requirements_structure.py +0 -0
  168. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_retry.py +0 -0
  169. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_review.py +0 -0
  170. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_runner_protocol.py +0 -0
  171. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_runner_protocol_annotations.py +0 -0
  172. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_runner_types.py +0 -0
  173. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_runner_utils.py +0 -0
  174. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_sdk_runner.py +0 -0
  175. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_sdk_runner_shared.py +0 -0
  176. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_session_mode.py +0 -0
  177. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_state.py +0 -0
  178. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_state_guard.py +0 -0
  179. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_state_retention.py +0 -0
  180. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_status.py +0 -0
  181. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_subprocess_runner.py +0 -0
  182. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/tests/test_telemetry.py +0 -0
  183. {claude_code_generator-0.4.5 → claude_code_generator-0.4.6}/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.4.5
3
+ Version: 0.4.6
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.4.5"
7
+ version = "0.4.6"
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" }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: claude-code-generator
3
- Version: 0.4.5
3
+ Version: 0.4.6
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
@@ -47,6 +47,7 @@ src/code_generator/orchestrator/_comments.py
47
47
  src/code_generator/orchestrator/_memory_writers.py
48
48
  src/code_generator/orchestrator/_phase5_precommit.py
49
49
  src/code_generator/orchestrator/cycle_loop.py
50
+ src/code_generator/orchestrator/cycle_prompts.py
50
51
  src/code_generator/orchestrator/ollama_budget.py
51
52
  src/code_generator/orchestrator/phase0_complexity.py
52
53
  src/code_generator/orchestrator/phase1_plan.py
@@ -57,9 +58,11 @@ src/code_generator/orchestrator/phase6_test.py
57
58
  src/code_generator/orchestrator/phase7_commit.py
58
59
  src/code_generator/prompts/__init__.py
59
60
  src/code_generator/prompts/hashes.py
61
+ src/code_generator/prompts/prompt-cycle-specializer.md
60
62
  src/code_generator/prompts/prompt-optimize-requirements.md
61
63
  src/code_generator/prompts/prompt-phase-0-complexity.md
62
64
  src/code_generator/prompts/prompt-phase-1-planning.md
65
+ src/code_generator/prompts/prompt-phase-2-batch-review.md
63
66
  src/code_generator/prompts/prompt-phase-2-issue-review.md
64
67
  src/code_generator/prompts/prompt-phase-3-implementation.md
65
68
  src/code_generator/prompts/prompt-phase-5-final-review.md
@@ -105,6 +108,7 @@ tests/test_crash_recovery.py
105
108
  tests/test_cycle_loop.py
106
109
  tests/test_cycle_loop_multicycle.py
107
110
  tests/test_cycle_ollama_model.py
111
+ tests/test_cycle_prompts.py
108
112
  tests/test_delta_planning.py
109
113
  tests/test_dependencies.py
110
114
  tests/test_detect.py
@@ -1,3 +1,3 @@
1
1
  """code-generator: orchestrator CLI for end-to-end project generation."""
2
2
 
3
- __version__ = "0.4.5"
3
+ __version__ = "0.4.6"
@@ -396,10 +396,16 @@ def generate_ollama_command(
396
396
 
397
397
  state_path = cg_dir / "state.json"
398
398
 
399
- # (1) Ollama preflight cheap env-var checks then live-daemon probe.
399
+ # (1) Strip any stray ANTHROPIC_* vars from the parent process env so the
400
+ # Ollama bypass runs without forcing the operator to `unset` them in their
401
+ # shell. Safe here because the scoped env pins ANTHROPIC_BASE_URL to
402
+ # localhost — no traffic can reach Anthropic.
403
+ env.assert_safe_environment_ollama()
404
+
405
+ # (2) Ollama preflight — cheap env-var checks then live-daemon probe.
400
406
  _run_ollama_preflight_or_exit()
401
407
 
402
- # (2) Build the scoped Ollama env — validates the #215 preconditions.
408
+ # (3) Build the scoped Ollama env — validates the #215 preconditions.
403
409
  # We don't pass the dict further; we just confirm the env builds without
404
410
  # raising. The orchestrator reads OLLAMA_API_KEY / ANTHROPIC_BASE_URL
405
411
  # when it actually spawns SDK sessions.
@@ -96,8 +96,14 @@ def _require_ollama_preconditions() -> str:
96
96
 
97
97
  Refuses the bypass when:
98
98
  * ``OLLAMA_API_KEY`` is unset or empty.
99
- * ``ANTHROPIC_API_KEY`` is non-empty in the parent env.
100
99
  * ``ANTHROPIC_BASE_URL`` is pre-set to anything other than the pinned URL.
100
+
101
+ A non-empty ``ANTHROPIC_API_KEY`` in the parent env is **not** a refusal:
102
+ ``_build_ollama_env`` strips every ``ANTHROPIC_*`` var from the returned
103
+ dict and re-pins ``ANTHROPIC_BASE_URL`` to localhost, so no traffic can
104
+ leak to Anthropic. Callers that want a clean parent ``os.environ`` for
105
+ incidental subprocesses should invoke :func:`assert_safe_environment_ollama`
106
+ before :func:`build_agent_env`.
101
107
  """
102
108
  token = os.environ.get("OLLAMA_API_KEY", "")
103
109
  if not token:
@@ -105,12 +111,6 @@ def _require_ollama_preconditions() -> str:
105
111
  "OLLAMA_API_KEY is unset or empty. Set it to the Ollama Pro token "
106
112
  "before running with provider='ollama'."
107
113
  )
108
- if os.environ.get("ANTHROPIC_API_KEY", ""):
109
- raise RuntimeError(
110
- "ANTHROPIC_API_KEY is set in the parent env. The Ollama bypass "
111
- "refuses to run when Anthropic credentials are present to prevent "
112
- "accidental API-credit spend."
113
- )
114
114
  preset_base = os.environ.get("ANTHROPIC_BASE_URL")
115
115
  if preset_base and preset_base != OLLAMA_BASE_URL:
116
116
  raise RuntimeError(
@@ -244,3 +244,27 @@ def assert_safe_environment() -> None:
244
244
  file=sys.stderr,
245
245
  )
246
246
  strip_dangerous_env()
247
+
248
+
249
+ def assert_safe_environment_ollama() -> None:
250
+ """Strip Anthropic env vars before the Ollama bypass runs.
251
+
252
+ Mirrors :func:`assert_safe_environment` for the Ollama codepath: the user
253
+ does not have to ``unset ANTHROPIC_API_KEY`` in their shell. An Anthropic
254
+ credential in the parent env is not a safety hazard here — the scoped
255
+ Ollama env pins ``ANTHROPIC_BASE_URL`` to ``http://localhost:11434`` and
256
+ re-authenticates with ``OLLAMA_API_KEY``, so no traffic reaches Anthropic.
257
+ The in-place strip is defensive: any incidental subprocess that does not
258
+ route through :func:`build_agent_env` still inherits a clean env.
259
+ """
260
+ offenders = [var for var in DANGEROUS_VARS if var in os.environ]
261
+ if not offenders:
262
+ return
263
+
264
+ listed = ", ".join(offenders)
265
+ print(
266
+ f"Using Ollama daemon at {OLLAMA_BASE_URL} "
267
+ f"(ignoring {listed} for this process).",
268
+ file=sys.stderr,
269
+ )
270
+ strip_dangerous_env()
@@ -24,6 +24,7 @@ from code_generator.gh.issues import (
24
24
  agent_from_labels,
25
25
  issue_state,
26
26
  list_issues,
27
+ post_comment,
27
28
  view_issue,
28
29
  )
29
30
  from code_generator.gh.labels import add_label, ensure_label, ensure_standard_labels
@@ -39,6 +40,7 @@ __all__ = [
39
40
  "agent_from_labels",
40
41
  "issue_state",
41
42
  "list_issues",
43
+ "post_comment",
42
44
  "view_issue",
43
45
  "add_label",
44
46
  "ensure_label",
@@ -79,6 +79,23 @@ def list_issues(
79
79
  return json.loads(raw) # type: ignore[no-any-return]
80
80
 
81
81
 
82
+ def post_comment(repo: RepoInfo, number: int, body: str) -> None:
83
+ """Post a comment on an issue via the ``gh`` CLI.
84
+
85
+ Args:
86
+ repo: Repository to target (provides ``--repo`` flag and ``GH_HOST`` env).
87
+ number: Issue number.
88
+ body: Markdown comment body.
89
+
90
+ Raises:
91
+ GhError: When the underlying ``gh issue comment`` invocation fails.
92
+ """
93
+ _run(
94
+ ["gh", "issue", "comment", str(number), "--repo", repo.full, "--body", body],
95
+ env={"GH_HOST": repo.host},
96
+ )
97
+
98
+
82
99
  def issue_state(repo: RepoInfo, number: int) -> str:
83
100
  """Return the issue state as a lowercase string (``"open"`` or ``"closed"``)."""
84
101
  raw = _run(
@@ -0,0 +1,267 @@
1
+ """Per-cycle specialized prompt generation.
2
+
3
+ After Phase 0 populates ``state.cycles`` the orchestrator calls
4
+ :func:`generate_cycle_prompts` once to materialise one specialized markdown
5
+ prompt per cycle under ``.code-generator/cycles_prompts/<requirements_hash>/``.
6
+
7
+ Each per-cycle file gives the downstream planning session (Phase 1) the
8
+ cycle's concrete objective together with the overall project vision and
9
+ pointers to preceding/following cycles, so milestones do not become
10
+ context-free silos.
11
+
12
+ The generator is idempotent: when every ``cycle_<id>.md`` file already
13
+ exists for the current hash, the function is a no-op, which makes
14
+ ``--continue`` safe.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import json
20
+ import os
21
+ from pathlib import Path
22
+ from typing import TYPE_CHECKING, Any
23
+
24
+ from code_generator.prompts import load_prompt
25
+ from code_generator.runner import rate_limit
26
+ from code_generator.runner.options import make_agent_options, max_turns_kwargs
27
+ from code_generator.state import CycleState
28
+
29
+ if TYPE_CHECKING:
30
+ import logging
31
+
32
+ from code_generator.runner.protocol import RunnerProtocol
33
+ from code_generator.state import State
34
+
35
+
36
+ _SPECIALIZER_DEFAULT_MODEL = "claude-opus-4-7"
37
+ # CLAUDE.md invariant #8; overridden via ``effective_model`` on Ollama — #219.
38
+
39
+
40
+ class CyclePromptGenerationError(RuntimeError):
41
+ """Raised when the cycle-specializer prompt cannot produce a valid output.
42
+
43
+ The caller (Phase 0) catches this and falls back to inlining the raw
44
+ requirements into Phase 1's volatile context.
45
+ """
46
+
47
+
48
+ def cycles_prompts_dir(project_dir: Path, requirements_hash: str) -> Path:
49
+ """Return the directory that holds per-cycle specialized prompts.
50
+
51
+ Args:
52
+ project_dir: Project root (the same path used for ``state.json``).
53
+ requirements_hash: SHA-256 hex digest of the canonical requirements.
54
+
55
+ Returns:
56
+ ``<project_dir>/.code-generator/cycles_prompts/<requirements_hash>/``.
57
+ """
58
+ return project_dir / ".code-generator" / "cycles_prompts" / requirements_hash
59
+
60
+
61
+ def _cycle_file(project_dir: Path, requirements_hash: str, cycle_id: int) -> Path:
62
+ return cycles_prompts_dir(project_dir, requirements_hash) / f"cycle_{cycle_id}.md"
63
+
64
+
65
+ def is_generated(
66
+ project_dir: Path,
67
+ requirements_hash: str,
68
+ cycle_ids: list[int],
69
+ ) -> bool:
70
+ """Return True when every ``cycle_<id>.md`` already exists for this hash.
71
+
72
+ An empty ``cycle_ids`` list returns ``True`` — there is nothing to generate.
73
+
74
+ Args:
75
+ project_dir: Project root directory.
76
+ requirements_hash: SHA-256 hex digest of the canonical requirements.
77
+ cycle_ids: IDs of the cycles that must have a generated prompt.
78
+ """
79
+ folder = cycles_prompts_dir(project_dir, requirements_hash)
80
+ if not folder.is_dir():
81
+ return False
82
+ return all(_cycle_file(project_dir, requirements_hash, cid).is_file() for cid in cycle_ids)
83
+
84
+
85
+ def load_cycle_prompt(project_dir: Path, requirements_hash: str, cycle_id: int) -> str:
86
+ """Read a previously-generated per-cycle prompt.
87
+
88
+ Args:
89
+ project_dir: Project root directory.
90
+ requirements_hash: SHA-256 hex digest of the canonical requirements.
91
+ cycle_id: Cycle id whose specialized prompt is requested.
92
+
93
+ Returns:
94
+ The raw markdown contents, suitable for verbatim injection into the
95
+ Phase 1 prompt's volatile-context block.
96
+
97
+ Raises:
98
+ FileNotFoundError: When the file is missing (the caller must handle
99
+ the fallback path — typically inlining the raw requirements).
100
+ """
101
+ path = _cycle_file(project_dir, requirements_hash, cycle_id)
102
+ return path.read_text(encoding="utf-8")
103
+
104
+
105
+ def _cycles_as_json(state: State) -> str:
106
+ """Serialize full cycles in *state* to a JSON string for prompt injection."""
107
+ cycles: list[dict[str, Any]] = []
108
+ for cycle in state.cycles:
109
+ if not isinstance(cycle, CycleState):
110
+ continue
111
+ cycles.append(
112
+ {
113
+ "id": cycle.id,
114
+ "name": cycle.name,
115
+ "milestone_title": cycle.milestone_title,
116
+ "scope": cycle.scope,
117
+ "depends_on": list(cycle.depends_on),
118
+ }
119
+ )
120
+ return json.dumps(cycles, indent=2)
121
+
122
+
123
+ def _atomic_write(path: Path, content: str) -> None:
124
+ """Write *content* to *path* via a sibling ``.tmp`` file + os.replace.
125
+
126
+ Mirrors the pattern used by :func:`code_generator.state.save_state`.
127
+ """
128
+ path.parent.mkdir(parents=True, exist_ok=True)
129
+ tmp = Path(str(path) + ".tmp")
130
+ tmp.write_text(content, encoding="utf-8")
131
+ os.replace(tmp, path)
132
+
133
+
134
+ def _parse_specializer_output(text: str, expected_ids: list[int]) -> dict[int, str]:
135
+ """Extract the ``{"cycle_N": "..."}`` JSON object from *text*.
136
+
137
+ Tolerates prose wrapping the JSON (the model occasionally adds a fenced
138
+ code block even under strict instructions). The first ``{`` and last
139
+ ``}`` delimit the candidate JSON object.
140
+
141
+ Args:
142
+ text: Raw text returned by the specializer call.
143
+ expected_ids: The cycle IDs that must appear as ``cycle_<id>`` keys.
144
+
145
+ Returns:
146
+ Mapping of cycle id → markdown briefing.
147
+
148
+ Raises:
149
+ CyclePromptGenerationError: On parse failure, on missing keys, or on
150
+ non-string values.
151
+ """
152
+ start = text.find("{")
153
+ end = text.rfind("}")
154
+ if start == -1 or end == -1 or end < start:
155
+ raise CyclePromptGenerationError("no JSON object found in specializer output")
156
+ try:
157
+ raw = json.loads(text[start : end + 1])
158
+ except json.JSONDecodeError as exc:
159
+ raise CyclePromptGenerationError(f"specializer output is not valid JSON: {exc}") from exc
160
+ if not isinstance(raw, dict):
161
+ raise CyclePromptGenerationError(
162
+ f"specializer output is not a JSON object (got {type(raw).__name__})"
163
+ )
164
+
165
+ out: dict[int, str] = {}
166
+ for cid in expected_ids:
167
+ key = f"cycle_{cid}"
168
+ value = raw.get(key)
169
+ if not isinstance(value, str) or not value.strip():
170
+ raise CyclePromptGenerationError(f"specializer output missing or empty key {key!r}")
171
+ out[cid] = value
172
+ return out
173
+
174
+
175
+ async def generate_cycle_prompts(
176
+ project_dir: Path,
177
+ state: State,
178
+ *,
179
+ runner_module: RunnerProtocol,
180
+ logger: logging.Logger,
181
+ max_turns: int | None = None,
182
+ effective_model: str | None = None,
183
+ ) -> None:
184
+ """Generate one specialized markdown prompt per cycle in *state*.
185
+
186
+ Single Opus 4.7 call emits ``{"cycle_1": "...", ...}``; each value is
187
+ written atomically to ``.code-generator/cycles_prompts/<hash>/cycle_<id>.md``.
188
+
189
+ The call is skipped entirely when every target file already exists for
190
+ the current ``state.requirements_hash`` — this is what makes
191
+ ``--continue`` a no-op on the second run.
192
+
193
+ Args:
194
+ project_dir: Project root directory.
195
+ state: Root state (must have ``requirements_hash`` set and
196
+ ``cycles`` populated by Phase 0).
197
+ runner_module: Runner module from ``get_runner()``.
198
+ logger: Phase logger (typically the Phase 0 logger).
199
+ max_turns: Optional debugging override; production callers leave ``None``.
200
+ effective_model: Optional Ollama tag override. When ``None`` the call
201
+ uses ``claude-opus-4-7``.
202
+
203
+ Raises:
204
+ CyclePromptGenerationError: On any failure — no partial files are left
205
+ behind (each file is written via ``tmp → os.replace`` only after
206
+ the JSON has been fully parsed).
207
+ """
208
+ if state.requirements_hash is None:
209
+ raise CyclePromptGenerationError(
210
+ "state.requirements_hash is unset — cannot generate specialized prompts"
211
+ )
212
+
213
+ cycle_ids = [c.id for c in state.cycles if isinstance(c, CycleState)]
214
+ if not cycle_ids:
215
+ logger.info("cycle-prompts: no cycles to specialize — skipping.")
216
+ return
217
+
218
+ if is_generated(project_dir, state.requirements_hash, cycle_ids):
219
+ logger.info(
220
+ "cycle-prompts: %d per-cycle prompts already exist at %s — skipping generation.",
221
+ len(cycle_ids),
222
+ cycles_prompts_dir(project_dir, state.requirements_hash),
223
+ )
224
+ return
225
+
226
+ requirements_path = project_dir / ".code-generator" / "requirements.md"
227
+ requirements_content = requirements_path.read_text(encoding="utf-8")
228
+ cycles_json = _cycles_as_json(state)
229
+
230
+ prompt = load_prompt(
231
+ "prompt-cycle-specializer.md",
232
+ REQUIREMENTS_CONTENT=requirements_content,
233
+ CYCLES_JSON=cycles_json,
234
+ )
235
+
236
+ model = effective_model or _SPECIALIZER_DEFAULT_MODEL
237
+ options = make_agent_options(
238
+ model=model,
239
+ allowed_tools=[],
240
+ cwd=str(project_dir),
241
+ **max_turns_kwargs(max_turns),
242
+ )
243
+
244
+ state_path = project_dir / ".code-generator" / "state.json"
245
+ logger.info(
246
+ "cycle-prompts: generating %d specialized prompts (model=%s).", len(cycle_ids), model
247
+ )
248
+ result = await rate_limit.main_loop(
249
+ runner_module,
250
+ prompt,
251
+ options,
252
+ state_path=state_path,
253
+ logger=logger,
254
+ )
255
+
256
+ briefings = _parse_specializer_output(result.text, cycle_ids)
257
+
258
+ for cid in cycle_ids:
259
+ _atomic_write(
260
+ _cycle_file(project_dir, state.requirements_hash, cid),
261
+ briefings[cid],
262
+ )
263
+ logger.info(
264
+ "cycle-prompts: wrote %d files under %s.",
265
+ len(cycle_ids),
266
+ cycles_prompts_dir(project_dir, state.requirements_hash),
267
+ )
@@ -17,6 +17,7 @@ from code_generator import effort as _effort
17
17
  from code_generator import memory as _memory
18
18
  from code_generator import state as _state
19
19
  from code_generator.logging_setup import log_phase_usage, setup_phase_logger
20
+ from code_generator.orchestrator import cycle_prompts as _cycle_prompts
20
21
  from code_generator.prompts import load_prompt
21
22
  from code_generator.runner import rate_limit
22
23
  from code_generator.runner.options import make_agent_options, max_turns_kwargs
@@ -285,6 +286,22 @@ async def run(
285
286
  _apply_result(state, output)
286
287
  logger.info("Phase 0: mode=%s, cycles=%d", state.mode, len(state.cycles))
287
288
  _state.save_state(state_path, state)
289
+ if state.mode == "multi-cycle" and state.cycles:
290
+ try:
291
+ await _cycle_prompts.generate_cycle_prompts(
292
+ project_dir,
293
+ state,
294
+ runner_module=runner_module,
295
+ logger=logger,
296
+ max_turns=max_turns,
297
+ effective_model=effective_model,
298
+ )
299
+ except _cycle_prompts.CyclePromptGenerationError as exc:
300
+ logger.warning(
301
+ "cycle-prompts: generation failed (%s); "
302
+ "Phase 1 will fall back to raw requirements.",
303
+ exc,
304
+ )
288
305
  return state
289
306
 
290
307
  # All retries exhausted — safe fallback.
@@ -15,6 +15,7 @@ from code_generator import gh
15
15
  from code_generator import memory as _memory
16
16
  from code_generator import state as _state
17
17
  from code_generator.logging_setup import log_phase_usage, setup_phase_logger
18
+ from code_generator.orchestrator import cycle_prompts as _cycle_prompts
18
19
  from code_generator.prompts import load_prompt
19
20
  from code_generator.runner import rate_limit
20
21
  from code_generator.runner._telemetry import accumulate_telemetry
@@ -115,6 +116,46 @@ _PHASE1_DEFAULT_MODEL = "claude-opus-4-7"
115
116
  # CLAUDE.md invariant #8; overridden via ``effective_model`` on Ollama — #219.
116
117
 
117
118
 
119
+ def _load_specialized_prompt(
120
+ project_dir: Path,
121
+ state: State,
122
+ cycle: CycleState | None,
123
+ ) -> str:
124
+ """Return the cycle-specialized briefing, or a raw-requirements fallback.
125
+
126
+ The preferred path reads
127
+ ``.code-generator/cycles_prompts/<requirements_hash>/cycle_<id>.md`` —
128
+ materialised by :mod:`code_generator.orchestrator.cycle_prompts` at the
129
+ end of Phase 0 for multi-cycle runs.
130
+
131
+ The fallback wraps ``.code-generator/requirements.md`` in a ``## Requirements``
132
+ section so the downstream prompt never collapses into an empty volatile
133
+ context block. The fallback fires in three cases:
134
+
135
+ - Single-cycle mode (no ``cycle`` object is in scope).
136
+ - Multi-cycle mode where ``requirements_hash`` is unset (should not happen
137
+ in production but is handled defensively).
138
+ - The specialized file is missing — e.g. the specializer call raised
139
+ :class:`~code_generator.orchestrator.cycle_prompts.CyclePromptGenerationError`
140
+ and Phase 0 swallowed the error.
141
+ """
142
+ requirements_path = project_dir / ".code-generator" / "requirements.md"
143
+
144
+ if state.mode == "multi-cycle" and cycle is not None and state.requirements_hash is not None:
145
+ try:
146
+ return _cycle_prompts.load_cycle_prompt(project_dir, state.requirements_hash, cycle.id)
147
+ except FileNotFoundError:
148
+ pass
149
+
150
+ try:
151
+ requirements_content = requirements_path.read_text(encoding="utf-8")
152
+ except FileNotFoundError:
153
+ # In production the file always exists (optimize/init guarantee it); this
154
+ # branch keeps unit tests green when they do not stage the file on disk.
155
+ return f"- **Requirements path:** {requirements_path}"
156
+ return f"## Requirements\n\n{requirements_content}"
157
+
158
+
118
159
  async def run(
119
160
  state: State,
120
161
  cycle: CycleState | None,
@@ -180,7 +221,7 @@ async def run(
180
221
  repo_map = _memory.read_cycle_repomap(memories_dir)
181
222
  prompt = load_prompt(
182
223
  "prompt-phase-1-planning.md",
183
- REQUIREMENTS_PATH=".code-generator/requirements.md",
224
+ CYCLE_SPECIALIZED_PROMPT=_load_specialized_prompt(project_dir, state, cycle),
184
225
  CYCLE_SCOPE=(
185
226
  cycle.scope
186
227
  if cycle is not None