claude-code-generator 0.5.5__tar.gz → 0.5.7__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 (198) hide show
  1. {claude_code_generator-0.5.5/src/claude_code_generator.egg-info → claude_code_generator-0.5.7}/PKG-INFO +1 -1
  2. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/pyproject.toml +1 -1
  3. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7/src/claude_code_generator.egg-info}/PKG-INFO +1 -1
  4. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/claude_code_generator.egg-info/SOURCES.txt +17 -0
  5. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/__init__.py +1 -1
  6. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/agents.py +45 -15
  7. claude_code_generator-0.5.7/src/code_generator/checklist.py +121 -0
  8. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/commands/_bench_io.py +6 -1
  9. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/commands/_dispatch.py +109 -70
  10. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/commands/bench.py +40 -7
  11. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/commands/bench_compare.py +52 -2
  12. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/commands/generate.py +31 -2
  13. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/commands/status.py +107 -0
  14. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/effort.py +26 -3
  15. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/env.py +27 -0
  16. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/gh/__init__.py +2 -0
  17. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/gh/issues.py +16 -0
  18. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/memory.py +1 -3
  19. claude_code_generator-0.5.7/src/code_generator/orchestrator/_cache_warmup.py +74 -0
  20. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/orchestrator/_client_lifecycle.py +104 -3
  21. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/orchestrator/cycle_loop.py +47 -6
  22. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/orchestrator/ollama_budget.py +2 -3
  23. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/orchestrator/phase0_complexity.py +31 -14
  24. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/orchestrator/phase1_plan.py +108 -32
  25. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/orchestrator/phase2_review.py +49 -15
  26. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/orchestrator/phase3_4_implement.py +82 -17
  27. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/orchestrator/phase5_closure.py +27 -23
  28. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/orchestrator/phase6_test.py +49 -6
  29. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/orchestrator/phase7_commit.py +39 -4
  30. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/preflight.py +10 -0
  31. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/prompts/prompt-phase-6-test.md +2 -1
  32. claude_code_generator-0.5.7/src/code_generator/runner/cache_breakpoints.py +356 -0
  33. claude_code_generator-0.5.7/src/code_generator/runner/compaction_pause.py +69 -0
  34. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/runner/fake_runner.py +55 -6
  35. claude_code_generator-0.5.7/src/code_generator/runner/memory_tool.py +187 -0
  36. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/runner/message_parsing.py +63 -1
  37. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/runner/options.py +240 -20
  38. claude_code_generator-0.5.7/src/code_generator/runner/phase_telemetry.py +85 -0
  39. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/runner/sdk_runner.py +69 -8
  40. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/runner/soft_reset.py +62 -4
  41. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/runner/subprocess_runner.py +5 -0
  42. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/runner/types.py +32 -10
  43. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/state.py +66 -4
  44. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_agents.py +138 -2
  45. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_bench.py +89 -3
  46. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_bench_compare.py +143 -11
  47. claude_code_generator-0.5.7/tests/test_cache_breakpoints.py +300 -0
  48. claude_code_generator-0.5.7/tests/test_cache_ttl_ordering.py +230 -0
  49. claude_code_generator-0.5.7/tests/test_cache_warmup.py +557 -0
  50. claude_code_generator-0.5.7/tests/test_checklist.py +250 -0
  51. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_cli_io_logging.py +2 -6
  52. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_client_lifecycle.py +182 -0
  53. claude_code_generator-0.5.7/tests/test_compaction_pause_handler.py +336 -0
  54. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_cycle_loop.py +1 -0
  55. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_cycle_loop_multicycle.py +1 -0
  56. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_cycle_prompts.py +71 -0
  57. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_dependencies.py +3 -3
  58. claude_code_generator-0.5.7/tests/test_dispatch_graph_report.py +507 -0
  59. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_effective_model_routing.py +2 -2
  60. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_effort.py +28 -3
  61. claude_code_generator-0.5.7/tests/test_effort_routing_consistency.py +63 -0
  62. claude_code_generator-0.5.7/tests/test_memory_tool.py +272 -0
  63. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_memory_writers.py +131 -0
  64. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_message_parsing.py +73 -3
  65. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_ollama_budget.py +2 -6
  66. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_options.py +442 -30
  67. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_phase0.py +2 -2
  68. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_phase1.py +201 -0
  69. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_phase2.py +129 -3
  70. claude_code_generator-0.5.7/tests/test_phase2_cache_regression.py +189 -0
  71. claude_code_generator-0.5.7/tests/test_phase2_multicycle_token_reduction.py +251 -0
  72. claude_code_generator-0.5.7/tests/test_phase2_token_reduction.py +186 -0
  73. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_phase3_4.py +87 -8
  74. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_phase5.py +10 -27
  75. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_phase6.py +114 -2
  76. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_phase7.py +274 -0
  77. claude_code_generator-0.5.7/tests/test_phase_telemetry.py +185 -0
  78. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_phase_token_logging.py +3 -3
  79. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_preflight.py +45 -0
  80. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_runner_types.py +133 -0
  81. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_sdk_runner.py +219 -0
  82. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_state.py +238 -12
  83. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_state_retention.py +2 -1
  84. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_status.py +294 -0
  85. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_subprocess_runner.py +1 -0
  86. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_telemetry.py +3 -2
  87. claude_code_generator-0.5.5/tests/test_dispatch_graph_report.py +0 -229
  88. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/LICENSE +0 -0
  89. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/README.md +0 -0
  90. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/setup.cfg +0 -0
  91. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/claude_code_generator.egg-info/dependency_links.txt +0 -0
  92. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/claude_code_generator.egg-info/entry_points.txt +0 -0
  93. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/claude_code_generator.egg-info/requires.txt +0 -0
  94. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/claude_code_generator.egg-info/top_level.txt +0 -0
  95. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/cli.py +0 -0
  96. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/commands/__init__.py +0 -0
  97. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/commands/_crash_recovery.py +0 -0
  98. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/commands/_detect.py +0 -0
  99. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/commands/_resume.py +0 -0
  100. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/commands/_validators.py +0 -0
  101. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/commands/bench_export.py +0 -0
  102. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/commands/init.py +0 -0
  103. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/commands/optimize.py +0 -0
  104. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/commands/review.py +0 -0
  105. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/gh/core.py +0 -0
  106. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/gh/labels.py +0 -0
  107. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/gh/milestones.py +0 -0
  108. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/git_ops.py +0 -0
  109. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/logging_setup.py +0 -0
  110. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/orchestrator/__init__.py +0 -0
  111. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/orchestrator/_comments.py +0 -0
  112. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/orchestrator/_memory_writers.py +0 -0
  113. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/orchestrator/_phase5_precommit.py +0 -0
  114. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/orchestrator/cycle_prompts.py +0 -0
  115. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/prompts/__init__.py +0 -0
  116. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/prompts/hashes.py +0 -0
  117. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/prompts/prompt-cycle-specializer.md +0 -0
  118. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/prompts/prompt-optimize-requirements.md +0 -0
  119. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/prompts/prompt-phase-0-complexity.md +0 -0
  120. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/prompts/prompt-phase-1-planning.md +0 -0
  121. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/prompts/prompt-phase-2-batch-review.md +0 -0
  122. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/prompts/prompt-phase-3-implementation.md +0 -0
  123. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/prompts/prompt-phase-5-final-review.md +0 -0
  124. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/prompts/prompt-phase-7-commit.md +0 -0
  125. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/prompts/prompt-review.md +0 -0
  126. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/repo_info.py +0 -0
  127. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/requirements_structure.py +0 -0
  128. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/runner/__init__.py +0 -0
  129. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/runner/_telemetry.py +0 -0
  130. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/runner/batch.py +0 -0
  131. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/runner/mcp.py +0 -0
  132. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/runner/protocol.py +0 -0
  133. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/runner/rate_limit.py +0 -0
  134. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/runner/retry.py +0 -0
  135. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/runner/state_guard.py +0 -0
  136. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/runner/utils.py +0 -0
  137. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/state_retention.py +0 -0
  138. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/templates/__init__.py +0 -0
  139. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/templates/angular.md +0 -0
  140. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/templates/base.md +0 -0
  141. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/templates/fastapi.md +0 -0
  142. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/templates/finance.md +0 -0
  143. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/templates/fullstack.md +0 -0
  144. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/templates/nestjs.md +0 -0
  145. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/src/code_generator/templates/python-cli.md +0 -0
  146. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_bench_export.py +0 -0
  147. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_bench_fixture.py +0 -0
  148. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_bench_regression.py +0 -0
  149. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_changelog.py +0 -0
  150. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_claude_md.py +0 -0
  151. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_comments.py +0 -0
  152. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_commit_message.py +0 -0
  153. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_crash_recovery.py +0 -0
  154. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_cycle_ollama_model.py +0 -0
  155. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_delta_planning.py +0 -0
  156. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_detect.py +0 -0
  157. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_docs_no_default_max_turns.py +0 -0
  158. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_docs_ollama_model_guide.py +0 -0
  159. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_docs_ollama_pro.py +0 -0
  160. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_env.py +0 -0
  161. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_generate.py +0 -0
  162. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_generate_ollama.py +0 -0
  163. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_generate_resume.py +0 -0
  164. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_gh.py +0 -0
  165. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_gh_labels.py +0 -0
  166. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_gh_milestones.py +0 -0
  167. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_gh_repo_threading.py +0 -0
  168. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_gh_submodules.py +0 -0
  169. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_git_ops.py +0 -0
  170. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_init.py +0 -0
  171. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_logging_setup.py +0 -0
  172. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_max_turns_cli_flag.py +0 -0
  173. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_mcp.py +0 -0
  174. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_memory.py +0 -0
  175. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_no_max_turns_in_call_sites.py +0 -0
  176. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_no_max_turns_literal.py +0 -0
  177. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_non_goals_grep_guard.py +0 -0
  178. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_ollama_rate_limit.py +0 -0
  179. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_optimize.py +0 -0
  180. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_phase5_precommit.py +0 -0
  181. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_phase_mcp_regression.py +0 -0
  182. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_preflight_ollama.py +0 -0
  183. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_prompt_drift.py +0 -0
  184. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_prompt_prefix_snapshots.py +0 -0
  185. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_prompt_prefix_stability.py +0 -0
  186. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_prompts.py +0 -0
  187. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_rate_limit.py +0 -0
  188. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_repo_info.py +0 -0
  189. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_requirements_structure.py +0 -0
  190. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_retry.py +0 -0
  191. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_review.py +0 -0
  192. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_runner_protocol.py +0 -0
  193. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_runner_protocol_annotations.py +0 -0
  194. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_runner_utils.py +0 -0
  195. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_sdk_runner_shared.py +0 -0
  196. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_session_mode.py +0 -0
  197. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/tests/test_state_guard.py +0 -0
  198. {claude_code_generator-0.5.5 → claude_code_generator-0.5.7}/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.5
