claude-code-generator 0.5.4__tar.gz → 0.5.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 (197) hide show
  1. {claude_code_generator-0.5.4/src/claude_code_generator.egg-info → claude_code_generator-0.5.6}/PKG-INFO +1 -1
  2. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/pyproject.toml +1 -1
  3. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6/src/claude_code_generator.egg-info}/PKG-INFO +1 -1
  4. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/claude_code_generator.egg-info/SOURCES.txt +17 -0
  5. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/__init__.py +1 -1
  6. claude_code_generator-0.5.6/src/code_generator/checklist.py +121 -0
  7. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/commands/_bench_io.py +6 -1
  8. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/commands/_dispatch.py +25 -9
  9. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/commands/bench.py +40 -7
  10. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/commands/bench_compare.py +52 -2
  11. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/commands/status.py +107 -0
  12. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/effort.py +26 -3
  13. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/gh/__init__.py +2 -0
  14. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/gh/issues.py +16 -0
  15. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/memory.py +1 -3
  16. claude_code_generator-0.5.6/src/code_generator/orchestrator/_cache_warmup.py +74 -0
  17. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/orchestrator/_client_lifecycle.py +104 -3
  18. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/orchestrator/cycle_loop.py +47 -6
  19. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/orchestrator/ollama_budget.py +2 -3
  20. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/orchestrator/phase0_complexity.py +31 -14
  21. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/orchestrator/phase1_plan.py +109 -35
  22. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/orchestrator/phase2_review.py +49 -15
  23. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/orchestrator/phase3_4_implement.py +82 -17
  24. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/orchestrator/phase5_closure.py +14 -5
  25. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/orchestrator/phase6_test.py +49 -6
  26. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/orchestrator/phase7_commit.py +39 -4
  27. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/preflight.py +10 -0
  28. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/prompts/prompt-phase-6-test.md +2 -1
  29. claude_code_generator-0.5.6/src/code_generator/runner/cache_breakpoints.py +356 -0
  30. claude_code_generator-0.5.6/src/code_generator/runner/compaction_pause.py +69 -0
  31. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/runner/fake_runner.py +55 -6
  32. claude_code_generator-0.5.6/src/code_generator/runner/memory_tool.py +187 -0
  33. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/runner/message_parsing.py +185 -1
  34. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/runner/options.py +240 -20
  35. claude_code_generator-0.5.6/src/code_generator/runner/phase_telemetry.py +85 -0
  36. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/runner/sdk_runner.py +118 -10
  37. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/runner/soft_reset.py +62 -4
  38. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/runner/subprocess_runner.py +5 -0
  39. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/runner/types.py +32 -10
  40. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/state.py +56 -3
  41. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_agents.py +0 -3
  42. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_bench.py +89 -3
  43. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_bench_compare.py +143 -11
  44. claude_code_generator-0.5.6/tests/test_cache_breakpoints.py +300 -0
  45. claude_code_generator-0.5.6/tests/test_cache_ttl_ordering.py +230 -0
  46. claude_code_generator-0.5.6/tests/test_cache_warmup.py +557 -0
  47. claude_code_generator-0.5.6/tests/test_checklist.py +250 -0
  48. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_cli_io_logging.py +2 -6
  49. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_client_lifecycle.py +182 -0
  50. claude_code_generator-0.5.6/tests/test_compaction_pause_handler.py +336 -0
  51. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_cycle_loop.py +1 -0
  52. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_cycle_loop_multicycle.py +1 -0
  53. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_cycle_prompts.py +71 -0
  54. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_dependencies.py +3 -3
  55. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_dispatch_graph_report.py +1 -3
  56. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_effective_model_routing.py +2 -2
  57. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_effort.py +28 -3
  58. claude_code_generator-0.5.6/tests/test_effort_routing_consistency.py +63 -0
  59. claude_code_generator-0.5.6/tests/test_memory_tool.py +272 -0
  60. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_memory_writers.py +131 -0
  61. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_message_parsing.py +235 -0
  62. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_ollama_budget.py +2 -6
  63. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_options.py +442 -30
  64. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_phase0.py +2 -2
  65. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_phase1.py +246 -0
  66. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_phase2.py +129 -3
  67. claude_code_generator-0.5.6/tests/test_phase2_cache_regression.py +189 -0
  68. claude_code_generator-0.5.6/tests/test_phase2_multicycle_token_reduction.py +251 -0
  69. claude_code_generator-0.5.6/tests/test_phase2_token_reduction.py +186 -0
  70. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_phase3_4.py +87 -8
  71. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_phase6.py +114 -2
  72. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_phase7.py +274 -0
  73. claude_code_generator-0.5.6/tests/test_phase_telemetry.py +185 -0
  74. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_phase_token_logging.py +3 -3
  75. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_preflight.py +45 -0
  76. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_runner_types.py +133 -0
  77. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_sdk_runner.py +219 -0
  78. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_state.py +238 -12
  79. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_state_retention.py +2 -1
  80. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_status.py +294 -0
  81. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_subprocess_runner.py +1 -0
  82. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_telemetry.py +3 -2
  83. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/LICENSE +0 -0
  84. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/README.md +0 -0
  85. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/setup.cfg +0 -0
  86. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/claude_code_generator.egg-info/dependency_links.txt +0 -0
  87. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/claude_code_generator.egg-info/entry_points.txt +0 -0
  88. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/claude_code_generator.egg-info/requires.txt +0 -0
  89. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/claude_code_generator.egg-info/top_level.txt +0 -0
  90. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/agents.py +0 -0
  91. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/cli.py +0 -0
  92. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/commands/__init__.py +0 -0
  93. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/commands/_crash_recovery.py +0 -0
  94. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/commands/_detect.py +0 -0
  95. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/commands/_resume.py +0 -0
  96. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/commands/_validators.py +0 -0
  97. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/commands/bench_export.py +0 -0
  98. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/commands/generate.py +0 -0
  99. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/commands/init.py +0 -0
  100. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/commands/optimize.py +0 -0
  101. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/commands/review.py +0 -0
  102. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/env.py +0 -0
  103. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/gh/core.py +0 -0
  104. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/gh/labels.py +0 -0
  105. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/gh/milestones.py +0 -0
  106. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/git_ops.py +0 -0
  107. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/logging_setup.py +0 -0
  108. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/orchestrator/__init__.py +0 -0
  109. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/orchestrator/_comments.py +0 -0
  110. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/orchestrator/_memory_writers.py +0 -0
  111. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/orchestrator/_phase5_precommit.py +0 -0
  112. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/orchestrator/cycle_prompts.py +0 -0
  113. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/prompts/__init__.py +0 -0
  114. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/prompts/hashes.py +0 -0
  115. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/prompts/prompt-cycle-specializer.md +0 -0
  116. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/prompts/prompt-optimize-requirements.md +0 -0
  117. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/prompts/prompt-phase-0-complexity.md +0 -0
  118. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/prompts/prompt-phase-1-planning.md +0 -0
  119. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/prompts/prompt-phase-2-batch-review.md +0 -0
  120. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/prompts/prompt-phase-3-implementation.md +0 -0
  121. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/prompts/prompt-phase-5-final-review.md +0 -0
  122. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/prompts/prompt-phase-7-commit.md +0 -0
  123. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/prompts/prompt-review.md +0 -0
  124. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/repo_info.py +0 -0
  125. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/requirements_structure.py +0 -0
  126. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/runner/__init__.py +0 -0
  127. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/runner/_telemetry.py +0 -0
  128. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/runner/batch.py +0 -0
  129. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/runner/mcp.py +0 -0
  130. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/runner/protocol.py +0 -0
  131. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/runner/rate_limit.py +0 -0
  132. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/runner/retry.py +0 -0
  133. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/runner/state_guard.py +0 -0
  134. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/runner/utils.py +0 -0
  135. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/state_retention.py +0 -0
  136. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/templates/__init__.py +0 -0
  137. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/templates/angular.md +0 -0
  138. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/templates/base.md +0 -0
  139. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/templates/fastapi.md +0 -0
  140. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/templates/finance.md +0 -0
  141. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/templates/fullstack.md +0 -0
  142. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/templates/nestjs.md +0 -0
  143. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/templates/python-cli.md +0 -0
  144. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_bench_export.py +0 -0
  145. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_bench_fixture.py +0 -0
  146. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_bench_regression.py +0 -0
  147. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_changelog.py +0 -0
  148. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_claude_md.py +0 -0
  149. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_comments.py +0 -0
  150. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_commit_message.py +0 -0
  151. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_crash_recovery.py +0 -0
  152. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_cycle_ollama_model.py +0 -0
  153. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_delta_planning.py +0 -0
  154. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_detect.py +0 -0
  155. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_docs_no_default_max_turns.py +0 -0
  156. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_docs_ollama_model_guide.py +0 -0
  157. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_docs_ollama_pro.py +0 -0
  158. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_env.py +0 -0
  159. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_generate.py +0 -0
  160. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_generate_ollama.py +0 -0
  161. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_generate_resume.py +0 -0
  162. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_gh.py +0 -0
  163. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_gh_labels.py +0 -0
  164. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_gh_milestones.py +0 -0
  165. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_gh_repo_threading.py +0 -0
  166. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_gh_submodules.py +0 -0
  167. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_git_ops.py +0 -0
  168. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_init.py +0 -0
  169. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_logging_setup.py +0 -0
  170. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_max_turns_cli_flag.py +0 -0
  171. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_mcp.py +0 -0
  172. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_memory.py +0 -0
  173. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_no_max_turns_in_call_sites.py +0 -0
  174. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_no_max_turns_literal.py +0 -0
  175. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_non_goals_grep_guard.py +0 -0
  176. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_ollama_rate_limit.py +0 -0
  177. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_optimize.py +0 -0
  178. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_phase5.py +0 -0
  179. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_phase5_precommit.py +0 -0
  180. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_phase_mcp_regression.py +0 -0
  181. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_preflight_ollama.py +0 -0
  182. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_prompt_drift.py +0 -0
  183. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_prompt_prefix_snapshots.py +0 -0
  184. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_prompt_prefix_stability.py +0 -0
  185. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_prompts.py +0 -0
  186. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_rate_limit.py +0 -0
  187. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_repo_info.py +0 -0
  188. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_requirements_structure.py +0 -0
  189. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_retry.py +0 -0
  190. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_review.py +0 -0
  191. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_runner_protocol.py +0 -0
  192. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_runner_protocol_annotations.py +0 -0
  193. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_runner_utils.py +0 -0
  194. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_sdk_runner_shared.py +0 -0
  195. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_session_mode.py +0 -0
  196. {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_state_guard.py +0 -0
  197. {claude_code_generator-0.5.4 → claude_code_generator-0.5.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.5.4
3
+ Version: 0.5.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.5.4"
7
+ version = "0.5.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.5.4
3
+ Version: 0.5.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
@@ -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.4"
3
+ __version__ = "0.5.6"
@@ -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
@@ -110,6 +111,9 @@ def _compute_and_persist_graph_report(project_dir: Path, log: logging.Logger) ->
110
111
  return
111
112
 
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")
113
117
  try:
114
118
  log.info("graph-report: refreshing (cmd=%s)", " ".join(cmd))
115
119
  subprocess.run( # noqa: S603
@@ -121,13 +125,21 @@ def _compute_and_persist_graph_report(project_dir: Path, log: logging.Logger) ->
121
125
  )
122
126
  except subprocess.CalledProcessError as exc:
123
127
  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
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)
142
+ return
131
143
  except subprocess.TimeoutExpired as exc:
132
144
  log.warning(
133
145
  "graph-report: graphify timed out after %ss; using fallback",
@@ -207,6 +219,8 @@ def _reset_failed_cycles_for_continue(
207
219
 
208
220
  reset_ids: list[int] = []
209
221
  for c in st.cycles:
222
+ if not isinstance(c, CycleState):
223
+ continue
210
224
  if c.status != "failed":
211
225
  continue
212
226
  if target_cycle is not None and c.id != target_cycle:
@@ -343,7 +357,7 @@ async def dispatch_async(
343
357
  # Delta planning: multi-cycle with completed cycles needs a merge, not a
344
358
  # fresh Phase 0 run. Single-mode delta is handled upstream (mode reset to
345
359
  # "unknown" forces Phase 0 to re-run via needs_phase0=True).
346
- completed = [c for c in st.cycles if c.status == "completed"]
360
+ completed = [c for c in st.cycles if isinstance(c, CycleState) and c.status == "completed"]
347
361
  if delta_hash is not None and st.mode == "multi-cycle" and completed and state_path is not None:
348
362
  first_new_id = await _apply_delta_plan(
349
363
  st,
@@ -504,7 +518,9 @@ def dispatch_orchestrator(
504
518
  state_module.save_state(state_path, st)
505
519
 
506
520
  elif run_mode == "delta":
507
- completed = [c for c in st.cycles if c.status == "completed"]
521
+ completed = [
522
+ c for c in st.cycles if isinstance(c, CycleState) and c.status == "completed"
523
+ ]
508
524
  if st.mode == "multi-cycle" and completed:
509
525
  # Defer hash update to async path — atomicity requires Phase 0
510
526
  # 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
 
@@ -28,6 +28,12 @@ METRIC_DIRECTION: dict[str, Literal["lower-better", "higher-better"]] = {
28
28
  "wall_seconds": "lower-better",
29
29
  "num_turns": "lower-better",
30
30
  "cache_hit_pct": "higher-better",
31
+ # v3 metrics (issue #248). New entries appended so v2 ordering is preserved.
32
+ "cache_write_5m": "lower-better",
33
+ "cache_write_1h": "lower-better",
34
+ "compaction_events": "lower-better",
35
+ "clear_events": "lower-better",
36
+ "cache_hit_ratio": "higher-better",
31
37
  }
32
38
 
33
39
  _METRICS_ORDER: list[str] = [
@@ -38,9 +44,19 @@ _METRICS_ORDER: list[str] = [
38
44
  "cache_hit_pct",
39
45
  "wall_seconds",
40
46
  "num_turns",
47
+ # v3 metrics — appended so legacy snapshots keep matching the v2 prefix.
48
+ "cache_write_5m",
49
+ "cache_write_1h",
50
+ "compaction_events",
51
+ "clear_events",
52
+ "cache_hit_ratio",
41
53
  ]
42
54
  _NUM_PHASES: int = 8
43
55
 
56
+ # Threshold for the cache-hit-ratio regression warning (issue #248).
57
+ # A ratio drop ≤ -0.05 (5 percentage points) triggers a WARNING line.
58
+ _CACHE_HIT_RATIO_REGRESSION_THRESHOLD: float = -0.05
59
+
44
60
 
45
61
  # ---------------------------------------------------------------------------
46
62
  # Data model
@@ -116,8 +132,12 @@ def _build_row(
116
132
  a_phase: dict | None,
117
133
  b_phase: dict | None,
118
134
  ) -> DeltaRow:
119
- a_val = float(a_phase[metric]) if a_phase is not None else None
120
- b_val = float(b_phase[metric]) if b_phase is not None else None
135
+ # ``.get`` (not bracket access) so a v2 report compared against a v3
136
+ # report surfaces ``None`` for the new metrics rather than ``KeyError``.
137
+ a_raw = a_phase.get(metric) if a_phase is not None else None
138
+ b_raw = b_phase.get(metric) if b_phase is not None else None
139
+ a_val = float(a_raw) if a_raw is not None else None
140
+ b_val = float(b_raw) if b_raw is not None else None
121
141
  delta, delta_pct = _delta_values(a_val, b_val)
122
142
  return DeltaRow(
123
143
  phase=phase,
@@ -196,6 +216,35 @@ def render_delta_table(rows: list[DeltaRow], console: Console | None = None) ->
196
216
  _console.print(_build_table(rows))
197
217
 
198
218
 
219
+ def _is_cache_hit_ratio_regression(row: DeltaRow) -> bool:
220
+ """True when *row* is a cache_hit_ratio regression ≤ -0.05 (issue #248)."""
221
+ if row.metric != "cache_hit_ratio" or row.delta is None:
222
+ return False
223
+ return row.delta <= _CACHE_HIT_RATIO_REGRESSION_THRESHOLD
224
+
225
+
226
+ def _format_regression_warning(row: DeltaRow) -> str:
227
+ """Format the WARNING line for a single cache_hit_ratio regression row."""
228
+ a_pct = (row.a or 0.0) * 100
229
+ b_pct = (row.b or 0.0) * 100
230
+ delta_pp = abs((row.delta or 0.0) * 100)
231
+ return (
232
+ f"WARNING: cache_hit_ratio regressed {delta_pp:.1f}pp on phase {row.phase} "
233
+ f"(A={a_pct:.1f}%, B={b_pct:.1f}%)"
234
+ )
235
+
236
+
237
+ def render_cache_hit_ratio_warnings(rows: list[DeltaRow], console: Console | None = None) -> None:
238
+ """Print one WARNING per cache_hit_ratio regression ≥5pp (issue #248).
239
+
240
+ Measurement-only — exit code stays 0 on regression.
241
+ """
242
+ _console = console or Console()
243
+ for row in rows:
244
+ if _is_cache_hit_ratio_regression(row):
245
+ _console.print(_format_regression_warning(row))
246
+
247
+
199
248
  def _build_table(rows: list[DeltaRow]) -> Table:
200
249
  table = Table(title="bench compare", show_header=True, header_style="bold")
201
250
  table.add_column("phase", style="dim")
@@ -247,3 +296,4 @@ def compare_command(
247
296
 
248
297
  rows = compute_deltas(a_report, b_report)
249
298
  render_delta_table(rows)
299
+ render_cache_hit_ratio_warnings(rows)
@@ -9,6 +9,7 @@ import typer
9
9
  from rich.console import Console
10
10
  from rich.table import Table
11
11
 
12
+ from code_generator import checklist as _checklist
12
13
  from code_generator import state as state_module
13
14
  from code_generator.runner.types import TokenUsage
14
15
  from code_generator.state_retention import CycleTelemetrySummary
@@ -40,9 +41,20 @@ def status_command() -> None:
40
41
  console.print(f"[bold red]⚠ Last error: {st.last_error}[/bold red]\n")
41
42
 
42
43
  _render_summary_table(console, st)
44
+ _render_checklist_summary(console, st, project_dir)
43
45
 
44
46
  if st.mode == "multi-cycle" and st.cycles:
45
47
  _render_cycles_table(console, st)
48
+ for cycle in st.cycles:
49
+ if isinstance(cycle, CycleTelemetrySummary):
50
+ continue
51
+ if not cycle.token_usage:
52
+ continue
53
+ console.print(f"\n[bold]{cycle.name}[/bold]")
54
+ _render_phase_telemetry_lines(console, cycle.token_usage)
55
+ elif st.token_usage:
56
+ console.print()
57
+ _render_phase_telemetry_lines(console, st.token_usage)
46
58
 
47
59
 
48
60
  def _pause_remaining(state: state_module.State) -> str:
@@ -145,6 +157,101 @@ def _format_pct_5h(token_usage: dict[str, TokenUsage]) -> str:
145
157
  return f"{pct:.1f}%"
146
158
 
147
159
 
160
+ # Canonical phase order + display labels for the per-phase telemetry block (#247).
161
+ _PHASE_LABELS: tuple[tuple[str, str], ...] = (
162
+ ("phase0", "0"),
163
+ ("phase1", "1"),
164
+ ("phase2", "2"),
165
+ ("phase3_4", "3/4"),
166
+ ("phase5", "5"),
167
+ ("phase6", "6"),
168
+ ("phase7", "7"),
169
+ )
170
+
171
+
172
+ def _humanize(n: int) -> str:
173
+ """Render *n* as a compact token count (``"12k"`` for ``12_345``).
174
+
175
+ Values below 1000 render verbatim. Values ≥ 1000 are divided by 1000 and
176
+ rounded with banker's rounding, matching the briefing's worked examples
177
+ (12_499 → ``"12k"``, 12_500 → ``"12k"``).
178
+ """
179
+ if n < 1000:
180
+ return str(n)
181
+ return f"{round(n / 1000)}k"
182
+
183
+
184
+ def _phase_cache_hit_pct(usage: TokenUsage) -> int:
185
+ """Per-phase cache hit percent, rounded to the nearest integer.
186
+
187
+ Returns ``0`` when the denominator is zero so callers never need to guard
188
+ against ``ZeroDivisionError``.
189
+ """
190
+ denominator = usage.cache_read + usage.cache_write + usage.input
191
+ if denominator <= 0:
192
+ return 0
193
+ return round(usage.cache_read / denominator * 100)
194
+
195
+
196
+ def _phase_has_activity(usage: TokenUsage) -> bool:
197
+ """Return True when *usage* records any non-zero counter worth printing."""
198
+ return bool(
199
+ usage.input
200
+ or usage.output
201
+ or usage.cache_read
202
+ or usage.cache_write
203
+ or usage.cache_write_5m
204
+ or usage.cache_write_1h
205
+ )
206
+
207
+
208
+ def _render_checklist_summary(
209
+ console: Console, state: state_module.State, project_dir: Path
210
+ ) -> None:
211
+ """Print ``features: <done>/<total>`` from the §13 checklist (issue #251).
212
+
213
+ Prints the dim placeholder ``features: -`` when ``state.requirements_hash``
214
+ is unset or the checklist file does not yet exist. The status command must
215
+ never crash because the checklist is missing — it is purely informational.
216
+ """
217
+ if state.requirements_hash is None:
218
+ console.print("[dim]features: -[/dim]")
219
+ return
220
+ checklist_path = (
221
+ project_dir / ".code-generator" / "checklists" / f"{state.requirements_hash}.json"
222
+ )
223
+ if not checklist_path.exists():
224
+ console.print("[dim]features: -[/dim]")
225
+ return
226
+ done, total = _checklist.done_total(checklist_path)
227
+ console.print(f"features: {done}/{total}")
228
+
229
+
230
+ def _render_phase_telemetry_lines(console: Console, token_usage: dict[str, TokenUsage]) -> None:
231
+ """Print one line per non-empty phase in canonical order (#247).
232
+
233
+ Format::
234
+
235
+ phase <label>: in=<X>k cache_read=<X>k(<P>%)
236
+ cache_write_5m=<X>k cache_write_1h=<X>k out=<X>k
237
+
238
+ Phases with all-zero counters are suppressed. Non-canonical keys are
239
+ silently skipped.
240
+ """
241
+ for key, label in _PHASE_LABELS:
242
+ usage = token_usage.get(key)
243
+ if usage is None or not _phase_has_activity(usage):
244
+ continue
245
+ console.print(
246
+ f"phase {label}: "
247
+ f"in={_humanize(usage.input)} "
248
+ f"cache_read={_humanize(usage.cache_read)}({_phase_cache_hit_pct(usage)}%) "
249
+ f"cache_write_5m={_humanize(usage.cache_write_5m)} "
250
+ f"cache_write_1h={_humanize(usage.cache_write_1h)} "
251
+ f"out={_humanize(usage.output)}"
252
+ )
253
+
254
+
148
255
  def _format_cache_hit_pct(cache_telemetry: dict[str, int | float]) -> str:
149
256
  """Format cache_hit_pct as XX.X% or '-' when telemetry is absent or all-zero.
150
257