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