claude-code-generator 0.5.2__tar.gz → 0.5.4__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.2/src/claude_code_generator.egg-info → claude_code_generator-0.5.4}/PKG-INFO +15 -4
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/README.md +14 -3
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/pyproject.toml +1 -1
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4/src/claude_code_generator.egg-info}/PKG-INFO +15 -4
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/claude_code_generator.egg-info/SOURCES.txt +1 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/__init__.py +1 -1
- claude_code_generator-0.5.4/src/code_generator/cli.py +93 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/commands/_dispatch.py +36 -50
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/logging_setup.py +21 -6
- claude_code_generator-0.5.4/tests/test_cli_io_logging.py +73 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_dispatch_graph_report.py +53 -51
- claude_code_generator-0.5.2/src/code_generator/cli.py +0 -53
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/LICENSE +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/setup.cfg +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/claude_code_generator.egg-info/dependency_links.txt +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/claude_code_generator.egg-info/entry_points.txt +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/claude_code_generator.egg-info/requires.txt +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/claude_code_generator.egg-info/top_level.txt +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/agents.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/commands/__init__.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/commands/_bench_io.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/commands/_crash_recovery.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/commands/_detect.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/commands/_resume.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/commands/_validators.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/commands/bench.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/commands/bench_compare.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/commands/bench_export.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/commands/generate.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/commands/init.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/commands/optimize.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/commands/review.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/commands/status.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/effort.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/env.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/gh/__init__.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/gh/core.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/gh/issues.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/gh/labels.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/gh/milestones.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/git_ops.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/memory.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/orchestrator/__init__.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/orchestrator/_client_lifecycle.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/orchestrator/_comments.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/orchestrator/_memory_writers.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/orchestrator/_phase5_precommit.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/orchestrator/cycle_loop.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/orchestrator/cycle_prompts.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/orchestrator/ollama_budget.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/orchestrator/phase0_complexity.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/orchestrator/phase1_plan.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/orchestrator/phase2_review.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/orchestrator/phase3_4_implement.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/orchestrator/phase5_closure.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/orchestrator/phase6_test.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/orchestrator/phase7_commit.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/preflight.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/prompts/__init__.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/prompts/hashes.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/prompts/prompt-cycle-specializer.md +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/prompts/prompt-optimize-requirements.md +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/prompts/prompt-phase-0-complexity.md +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/prompts/prompt-phase-1-planning.md +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/prompts/prompt-phase-2-batch-review.md +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/prompts/prompt-phase-3-implementation.md +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/prompts/prompt-phase-5-final-review.md +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/prompts/prompt-phase-6-test.md +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/prompts/prompt-phase-7-commit.md +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/prompts/prompt-review.md +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/repo_info.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/requirements_structure.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/runner/__init__.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/runner/_telemetry.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/runner/batch.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/runner/fake_runner.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/runner/mcp.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/runner/message_parsing.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/runner/options.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/runner/protocol.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/runner/rate_limit.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/runner/retry.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/runner/sdk_runner.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/runner/soft_reset.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/runner/state_guard.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/runner/subprocess_runner.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/runner/types.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/runner/utils.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/state.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/state_retention.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/templates/__init__.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/templates/angular.md +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/templates/base.md +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/templates/fastapi.md +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/templates/finance.md +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/templates/fullstack.md +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/templates/nestjs.md +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/templates/python-cli.md +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_agents.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_bench.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_bench_compare.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_bench_export.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_bench_fixture.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_bench_regression.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_changelog.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_claude_md.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_client_lifecycle.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_comments.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_commit_message.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_crash_recovery.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_cycle_loop.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_cycle_loop_multicycle.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_cycle_ollama_model.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_cycle_prompts.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_delta_planning.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_dependencies.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_detect.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_docs_no_default_max_turns.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_docs_ollama_model_guide.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_docs_ollama_pro.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_effective_model_routing.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_effort.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_env.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_generate.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_generate_ollama.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_generate_resume.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_gh.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_gh_labels.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_gh_milestones.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_gh_repo_threading.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_gh_submodules.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_git_ops.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_init.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_logging_setup.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_max_turns_cli_flag.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_mcp.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_memory.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_memory_writers.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_message_parsing.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_no_max_turns_in_call_sites.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_no_max_turns_literal.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_non_goals_grep_guard.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_ollama_budget.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_ollama_rate_limit.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_optimize.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_options.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_phase0.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_phase1.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_phase2.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_phase3_4.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_phase5.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_phase5_precommit.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_phase6.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_phase7.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_phase_mcp_regression.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_phase_token_logging.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_preflight.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_preflight_ollama.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_prompt_drift.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_prompt_prefix_snapshots.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_prompt_prefix_stability.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_prompts.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_rate_limit.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_repo_info.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_requirements_structure.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_retry.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_review.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_runner_protocol.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_runner_protocol_annotations.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_runner_types.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_runner_utils.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_sdk_runner.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_sdk_runner_shared.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_session_mode.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_state.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_state_guard.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_state_retention.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_status.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_subprocess_runner.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_telemetry.py +0 -0
- {claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/tests/test_version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: claude-code-generator
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.4
|
|
4
4
|
Summary: Orchestrator CLI that drives Claude Code end-to-end to generate whole projects from a requirements.md file.
|
|
5
5
|
Author: Silvio Baratto
|
|
6
6
|
License: MIT
|
|
@@ -55,17 +55,28 @@ cd code-generator && pip install -e ".[dev]"
|
|
|
55
55
|
|
|
56
56
|
### Optional: codebase graph (graphify)
|
|
57
57
|
|
|
58
|
-
Phases 0, 1, and 2 inject a knowledge-graph report from [graphify](https://github.com/safishamsi/graphify) for richer code orientation. When the
|
|
58
|
+
Phases 0, 1, and 2 inject a knowledge-graph report from [graphify](https://github.com/safishamsi/graphify) for richer code orientation. When the graph is missing, the orchestrator falls back to a sentinel string and continues — nothing breaks.
|
|
59
|
+
|
|
60
|
+
**Install the CLI** (required to use graphify at all):
|
|
59
61
|
|
|
60
62
|
```bash
|
|
61
63
|
pipx install graphifyy # CLI binary is `graphify`
|
|
62
64
|
```
|
|
63
65
|
|
|
66
|
+
**Seed the graph (one-time, per project)**: graphify's full LLM-driven build runs through the `/graphify` slash-command inside an AI assistant — *not* through the shell binary. From inside Claude Code (or any other graphify-supported assistant) run:
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
/graphify .
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
This produces `graphify-out/graph.json` and `graphify-out/GRAPH_REPORT.md`.
|
|
73
|
+
|
|
74
|
+
**Subsequent code-generator runs** will automatically refresh the graph with `graphify update .` (AST-only, no LLM cost). Doc / paper / image changes still need a manual `/graphify .` re-run inside your assistant.
|
|
75
|
+
|
|
64
76
|
Add to your project's `.gitignore`:
|
|
65
77
|
|
|
66
78
|
```
|
|
67
|
-
graphify-out/
|
|
68
|
-
graphify-out/cost.json
|
|
79
|
+
graphify-out/
|
|
69
80
|
```
|
|
70
81
|
|
|
71
82
|
## Authentication
|
|
@@ -17,17 +17,28 @@ cd code-generator && pip install -e ".[dev]"
|
|
|
17
17
|
|
|
18
18
|
### Optional: codebase graph (graphify)
|
|
19
19
|
|
|
20
|
-
Phases 0, 1, and 2 inject a knowledge-graph report from [graphify](https://github.com/safishamsi/graphify) for richer code orientation. When the
|
|
20
|
+
Phases 0, 1, and 2 inject a knowledge-graph report from [graphify](https://github.com/safishamsi/graphify) for richer code orientation. When the graph is missing, the orchestrator falls back to a sentinel string and continues — nothing breaks.
|
|
21
|
+
|
|
22
|
+
**Install the CLI** (required to use graphify at all):
|
|
21
23
|
|
|
22
24
|
```bash
|
|
23
25
|
pipx install graphifyy # CLI binary is `graphify`
|
|
24
26
|
```
|
|
25
27
|
|
|
28
|
+
**Seed the graph (one-time, per project)**: graphify's full LLM-driven build runs through the `/graphify` slash-command inside an AI assistant — *not* through the shell binary. From inside Claude Code (or any other graphify-supported assistant) run:
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
/graphify .
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
This produces `graphify-out/graph.json` and `graphify-out/GRAPH_REPORT.md`.
|
|
35
|
+
|
|
36
|
+
**Subsequent code-generator runs** will automatically refresh the graph with `graphify update .` (AST-only, no LLM cost). Doc / paper / image changes still need a manual `/graphify .` re-run inside your assistant.
|
|
37
|
+
|
|
26
38
|
Add to your project's `.gitignore`:
|
|
27
39
|
|
|
28
40
|
```
|
|
29
|
-
graphify-out/
|
|
30
|
-
graphify-out/cost.json
|
|
41
|
+
graphify-out/
|
|
31
42
|
```
|
|
32
43
|
|
|
33
44
|
## Authentication
|
|
@@ -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.4"
|
|
8
8
|
description = "Orchestrator CLI that drives Claude Code end-to-end to generate whole projects from a requirements.md file."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: claude-code-generator
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.4
|
|
4
4
|
Summary: Orchestrator CLI that drives Claude Code end-to-end to generate whole projects from a requirements.md file.
|
|
5
5
|
Author: Silvio Baratto
|
|
6
6
|
License: MIT
|
|
@@ -55,17 +55,28 @@ cd code-generator && pip install -e ".[dev]"
|
|
|
55
55
|
|
|
56
56
|
### Optional: codebase graph (graphify)
|
|
57
57
|
|
|
58
|
-
Phases 0, 1, and 2 inject a knowledge-graph report from [graphify](https://github.com/safishamsi/graphify) for richer code orientation. When the
|
|
58
|
+
Phases 0, 1, and 2 inject a knowledge-graph report from [graphify](https://github.com/safishamsi/graphify) for richer code orientation. When the graph is missing, the orchestrator falls back to a sentinel string and continues — nothing breaks.
|
|
59
|
+
|
|
60
|
+
**Install the CLI** (required to use graphify at all):
|
|
59
61
|
|
|
60
62
|
```bash
|
|
61
63
|
pipx install graphifyy # CLI binary is `graphify`
|
|
62
64
|
```
|
|
63
65
|
|
|
66
|
+
**Seed the graph (one-time, per project)**: graphify's full LLM-driven build runs through the `/graphify` slash-command inside an AI assistant — *not* through the shell binary. From inside Claude Code (or any other graphify-supported assistant) run:
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
/graphify .
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
This produces `graphify-out/graph.json` and `graphify-out/GRAPH_REPORT.md`.
|
|
73
|
+
|
|
74
|
+
**Subsequent code-generator runs** will automatically refresh the graph with `graphify update .` (AST-only, no LLM cost). Doc / paper / image changes still need a manual `/graphify .` re-run inside your assistant.
|
|
75
|
+
|
|
64
76
|
Add to your project's `.gitignore`:
|
|
65
77
|
|
|
66
78
|
```
|
|
67
|
-
graphify-out/
|
|
68
|
-
graphify-out/cost.json
|
|
79
|
+
graphify-out/
|
|
69
80
|
```
|
|
70
81
|
|
|
71
82
|
## Authentication
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""Entry-point CLI for code-generator.
|
|
2
|
+
|
|
3
|
+
Exposes a Typer application with --version and subcommands:
|
|
4
|
+
init, status, generate, review.
|
|
5
|
+
|
|
6
|
+
At import time we reconfigure stdout/stderr for line-buffered output (the
|
|
7
|
+
in-process equivalent of ``PYTHONUNBUFFERED=1``) so phase progress shows up
|
|
8
|
+
in the terminal in real time even when the CLI is invoked through buffering
|
|
9
|
+
wrappers like ``conda run``. The default log level is DEBUG, overridable
|
|
10
|
+
via the ``LOGLEVEL`` env var (set ``LOGLEVEL=INFO`` for the quieter behaviour
|
|
11
|
+
that earlier 0.4.x releases shipped with).
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import contextlib
|
|
17
|
+
import logging
|
|
18
|
+
import os
|
|
19
|
+
import sys
|
|
20
|
+
from typing import Annotated
|
|
21
|
+
|
|
22
|
+
import typer
|
|
23
|
+
|
|
24
|
+
import code_generator
|
|
25
|
+
from code_generator.commands.bench import bench_app
|
|
26
|
+
from code_generator.commands.generate import generate_app
|
|
27
|
+
from code_generator.commands.init import init_command
|
|
28
|
+
from code_generator.commands.optimize import optimize_command
|
|
29
|
+
from code_generator.commands.review import review_command
|
|
30
|
+
from code_generator.commands.status import status_command
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _configure_io_and_logging() -> None:
|
|
34
|
+
"""Force line-buffered stdout/stderr and apply LOGLEVEL (default DEBUG).
|
|
35
|
+
|
|
36
|
+
Equivalent to running with ``PYTHONUNBUFFERED=1 LOGLEVEL=DEBUG`` from
|
|
37
|
+
the shell, but always-on so users don't have to remember the env vars.
|
|
38
|
+
Both can still be overridden externally — ``LOGLEVEL=INFO`` reverts to
|
|
39
|
+
the quieter default.
|
|
40
|
+
|
|
41
|
+
Idempotent: safe to call multiple times (the ``reconfigure`` call is a
|
|
42
|
+
no-op when line buffering is already on, and ``logging.basicConfig`` is
|
|
43
|
+
a no-op once root handlers exist).
|
|
44
|
+
"""
|
|
45
|
+
# Line-buffered console I/O: each ``\n`` flushes immediately. The
|
|
46
|
+
# `contextlib.suppress` wrapper handles streams that are already buffered
|
|
47
|
+
# as needed or are non-seekable (e.g. piped to another process).
|
|
48
|
+
for stream in (sys.stdout, sys.stderr):
|
|
49
|
+
with contextlib.suppress(AttributeError, OSError):
|
|
50
|
+
stream.reconfigure(line_buffering=True) # type: ignore[union-attr]
|
|
51
|
+
|
|
52
|
+
level_name = os.environ.get("LOGLEVEL", "DEBUG").upper()
|
|
53
|
+
level = getattr(logging, level_name, logging.DEBUG)
|
|
54
|
+
logging.getLogger("code_generator").setLevel(level)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
_configure_io_and_logging()
|
|
58
|
+
|
|
59
|
+
app = typer.Typer(
|
|
60
|
+
name="code-generator",
|
|
61
|
+
help="Orchestrate Claude Code to generate whole projects from a requirements.md.",
|
|
62
|
+
no_args_is_help=True,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _version_callback(value: bool) -> None:
|
|
67
|
+
if value:
|
|
68
|
+
typer.echo(f"code-generator {code_generator.__version__}")
|
|
69
|
+
raise typer.Exit()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@app.callback()
|
|
73
|
+
def root(
|
|
74
|
+
version: Annotated[
|
|
75
|
+
bool | None,
|
|
76
|
+
typer.Option(
|
|
77
|
+
"--version",
|
|
78
|
+
"-V",
|
|
79
|
+
help="Show version and exit.",
|
|
80
|
+
callback=_version_callback,
|
|
81
|
+
is_eager=True,
|
|
82
|
+
),
|
|
83
|
+
] = None,
|
|
84
|
+
) -> None:
|
|
85
|
+
"""code-generator CLI root."""
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
app.add_typer(bench_app, name="bench")
|
|
89
|
+
app.add_typer(generate_app, name="generate")
|
|
90
|
+
app.command(name="init")(init_command)
|
|
91
|
+
app.command(name="status")(status_command)
|
|
92
|
+
app.command(name="optimize")(optimize_command)
|
|
93
|
+
app.command(name="review")(review_command)
|
{claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/commands/_dispatch.py
RENAMED
|
@@ -37,34 +37,12 @@ __all__ = ["dispatch_async", "dispatch_orchestrator"]
|
|
|
37
37
|
_GRAPHIFY_TIMEOUT = int(os.environ.get("CODE_GENERATOR_GRAPHIFY_TIMEOUT", "600"))
|
|
38
38
|
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
"
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
path (``.``, ``./src``). Passing an absolute path like
|
|
47
|
-
``/Volumes/External SSD/optimizer`` makes graphify exit 1 with
|
|
48
|
-
``error: unknown command '<path>'``. We always pass ``.`` and rely on
|
|
49
|
-
``subprocess.run(cwd=project_dir)`` to anchor it.
|
|
50
|
-
|
|
51
|
-
Graphify's ``--update`` flag *requires* an existing ``graph.json`` to
|
|
52
|
-
merge into ("re-extract only changed files, merge into existing graph").
|
|
53
|
-
On a virgin project tree it fails with exit 1. We detect that case by
|
|
54
|
-
looking for ``graphify-out/graph.json`` and skipping ``--update`` on the
|
|
55
|
-
first run.
|
|
56
|
-
|
|
57
|
-
Args:
|
|
58
|
-
project_dir: Project root directory.
|
|
59
|
-
|
|
60
|
-
Returns:
|
|
61
|
-
The argv list to pass to ``subprocess.run``.
|
|
62
|
-
"""
|
|
63
|
-
graph_json = project_dir / "graphify-out" / "graph.json"
|
|
64
|
-
base = ["graphify", ".", "--no-viz"]
|
|
65
|
-
if graph_json.exists():
|
|
66
|
-
base.append("--update")
|
|
67
|
-
return base
|
|
40
|
+
_SETUP_HINT = (
|
|
41
|
+
"graph-report: graphify-out/graph.json not found — run `/graphify .` once "
|
|
42
|
+
"inside Claude Code (or another graphify-compatible assistant) to seed the "
|
|
43
|
+
"knowledge graph. After that, code-generator will keep it fresh "
|
|
44
|
+
"automatically with `graphify update .`. Using fallback for now."
|
|
45
|
+
)
|
|
68
46
|
|
|
69
47
|
|
|
70
48
|
def _decode_stderr(stderr: bytes | str | None) -> str:
|
|
@@ -77,35 +55,37 @@ def _decode_stderr(stderr: bytes | str | None) -> str:
|
|
|
77
55
|
|
|
78
56
|
|
|
79
57
|
def _compute_and_persist_graph_report(project_dir: Path, log: logging.Logger) -> None:
|
|
80
|
-
"""
|
|
58
|
+
"""Refresh the codebase graph via graphify and persist the report.
|
|
59
|
+
|
|
60
|
+
Graphify's shell CLI does **not** support a "build" subcommand — the full
|
|
61
|
+
LLM-driven build only happens through the ``/graphify`` slash-command
|
|
62
|
+
inside an AI assistant (Claude Code, Codex, Cursor, etc.). The shell
|
|
63
|
+
binary only supports maintenance subcommands like ``update``, ``watch``,
|
|
64
|
+
``query``.
|
|
81
65
|
|
|
82
|
-
|
|
83
|
-
and copies the resulting markdown into
|
|
84
|
-
``.code-generator/memories/cycle-repo-map.md`` so Phases 0, 1, 2 can read
|
|
85
|
-
it via :func:`code_generator.memory.read_cycle_repomap`.
|
|
66
|
+
Strategy:
|
|
86
67
|
|
|
87
|
-
|
|
68
|
+
1. If ``graphify-out/graph.json`` does **not** exist, the user has never
|
|
69
|
+
seeded the graph. Log a one-time setup hint, write the fallback
|
|
70
|
+
sentinel, and return — do not call subprocess. The user must run
|
|
71
|
+
``/graphify .`` in their assistant once to seed it.
|
|
88
72
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
--no-viz``. Graphify's SHA256-keyed cache in ``graphify-out/cache/``
|
|
95
|
-
keeps subsequent runs cheap.
|
|
73
|
+
2. If it exists, run ``graphify update .`` (cwd=project_dir). This is
|
|
74
|
+
AST-only re-extraction, no LLM cost, fast on cached files. It
|
|
75
|
+
refreshes ``graph.json`` and ``GRAPH_REPORT.md``. Doc / paper /
|
|
76
|
+
image changes are *not* picked up — for those the user must run
|
|
77
|
+
``/graphify .`` again in their assistant.
|
|
96
78
|
|
|
97
79
|
Falls back to :data:`code_generator.memory.REPOMAP_FALLBACK` on any
|
|
98
80
|
failure (binary missing, timeout, non-zero exit, missing report file).
|
|
99
81
|
Stderr from graphify is captured and logged at WARN level on failure so
|
|
100
82
|
the user can diagnose without re-running by hand.
|
|
101
83
|
|
|
102
|
-
Auth: graphify inherits the parent process env
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
process environment when the orchestrator runs on the Anthropic Max
|
|
108
|
-
path, so passing the inherited env to graphify is safe.
|
|
84
|
+
Auth: graphify inherits the parent process env. ``graphify update`` is
|
|
85
|
+
AST-only and makes no LLM calls, so OAuth context is irrelevant on this
|
|
86
|
+
codepath — but we still don't strip ``ANTHROPIC_*`` because the startup
|
|
87
|
+
``env.assert_safe_environment()`` check (CLAUDE.md non-negotiable #1)
|
|
88
|
+
already guarantees those vars are absent on the Anthropic Max path.
|
|
109
89
|
|
|
110
90
|
Args:
|
|
111
91
|
project_dir: Project root directory (graphify writes ``graphify-out/``
|
|
@@ -114,6 +94,7 @@ def _compute_and_persist_graph_report(project_dir: Path, log: logging.Logger) ->
|
|
|
114
94
|
"""
|
|
115
95
|
memories_dir = project_dir / ".code-generator" / "memories"
|
|
116
96
|
report_path = project_dir / "graphify-out" / "GRAPH_REPORT.md"
|
|
97
|
+
graph_json = project_dir / "graphify-out" / "graph.json"
|
|
117
98
|
|
|
118
99
|
if shutil.which("graphify") is None:
|
|
119
100
|
log.warning(
|
|
@@ -123,9 +104,14 @@ def _compute_and_persist_graph_report(project_dir: Path, log: logging.Logger) ->
|
|
|
123
104
|
_memory.write_memory_file(memories_dir, "cycle-repo-map.md", _memory.REPOMAP_FALLBACK)
|
|
124
105
|
return
|
|
125
106
|
|
|
126
|
-
|
|
107
|
+
if not graph_json.exists():
|
|
108
|
+
log.info(_SETUP_HINT)
|
|
109
|
+
_memory.write_memory_file(memories_dir, "cycle-repo-map.md", _memory.REPOMAP_FALLBACK)
|
|
110
|
+
return
|
|
111
|
+
|
|
112
|
+
cmd = ["graphify", "update", "."]
|
|
127
113
|
try:
|
|
128
|
-
log.info("graph-report:
|
|
114
|
+
log.info("graph-report: refreshing (cmd=%s)", " ".join(cmd))
|
|
129
115
|
subprocess.run( # noqa: S603
|
|
130
116
|
cmd,
|
|
131
117
|
cwd=project_dir,
|
{claude_code_generator-0.5.2 → claude_code_generator-0.5.4}/src/code_generator/logging_setup.py
RENAMED
|
@@ -3,9 +3,14 @@
|
|
|
3
3
|
Each phase gets its own log file under .code-generator/logs/ and a shared
|
|
4
4
|
RichHandler for colour-coded console output. The setup is idempotent: calling
|
|
5
5
|
setup_phase_logger() twice for the same phase does not duplicate handlers.
|
|
6
|
+
|
|
7
|
+
The default console level is DEBUG, overridable via the ``LOGLEVEL`` env var
|
|
8
|
+
(``LOGLEVEL=INFO`` for the quieter behaviour shipped in 0.4.x). The file
|
|
9
|
+
handler always writes at DEBUG so post-mortem inspection retains everything.
|
|
6
10
|
"""
|
|
7
11
|
|
|
8
12
|
import logging
|
|
13
|
+
import os
|
|
9
14
|
from pathlib import Path
|
|
10
15
|
|
|
11
16
|
from rich.logging import RichHandler
|
|
@@ -13,11 +18,19 @@ from rich.logging import RichHandler
|
|
|
13
18
|
_FILE_FORMATTER = logging.Formatter("%(asctime)s %(levelname)s %(message)s")
|
|
14
19
|
|
|
15
20
|
|
|
21
|
+
def _resolve_level(default: int = logging.DEBUG) -> int:
|
|
22
|
+
"""Read the ``LOGLEVEL`` env var, falling back to *default* on miss/typo."""
|
|
23
|
+
name = os.environ.get("LOGLEVEL", "").upper()
|
|
24
|
+
if not name:
|
|
25
|
+
return default
|
|
26
|
+
return getattr(logging, name, default)
|
|
27
|
+
|
|
28
|
+
|
|
16
29
|
def setup_phase_logger(
|
|
17
30
|
phase_name: str,
|
|
18
31
|
project_dir: Path,
|
|
19
32
|
*,
|
|
20
|
-
level: int =
|
|
33
|
+
level: int | None = None,
|
|
21
34
|
) -> logging.Logger:
|
|
22
35
|
"""Configure and return a logger for a single orchestration phase.
|
|
23
36
|
|
|
@@ -29,21 +42,23 @@ def setup_phase_logger(
|
|
|
29
42
|
phase_name: Short identifier for the phase (e.g. ``"planning"``).
|
|
30
43
|
project_dir: Root directory of the user's project. Logs are written to
|
|
31
44
|
``project_dir / ".code-generator" / "logs" / f"{phase_name}.log"``.
|
|
32
|
-
level: Python logging level applied to the logger.
|
|
45
|
+
level: Python logging level applied to the logger. Defaults to the
|
|
46
|
+
value of the ``LOGLEVEL`` env var, or DEBUG if unset.
|
|
33
47
|
|
|
34
48
|
Returns:
|
|
35
49
|
Configured :class:`logging.Logger` instance.
|
|
36
50
|
"""
|
|
51
|
+
effective_level = level if level is not None else _resolve_level()
|
|
37
52
|
logger = logging.getLogger(f"code_generator.phase.{phase_name}")
|
|
38
53
|
|
|
39
54
|
if _already_configured(logger):
|
|
40
55
|
return logger
|
|
41
56
|
|
|
42
|
-
logger.setLevel(
|
|
57
|
+
logger.setLevel(effective_level)
|
|
43
58
|
logger.propagate = False
|
|
44
59
|
|
|
45
60
|
_attach_file_handler(logger, project_dir, phase_name)
|
|
46
|
-
_attach_rich_handler(logger)
|
|
61
|
+
_attach_rich_handler(logger, effective_level)
|
|
47
62
|
|
|
48
63
|
return logger
|
|
49
64
|
|
|
@@ -67,9 +82,9 @@ def _attach_file_handler(
|
|
|
67
82
|
logger.addHandler(handler)
|
|
68
83
|
|
|
69
84
|
|
|
70
|
-
def _attach_rich_handler(logger: logging.Logger) -> None:
|
|
85
|
+
def _attach_rich_handler(logger: logging.Logger, level: int) -> None:
|
|
71
86
|
handler = RichHandler(show_time=False, show_path=False)
|
|
72
|
-
handler.setLevel(
|
|
87
|
+
handler.setLevel(level)
|
|
73
88
|
logger.addHandler(handler)
|
|
74
89
|
|
|
75
90
|
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Tests for ``cli._configure_io_and_logging``.
|
|
2
|
+
|
|
3
|
+
The CLI applies two startup defaults so users don't need to set env vars
|
|
4
|
+
manually: line-buffered stdout/stderr (PYTHONUNBUFFERED=1) and DEBUG-level
|
|
5
|
+
console logging (LOGLEVEL=DEBUG). Both are overridable via ``LOGLEVEL``.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
from typing import TYPE_CHECKING
|
|
12
|
+
|
|
13
|
+
from code_generator import cli
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
import pytest
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TestConfigureIoAndLogging:
|
|
20
|
+
"""``_configure_io_and_logging`` sets sane defaults at CLI startup."""
|
|
21
|
+
|
|
22
|
+
def test_default_loglevel_is_debug(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
23
|
+
"""When ``LOGLEVEL`` is unset, the code_generator namespace uses DEBUG."""
|
|
24
|
+
monkeypatch.delenv("LOGLEVEL", raising=False)
|
|
25
|
+
cli._configure_io_and_logging()
|
|
26
|
+
|
|
27
|
+
assert logging.getLogger("code_generator").level == logging.DEBUG
|
|
28
|
+
|
|
29
|
+
def test_loglevel_env_overrides_default(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
30
|
+
"""``LOGLEVEL=INFO`` reverts to the quieter 0.4.x default."""
|
|
31
|
+
monkeypatch.setenv("LOGLEVEL", "INFO")
|
|
32
|
+
cli._configure_io_and_logging()
|
|
33
|
+
|
|
34
|
+
assert logging.getLogger("code_generator").level == logging.INFO
|
|
35
|
+
|
|
36
|
+
def test_unknown_loglevel_falls_back_to_debug(
|
|
37
|
+
self, monkeypatch: pytest.MonkeyPatch
|
|
38
|
+
) -> None:
|
|
39
|
+
"""Typos like ``LOGLEVEL=NOPE`` fall back to DEBUG, not raise."""
|
|
40
|
+
monkeypatch.setenv("LOGLEVEL", "NOPE")
|
|
41
|
+
cli._configure_io_and_logging()
|
|
42
|
+
|
|
43
|
+
assert logging.getLogger("code_generator").level == logging.DEBUG
|
|
44
|
+
|
|
45
|
+
def test_idempotent_when_called_twice(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
46
|
+
"""Calling twice in a row does not raise (e.g. on `--version` re-import)."""
|
|
47
|
+
monkeypatch.setenv("LOGLEVEL", "INFO")
|
|
48
|
+
cli._configure_io_and_logging()
|
|
49
|
+
cli._configure_io_and_logging() # must not raise
|
|
50
|
+
|
|
51
|
+
assert logging.getLogger("code_generator").level == logging.INFO
|
|
52
|
+
|
|
53
|
+
def test_stdout_is_line_buffered_after_setup(
|
|
54
|
+
self, monkeypatch: pytest.MonkeyPatch
|
|
55
|
+
) -> None:
|
|
56
|
+
"""sys.stdout / sys.stderr must report line buffering after setup.
|
|
57
|
+
|
|
58
|
+
On terminals where stdout is already line-buffered, this is a no-op;
|
|
59
|
+
when running through a pipe (default block buffering), the call
|
|
60
|
+
switches the stream to line buffering.
|
|
61
|
+
"""
|
|
62
|
+
import sys
|
|
63
|
+
|
|
64
|
+
monkeypatch.delenv("LOGLEVEL", raising=False)
|
|
65
|
+
cli._configure_io_and_logging()
|
|
66
|
+
|
|
67
|
+
# `line_buffering` attribute exists on TextIOWrapper. After
|
|
68
|
+
# reconfigure(line_buffering=True), it must be True. Streams that
|
|
69
|
+
# don't support the attribute (rare) are intentionally tolerated.
|
|
70
|
+
for stream in (sys.stdout, sys.stderr):
|
|
71
|
+
buffering = getattr(stream, "line_buffering", None)
|
|
72
|
+
if buffering is not None:
|
|
73
|
+
assert buffering is True, f"{stream} not line-buffered"
|