3
+ Version: 0.5.7
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.5"
7
+ version = "0.5.7"
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.5.5
3
+ Version: 0.5.7
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
@@ -9,6 +9,7 @@ src/claude_code_generator.egg-info/requires.txt
9
9
  src/claude_code_generator.egg-info/top_level.txt
10
10
  src/code_generator/__init__.py
11
11
  src/code_generator/agents.py
12
+ src/code_generator/checklist.py
12
13
  src/code_generator/cli.py
13
14
  src/code_generator/effort.py
14
15
  src/code_generator/env.py
@@ -41,6 +42,7 @@ src/code_generator/gh/issues.py
41
42
  src/code_generator/gh/labels.py
42
43
  src/code_generator/gh/milestones.py
43
44
  src/code_generator/orchestrator/__init__.py
45
+ src/code_generator/orchestrator/_cache_warmup.py
44
46
  src/code_generator/orchestrator/_client_lifecycle.py
45
47
  src/code_generator/orchestrator/_comments.py
46
48
  src/code_generator/orchestrator/_memory_writers.py
@@ -70,10 +72,14 @@ src/code_generator/prompts/prompt-review.md
70
72
  src/code_generator/runner/__init__.py
71
73
  src/code_generator/runner/_telemetry.py
72
74
  src/code_generator/runner/batch.py
