claude-code-generator 0.5.7__tar.gz → 0.5.9__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.7/src/claude_code_generator.egg-info → claude_code_generator-0.5.9}/PKG-INFO +1 -1
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/pyproject.toml +2 -2
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9/src/claude_code_generator.egg-info}/PKG-INFO +1 -1
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/claude_code_generator.egg-info/SOURCES.txt +2 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/__init__.py +1 -1
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/commands/_dispatch.py +1 -3
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/commands/generate.py +3 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/env.py +28 -0
- claude_code_generator-0.5.9/src/code_generator/exceptions.py +26 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/orchestrator/cycle_loop.py +43 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/runner/sdk_runner.py +12 -9
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/runner/subprocess_runner.py +3 -1
- claude_code_generator-0.5.9/tests/test_ollama_hardening.py +366 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_phase1.py +0 -2
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/LICENSE +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/README.md +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/setup.cfg +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/claude_code_generator.egg-info/dependency_links.txt +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/claude_code_generator.egg-info/entry_points.txt +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/claude_code_generator.egg-info/requires.txt +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/claude_code_generator.egg-info/top_level.txt +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/agents.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/checklist.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/cli.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/commands/__init__.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/commands/_bench_io.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/commands/_crash_recovery.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/commands/_detect.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/commands/_resume.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/commands/_validators.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/commands/bench.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/commands/bench_compare.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/commands/bench_export.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/commands/init.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/commands/optimize.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/commands/review.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/commands/status.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/effort.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/gh/__init__.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/gh/core.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/gh/issues.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/gh/labels.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/gh/milestones.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/git_ops.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/logging_setup.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/memory.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/orchestrator/__init__.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/orchestrator/_cache_warmup.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/orchestrator/_client_lifecycle.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/orchestrator/_comments.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/orchestrator/_memory_writers.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/orchestrator/_phase5_precommit.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/orchestrator/cycle_prompts.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/orchestrator/ollama_budget.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/orchestrator/phase0_complexity.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/orchestrator/phase1_plan.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/orchestrator/phase2_review.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/orchestrator/phase3_4_implement.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/orchestrator/phase5_closure.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/orchestrator/phase6_test.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/orchestrator/phase7_commit.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/preflight.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/prompts/__init__.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/prompts/hashes.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/prompts/prompt-cycle-specializer.md +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/prompts/prompt-optimize-requirements.md +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/prompts/prompt-phase-0-complexity.md +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/prompts/prompt-phase-1-planning.md +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/prompts/prompt-phase-2-batch-review.md +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/prompts/prompt-phase-3-implementation.md +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/prompts/prompt-phase-5-final-review.md +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/prompts/prompt-phase-6-test.md +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/prompts/prompt-phase-7-commit.md +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/prompts/prompt-review.md +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/repo_info.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/requirements_structure.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/runner/__init__.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/runner/_telemetry.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/runner/batch.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/runner/cache_breakpoints.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/runner/compaction_pause.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/runner/fake_runner.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/runner/mcp.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/runner/memory_tool.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/runner/message_parsing.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/runner/options.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/runner/phase_telemetry.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/runner/protocol.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/runner/rate_limit.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/runner/retry.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/runner/soft_reset.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/runner/state_guard.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/runner/types.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/runner/utils.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/state.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/state_retention.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/templates/__init__.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/templates/angular.md +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/templates/base.md +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/templates/fastapi.md +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/templates/finance.md +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/templates/fullstack.md +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/templates/nestjs.md +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/templates/python-cli.md +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_agents.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_bench.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_bench_compare.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_bench_export.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_bench_fixture.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_bench_regression.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_cache_breakpoints.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_cache_ttl_ordering.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_cache_warmup.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_changelog.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_checklist.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_claude_md.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_cli_io_logging.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_client_lifecycle.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_comments.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_commit_message.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_compaction_pause_handler.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_crash_recovery.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_cycle_loop.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_cycle_loop_multicycle.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_cycle_ollama_model.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_cycle_prompts.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_delta_planning.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_dependencies.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_detect.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_dispatch_graph_report.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_docs_no_default_max_turns.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_docs_ollama_model_guide.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_docs_ollama_pro.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_effective_model_routing.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_effort.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_effort_routing_consistency.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_env.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_generate.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_generate_ollama.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_generate_resume.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_gh.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_gh_labels.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_gh_milestones.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_gh_repo_threading.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_gh_submodules.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_git_ops.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_init.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_logging_setup.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_max_turns_cli_flag.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_mcp.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_memory.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_memory_tool.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_memory_writers.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_message_parsing.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_no_max_turns_in_call_sites.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_no_max_turns_literal.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_non_goals_grep_guard.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_ollama_budget.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_ollama_rate_limit.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_optimize.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_options.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_phase0.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_phase2.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_phase2_cache_regression.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_phase2_multicycle_token_reduction.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_phase2_token_reduction.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_phase3_4.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_phase5.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_phase5_precommit.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_phase6.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_phase7.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_phase_mcp_regression.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_phase_telemetry.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_phase_token_logging.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_preflight.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_preflight_ollama.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_prompt_drift.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_prompt_prefix_snapshots.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_prompt_prefix_stability.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_prompts.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_rate_limit.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_repo_info.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_requirements_structure.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_retry.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_review.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_runner_protocol.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_runner_protocol_annotations.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_runner_types.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_runner_utils.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_sdk_runner.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_sdk_runner_shared.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_session_mode.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_state.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_state_guard.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_state_retention.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_status.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_subprocess_runner.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/tests/test_telemetry.py +0 -0
- {claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/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.9"
|
|
8
8
|
description = "Orchestrator CLI that drives Claude Code end-to-end to generate whole projects from a requirements.md file."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -96,4 +96,4 @@ asyncio_mode = "auto"
|
|
|
96
96
|
|
|
97
97
|
[tool.pyright]
|
|
98
98
|
# Test mocks intentionally use unused parameters to match the production interface signature.
|
|
99
|
-
reportUnusedParameter =
|
|
99
|
+
reportUnusedParameter = false
|
|
@@ -13,6 +13,7 @@ src/code_generator/checklist.py
|
|
|
13
13
|
src/code_generator/cli.py
|
|
14
14
|
src/code_generator/effort.py
|
|
15
15
|
src/code_generator/env.py
|
|
16
|
+
src/code_generator/exceptions.py
|
|
16
17
|
src/code_generator/git_ops.py
|
|
17
18
|
src/code_generator/logging_setup.py
|
|
18
19
|
src/code_generator/memory.py
|
|
@@ -151,6 +152,7 @@ tests/test_no_max_turns_in_call_sites.py
|
|
|
151
152
|
tests/test_no_max_turns_literal.py
|
|
152
153
|
tests/test_non_goals_grep_guard.py
|
|
153
154
|
tests/test_ollama_budget.py
|
|
155
|
+
tests/test_ollama_hardening.py
|
|
154
156
|
tests/test_ollama_rate_limit.py
|
|
155
157
|
tests/test_optimize.py
|
|
156
158
|
tests/test_options.py
|
{claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/commands/_dispatch.py
RENAMED
|
@@ -310,9 +310,7 @@ async def _apply_delta_plan(
|
|
|
310
310
|
"Phase 0 failed during delta planning; aborting to preserve cycle history."
|
|
311
311
|
)
|
|
312
312
|
|
|
313
|
-
new_cycles = state_module.append_new_cycles(
|
|
314
|
-
st, raw_cycles, ollama_model=effective_model
|
|
315
|
-
)
|
|
313
|
+
new_cycles = state_module.append_new_cycles(st, raw_cycles, ollama_model=effective_model)
|
|
316
314
|
|
|
317
315
|
if not new_cycles:
|
|
318
316
|
logger.info("Delta planning: no new cycles detected.")
|
{claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/commands/generate.py
RENAMED
|
@@ -460,6 +460,9 @@ def generate_ollama_command(
|
|
|
460
460
|
)
|
|
461
461
|
raise typer.Exit(code=2)
|
|
462
462
|
|
|
463
|
+
# #268: advisory warning when the Ollama path carries a claude-* model tag.
|
|
464
|
+
env.warn_on_claude_model_for_ollama(effective_model)
|
|
465
|
+
|
|
463
466
|
# (6) Persist the resolved model on existing cycles before dispatch so
|
|
464
467
|
# --continue resume stays consistent across sessions. New cycles
|
|
465
468
|
# created by phase 0/1 inherit the tag via the orchestrator thread
|
|
@@ -286,6 +286,34 @@ def assert_safe_environment() -> None:
|
|
|
286
286
|
strip_dangerous_env()
|
|
287
287
|
|
|
288
288
|
|
|
289
|
+
def warn_on_claude_model_for_ollama(model: str | None) -> None:
|
|
290
|
+
"""Emit a WARNING when an Ollama-localhost env carries a ``claude-*`` model tag.
|
|
291
|
+
|
|
292
|
+
The Ollama codepath (#218) routes through a local daemon, so a
|
|
293
|
+
``claude-opus-4-7`` / ``claude-sonnet-4-6`` / ``claude-haiku-*`` tag
|
|
294
|
+
almost always indicates a misconfiguration — the operator intended a
|
|
295
|
+
local model but typed an Anthropic model name. The warning is purely
|
|
296
|
+
advisory; the upstream guarantee lives in the orchestrator's
|
|
297
|
+
``_run_phases`` guard, which raises
|
|
298
|
+
:class:`~code_generator.exceptions.OllamaModelRequiredError` for the
|
|
299
|
+
same condition.
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
model: The resolved effective model tag, or ``None`` (no-op).
|
|
303
|
+
"""
|
|
304
|
+
if model is not None and model.startswith("claude-"):
|
|
305
|
+
_logger.warning(
|
|
306
|
+
"Ollama daemon routing is active (ANTHROPIC_BASE_URL=%s) "
|
|
307
|
+
"but the model tag %r starts with 'claude-'. "
|
|
308
|
+
"This likely indicates a misconfiguration — did you mean "
|
|
309
|
+
"to use an Ollama model tag (e.g. 'qwen3-coder:480b:cloud')? "
|
|
310
|
+
"Use `code-generator generate ollama --model <tag>` for "
|
|
311
|
+
"single-model Ollama runs.",
|
|
312
|
+
OLLAMA_BASE_URL,
|
|
313
|
+
model,
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
|
|
289
317
|
def assert_safe_environment_custom_model() -> None:
|
|
290
318
|
"""Strip Anthropic API creds but preserve routing for custom model endpoints.
|
|
291
319
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Shared exception classes for the code-generator pipeline.
|
|
2
|
+
|
|
3
|
+
Single-responsibility: domain exception types only — no runner errors
|
|
4
|
+
(those live in runner/types.py), no preflight errors (preflight.py).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class OllamaModelRequiredError(RuntimeError):
|
|
11
|
+
"""Raised when the Ollama codepath has no resolvable model tag.
|
|
12
|
+
|
|
13
|
+
On the Ollama single-model path (#218), every phase requires a concrete
|
|
14
|
+
model tag (e.g. ``qwen3-coder:480b:cloud``). This error fires when the
|
|
15
|
+
orchestrator detects that no model is available — either because
|
|
16
|
+
``--model`` wasn't passed on a fresh run, or because the stored
|
|
17
|
+
``CycleState.ollama_model`` is ``None`` on ``--continue``.
|
|
18
|
+
|
|
19
|
+
The ``fix_action`` string is a ready-to-paste remediation for the
|
|
20
|
+
operator; it is included in the error message automatically.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, reason: str, fix_action: str) -> None:
|
|
24
|
+
self.reason = reason
|
|
25
|
+
self.fix_action = fix_action
|
|
26
|
+
super().__init__(f"{reason} {fix_action}")
|
|
@@ -14,11 +14,14 @@ Topological iteration:
|
|
|
14
14
|
from __future__ import annotations
|
|
15
15
|
|
|
16
16
|
import logging
|
|
17
|
+
import os
|
|
17
18
|
import shutil
|
|
18
19
|
from typing import TYPE_CHECKING, Any, Literal
|
|
19
20
|
|
|
21
|
+
from code_generator import env as _env
|
|
20
22
|
from code_generator import gh, git_ops
|
|
21
23
|
from code_generator import state as _state
|
|
24
|
+
from code_generator.exceptions import OllamaModelRequiredError
|
|
22
25
|
from code_generator.logging_setup import setup_phase_logger
|
|
23
26
|
from code_generator.orchestrator import (
|
|
24
27
|
phase1_plan,
|
|
@@ -171,6 +174,12 @@ def _resolve_effective_model(state: State, cycle: CycleState | None) -> str | No
|
|
|
171
174
|
Priority: cycle.ollama_model (multi-cycle) > state.cycles[-1].ollama_model
|
|
172
175
|
(single-mode fallback when ``cycle`` is not yet bound). The function never
|
|
173
176
|
reads ``state.ollama_model`` because the field is cycle-level per #217.
|
|
177
|
+
|
|
178
|
+
Raises:
|
|
179
|
+
OllamaModelRequiredError: When the Ollama codepath is detected
|
|
180
|
+
(``ANTHROPIC_BASE_URL`` is localhost) and no model tag can be
|
|
181
|
+
resolved from state/cycle — this prevents a silent fallback
|
|
182
|
+
to an Anthropic model name.
|
|
174
183
|
"""
|
|
175
184
|
if cycle is not None and cycle.ollama_model:
|
|
176
185
|
return cycle.ollama_model
|
|
@@ -178,6 +187,21 @@ def _resolve_effective_model(state: State, cycle: CycleState | None) -> str | No
|
|
|
178
187
|
last = state.cycles[-1]
|
|
179
188
|
if isinstance(last, CycleState) and last.ollama_model:
|
|
180
189
|
return last.ollama_model
|
|
190
|
+
|
|
191
|
+
if _env._is_localhost_base_url(os.environ.get("ANTHROPIC_BASE_URL")): # noqa: SLF001
|
|
192
|
+
raise OllamaModelRequiredError(
|
|
193
|
+
reason=(
|
|
194
|
+
"Ollama daemon routing is active but no model tag is resolvable "
|
|
195
|
+
"from state.json. This happens when --continue is used on a "
|
|
196
|
+
"legacy state file that predates the ollama_model field, or "
|
|
197
|
+
"when --model was omitted on a fresh run."
|
|
198
|
+
),
|
|
199
|
+
fix_action=(
|
|
200
|
+
"Re-run with --model <tag> to supply the model explicitly, e.g.: "
|
|
201
|
+
"code-generator generate ollama --model qwen3-coder:480b:cloud"
|
|
202
|
+
),
|
|
203
|
+
)
|
|
204
|
+
|
|
181
205
|
return None
|
|
182
206
|
|
|
183
207
|
|
|
@@ -231,6 +255,25 @@ async def _run_phases(
|
|
|
231
255
|
if effective_model is None:
|
|
232
256
|
effective_model = _resolve_effective_model(state, cycle)
|
|
233
257
|
|
|
258
|
+
# #268: belt-and-suspenders — when the Ollama codepath is active
|
|
259
|
+
# (effective_model is set) but the tag starts with "claude-", the operator
|
|
260
|
+
# almost certainly intended a local model. Refuse before any phase runs
|
|
261
|
+
# so a mistyped tag never reaches the daemon (which would emit a cryptic
|
|
262
|
+
# "model may not exist").
|
|
263
|
+
if effective_model is not None and effective_model.startswith("claude-"):
|
|
264
|
+
raise OllamaModelRequiredError(
|
|
265
|
+
reason=(
|
|
266
|
+
f"Ollama single-model routing is active but the model tag "
|
|
267
|
+
f"{effective_model!r} starts with 'claude-'. "
|
|
268
|
+
"This likely indicates a misconfiguration — did you mean "
|
|
269
|
+
"to use an Ollama model tag?"
|
|
270
|
+
),
|
|
271
|
+
fix_action=(
|
|
272
|
+
"Pass a valid Ollama model tag instead, e.g.: "
|
|
273
|
+
"code-generator generate ollama --model qwen3-coder:480b:cloud"
|
|
274
|
+
),
|
|
275
|
+
)
|
|
276
|
+
|
|
234
277
|
# #220: Ollama per-cycle budget tracker. ``effective_model is not None``
|
|
235
278
|
# uniquely identifies the Ollama codepath inside the orchestrator, so we
|
|
236
279
|
# key activation off it rather than threading an extra ``provider`` flag.
|
{claude_code_generator-0.5.7 → claude_code_generator-0.5.9}/src/code_generator/runner/sdk_runner.py
RENAMED
|
@@ -26,6 +26,7 @@ from code_generator.runner.message_parsing import (
|
|
|
26
26
|
render_prompt,
|
|
27
27
|
render_system_message,
|
|
28
28
|
)
|
|
29
|
+
from code_generator.runner.state_guard import protect_state_file
|
|
29
30
|
from code_generator.runner.types import (
|
|
30
31
|
ApiUpstreamError,
|
|
31
32
|
MaxTurnsExceeded,
|
|
@@ -332,11 +333,12 @@ async def run(
|
|
|
332
333
|
)
|
|
333
334
|
|
|
334
335
|
t0 = time.monotonic()
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
336
|
+
with protect_state_file(state_path):
|
|
337
|
+
async with ClaudeSDKClient(options=options) as client:
|
|
338
|
+
await client.query(prompt)
|
|
339
|
+
text, session_id, usage, compaction_events, clear_events = await _drain_messages(
|
|
340
|
+
client, logger, state_path
|
|
341
|
+
)
|
|
340
342
|
wall_seconds = time.monotonic() - t0
|
|
341
343
|
|
|
342
344
|
logger.info("SDK session complete.")
|
|
@@ -412,10 +414,11 @@ async def run_with_shared_client(
|
|
|
412
414
|
prompt = pause_handler.consume(prompt, issue_number=consume_issue)
|
|
413
415
|
|
|
414
416
|
t0 = time.monotonic()
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
417
|
+
with protect_state_file(state_path):
|
|
418
|
+
await client.query(prompt)
|
|
419
|
+
text, session_id, usage, compaction_events, clear_events = await _drain_messages(
|
|
420
|
+
client, logger, state_path, pause_handler=pause_handler
|
|
421
|
+
)
|
|
419
422
|
wall_seconds = time.monotonic() - t0
|
|
420
423
|
|
|
421
424
|
logger.info("SDK shared-client session complete.")
|
|
@@ -20,6 +20,7 @@ from typing import TYPE_CHECKING, Any
|
|
|
20
20
|
|
|
21
21
|
from code_generator import env as _env
|
|
22
22
|
from code_generator import state as _state
|
|
23
|
+
from code_generator.runner.state_guard import protect_state_file
|
|
23
24
|
|
|
24
25
|
# Import shared types from the canonical module (DIP: depend on abstraction).
|
|
25
26
|
# Re-exported here so callers that previously imported from subprocess_runner
|
|
@@ -350,7 +351,8 @@ async def run(
|
|
|
350
351
|
raise ApiUpstreamError(f"claude CLI exited with code {proc.returncode}")
|
|
351
352
|
|
|
352
353
|
t0 = time.monotonic()
|
|
353
|
-
|
|
354
|
+
with protect_state_file(state_path):
|
|
355
|
+
await asyncio.to_thread(_run_subprocess)
|
|
354
356
|
wall_seconds = time.monotonic() - t0
|
|
355
357
|
|
|
356
358
|
return RunResult(
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
"""Tests for Ollama single-model hardening (issue #268).
|
|
2
|
+
|
|
3
|
+
AC coverage:
|
|
4
|
+
- OllamaModelRequiredError exists with fix-action message
|
|
5
|
+
- _resolve_effective_model raises when on Ollama path and no model resolvable
|
|
6
|
+
- _run_phases raises when effective_model starts with claude-
|
|
7
|
+
- env warns when model tag starts with claude-* on localhost routing
|
|
8
|
+
- Legacy state.json without ollama_model raises clear error on --continue
|
|
9
|
+
- Anthropic Max path unchanged (effective_model=None)
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import logging
|
|
15
|
+
from typing import TYPE_CHECKING
|
|
16
|
+
from unittest.mock import AsyncMock, MagicMock, patch
|
|
17
|
+
|
|
18
|
+
import pytest
|
|
19
|
+
|
|
20
|
+
from code_generator import state as _state
|
|
21
|
+
from code_generator.exceptions import OllamaModelRequiredError
|
|
22
|
+
from code_generator.runner.types import RunResult, TokenUsage
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
_OLLAMA_TAG = "qwen3-coder:480b:cloud"
|
|
29
|
+
_CLAUDE_TAG = "claude-opus-4-7"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
# Helpers
|
|
34
|
+
# ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _make_runner() -> MagicMock:
|
|
38
|
+
runner = MagicMock()
|
|
39
|
+
runner.run = AsyncMock(
|
|
40
|
+
return_value=RunResult(
|
|
41
|
+
text='{"mode":"single","cycles":[]}', session_id=None, usage=TokenUsage()
|
|
42
|
+
),
|
|
43
|
+
)
|
|
44
|
+
return runner
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _make_state(tmp_path: Path) -> tuple[_state.State, Path]:
|
|
48
|
+
st = _state.load_state(tmp_path / "missing.json")
|
|
49
|
+
cg = tmp_path / ".code-generator"
|
|
50
|
+
cg.mkdir(parents=True, exist_ok=True)
|
|
51
|
+
(cg / "memories").mkdir(exist_ok=True)
|
|
52
|
+
state_path = cg / "state.json"
|
|
53
|
+
_state.save_state(state_path, st)
|
|
54
|
+
return st, state_path
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _make_cycle(ollama_model: str | None) -> _state.CycleState:
|
|
58
|
+
return _state.CycleState(
|
|
59
|
+
id=1,
|
|
60
|
+
name="c1",
|
|
61
|
+
milestone_number=1,
|
|
62
|
+
milestone_title="c1",
|
|
63
|
+
status="open",
|
|
64
|
+
phase=None,
|
|
65
|
+
issues=[],
|
|
66
|
+
commit_sha=None,
|
|
67
|
+
ollama_model=ollama_model,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _make_cycle_no_ollama_field() -> _state.CycleState:
|
|
72
|
+
"""Create a CycleState that has no ollama_model attribute set."""
|
|
73
|
+
cycle = _state.CycleState(
|
|
74
|
+
id=1,
|
|
75
|
+
name="c1",
|
|
76
|
+
milestone_number=1,
|
|
77
|
+
milestone_title="c1",
|
|
78
|
+
status="open",
|
|
79
|
+
phase=None,
|
|
80
|
+
issues=[],
|
|
81
|
+
commit_sha=None,
|
|
82
|
+
)
|
|
83
|
+
# Simulate legacy state.json: remove the ollama_model attr
|
|
84
|
+
object.__setattr__(cycle, "ollama_model", None)
|
|
85
|
+
return cycle
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# ---------------------------------------------------------------------------
|
|
89
|
+
# OllamaModelRequiredError
|
|
90
|
+
# ---------------------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class TestOllamaModelRequiredError:
|
|
94
|
+
def test_exception_includes_reason_and_fix_action(self) -> None:
|
|
95
|
+
err = OllamaModelRequiredError("no model found", "pass --model <tag>")
|
|
96
|
+
assert "no model found" in str(err)
|
|
97
|
+
assert "pass --model <tag>" in str(err)
|
|
98
|
+
|
|
99
|
+
def test_exception_is_runtime_error(self) -> None:
|
|
100
|
+
err = OllamaModelRequiredError("x", "y")
|
|
101
|
+
assert isinstance(err, RuntimeError)
|
|
102
|
+
|
|
103
|
+
def test_reason_and_fix_action_accessible_as_attrs(self) -> None:
|
|
104
|
+
err = OllamaModelRequiredError("reason text", "fix text")
|
|
105
|
+
assert err.reason == "reason text"
|
|
106
|
+
assert err.fix_action == "fix text"
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
# ---------------------------------------------------------------------------
|
|
110
|
+
# _resolve_effective_model — raises when on Ollama path, no model resolvable
|
|
111
|
+
# ---------------------------------------------------------------------------
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class TestResolveEffectiveModelRaisesOnMissingModel:
|
|
115
|
+
def test_raises_when_cycle_has_ollama_model_none_and_state_has_none(
|
|
116
|
+
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
117
|
+
) -> None:
|
|
118
|
+
from code_generator.orchestrator import cycle_loop
|
|
119
|
+
|
|
120
|
+
monkeypatch.setenv("ANTHROPIC_BASE_URL", "http://localhost:11434")
|
|
121
|
+
state, _ = _make_state(tmp_path)
|
|
122
|
+
cycle = _make_cycle(None)
|
|
123
|
+
state.cycles = [cycle]
|
|
124
|
+
|
|
125
|
+
with pytest.raises(OllamaModelRequiredError, match="no model tag"):
|
|
126
|
+
cycle_loop._resolve_effective_model(state, cycle)
|
|
127
|
+
|
|
128
|
+
def test_raises_when_cycle_is_none_and_state_cycles_have_none_model(
|
|
129
|
+
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
130
|
+
) -> None:
|
|
131
|
+
from code_generator.orchestrator import cycle_loop
|
|
132
|
+
|
|
133
|
+
monkeypatch.setenv("ANTHROPIC_BASE_URL", "http://localhost:11434")
|
|
134
|
+
state, _ = _make_state(tmp_path)
|
|
135
|
+
cycle = _make_cycle(None)
|
|
136
|
+
state.cycles = [cycle]
|
|
137
|
+
|
|
138
|
+
with pytest.raises(OllamaModelRequiredError, match="no model tag"):
|
|
139
|
+
cycle_loop._resolve_effective_model(state, None)
|
|
140
|
+
|
|
141
|
+
def test_returns_model_when_cycle_has_valid_tag(self, tmp_path: Path) -> None:
|
|
142
|
+
from code_generator.orchestrator import cycle_loop
|
|
143
|
+
|
|
144
|
+
state, _ = _make_state(tmp_path)
|
|
145
|
+
cycle = _make_cycle(_OLLAMA_TAG)
|
|
146
|
+
state.cycles = [cycle]
|
|
147
|
+
|
|
148
|
+
result = cycle_loop._resolve_effective_model(state, cycle)
|
|
149
|
+
assert result == _OLLAMA_TAG
|
|
150
|
+
|
|
151
|
+
def test_returns_none_on_anthropic_max_path(
|
|
152
|
+
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
153
|
+
) -> None:
|
|
154
|
+
"""When no cycle has ollama_model set (None), returns None — Anthropic Max."""
|
|
155
|
+
from code_generator.orchestrator import cycle_loop
|
|
156
|
+
|
|
157
|
+
monkeypatch.delenv("ANTHROPIC_BASE_URL", raising=False)
|
|
158
|
+
state, _ = _make_state(tmp_path)
|
|
159
|
+
# No cycles in state → anthropic max path
|
|
160
|
+
result = cycle_loop._resolve_effective_model(state, None)
|
|
161
|
+
assert result is None
|
|
162
|
+
|
|
163
|
+
def test_returns_none_when_cycle_none_and_no_cycles_in_state(
|
|
164
|
+
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
165
|
+
) -> None:
|
|
166
|
+
from code_generator.orchestrator import cycle_loop
|
|
167
|
+
|
|
168
|
+
monkeypatch.delenv("ANTHROPIC_BASE_URL", raising=False)
|
|
169
|
+
state, _ = _make_state(tmp_path)
|
|
170
|
+
state.cycles = []
|
|
171
|
+
|
|
172
|
+
result = cycle_loop._resolve_effective_model(state, None)
|
|
173
|
+
assert result is None
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
# ---------------------------------------------------------------------------
|
|
177
|
+
# _run_phases — raises when effective_model starts with "claude-"
|
|
178
|
+
# ---------------------------------------------------------------------------
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class TestRunPhasesRejectsClaudeModelOnOllamaPath:
|
|
182
|
+
@pytest.mark.asyncio
|
|
183
|
+
async def test_raises_when_effective_model_starts_with_claude_prefix(
|
|
184
|
+
self, tmp_path: Path
|
|
185
|
+
) -> None:
|
|
186
|
+
from code_generator.orchestrator import cycle_loop
|
|
187
|
+
|
|
188
|
+
state, _ = _make_state(tmp_path)
|
|
189
|
+
cycle = _make_cycle(_CLAUDE_TAG)
|
|
190
|
+
state.cycles = [cycle]
|
|
191
|
+
|
|
192
|
+
with pytest.raises(OllamaModelRequiredError, match="claude-"):
|
|
193
|
+
await cycle_loop._run_phases(
|
|
194
|
+
state=state,
|
|
195
|
+
cycle=cycle,
|
|
196
|
+
project_dir=tmp_path,
|
|
197
|
+
runner_module=_make_runner(),
|
|
198
|
+
log_prefix="",
|
|
199
|
+
start_phase=1,
|
|
200
|
+
effective_model=_CLAUDE_TAG,
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
@pytest.mark.asyncio
|
|
204
|
+
async def test_does_not_raise_when_effective_model_is_none(
|
|
205
|
+
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
206
|
+
) -> None:
|
|
207
|
+
from code_generator.orchestrator import cycle_loop
|
|
208
|
+
|
|
209
|
+
monkeypatch.delenv("ANTHROPIC_BASE_URL", raising=False)
|
|
210
|
+
state, _ = _make_state(tmp_path)
|
|
211
|
+
cycle = _make_cycle(None)
|
|
212
|
+
state.cycles = [cycle]
|
|
213
|
+
|
|
214
|
+
with (
|
|
215
|
+
patch.object(cycle_loop.phase1_plan, "run", new=AsyncMock()),
|
|
216
|
+
patch.object(cycle_loop.phase2_review, "run", new=AsyncMock()),
|
|
217
|
+
patch.object(cycle_loop.phase3_4_implement, "run", new=AsyncMock()),
|
|
218
|
+
patch.object(
|
|
219
|
+
cycle_loop.phase5_closure,
|
|
220
|
+
"run",
|
|
221
|
+
new=AsyncMock(return_value=MagicMock(new_issues=[])),
|
|
222
|
+
),
|
|
223
|
+
patch.object(cycle_loop.phase6_test, "run", new=AsyncMock()),
|
|
224
|
+
patch.object(cycle_loop.phase7_commit, "run", new=AsyncMock()),
|
|
225
|
+
patch.object(cycle_loop, "reset_cycle_summary", lambda *a, **k: None),
|
|
226
|
+
patch.object(cycle_loop, "write_project_plan", lambda *a, **k: None),
|
|
227
|
+
patch.object(cycle_loop, "append_cycle_summaries", lambda *a, **k: None),
|
|
228
|
+
patch.object(cycle_loop, "populate_cycle_start_fields", lambda *a, **k: None),
|
|
229
|
+
patch.object(_state, "get_issues", lambda *a, **k: []),
|
|
230
|
+
patch.object(cycle_loop, "_capture_base_commit", lambda *a, **k: None),
|
|
231
|
+
):
|
|
232
|
+
await cycle_loop._run_phases(
|
|
233
|
+
state=state,
|
|
234
|
+
cycle=cycle,
|
|
235
|
+
project_dir=tmp_path,
|
|
236
|
+
runner_module=_make_runner(),
|
|
237
|
+
log_prefix="",
|
|
238
|
+
start_phase=1,
|
|
239
|
+
effective_model=None,
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
@pytest.mark.asyncio
|
|
243
|
+
async def test_does_not_raise_when_effective_model_is_valid_ollama_tag(
|
|
244
|
+
self, tmp_path: Path
|
|
245
|
+
) -> None:
|
|
246
|
+
from code_generator.orchestrator import cycle_loop
|
|
247
|
+
|
|
248
|
+
state, _ = _make_state(tmp_path)
|
|
249
|
+
cycle = _make_cycle(_OLLAMA_TAG)
|
|
250
|
+
state.cycles = [cycle]
|
|
251
|
+
|
|
252
|
+
with (
|
|
253
|
+
patch.object(cycle_loop.phase1_plan, "run", new=AsyncMock()),
|
|
254
|
+
patch.object(cycle_loop.phase2_review, "run", new=AsyncMock()),
|
|
255
|
+
patch.object(cycle_loop.phase3_4_implement, "run", new=AsyncMock()),
|
|
256
|
+
patch.object(
|
|
257
|
+
cycle_loop.phase5_closure,
|
|
258
|
+
"run",
|
|
259
|
+
new=AsyncMock(return_value=MagicMock(new_issues=[])),
|
|
260
|
+
),
|
|
261
|
+
patch.object(cycle_loop.phase6_test, "run", new=AsyncMock()),
|
|
262
|
+
patch.object(cycle_loop.phase7_commit, "run", new=AsyncMock()),
|
|
263
|
+
patch.object(cycle_loop, "reset_cycle_summary", lambda *a, **k: None),
|
|
264
|
+
patch.object(cycle_loop, "write_project_plan", lambda *a, **k: None),
|
|
265
|
+
patch.object(cycle_loop, "append_cycle_summaries", lambda *a, **k: None),
|
|
266
|
+
patch.object(cycle_loop, "populate_cycle_start_fields", lambda *a, **k: None),
|
|
267
|
+
patch.object(_state, "get_issues", lambda *a, **k: []),
|
|
268
|
+
patch.object(cycle_loop, "_capture_base_commit", lambda *a, **k: None),
|
|
269
|
+
):
|
|
270
|
+
await cycle_loop._run_phases(
|
|
271
|
+
state=state,
|
|
272
|
+
cycle=cycle,
|
|
273
|
+
project_dir=tmp_path,
|
|
274
|
+
runner_module=_make_runner(),
|
|
275
|
+
log_prefix="",
|
|
276
|
+
start_phase=1,
|
|
277
|
+
effective_model=_OLLAMA_TAG,
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
# ---------------------------------------------------------------------------
|
|
282
|
+
# env.warn_on_claude_model_for_ollama — WARNING for claude-* + localhost
|
|
283
|
+
# ---------------------------------------------------------------------------
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
class TestEnvWarnsOnClaudeModelWithLocalhost:
|
|
287
|
+
def test_warns_when_model_starts_with_claude_prefix(self, caplog) -> None:
|
|
288
|
+
from code_generator import env
|
|
289
|
+
|
|
290
|
+
with caplog.at_level(logging.WARNING, logger="code_generator.env"):
|
|
291
|
+
env.warn_on_claude_model_for_ollama("claude-opus-4-7")
|
|
292
|
+
|
|
293
|
+
assert any("claude-" in record.message for record in caplog.records), (
|
|
294
|
+
f"expected WARNING with 'claude-', got: {[r.message for r in caplog.records]}"
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
def test_does_not_warn_when_model_is_valid_ollama_tag(self, caplog) -> None:
|
|
298
|
+
from code_generator import env
|
|
299
|
+
|
|
300
|
+
with caplog.at_level(logging.WARNING, logger="code_generator.env"):
|
|
301
|
+
env.warn_on_claude_model_for_ollama("qwen3-coder:480b:cloud")
|
|
302
|
+
|
|
303
|
+
assert not caplog.records, (
|
|
304
|
+
f"expected no WARNING, got: {[r.message for r in caplog.records]}"
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
def test_does_not_raise_or_warn_when_model_is_none(self, caplog) -> None:
|
|
308
|
+
from code_generator import env
|
|
309
|
+
|
|
310
|
+
with caplog.at_level(logging.WARNING, logger="code_generator.env"):
|
|
311
|
+
env.warn_on_claude_model_for_ollama(None)
|
|
312
|
+
|
|
313
|
+
assert not caplog.records
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
# ---------------------------------------------------------------------------
|
|
317
|
+
# Legacy state.json without ollama_model → clear error on --continue
|
|
318
|
+
# ---------------------------------------------------------------------------
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
class TestLegacyStateWithoutOllamaModel:
|
|
322
|
+
@pytest.mark.asyncio
|
|
323
|
+
async def test_resolve_effective_model_raises_for_legacy_cycle_no_model(
|
|
324
|
+
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
325
|
+
) -> None:
|
|
326
|
+
from code_generator.orchestrator import cycle_loop
|
|
327
|
+
|
|
328
|
+
monkeypatch.setenv("ANTHROPIC_BASE_URL", "http://localhost:11434")
|
|
329
|
+
state, _ = _make_state(tmp_path)
|
|
330
|
+
cycle = _make_cycle_no_ollama_field()
|
|
331
|
+
state.cycles = [cycle]
|
|
332
|
+
|
|
333
|
+
with pytest.raises(OllamaModelRequiredError, match="--continue"):
|
|
334
|
+
cycle_loop._resolve_effective_model(state, cycle)
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
# ---------------------------------------------------------------------------
|
|
338
|
+
# Anthropic Max path regression guard
|
|
339
|
+
# ---------------------------------------------------------------------------
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
class TestAnthropicMaxPathUnchanged:
|
|
343
|
+
def test_resolve_effective_model_returns_none_when_no_ollama_model_anywhere(
|
|
344
|
+
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
345
|
+
) -> None:
|
|
346
|
+
from code_generator.orchestrator import cycle_loop
|
|
347
|
+
|
|
348
|
+
monkeypatch.delenv("ANTHROPIC_BASE_URL", raising=False)
|
|
349
|
+
state, _ = _make_state(tmp_path)
|
|
350
|
+
cycle = _make_cycle(None)
|
|
351
|
+
state.cycles = [cycle]
|
|
352
|
+
|
|
353
|
+
result = cycle_loop._resolve_effective_model(state, cycle)
|
|
354
|
+
assert result is None, f"Anthropic Max path broken: {result=}, expected None"
|
|
355
|
+
|
|
356
|
+
def test_resolve_effective_model_returns_none_empty_cycles_single_mode(
|
|
357
|
+
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
358
|
+
) -> None:
|
|
359
|
+
from code_generator.orchestrator import cycle_loop
|
|
360
|
+
|
|
361
|
+
monkeypatch.delenv("ANTHROPIC_BASE_URL", raising=False)
|
|
362
|
+
state, _ = _make_state(tmp_path)
|
|
363
|
+
state.cycles = []
|
|
364
|
+
|
|
365
|
+
result = cycle_loop._resolve_effective_model(state, None)
|
|
366
|
+
assert result is None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|