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.
- {claude_code_generator-0.5.4/src/claude_code_generator.egg-info → claude_code_generator-0.5.6}/PKG-INFO +1 -1
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/pyproject.toml +1 -1
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6/src/claude_code_generator.egg-info}/PKG-INFO +1 -1
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/claude_code_generator.egg-info/SOURCES.txt +17 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/__init__.py +1 -1
- claude_code_generator-0.5.6/src/code_generator/checklist.py +121 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/commands/_bench_io.py +6 -1
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/commands/_dispatch.py +25 -9
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/commands/bench.py +40 -7
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/commands/bench_compare.py +52 -2
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/commands/status.py +107 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/effort.py +26 -3
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/gh/__init__.py +2 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/gh/issues.py +16 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/memory.py +1 -3
- claude_code_generator-0.5.6/src/code_generator/orchestrator/_cache_warmup.py +74 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/orchestrator/_client_lifecycle.py +104 -3
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/orchestrator/cycle_loop.py +47 -6
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/orchestrator/ollama_budget.py +2 -3
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/orchestrator/phase0_complexity.py +31 -14
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/orchestrator/phase1_plan.py +109 -35
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/orchestrator/phase2_review.py +49 -15
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/orchestrator/phase3_4_implement.py +82 -17
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/orchestrator/phase5_closure.py +14 -5
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/orchestrator/phase6_test.py +49 -6
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/orchestrator/phase7_commit.py +39 -4
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/preflight.py +10 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/prompts/prompt-phase-6-test.md +2 -1
- claude_code_generator-0.5.6/src/code_generator/runner/cache_breakpoints.py +356 -0
- claude_code_generator-0.5.6/src/code_generator/runner/compaction_pause.py +69 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/runner/fake_runner.py +55 -6
- claude_code_generator-0.5.6/src/code_generator/runner/memory_tool.py +187 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/runner/message_parsing.py +185 -1
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/runner/options.py +240 -20
- claude_code_generator-0.5.6/src/code_generator/runner/phase_telemetry.py +85 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/runner/sdk_runner.py +118 -10
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/runner/soft_reset.py +62 -4
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/runner/subprocess_runner.py +5 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/runner/types.py +32 -10
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/state.py +56 -3
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_agents.py +0 -3
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_bench.py +89 -3
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_bench_compare.py +143 -11
- claude_code_generator-0.5.6/tests/test_cache_breakpoints.py +300 -0
- claude_code_generator-0.5.6/tests/test_cache_ttl_ordering.py +230 -0
- claude_code_generator-0.5.6/tests/test_cache_warmup.py +557 -0
- claude_code_generator-0.5.6/tests/test_checklist.py +250 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_cli_io_logging.py +2 -6
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_client_lifecycle.py +182 -0
- claude_code_generator-0.5.6/tests/test_compaction_pause_handler.py +336 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_cycle_loop.py +1 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_cycle_loop_multicycle.py +1 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_cycle_prompts.py +71 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_dependencies.py +3 -3
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_dispatch_graph_report.py +1 -3
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_effective_model_routing.py +2 -2
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_effort.py +28 -3
- claude_code_generator-0.5.6/tests/test_effort_routing_consistency.py +63 -0
- claude_code_generator-0.5.6/tests/test_memory_tool.py +272 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_memory_writers.py +131 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_message_parsing.py +235 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_ollama_budget.py +2 -6
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_options.py +442 -30
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_phase0.py +2 -2
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_phase1.py +246 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_phase2.py +129 -3
- claude_code_generator-0.5.6/tests/test_phase2_cache_regression.py +189 -0
- claude_code_generator-0.5.6/tests/test_phase2_multicycle_token_reduction.py +251 -0
- claude_code_generator-0.5.6/tests/test_phase2_token_reduction.py +186 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_phase3_4.py +87 -8
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_phase6.py +114 -2
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_phase7.py +274 -0
- claude_code_generator-0.5.6/tests/test_phase_telemetry.py +185 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_phase_token_logging.py +3 -3
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_preflight.py +45 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_runner_types.py +133 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_sdk_runner.py +219 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_state.py +238 -12
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_state_retention.py +2 -1
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_status.py +294 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_subprocess_runner.py +1 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_telemetry.py +3 -2
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/LICENSE +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/README.md +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/setup.cfg +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/claude_code_generator.egg-info/dependency_links.txt +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/claude_code_generator.egg-info/entry_points.txt +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/claude_code_generator.egg-info/requires.txt +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/claude_code_generator.egg-info/top_level.txt +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/agents.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/cli.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/commands/__init__.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/commands/_crash_recovery.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/commands/_detect.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/commands/_resume.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/commands/_validators.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/commands/bench_export.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/commands/generate.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/commands/init.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/commands/optimize.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/commands/review.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/env.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/gh/core.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/gh/labels.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/gh/milestones.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/git_ops.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/logging_setup.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/orchestrator/__init__.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/orchestrator/_comments.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/orchestrator/_memory_writers.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/orchestrator/_phase5_precommit.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/orchestrator/cycle_prompts.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/prompts/__init__.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/prompts/hashes.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/prompts/prompt-cycle-specializer.md +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/prompts/prompt-optimize-requirements.md +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/prompts/prompt-phase-0-complexity.md +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/prompts/prompt-phase-1-planning.md +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/prompts/prompt-phase-2-batch-review.md +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/prompts/prompt-phase-3-implementation.md +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/prompts/prompt-phase-5-final-review.md +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/prompts/prompt-phase-7-commit.md +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/prompts/prompt-review.md +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/repo_info.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/requirements_structure.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/runner/__init__.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/runner/_telemetry.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/runner/batch.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/runner/mcp.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/runner/protocol.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/runner/rate_limit.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/runner/retry.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/runner/state_guard.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/runner/utils.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/state_retention.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/templates/__init__.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/templates/angular.md +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/templates/base.md +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/templates/fastapi.md +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/templates/finance.md +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/templates/fullstack.md +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/templates/nestjs.md +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/templates/python-cli.md +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_bench_export.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_bench_fixture.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_bench_regression.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_changelog.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_claude_md.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_comments.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_commit_message.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_crash_recovery.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_cycle_ollama_model.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_delta_planning.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_detect.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_docs_no_default_max_turns.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_docs_ollama_model_guide.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_docs_ollama_pro.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_env.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_generate.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_generate_ollama.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_generate_resume.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_gh.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_gh_labels.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_gh_milestones.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_gh_repo_threading.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_gh_submodules.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_git_ops.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_init.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_logging_setup.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_max_turns_cli_flag.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_mcp.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_memory.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_no_max_turns_in_call_sites.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_no_max_turns_literal.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_non_goals_grep_guard.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_ollama_rate_limit.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_optimize.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_phase5.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_phase5_precommit.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_phase_mcp_regression.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_preflight_ollama.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_prompt_drift.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_prompt_prefix_snapshots.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_prompt_prefix_stability.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_prompts.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_rate_limit.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_repo_info.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_requirements_structure.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_retry.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_review.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_runner_protocol.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_runner_protocol_annotations.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_runner_utils.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_sdk_runner_shared.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_session_mode.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_state_guard.py +0 -0
- {claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/tests/test_version.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "claude-code-generator"
|
|
7
|
-
version = "0.5.
|
|
7
|
+
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" }
|
|
@@ -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
|
|
@@ -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)
|
{claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/commands/_bench_io.py
RENAMED
|
@@ -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
|
-
|
|
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:
|
{claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/commands/_dispatch.py
RENAMED
|
@@ -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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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 = [
|
|
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.
|
{claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/commands/bench.py
RENAMED
|
@@ -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=
|
|
7
|
+
Output schema (schema_version=3, see _bench_io.SCHEMA_VERSION):
|
|
8
8
|
{schema_version, timestamp_utc, session_mode, fixture,
|
|
9
|
-
cycles: [{cycle_name,
|
|
10
|
-
|
|
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
|
|
70
|
+
"""Convert a RunResult into the v3 phase-metrics dict (issue #248).
|
|
54
71
|
|
|
55
|
-
|
|
56
|
-
|
|
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
|
|
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
|
-
|
|
120
|
-
|
|
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)
|
{claude_code_generator-0.5.4 → claude_code_generator-0.5.6}/src/code_generator/commands/status.py
RENAMED
|
@@ -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
|
|