75
+ src/code_generator/runner/cache_breakpoints.py
76
+ src/code_generator/runner/compaction_pause.py
73
77
  src/code_generator/runner/fake_runner.py
74
78
  src/code_generator/runner/mcp.py
79
+ src/code_generator/runner/memory_tool.py
75
80
  src/code_generator/runner/message_parsing.py
76
81
  src/code_generator/runner/options.py
82
+ src/code_generator/runner/phase_telemetry.py
77
83
  src/code_generator/runner/protocol.py
78
84
  src/code_generator/runner/rate_limit.py
79
85
  src/code_generator/runner/retry.py
@@ -97,12 +103,17 @@ tests/test_bench_compare.py
97
103
  tests/test_bench_export.py
98
104
  tests/test_bench_fixture.py
99
105
  tests/test_bench_regression.py
106
+ tests/test_cache_breakpoints.py
107
+ tests/test_cache_ttl_ordering.py
108
+ tests/test_cache_warmup.py
100
109
  tests/test_changelog.py
110
+ tests/test_checklist.py
101
111
  tests/test_claude_md.py
102
112
  tests/test_cli_io_logging.py
103
113
  tests/test_client_lifecycle.py
104
114
  tests/test_comments.py
105
115
  tests/test_commit_message.py
116
+ tests/test_compaction_pause_handler.py
106
117
  tests/test_crash_recovery.py
107
118
  tests/test_cycle_loop.py
108
119
  tests/test_cycle_loop_multicycle.py
@@ -117,6 +128,7 @@ tests/test_docs_ollama_model_guide.py
117
128
  tests/test_docs_ollama_pro.py
118
129
  tests/test_effective_model_routing.py
119
130
  tests/test_effort.py
131
+ tests/test_effort_routing_consistency.py
120
132
  tests/test_env.py
121
133
  tests/test_generate.py
122
134
  tests/test_generate_ollama.py
@@ -132,6 +144,7 @@ tests/test_logging_setup.py
132
144
  tests/test_max_turns_cli_flag.py
133
145
  tests/test_mcp.py
134
146
  tests/test_memory.py
147
+ tests/test_memory_tool.py
135
148
  tests/test_memory_writers.py
136
149
  tests/test_message_parsing.py
137
150
  tests/test_no_max_turns_in_call_sites.py
@@ -144,12 +157,16 @@ tests/test_options.py
144
157
  tests/test_phase0.py
145
158
  tests/test_phase1.py
146
159
  tests/test_phase2.py
160
+ tests/test_phase2_cache_regression.py
161
+ tests/test_phase2_multicycle_token_reduction.py
162
+ tests/test_phase2_token_reduction.py
147
163
  tests/test_phase3_4.py
148
164
  tests/test_phase5.py
149
165
  tests/test_phase5_precommit.py
150
166
  tests/test_phase6.py
151
167
  tests/test_phase7.py
152
168
  tests/test_phase_mcp_regression.py
169
+ tests/test_phase_telemetry.py
153
170
  tests/test_phase_token_logging.py
154
171
  tests/test_preflight.py
155
172
  tests/test_preflight_ollama.py
@@ -1,3 +1,3 @@
1
1
  """code-generator: orchestrator CLI for end-to-end project generation."""
2
2
 
3
- __version__ = "0.5.5"
3
+ __version__ = "0.5.6"
@@ -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))
@@ -0,0 +1,121 @@
1
+ """Feature completion checklist (issue #249).
2
+
3
+ Persists a JSON array of per-issue completion entries at
4
+ ``.code-generator/checklists/<requirements_hash>.json``. Phase 1 seeds the
5
+ file; Phase 6 ticks ``tests_passed``; Phase 7 ticks ``committed``;
6
+ ``feature_done`` is derived (``tests_passed AND committed``) so the model
7
+ cannot self-report completion.
8
+
9
+ All writes follow the ``tmp → os.replace(tmp, path)`` pattern so a crash
10
+ mid-write cannot corrupt the file. The module is orchestrator-only state —
11
+ the SDK runner has no Write access to ``.code-generator/checklists/``.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import json
17
+ import logging
18
+ import os
19
+ from dataclasses import asdict, dataclass
20
+ from typing import TYPE_CHECKING
21
+
22
+ if TYPE_CHECKING:
23
+ from collections.abc import Iterable
24
+ from pathlib import Path
25
+
26
+ from code_generator.state import IssueState
27
+
28
+ _logger = logging.getLogger(__name__)
29
+
30
+
31
+ @dataclass
32
+ class ChecklistEntry:
33
+ """One row in the per-feature completion checklist."""
34
+
35
+ issue_number: int
36
+ title: str
37
+ tests_passed: bool = False
38
+ committed: bool = False
39
+ feature_done: bool = False
40
+
41
+ def recompute_feature_done(self) -> None:
42
+ """Set ``feature_done`` from the two underlying flags."""
43
+ self.feature_done = self.tests_passed and self.committed
44
+
45
+
46
+ def seed(path: Path, issues: Iterable[IssueState]) -> None:
47
+ """Atomically write the checklist for *issues* to *path* (idempotent).
48
+
49
+ Re-seeding with the same data is a no-op (file content stays byte-identical).
50
+ Re-seeding with new entries appends them while preserving any existing
51
+ ticks on the entries already in the file.
52
+ """
53
+ new_entries = [_entry_from_issue(issue) for issue in issues]
54
+ existing = {e.issue_number: e for e in load(path)}
55
+ merged: list[ChecklistEntry] = []
56
+ for fresh in new_entries:
57
+ prior = existing.get(fresh.issue_number)
58
+ merged.append(prior if prior is not None else fresh)
59
+ _atomic_write_json(path, [asdict(e) for e in merged])
60
+
61
+
62
+ def load(path: Path) -> list[ChecklistEntry]:
63
+ """Return the checklist entries; ``[]`` when the file is absent."""
64
+ if not path.exists():
65
+ return []
66
+ raw = json.loads(path.read_text(encoding="utf-8"))
67
+ return [_entry_from_dict(item) for item in raw]
68
+
69
+
70
+ def tick_tests_passed(path: Path, issue_number: int) -> None:
71
+ """Set ``tests_passed=True`` on *issue_number*; recompute ``feature_done``."""
72
+ _tick(path, issue_number, "tests_passed")
73
+
74
+
75
+ def tick_committed(path: Path, issue_number: int) -> None:
76
+ """Set ``committed=True`` on *issue_number*; recompute ``feature_done``."""
77
+ _tick(path, issue_number, "committed")
78
+
79
+
80
+ def done_total(path: Path) -> tuple[int, int]:
81
+ """Return ``(feature_done_count, total_entries)`` — used by ``status``."""
82
+ entries = load(path)
83
+ return sum(1 for e in entries if e.feature_done), len(entries)
84
+
85
+
86
+ def _tick(path: Path, issue_number: int, field: str) -> None:
87
+ """Mutate *field* to True for *issue_number*; warn + no-op when missing."""
88
+ entries = load(path)
89
+ target = next((e for e in entries if e.issue_number == issue_number), None)
90
+ if target is None:
91
+ _logger.warning(
92
+ "checklist tick: issue_number=%s not found in %s — no-op.",
93
+ issue_number,
94
+ path,
95
+ )
96
+ return
97
+ setattr(target, field, True)
98
+ target.recompute_feature_done()
99
+ _atomic_write_json(path, [asdict(e) for e in entries])
100
+
101
+
102
+ def _entry_from_issue(issue: IssueState) -> ChecklistEntry:
103
+ return ChecklistEntry(issue_number=issue.number, title=issue.title)
104
+
105
+
106
+ def _entry_from_dict(item: dict) -> ChecklistEntry:
107
+ return ChecklistEntry(
108
+ issue_number=int(item["issue_number"]),
109
+ title=str(item.get("title", "")),
110
+ tests_passed=bool(item.get("tests_passed", False)),
111
+ committed=bool(item.get("committed", False)),
112
+ feature_done=bool(item.get("feature_done", False)),
113
+ )
114
+
115
+
116
+ def _atomic_write_json(path: Path, payload: list[dict]) -> None:
117
+ """``tmp.write_text(...) → os.replace(tmp, path)`` atomic write."""
118
+ path.parent.mkdir(parents=True, exist_ok=True)
119
+ tmp_path = path.with_suffix(path.suffix + ".tmp")
120
+ tmp_path.write_text(json.dumps(payload, indent=2), encoding="utf-8")
121
+ os.replace(tmp_path, path)
@@ -17,7 +17,12 @@ if TYPE_CHECKING:
17
17
 
18
18
  # Bump this + add a migration note for any breaking schema change.
19
19
  # v2 (issue #212): per-phase dicts + totals now include ``num_turns``.
20
- SCHEMA_VERSION: int = 2
20
+ # v3 (issue #248): per-phase dicts + totals add ``cache_write_5m``,
21
+ # ``cache_write_1h``, ``compaction_events``, ``clear_events``, and
22
+ # ``cache_hit_ratio`` (0.0-1.0 float). v2 readers still parse the original
23
+ # keys; v3 readers should fall back to ``.get(metric)`` so cross-version
24
+ # comparisons surface ``None`` rather than ``KeyError``.
25
+ SCHEMA_VERSION: int = 3
21
26
 
22
27
 
23
28
  def write_output(path: Path | None, payload: dict[str, Any]) -> None:
@@ -19,6 +19,7 @@ import typer
19
19
  from code_generator import memory as _memory
20
20
  from code_generator.commands._crash_recovery import check_and_log_crash_recovery
21
21
  from code_generator.commands._resume import next_start_phase, resolve_continue_multi_cycle
22
+ from code_generator.state import CycleState
22
23
 
23
24
  if TYPE_CHECKING:
24
25
  from pathlib import Path
@@ -36,14 +37,51 @@ __all__ = ["dispatch_async", "dispatch_orchestrator"]
36
37
 
37
38
  _GRAPHIFY_TIMEOUT = int(os.environ.get("CODE_GENERATOR_GRAPHIFY_TIMEOUT", "600"))
38
39
 
40
+ _GRAPHIFY_EXTRACT_CMD = ["graphify", "extract", "."]
41
+ _GRAPHIFY_UPDATE_CMD = ["graphify", "update", "."]
39
42
 
40
43
  _SETUP_HINT = (
41
- "graph-report: graphify-out/graph.json not found run `/graphify .` once "
42
- "inside Claude Code (or another graphify-compatible assistant) to seed the "
43
- "knowledge graph. After that, code-generator will keep it fresh "
44
- "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."
45
48
  )
46
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
+
47
85
 
48
86
  def _decode_stderr(stderr: bytes | str | None) -> str:
49
87
  """Decode subprocess stderr to a short single-line string for logging."""
@@ -54,42 +92,65 @@ def _decode_stderr(stderr: bytes | str | None) -> str:
54
92
  return text[:500]
55
93
 
56
94
 
57
- def _compute_and_persist_graph_report(project_dir: Path, log: logging.Logger) -> None:
58
- """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)
59
100
 
60
- Graphify's shell CLI does **not** support a "build" subcommand — the full
61
- LLM-driven build only happens through the ``/graphify`` slash-command
62
- inside an AI assistant (Claude Code, Codex, Cursor, etc.). The shell
63
- binary only supports maintenance subcommands like ``update``, ``watch``,
64
- ``query``.
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
132
+
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.
65
138
 
66
139
  Strategy:
67
140
 
68
- 1. If ``graphify-out/graph.json`` does **not** exist, the user has never
69
- seeded the graph. Log a one-time setup hint, write the fallback
70
- sentinel, and return do not call subprocess. The user must run
71
- ``/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`.
72
145
 
73
- 2. If it exists, run ``graphify update .`` (cwd=project_dir). This is
74
- AST-only re-extraction, no LLM cost, fast on cached files. It
75
- refreshes ``graph.json`` and ``GRAPH_REPORT.md``. Doc / paper /
76
- image changes are *not* picked up — for those the user must run
77
- ``/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``.
78
148
 
79
149
  Falls back to :data:`code_generator.memory.REPOMAP_FALLBACK` on any
80
150
  failure (binary missing, timeout, non-zero exit, missing report file).
81
- Stderr from graphify is captured and logged at WARN level on failure so
82
- the user can diagnose without re-running by hand.
83
-
84
- Auth: graphify inherits the parent process env. ``graphify update`` is
85
- AST-only and makes no LLM calls, so OAuth context is irrelevant on this
86
- codepath — but we still don't strip ``ANTHROPIC_*`` because the startup
87
- ``env.assert_safe_environment()`` check (CLAUDE.md non-negotiable #1)
88
- already guarantees those vars are absent on the Anthropic Max path.
89
151
 
90
152
  Args:
91
- project_dir: Project root directory (graphify writes ``graphify-out/``
92
- into this directory).
153
+ project_dir: Project root directory.
93
154
  log: Phase logger.
94
155
  """
95
156
  memories_dir = project_dir / ".code-generator" / "memories"
@@ -105,42 +166,14 @@ def _compute_and_persist_graph_report(project_dir: Path, log: logging.Logger) ->
105
166
  return
106
167
 
107
168
  if not graph_json.exists():
108
- log.info(_SETUP_HINT)
109
- _memory.write_memory_file(memories_dir, "cycle-repo-map.md", _memory.REPOMAP_FALLBACK)
110
- return
111
-
112
- cmd = ["graphify", "update", "."]
113
- try:
114
- log.info("graph-report: refreshing (cmd=%s)", " ".join(cmd))
115
- subprocess.run( # noqa: S603
116
- cmd,
117
- cwd=project_dir,
118
- timeout=_GRAPHIFY_TIMEOUT,
119
- capture_output=True,
120
- check=True,
121
- )
122
- except subprocess.CalledProcessError as exc:
123
- stderr = _decode_stderr(exc.stderr)
124
- log.warning(
125
- "graph-report: graphify exited %d; using fallback. stderr: %s",
126
- exc.returncode,
127
- stderr or "<empty>",
128
- )
129
- _memory.write_memory_file(memories_dir, "cycle-repo-map.md", _memory.REPOMAP_FALLBACK)
130
- return
131
- except subprocess.TimeoutExpired as exc:
132
- log.warning(
133
- "graph-report: graphify timed out after %ss; using fallback",
134
- exc.timeout,
135
- )
136
- _memory.write_memory_file(memories_dir, "cycle-repo-map.md", _memory.REPOMAP_FALLBACK)
137
- return
138
- except (FileNotFoundError, OSError) as exc:
139
- # `FileNotFoundError` here means the `graphify` binary disappeared
140
- # between `shutil.which` and `subprocess.run`. Extremely unlikely.
141
- log.warning("graph-report: could not invoke graphify (%s); using fallback", exc)
142
- _memory.write_memory_file(memories_dir, "cycle-repo-map.md", _memory.REPOMAP_FALLBACK)
143
- return
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):
176
+ return
144
177
 
145
178
  try:
146
179
  content = report_path.read_text(encoding="utf-8")
@@ -207,6 +240,8 @@ def _reset_failed_cycles_for_continue(
207
240
 
208
241
  reset_ids: list[int] = []
209
242
  for c in st.cycles:
243
+ if not isinstance(c, CycleState):
244
+ continue
210
245
  if c.status != "failed":
211
246
  continue
212
247
  if target_cycle is not None and c.id != target_cycle:
@@ -275,7 +310,9 @@ async def _apply_delta_plan(
275
310
  "Phase 0 failed during delta planning; aborting to preserve cycle history."
276
311
  )
277
312
 
278
- new_cycles = state_module.append_new_cycles(st, raw_cycles)
313
+ new_cycles = state_module.append_new_cycles(
314
+ st, raw_cycles, ollama_model=effective_model
315
+ )
279
316
 
280
317
  if not new_cycles:
281
318
  logger.info("Delta planning: no new cycles detected.")
@@ -330,7 +367,7 @@ async def dispatch_async(
330
367
  # Build the codebase graph once at the top of dispatch so Phase 0
331
368
  # (complexity analysis) sees a fresh report alongside Phases 1 and 2.
332
369
  graph_logger = setup_phase_logger("graph-report", project_dir)
333
- _compute_and_persist_graph_report(project_dir, graph_logger)
370
+ _compute_and_persist_graph_report(project_dir, graph_logger, ollama_model=ollama_model)
334
371
 
335
372
  # Phase 0: determine mode when not already known.
336
373
  if mode == "auto":
@@ -343,7 +380,7 @@ async def dispatch_async(
343
380
  # Delta planning: multi-cycle with completed cycles needs a merge, not a
344
381
  # fresh Phase 0 run. Single-mode delta is handled upstream (mode reset to
345
382
  # "unknown" forces Phase 0 to re-run via needs_phase0=True).
346
- completed = [c for c in st.cycles if c.status == "completed"]
383
+ completed = [c for c in st.cycles if isinstance(c, CycleState) and c.status == "completed"]
347
384
  if delta_hash is not None and st.mode == "multi-cycle" and completed and state_path is not None:
348
385
  first_new_id = await _apply_delta_plan(
349
386
  st,
@@ -504,7 +541,9 @@ def dispatch_orchestrator(
504
541
  state_module.save_state(state_path, st)
505
542
 
506
543
  elif run_mode == "delta":
507
- completed = [c for c in st.cycles if c.status == "completed"]
544
+ completed = [
545
+ c for c in st.cycles if isinstance(c, CycleState) and c.status == "completed"
546
+ ]
508
547
  if st.mode == "multi-cycle" and completed:
509
548
  # Defer hash update to async path — atomicity requires Phase 0
510
549
  # to succeed before we store the new hash.
@@ -4,10 +4,19 @@ Signature:
4
4
  code-generator bench [--fake|--real] [--session-mode fresh|shared]
5
5
  [--cycles N] [--fixture PATH] [--output FILE]
6
6
 
7
- Output schema (schema_version=1):
7
+ Output schema (schema_version=3, see _bench_io.SCHEMA_VERSION):
8
8
  {schema_version, timestamp_utc, session_mode, fixture,
9
- cycles: [{cycle_name, phases: {"0"…"7": {input, output,
10
- cache_read, cache_write, cache_hit_pct, wall_seconds}}, totals}]}
9
+ cycles: [{cycle_name,
10
+ phases: {"0"…"7": {input, output, cache_read, cache_write,
11
+ cache_hit_pct, wall_seconds, num_turns,
12
+ cache_write_5m, cache_write_1h,
13
+ compaction_events, clear_events,
14
+ cache_hit_ratio}},
15
+ totals}]}
16
+
17
+ v3 (issue #248): added the TTL split (cache_write_5m / cache_write_1h), the
18
+ SDK lifecycle counters (compaction_events / clear_events), and the 0.0-1.0
19
+ ``cache_hit_ratio`` float used by ``bench compare`` for regression flagging.
11
20
 
12
21
  Bump schema_version + add a migration note for any breaking change.
13
22
  """
@@ -49,11 +58,20 @@ _LOGGER = logging.getLogger(__name__)
49
58
  # ---------------------------------------------------------------------------
50
59
 
51
60
 
61
+ def _cache_hit_ratio(cache_read: int, cache_write: int, input_tokens: int) -> float:
62
+ """Return the 0–1 cache hit ratio (issue #248). Zero denominator → 0.0."""
63
+ denominator = cache_read + cache_write + input_tokens
64
+ if denominator <= 0:
65
+ return 0.0
66
+ return cache_read / denominator
67
+
68
+
52
69
  def _phase_metrics(result: RunResult) -> dict[str, int | float]:
53
- """Convert a RunResult into the seven-key phase-metrics dict (schema v2).
70
+ """Convert a RunResult into the v3 phase-metrics dict (issue #248).
54
71
 
55
- Includes ``num_turns`` as of issue #212 so operators can see per-phase
56
- agent-loop turn counts alongside the four token counters.
72
+ v3 adds the TTL split (``cache_write_5m`` / ``cache_write_1h``), the SDK
73
+ lifecycle counters (``compaction_events`` / ``clear_events``), and the
74
+ 0.0-1.0 ``cache_hit_ratio`` float alongside the existing v2 keys.
57
75
  """
58
76
  u = result.usage
59
77
  denominator = u.cache_read + u.cache_write + u.input
@@ -66,14 +84,20 @@ def _phase_metrics(result: RunResult) -> dict[str, int | float]:
66
84
  "cache_hit_pct": hit_pct,
67
85
  "wall_seconds": result.wall_seconds,
68
86
  "num_turns": u.num_turns,
87
+ "cache_write_5m": u.cache_write_5m,
88
+ "cache_write_1h": u.cache_write_1h,
89
+ "compaction_events": u.compaction_events,
90
+ "clear_events": u.clear_events,
91
+ "cache_hit_ratio": _cache_hit_ratio(u.cache_read, u.cache_write, u.input),
69
92
  }
70
93
 
71
94
 
72
95
  def _aggregate_totals(phases: dict[str, dict[str, int | float]]) -> dict[str, int | float]:
73
- """Sum per-phase metrics into cycle-level totals with recomputed cache_hit_pct."""
96
+ """Sum per-phase metrics into cycle-level totals with recomputed ratios."""
74
97
  total_input = total_output = total_read = total_write = 0
75
98
  total_turns = 0
76
99
  total_wall = 0.0
100
+ total_5m = total_1h = total_compaction = total_clear = 0
77
101
  for m in phases.values():
78
102
  total_input += int(m["input"])
79
103
  total_output += int(m["output"])
@@ -81,6 +105,10 @@ def _aggregate_totals(phases: dict[str, dict[str, int | float]]) -> dict[str, in
81
105
  total_write += int(m["cache_write"])
82
106
  total_turns += int(m.get("num_turns", 0))
83
107
  total_wall += float(m["wall_seconds"])
108
+ total_5m += int(m.get("cache_write_5m", 0))
109
+ total_1h += int(m.get("cache_write_1h", 0))
110
+ total_compaction += int(m.get("compaction_events", 0))
111
+ total_clear += int(m.get("clear_events", 0))
84
112
 
85
113
  denominator = total_read + total_write + total_input
86
114
  hit_pct = round(total_read / denominator * 100, 1) if denominator else 0.0
@@ -92,6 +120,11 @@ def _aggregate_totals(phases: dict[str, dict[str, int | float]]) -> dict[str, in
92
120
  "cache_hit_pct": hit_pct,
93
121
  "wall_seconds": total_wall,
94
122
  "num_turns": total_turns,
123
+ "cache_write_5m": total_5m,
124
+ "cache_write_1h": total_1h,
125
+ "compaction_events": total_compaction,
126
+ "clear_events": total_clear,
127
+ "cache_hit_ratio": _cache_hit_ratio(total_read, total_write, total_input),
95
128
  }
96
129
 
97
130