claude-code-generator 0.1.0__tar.gz → 0.2.0__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.1.0/src/claude_code_generator.egg-info → claude_code_generator-0.2.0}/PKG-INFO +7 -5
- {claude_code_generator-0.1.0 → claude_code_generator-0.2.0}/README.md +6 -4
- {claude_code_generator-0.1.0 → claude_code_generator-0.2.0}/pyproject.toml +5 -1
- {claude_code_generator-0.1.0 → claude_code_generator-0.2.0/src/claude_code_generator.egg-info}/PKG-INFO +7 -5
- {claude_code_generator-0.1.0 → claude_code_generator-0.2.0}/src/claude_code_generator.egg-info/SOURCES.txt +38 -1
- {claude_code_generator-0.1.0 → claude_code_generator-0.2.0}/src/code_generator/__init__.py +1 -1
- {claude_code_generator-0.1.0 → claude_code_generator-0.2.0}/src/code_generator/cli.py +2 -0
- claude_code_generator-0.2.0/src/code_generator/commands/_detect.py +59 -0
- claude_code_generator-0.2.0/src/code_generator/commands/_dispatch.py +278 -0
- claude_code_generator-0.2.0/src/code_generator/commands/_resume.py +73 -0
- claude_code_generator-0.2.0/src/code_generator/commands/generate.py +126 -0
- {claude_code_generator-0.1.0 → claude_code_generator-0.2.0}/src/code_generator/commands/init.py +1 -3
- claude_code_generator-0.2.0/src/code_generator/commands/optimize.py +158 -0
- {claude_code_generator-0.1.0 → claude_code_generator-0.2.0}/src/code_generator/commands/review.py +18 -28
- claude_code_generator-0.2.0/src/code_generator/commands/status.py +192 -0
- claude_code_generator-0.2.0/src/code_generator/effort.py +164 -0
- claude_code_generator-0.2.0/src/code_generator/gh/__init__.py +51 -0
- claude_code_generator-0.2.0/src/code_generator/gh/core.py +111 -0
- claude_code_generator-0.2.0/src/code_generator/gh/issues.py +66 -0
- claude_code_generator-0.2.0/src/code_generator/gh/labels.py +102 -0
- claude_code_generator-0.2.0/src/code_generator/gh/milestones.py +89 -0
- claude_code_generator-0.2.0/src/code_generator/git_ops.py +213 -0
- {claude_code_generator-0.1.0 → claude_code_generator-0.2.0}/src/code_generator/logging_setup.py +61 -0
- claude_code_generator-0.2.0/src/code_generator/orchestrator/_comments.py +72 -0
- {claude_code_generator-0.1.0 → claude_code_generator-0.2.0}/src/code_generator/orchestrator/cycle_loop.py +239 -123
- claude_code_generator-0.2.0/src/code_generator/orchestrator/phase0_complexity.py +315 -0
- claude_code_generator-0.2.0/src/code_generator/orchestrator/phase1_plan.py +252 -0
- {claude_code_generator-0.1.0 → claude_code_generator-0.2.0}/src/code_generator/orchestrator/phase2_review.py +38 -34
- claude_code_generator-0.2.0/src/code_generator/orchestrator/phase3_4_implement.py +306 -0
- claude_code_generator-0.2.0/src/code_generator/orchestrator/phase5_closure.py +246 -0
- claude_code_generator-0.2.0/src/code_generator/orchestrator/phase6_test.py +117 -0
- claude_code_generator-0.2.0/src/code_generator/orchestrator/phase7_commit.py +336 -0
- claude_code_generator-0.2.0/src/code_generator/prompts/__init__.py +146 -0
- claude_code_generator-0.2.0/src/code_generator/prompts/prompt-optimize-requirements.md +70 -0
- {claude_code_generator-0.1.0 → claude_code_generator-0.2.0}/src/code_generator/prompts/prompt-phase-0-complexity.md +64 -4
- {claude_code_generator-0.1.0 → claude_code_generator-0.2.0}/src/code_generator/prompts/prompt-phase-1-planning.md +14 -5
- {claude_code_generator-0.1.0 → claude_code_generator-0.2.0}/src/code_generator/prompts/prompt-phase-2-issue-review.md +5 -1
- {claude_code_generator-0.1.0 → claude_code_generator-0.2.0}/src/code_generator/prompts/prompt-phase-3-implementation.md +8 -4
- {claude_code_generator-0.1.0 → claude_code_generator-0.2.0}/src/code_generator/prompts/prompt-phase-5-final-review.md +25 -1
- {claude_code_generator-0.1.0 → claude_code_generator-0.2.0}/src/code_generator/prompts/prompt-phase-7-commit.md +53 -6
- claude_code_generator-0.2.0/src/code_generator/requirements_structure.py +78 -0
- claude_code_generator-0.2.0/src/code_generator/runner/__init__.py +52 -0
- claude_code_generator-0.2.0/src/code_generator/runner/message_parsing.py +162 -0
- claude_code_generator-0.2.0/src/code_generator/runner/options.py +41 -0
- claude_code_generator-0.2.0/src/code_generator/runner/protocol.py +60 -0
- {claude_code_generator-0.1.0 → claude_code_generator-0.2.0}/src/code_generator/runner/rate_limit.py +70 -6
- {claude_code_generator-0.1.0 → claude_code_generator-0.2.0}/src/code_generator/runner/retry.py +5 -9
- claude_code_generator-0.2.0/src/code_generator/runner/sdk_runner.py +211 -0
- claude_code_generator-0.2.0/src/code_generator/runner/subprocess_runner.py +334 -0
- claude_code_generator-0.2.0/src/code_generator/runner/types.py +166 -0
- claude_code_generator-0.2.0/src/code_generator/runner/utils.py +16 -0
- claude_code_generator-0.2.0/src/code_generator/state.py +364 -0
- claude_code_generator-0.2.0/tests/test_comments.py +342 -0
- claude_code_generator-0.2.0/tests/test_commit_message.py +138 -0
- claude_code_generator-0.2.0/tests/test_cycle_loop.py +1476 -0
- claude_code_generator-0.2.0/tests/test_cycle_loop_multicycle.py +903 -0
- claude_code_generator-0.2.0/tests/test_delta_planning.py +493 -0
- claude_code_generator-0.2.0/tests/test_detect.py +259 -0
- claude_code_generator-0.2.0/tests/test_effort.py +274 -0
- claude_code_generator-0.2.0/tests/test_generate.py +419 -0
- claude_code_generator-0.2.0/tests/test_generate_resume.py +1339 -0
- claude_code_generator-0.2.0/tests/test_gh.py +347 -0
- claude_code_generator-0.2.0/tests/test_gh_labels.py +150 -0
- claude_code_generator-0.2.0/tests/test_gh_milestones.py +158 -0
- claude_code_generator-0.2.0/tests/test_gh_submodules.py +223 -0
- claude_code_generator-0.2.0/tests/test_git_ops.py +506 -0
- {claude_code_generator-0.1.0 → claude_code_generator-0.2.0}/tests/test_init.py +4 -12
- {claude_code_generator-0.1.0 → claude_code_generator-0.2.0}/tests/test_logging_setup.py +1 -3
- claude_code_generator-0.2.0/tests/test_message_parsing.py +432 -0
- claude_code_generator-0.2.0/tests/test_optimize.py +621 -0
- claude_code_generator-0.2.0/tests/test_options.py +95 -0
- claude_code_generator-0.2.0/tests/test_phase0.py +401 -0
- claude_code_generator-0.2.0/tests/test_phase1.py +687 -0
- claude_code_generator-0.2.0/tests/test_phase2.py +506 -0
- claude_code_generator-0.2.0/tests/test_phase3_4.py +1080 -0
- claude_code_generator-0.2.0/tests/test_phase5.py +787 -0
- {claude_code_generator-0.1.0 → claude_code_generator-0.2.0}/tests/test_phase6.py +95 -0
- claude_code_generator-0.2.0/tests/test_phase7.py +1036 -0
- claude_code_generator-0.2.0/tests/test_phase_token_logging.py +742 -0
- claude_code_generator-0.2.0/tests/test_prompts.py +433 -0
- claude_code_generator-0.2.0/tests/test_rate_limit.py +871 -0
- claude_code_generator-0.2.0/tests/test_requirements_structure.py +290 -0
- {claude_code_generator-0.1.0 → claude_code_generator-0.2.0}/tests/test_retry.py +75 -0
- {claude_code_generator-0.1.0 → claude_code_generator-0.2.0}/tests/test_review.py +3 -9
- claude_code_generator-0.2.0/tests/test_runner_protocol.py +208 -0
- claude_code_generator-0.2.0/tests/test_runner_protocol_annotations.py +214 -0
- claude_code_generator-0.2.0/tests/test_runner_types.py +311 -0
- claude_code_generator-0.2.0/tests/test_runner_utils.py +104 -0
- claude_code_generator-0.2.0/tests/test_sdk_runner.py +902 -0
- claude_code_generator-0.2.0/tests/test_state.py +1154 -0
- claude_code_generator-0.2.0/tests/test_status.py +541 -0
- claude_code_generator-0.2.0/tests/test_subprocess_runner.py +1147 -0
- {claude_code_generator-0.1.0 → claude_code_generator-0.2.0}/tests/test_version.py +11 -0
- claude_code_generator-0.1.0/src/code_generator/commands/generate.py +0 -252
- claude_code_generator-0.1.0/src/code_generator/commands/status.py +0 -83
- claude_code_generator-0.1.0/src/code_generator/gh.py +0 -331
- claude_code_generator-0.1.0/src/code_generator/orchestrator/phase0_complexity.py +0 -159
- claude_code_generator-0.1.0/src/code_generator/orchestrator/phase1_plan.py +0 -170
- claude_code_generator-0.1.0/src/code_generator/orchestrator/phase3_4_implement.py +0 -164
- claude_code_generator-0.1.0/src/code_generator/orchestrator/phase5_closure.py +0 -154
- claude_code_generator-0.1.0/src/code_generator/orchestrator/phase6_test.py +0 -98
- claude_code_generator-0.1.0/src/code_generator/orchestrator/phase7_commit.py +0 -167
- claude_code_generator-0.1.0/src/code_generator/prompts/__init__.py +0 -86
- claude_code_generator-0.1.0/src/code_generator/runner/__init__.py +0 -26
- claude_code_generator-0.1.0/src/code_generator/runner/sdk_runner.py +0 -267
- claude_code_generator-0.1.0/src/code_generator/runner/subprocess_runner.py +0 -200
- claude_code_generator-0.1.0/src/code_generator/state.py +0 -178
- claude_code_generator-0.1.0/tests/test_cycle_loop.py +0 -573
- claude_code_generator-0.1.0/tests/test_generate.py +0 -180
- claude_code_generator-0.1.0/tests/test_generate_resume.py +0 -436
- claude_code_generator-0.1.0/tests/test_gh.py +0 -365
- claude_code_generator-0.1.0/tests/test_phase0.py +0 -175
- claude_code_generator-0.1.0/tests/test_phase1.py +0 -223
- claude_code_generator-0.1.0/tests/test_phase2.py +0 -224
- claude_code_generator-0.1.0/tests/test_phase3_4.py +0 -229
- claude_code_generator-0.1.0/tests/test_phase5.py +0 -200
- claude_code_generator-0.1.0/tests/test_phase7.py +0 -257
- claude_code_generator-0.1.0/tests/test_prompts.py +0 -185
- claude_code_generator-0.1.0/tests/test_rate_limit.py +0 -260
- claude_code_generator-0.1.0/tests/test_sdk_runner.py +0 -285
- claude_code_generator-0.1.0/tests/test_state.py +0 -224
- claude_code_generator-0.1.0/tests/test_status.py +0 -106
- claude_code_generator-0.1.0/tests/test_subprocess_runner.py +0 -213
- {claude_code_generator-0.1.0 → claude_code_generator-0.2.0}/LICENSE +0 -0
- {claude_code_generator-0.1.0 → claude_code_generator-0.2.0}/setup.cfg +0 -0
- {claude_code_generator-0.1.0 → claude_code_generator-0.2.0}/src/claude_code_generator.egg-info/dependency_links.txt +0 -0
- {claude_code_generator-0.1.0 → claude_code_generator-0.2.0}/src/claude_code_generator.egg-info/entry_points.txt +0 -0
- {claude_code_generator-0.1.0 → claude_code_generator-0.2.0}/src/claude_code_generator.egg-info/requires.txt +0 -0
- {claude_code_generator-0.1.0 → claude_code_generator-0.2.0}/src/claude_code_generator.egg-info/top_level.txt +0 -0
- {claude_code_generator-0.1.0 → claude_code_generator-0.2.0}/src/code_generator/agents.py +0 -0
- {claude_code_generator-0.1.0 → claude_code_generator-0.2.0}/src/code_generator/commands/__init__.py +0 -0
- {claude_code_generator-0.1.0 → claude_code_generator-0.2.0}/src/code_generator/env.py +0 -0
- {claude_code_generator-0.1.0 → claude_code_generator-0.2.0}/src/code_generator/orchestrator/__init__.py +0 -0
- {claude_code_generator-0.1.0 → claude_code_generator-0.2.0}/src/code_generator/prompts/prompt-phase-6-test.md +0 -0
- {claude_code_generator-0.1.0 → claude_code_generator-0.2.0}/src/code_generator/prompts/prompt-review.md +0 -0
- {claude_code_generator-0.1.0 → claude_code_generator-0.2.0}/src/code_generator/templates/__init__.py +0 -0
- {claude_code_generator-0.1.0 → claude_code_generator-0.2.0}/src/code_generator/templates/angular.md +0 -0
- {claude_code_generator-0.1.0 → claude_code_generator-0.2.0}/src/code_generator/templates/base.md +0 -0
- {claude_code_generator-0.1.0 → claude_code_generator-0.2.0}/src/code_generator/templates/fastapi.md +0 -0
- {claude_code_generator-0.1.0 → claude_code_generator-0.2.0}/src/code_generator/templates/finance.md +0 -0
- {claude_code_generator-0.1.0 → claude_code_generator-0.2.0}/src/code_generator/templates/fullstack.md +0 -0
- {claude_code_generator-0.1.0 → claude_code_generator-0.2.0}/src/code_generator/templates/nestjs.md +0 -0
- {claude_code_generator-0.1.0 → claude_code_generator-0.2.0}/src/code_generator/templates/python-cli.md +0 -0
- {claude_code_generator-0.1.0 → claude_code_generator-0.2.0}/tests/test_agents.py +0 -0
- {claude_code_generator-0.1.0 → claude_code_generator-0.2.0}/tests/test_env.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: claude-code-generator
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
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
|
|
@@ -80,7 +80,9 @@ Read `.code-generator/requirements.md` and orchestrate Claude Code through the 0
|
|
|
80
80
|
| 3-4 | Implementation, fresh SDK session per issue (TDD) | Sonnet 4.6 |
|
|
81
81
|
| 5 | Closure + cross-module review | Opus 4.6 |
|
|
82
82
|
| 6 | Test suite, max 3 retries on failure | Sonnet 4.6 |
|
|
83
|
-
| 7 | Commit message + git add/commit/push |
|
|
83
|
+
| 7 | Commit message + git add/commit/push | Opus 4.6 |
|
|
84
|
+
|
|
85
|
+
Every phase logs a completion summary with token counts (`in`, `cache_read`, `cache_write`, `out`) and persists them to `state.json`.
|
|
84
86
|
|
|
85
87
|
Flags:
|
|
86
88
|
|
|
@@ -102,7 +104,7 @@ Run a standalone Opus 4.6 review of the current codebase against the design prin
|
|
|
102
104
|
|
|
103
105
|
### `code-generator status`
|
|
104
106
|
|
|
105
|
-
Print a Rich table with the current mode, current cycle, current phase, open/closed issue counts, the rate-limit pause state (with minutes remaining if paused), and
|
|
107
|
+
Print a Rich table with the current mode, current cycle, current phase, open/closed issue counts, the rate-limit pause state (with minutes remaining if paused), per-cycle token consumption columns (`in | cache_read | cache_write | out`), wall-clock elapsed time, and any `last_error` highlighted in red.
|
|
106
108
|
|
|
107
109
|
## Safety constraints
|
|
108
110
|
|
|
@@ -115,7 +117,7 @@ The tool enforces eight non-negotiables (see `CLAUDE.md` for the full list):
|
|
|
115
117
|
5. **Wait-and-resume rate-limit handling.** Rate limits are not retried with exponential backoff — the tool sleeps until `resets_at + 60s` and resumes the same session. Backoff (10→20→40→80→120s) only applies to non-rate-limit transient errors.
|
|
116
118
|
6. **Fresh SDK session per issue.** The implementation phase never reuses conversational context across issues — each issue is a `/clear`-equivalent fresh session.
|
|
117
119
|
7. **Atomic state writes.** `.code-generator/state.json` is updated via `tmp → os.replace(tmp, STATE)` so a crash mid-write cannot corrupt it.
|
|
118
|
-
8. **Fixed model per phase.** Opus 4.6 for phases 0/1/2/5; Sonnet 4.6 for phases 3/4/6
|
|
120
|
+
8. **Fixed model per phase.** Opus 4.6 for phases 0/1/2/5/7; Sonnet 4.6 for phases 3/4/6.
|
|
119
121
|
|
|
120
122
|
## Troubleshooting
|
|
121
123
|
|
|
@@ -151,7 +153,7 @@ src/code_generator/
|
|
|
151
153
|
│ ├── phase3_4_implement.py # TDD loop, fresh session per issue
|
|
152
154
|
│ ├── phase5_closure.py # closure + fix-issue feedback loop
|
|
153
155
|
│ ├── phase6_test.py # test runner, max 3 retries
|
|
154
|
-
│ ├── phase7_commit.py #
|
|
156
|
+
│ ├── phase7_commit.py # Opus commit message + push with rebase retry
|
|
155
157
|
│ └── cycle_loop.py # multi-cycle driver
|
|
156
158
|
└── commands/ # init, status, generate, review (Typer commands)
|
|
157
159
|
```
|
|
@@ -49,7 +49,9 @@ Read `.code-generator/requirements.md` and orchestrate Claude Code through the 0
|
|
|
49
49
|
| 3-4 | Implementation, fresh SDK session per issue (TDD) | Sonnet 4.6 |
|
|
50
50
|
| 5 | Closure + cross-module review | Opus 4.6 |
|
|
51
51
|
| 6 | Test suite, max 3 retries on failure | Sonnet 4.6 |
|
|
52
|
-
| 7 | Commit message + git add/commit/push |
|
|
52
|
+
| 7 | Commit message + git add/commit/push | Opus 4.6 |
|
|
53
|
+
|
|
54
|
+
Every phase logs a completion summary with token counts (`in`, `cache_read`, `cache_write`, `out`) and persists them to `state.json`.
|
|
53
55
|
|
|
54
56
|
Flags:
|
|
55
57
|
|
|
@@ -71,7 +73,7 @@ Run a standalone Opus 4.6 review of the current codebase against the design prin
|
|
|
71
73
|
|
|
72
74
|
### `code-generator status`
|
|
73
75
|
|
|
74
|
-
Print a Rich table with the current mode, current cycle, current phase, open/closed issue counts, the rate-limit pause state (with minutes remaining if paused), and
|
|
76
|
+
Print a Rich table with the current mode, current cycle, current phase, open/closed issue counts, the rate-limit pause state (with minutes remaining if paused), per-cycle token consumption columns (`in | cache_read | cache_write | out`), wall-clock elapsed time, and any `last_error` highlighted in red.
|
|
75
77
|
|
|
76
78
|
## Safety constraints
|
|
77
79
|
|
|
@@ -84,7 +86,7 @@ The tool enforces eight non-negotiables (see `CLAUDE.md` for the full list):
|
|
|
84
86
|
5. **Wait-and-resume rate-limit handling.** Rate limits are not retried with exponential backoff — the tool sleeps until `resets_at + 60s` and resumes the same session. Backoff (10→20→40→80→120s) only applies to non-rate-limit transient errors.
|
|
85
87
|
6. **Fresh SDK session per issue.** The implementation phase never reuses conversational context across issues — each issue is a `/clear`-equivalent fresh session.
|
|
86
88
|
7. **Atomic state writes.** `.code-generator/state.json` is updated via `tmp → os.replace(tmp, STATE)` so a crash mid-write cannot corrupt it.
|
|
87
|
-
8. **Fixed model per phase.** Opus 4.6 for phases 0/1/2/5; Sonnet 4.6 for phases 3/4/6
|
|
89
|
+
8. **Fixed model per phase.** Opus 4.6 for phases 0/1/2/5/7; Sonnet 4.6 for phases 3/4/6.
|
|
88
90
|
|
|
89
91
|
## Troubleshooting
|
|
90
92
|
|
|
@@ -120,7 +122,7 @@ src/code_generator/
|
|
|
120
122
|
│ ├── phase3_4_implement.py # TDD loop, fresh session per issue
|
|
121
123
|
│ ├── phase5_closure.py # closure + fix-issue feedback loop
|
|
122
124
|
│ ├── phase6_test.py # test runner, max 3 retries
|
|
123
|
-
│ ├── phase7_commit.py #
|
|
125
|
+
│ ├── phase7_commit.py # Opus commit message + push with rebase retry
|
|
124
126
|
│ └── cycle_loop.py # multi-cycle driver
|
|
125
127
|
└── commands/ # init, status, generate, review (Typer commands)
|
|
126
128
|
```
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "claude-code-generator"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.0"
|
|
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" }
|
|
@@ -69,3 +69,7 @@ ignore = []
|
|
|
69
69
|
[tool.pytest.ini_options]
|
|
70
70
|
testpaths = ["tests"]
|
|
71
71
|
asyncio_mode = "auto"
|
|
72
|
+
|
|
73
|
+
[tool.pyright]
|
|
74
|
+
# Test mocks intentionally use unused parameters to match the production interface signature.
|
|
75
|
+
reportUnusedParameter = "none"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: claude-code-generator
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
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
|
|
@@ -80,7 +80,9 @@ Read `.code-generator/requirements.md` and orchestrate Claude Code through the 0
|
|
|
80
80
|
| 3-4 | Implementation, fresh SDK session per issue (TDD) | Sonnet 4.6 |
|
|
81
81
|
| 5 | Closure + cross-module review | Opus 4.6 |
|
|
82
82
|
| 6 | Test suite, max 3 retries on failure | Sonnet 4.6 |
|
|
83
|
-
| 7 | Commit message + git add/commit/push |
|
|
83
|
+
| 7 | Commit message + git add/commit/push | Opus 4.6 |
|
|
84
|
+
|
|
85
|
+
Every phase logs a completion summary with token counts (`in`, `cache_read`, `cache_write`, `out`) and persists them to `state.json`.
|
|
84
86
|
|
|
85
87
|
Flags:
|
|
86
88
|
|
|
@@ -102,7 +104,7 @@ Run a standalone Opus 4.6 review of the current codebase against the design prin
|
|
|
102
104
|
|
|
103
105
|
### `code-generator status`
|
|
104
106
|
|
|
105
|
-
Print a Rich table with the current mode, current cycle, current phase, open/closed issue counts, the rate-limit pause state (with minutes remaining if paused), and
|
|
107
|
+
Print a Rich table with the current mode, current cycle, current phase, open/closed issue counts, the rate-limit pause state (with minutes remaining if paused), per-cycle token consumption columns (`in | cache_read | cache_write | out`), wall-clock elapsed time, and any `last_error` highlighted in red.
|
|
106
108
|
|
|
107
109
|
## Safety constraints
|
|
108
110
|
|
|
@@ -115,7 +117,7 @@ The tool enforces eight non-negotiables (see `CLAUDE.md` for the full list):
|
|
|
115
117
|
5. **Wait-and-resume rate-limit handling.** Rate limits are not retried with exponential backoff — the tool sleeps until `resets_at + 60s` and resumes the same session. Backoff (10→20→40→80→120s) only applies to non-rate-limit transient errors.
|
|
116
118
|
6. **Fresh SDK session per issue.** The implementation phase never reuses conversational context across issues — each issue is a `/clear`-equivalent fresh session.
|
|
117
119
|
7. **Atomic state writes.** `.code-generator/state.json` is updated via `tmp → os.replace(tmp, STATE)` so a crash mid-write cannot corrupt it.
|
|
118
|
-
8. **Fixed model per phase.** Opus 4.6 for phases 0/1/2/5; Sonnet 4.6 for phases 3/4/6
|
|
120
|
+
8. **Fixed model per phase.** Opus 4.6 for phases 0/1/2/5/7; Sonnet 4.6 for phases 3/4/6.
|
|
119
121
|
|
|
120
122
|
## Troubleshooting
|
|
121
123
|
|
|
@@ -151,7 +153,7 @@ src/code_generator/
|
|
|
151
153
|
│ ├── phase3_4_implement.py # TDD loop, fresh session per issue
|
|
152
154
|
│ ├── phase5_closure.py # closure + fix-issue feedback loop
|
|
153
155
|
│ ├── phase6_test.py # test runner, max 3 retries
|
|
154
|
-
│ ├── phase7_commit.py #
|
|
156
|
+
│ ├── phase7_commit.py # Opus commit message + push with rebase retry
|
|
155
157
|
│ └── cycle_loop.py # multi-cycle driver
|
|
156
158
|
└── commands/ # init, status, generate, review (Typer commands)
|
|
157
159
|
```
|
|
@@ -10,16 +10,28 @@ src/claude_code_generator.egg-info/top_level.txt
|
|
|
10
10
|
src/code_generator/__init__.py
|
|
11
11
|
src/code_generator/agents.py
|
|
12
12
|
src/code_generator/cli.py
|
|
13
|
+
src/code_generator/effort.py
|
|
13
14
|
src/code_generator/env.py
|
|
14
|
-
src/code_generator/
|
|
15
|
+
src/code_generator/git_ops.py
|
|
15
16
|
src/code_generator/logging_setup.py
|
|
17
|
+
src/code_generator/requirements_structure.py
|
|
16
18
|
src/code_generator/state.py
|
|
17
19
|
src/code_generator/commands/__init__.py
|
|
20
|
+
src/code_generator/commands/_detect.py
|
|
21
|
+
src/code_generator/commands/_dispatch.py
|
|
22
|
+
src/code_generator/commands/_resume.py
|
|
18
23
|
src/code_generator/commands/generate.py
|
|
19
24
|
src/code_generator/commands/init.py
|
|
25
|
+
src/code_generator/commands/optimize.py
|
|
20
26
|
src/code_generator/commands/review.py
|
|
21
27
|
src/code_generator/commands/status.py
|
|
28
|
+
src/code_generator/gh/__init__.py
|
|
29
|
+
src/code_generator/gh/core.py
|
|
30
|
+
src/code_generator/gh/issues.py
|
|
31
|
+
src/code_generator/gh/labels.py
|
|
32
|
+
src/code_generator/gh/milestones.py
|
|
22
33
|
src/code_generator/orchestrator/__init__.py
|
|
34
|
+
src/code_generator/orchestrator/_comments.py
|
|
23
35
|
src/code_generator/orchestrator/cycle_loop.py
|
|
24
36
|
src/code_generator/orchestrator/phase0_complexity.py
|
|
25
37
|
src/code_generator/orchestrator/phase1_plan.py
|
|
@@ -29,6 +41,7 @@ src/code_generator/orchestrator/phase5_closure.py
|
|
|
29
41
|
src/code_generator/orchestrator/phase6_test.py
|
|
30
42
|
src/code_generator/orchestrator/phase7_commit.py
|
|
31
43
|
src/code_generator/prompts/__init__.py
|
|
44
|
+
src/code_generator/prompts/prompt-optimize-requirements.md
|
|
32
45
|
src/code_generator/prompts/prompt-phase-0-complexity.md
|
|
33
46
|
src/code_generator/prompts/prompt-phase-1-planning.md
|
|
34
47
|
src/code_generator/prompts/prompt-phase-2-issue-review.md
|
|
@@ -38,10 +51,15 @@ src/code_generator/prompts/prompt-phase-6-test.md
|
|
|
38
51
|
src/code_generator/prompts/prompt-phase-7-commit.md
|
|
39
52
|
src/code_generator/prompts/prompt-review.md
|
|
40
53
|
src/code_generator/runner/__init__.py
|
|
54
|
+
src/code_generator/runner/message_parsing.py
|
|
55
|
+
src/code_generator/runner/options.py
|
|
56
|
+
src/code_generator/runner/protocol.py
|
|
41
57
|
src/code_generator/runner/rate_limit.py
|
|
42
58
|
src/code_generator/runner/retry.py
|
|
43
59
|
src/code_generator/runner/sdk_runner.py
|
|
44
60
|
src/code_generator/runner/subprocess_runner.py
|
|
61
|
+
src/code_generator/runner/types.py
|
|
62
|
+
src/code_generator/runner/utils.py
|
|
45
63
|
src/code_generator/templates/__init__.py
|
|
46
64
|
src/code_generator/templates/angular.md
|
|
47
65
|
src/code_generator/templates/base.md
|
|
@@ -51,13 +69,26 @@ src/code_generator/templates/fullstack.md
|
|
|
51
69
|
src/code_generator/templates/nestjs.md
|
|
52
70
|
src/code_generator/templates/python-cli.md
|
|
53
71
|
tests/test_agents.py
|
|
72
|
+
tests/test_comments.py
|
|
73
|
+
tests/test_commit_message.py
|
|
54
74
|
tests/test_cycle_loop.py
|
|
75
|
+
tests/test_cycle_loop_multicycle.py
|
|
76
|
+
tests/test_delta_planning.py
|
|
77
|
+
tests/test_detect.py
|
|
78
|
+
tests/test_effort.py
|
|
55
79
|
tests/test_env.py
|
|
56
80
|
tests/test_generate.py
|
|
57
81
|
tests/test_generate_resume.py
|
|
58
82
|
tests/test_gh.py
|
|
83
|
+
tests/test_gh_labels.py
|
|
84
|
+
tests/test_gh_milestones.py
|
|
85
|
+
tests/test_gh_submodules.py
|
|
86
|
+
tests/test_git_ops.py
|
|
59
87
|
tests/test_init.py
|
|
60
88
|
tests/test_logging_setup.py
|
|
89
|
+
tests/test_message_parsing.py
|
|
90
|
+
tests/test_optimize.py
|
|
91
|
+
tests/test_options.py
|
|
61
92
|
tests/test_phase0.py
|
|
62
93
|
tests/test_phase1.py
|
|
63
94
|
tests/test_phase2.py
|
|
@@ -65,10 +96,16 @@ tests/test_phase3_4.py
|
|
|
65
96
|
tests/test_phase5.py
|
|
66
97
|
tests/test_phase6.py
|
|
67
98
|
tests/test_phase7.py
|
|
99
|
+
tests/test_phase_token_logging.py
|
|
68
100
|
tests/test_prompts.py
|
|
69
101
|
tests/test_rate_limit.py
|
|
102
|
+
tests/test_requirements_structure.py
|
|
70
103
|
tests/test_retry.py
|
|
71
104
|
tests/test_review.py
|
|
105
|
+
tests/test_runner_protocol.py
|
|
106
|
+
tests/test_runner_protocol_annotations.py
|
|
107
|
+
tests/test_runner_types.py
|
|
108
|
+
tests/test_runner_utils.py
|
|
72
109
|
tests/test_sdk_runner.py
|
|
73
110
|
tests/test_state.py
|
|
74
111
|
tests/test_status.py
|
|
@@ -11,6 +11,7 @@ import typer
|
|
|
11
11
|
import code_generator
|
|
12
12
|
from code_generator.commands.generate import generate_command
|
|
13
13
|
from code_generator.commands.init import init_command
|
|
14
|
+
from code_generator.commands.optimize import optimize_command
|
|
14
15
|
from code_generator.commands.review import review_command
|
|
15
16
|
from code_generator.commands.status import status_command
|
|
16
17
|
|
|
@@ -46,4 +47,5 @@ def root(
|
|
|
46
47
|
app.command(name="init")(init_command)
|
|
47
48
|
app.command(name="status")(status_command)
|
|
48
49
|
app.command(name="generate")(generate_command)
|
|
50
|
+
app.command(name="optimize")(optimize_command)
|
|
49
51
|
app.command(name="review")(review_command)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Re-run detection for the generate command.
|
|
2
|
+
|
|
3
|
+
Compares the current requirements.md hash against the stored hash in state to
|
|
4
|
+
decide the correct execution path on startup.
|
|
5
|
+
|
|
6
|
+
The sole exported function is a pure function (no I/O) so it is trivially
|
|
7
|
+
unit-testable.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import TYPE_CHECKING, Literal
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from code_generator import state as state_module
|
|
16
|
+
|
|
17
|
+
RunMode = Literal["first-run", "resume", "no-op", "delta"]
|
|
18
|
+
|
|
19
|
+
__all__ = ["RunMode", "detect_run_mode"]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _run_is_complete(state: state_module.State) -> bool:
|
|
23
|
+
"""Return True when the generation run is fully complete.
|
|
24
|
+
|
|
25
|
+
Single mode: complete when phase == 7 (final commit phase).
|
|
26
|
+
Multi-cycle mode: complete when every cycle has status "completed"
|
|
27
|
+
and at least one cycle exists.
|
|
28
|
+
"""
|
|
29
|
+
if state.mode == "multi-cycle":
|
|
30
|
+
return len(state.cycles) > 0 and all(c.status == "completed" for c in state.cycles)
|
|
31
|
+
return state.phase == 7
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def detect_run_mode(state: state_module.State, current_hash: str) -> RunMode:
|
|
35
|
+
"""Determine how the generate command should proceed given existing state.
|
|
36
|
+
|
|
37
|
+
This is a pure function: it reads ``state`` and ``current_hash`` and
|
|
38
|
+
returns a decision literal. It never performs I/O.
|
|
39
|
+
|
|
40
|
+
Decision rules (evaluated in order):
|
|
41
|
+
1. No stored hash → ``"first-run"`` (treat as a brand-new run)
|
|
42
|
+
2. Hashes differ → ``"delta"`` (requirements changed)
|
|
43
|
+
3. Run complete → ``"no-op"`` (nothing left to do)
|
|
44
|
+
4. Otherwise → ``"resume"`` (same requirements, incomplete run)
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
state: Loaded root state (may have requirements_hash=None for old runs).
|
|
48
|
+
current_hash: SHA-256 hex digest of the current requirements.md.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
One of ``"first-run"``, ``"resume"``, ``"no-op"``, or ``"delta"``.
|
|
52
|
+
"""
|
|
53
|
+
if state.requirements_hash is None:
|
|
54
|
+
return "first-run"
|
|
55
|
+
if state.requirements_hash != current_hash:
|
|
56
|
+
return "delta"
|
|
57
|
+
if _run_is_complete(state):
|
|
58
|
+
return "no-op"
|
|
59
|
+
return "resume"
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
"""Dispatch helpers for the generate command.
|
|
2
|
+
|
|
3
|
+
Handles resolving resume parameters from CLI flags and delegating to the
|
|
4
|
+
async orchestration pipeline. Separated from the Typer command definition
|
|
5
|
+
so both layers can be tested in isolation.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import asyncio
|
|
11
|
+
import logging
|
|
12
|
+
from typing import TYPE_CHECKING
|
|
13
|
+
|
|
14
|
+
import typer
|
|
15
|
+
|
|
16
|
+
from code_generator.commands._resume import next_start_phase, resolve_continue_multi_cycle
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
from code_generator import state as state_module
|
|
22
|
+
|
|
23
|
+
_logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
__all__ = ["dispatch_async", "dispatch_orchestrator"]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
async def _apply_delta_plan(
|
|
29
|
+
st: state_module.State,
|
|
30
|
+
project_dir: Path,
|
|
31
|
+
*,
|
|
32
|
+
runner_module: object,
|
|
33
|
+
delta_hash: str,
|
|
34
|
+
state_path: Path,
|
|
35
|
+
logger: logging.Logger,
|
|
36
|
+
) -> int | None:
|
|
37
|
+
"""Run Phase 0 and merge new cycles into state for multi-cycle delta planning.
|
|
38
|
+
|
|
39
|
+
Atomicity guarantee: state is only mutated after a successful Phase 0
|
|
40
|
+
response. If Phase 0 fails, a ``RuntimeError`` is raised and neither
|
|
41
|
+
``state.cycles`` nor ``state.requirements_hash`` is changed.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
st: Root state (mutated in place on success).
|
|
45
|
+
project_dir: Project root directory.
|
|
46
|
+
runner_module: Runner module for Phase 0 execution.
|
|
47
|
+
delta_hash: The new requirements hash to store on success.
|
|
48
|
+
state_path: Path to state.json for atomic persistence.
|
|
49
|
+
logger: Phase logger.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
ID of the first newly appended cycle, or ``None`` when the new plan
|
|
53
|
+
contains no scopes that are not already in ``state.cycles``.
|
|
54
|
+
|
|
55
|
+
Raises:
|
|
56
|
+
RuntimeError: When Phase 0 returns ``None`` (all retries exhausted).
|
|
57
|
+
"""
|
|
58
|
+
from code_generator import state as state_module
|
|
59
|
+
from code_generator.orchestrator import phase0_complexity
|
|
60
|
+
|
|
61
|
+
raw_cycles = await phase0_complexity.get_raw_cycle_plan(
|
|
62
|
+
project_dir,
|
|
63
|
+
runner_module=runner_module, # type: ignore[arg-type]
|
|
64
|
+
logger=logger,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
if raw_cycles is None:
|
|
68
|
+
raise RuntimeError(
|
|
69
|
+
"Phase 0 failed during delta planning; aborting to preserve cycle history."
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
new_cycles = state_module.append_new_cycles(st, raw_cycles)
|
|
73
|
+
|
|
74
|
+
if not new_cycles:
|
|
75
|
+
logger.info("Delta planning: no new cycles detected.")
|
|
76
|
+
st.requirements_hash = delta_hash
|
|
77
|
+
state_module.save_state(state_path, st)
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
# Extend state atomically: build new list, then assign all at once.
|
|
81
|
+
st.cycles = list(st.cycles) + new_cycles
|
|
82
|
+
st.current_cycle = new_cycles[0].id
|
|
83
|
+
st.requirements_hash = delta_hash
|
|
84
|
+
state_module.save_state(state_path, st)
|
|
85
|
+
return new_cycles[0].id
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
async def dispatch_async(
|
|
89
|
+
st: state_module.State,
|
|
90
|
+
project_dir: Path,
|
|
91
|
+
*,
|
|
92
|
+
dry_run: bool,
|
|
93
|
+
start_phase: int,
|
|
94
|
+
start_cycle: int | None,
|
|
95
|
+
mode: str,
|
|
96
|
+
delta_hash: str | None = None,
|
|
97
|
+
state_path: Path | None = None,
|
|
98
|
+
) -> None:
|
|
99
|
+
"""Async entry point for the orchestration pipeline.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
st: Root state object.
|
|
103
|
+
project_dir: Project root directory.
|
|
104
|
+
dry_run: When True, only phases 0 and 1 run.
|
|
105
|
+
start_phase: Resume from this phase (1 = full run).
|
|
106
|
+
start_cycle: Resume from this cycle (multi-cycle only).
|
|
107
|
+
mode: ``"auto"``, ``"single"``, or ``"multi-cycle"``.
|
|
108
|
+
delta_hash: When set, triggers delta planning for multi-cycle mode
|
|
109
|
+
with completed cycles (the new requirements hash to persist on
|
|
110
|
+
success). ``None`` disables delta planning.
|
|
111
|
+
state_path: Path to state.json; required when ``delta_hash`` is set.
|
|
112
|
+
"""
|
|
113
|
+
from code_generator.logging_setup import setup_phase_logger
|
|
114
|
+
from code_generator.orchestrator import cycle_loop, phase0_complexity
|
|
115
|
+
from code_generator.runner import get_runner
|
|
116
|
+
|
|
117
|
+
runner_module = get_runner()
|
|
118
|
+
logger = setup_phase_logger("generate", project_dir)
|
|
119
|
+
|
|
120
|
+
# Phase 0: determine mode when not already known.
|
|
121
|
+
if mode == "auto":
|
|
122
|
+
needs_phase0 = st.mode not in ("single", "multi-cycle")
|
|
123
|
+
else:
|
|
124
|
+
needs_phase0 = False
|
|
125
|
+
# Force the mode from the flag.
|
|
126
|
+
st.mode = mode # type: ignore[assignment]
|
|
127
|
+
|
|
128
|
+
# Delta planning: multi-cycle with completed cycles needs a merge, not a
|
|
129
|
+
# fresh Phase 0 run. Single-mode delta is handled upstream (mode reset to
|
|
130
|
+
# "unknown" forces Phase 0 to re-run via needs_phase0=True).
|
|
131
|
+
completed = [c for c in st.cycles if c.status == "completed"]
|
|
132
|
+
if delta_hash is not None and st.mode == "multi-cycle" and completed and state_path is not None:
|
|
133
|
+
first_new_id = await _apply_delta_plan(
|
|
134
|
+
st,
|
|
135
|
+
project_dir,
|
|
136
|
+
runner_module=runner_module,
|
|
137
|
+
delta_hash=delta_hash,
|
|
138
|
+
state_path=state_path,
|
|
139
|
+
logger=logger,
|
|
140
|
+
)
|
|
141
|
+
if first_new_id is None:
|
|
142
|
+
return # No new cycles — nothing to run.
|
|
143
|
+
start_cycle = first_new_id
|
|
144
|
+
start_phase = 1
|
|
145
|
+
needs_phase0 = False
|
|
146
|
+
|
|
147
|
+
if needs_phase0:
|
|
148
|
+
await phase0_complexity.run(st, project_dir, runner_module=runner_module, logger=logger)
|
|
149
|
+
|
|
150
|
+
# Dry-run: planning only (phase 0 + phase 1).
|
|
151
|
+
if dry_run:
|
|
152
|
+
from code_generator.orchestrator import phase1_plan
|
|
153
|
+
|
|
154
|
+
logger.info("--dry-run: running phase 1 (planning only).")
|
|
155
|
+
await phase1_plan.run(st, None, project_dir, runner_module=runner_module, logger=logger)
|
|
156
|
+
return
|
|
157
|
+
|
|
158
|
+
# Dispatch to cycle loop.
|
|
159
|
+
if st.mode == "multi-cycle":
|
|
160
|
+
await cycle_loop.run_multi_cycle(
|
|
161
|
+
st,
|
|
162
|
+
project_dir,
|
|
163
|
+
runner_module=runner_module,
|
|
164
|
+
logger=logger,
|
|
165
|
+
start_cycle=start_cycle,
|
|
166
|
+
start_phase=start_phase,
|
|
167
|
+
)
|
|
168
|
+
else:
|
|
169
|
+
await cycle_loop.run_single_mode(
|
|
170
|
+
st,
|
|
171
|
+
project_dir,
|
|
172
|
+
runner_module=runner_module,
|
|
173
|
+
logger=logger,
|
|
174
|
+
start_phase=start_phase,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def dispatch_orchestrator(
|
|
179
|
+
st: state_module.State,
|
|
180
|
+
project_dir: Path,
|
|
181
|
+
*,
|
|
182
|
+
dry_run: bool,
|
|
183
|
+
phase: int | None,
|
|
184
|
+
continue_: bool,
|
|
185
|
+
mode: str,
|
|
186
|
+
cycle: int | None,
|
|
187
|
+
state_path: Path,
|
|
188
|
+
requirements_hash: str | None = None,
|
|
189
|
+
) -> None:
|
|
190
|
+
"""Resolve resume parameters and dispatch the async orchestrator.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
st: Root state.
|
|
194
|
+
project_dir: Project root directory.
|
|
195
|
+
dry_run: When True, only phase 0/1 planning runs.
|
|
196
|
+
phase: Resume from this explicit phase number.
|
|
197
|
+
continue_: Resume from the last completed phase/cycle in state.
|
|
198
|
+
mode: ``"auto"``, ``"single"``, or ``"multi-cycle"``.
|
|
199
|
+
cycle: Resume from this specific cycle (multi-cycle only).
|
|
200
|
+
state_path: Path to state.json, used for atomic hash updates.
|
|
201
|
+
requirements_hash: SHA-256 digest of the current requirements.md, or
|
|
202
|
+
None when the file is absent (detection is skipped).
|
|
203
|
+
"""
|
|
204
|
+
from code_generator import state as state_module
|
|
205
|
+
|
|
206
|
+
# Re-run detection: only when no explicit resume flags override intent.
|
|
207
|
+
# --continue and --phase N express explicit user intent, so they bypass
|
|
208
|
+
# detection entirely to avoid surprising "nothing to do" responses.
|
|
209
|
+
delta_hash_for_async: str | None = None
|
|
210
|
+
if not continue_ and phase is None and requirements_hash is not None:
|
|
211
|
+
from code_generator.commands._detect import detect_run_mode
|
|
212
|
+
|
|
213
|
+
run_mode = detect_run_mode(st, requirements_hash)
|
|
214
|
+
|
|
215
|
+
if run_mode == "no-op":
|
|
216
|
+
_logger.info("nothing to do: all cycles complete, requirements unchanged")
|
|
217
|
+
typer.echo("All cycles complete. Nothing to do.")
|
|
218
|
+
return
|
|
219
|
+
|
|
220
|
+
if run_mode == "first-run":
|
|
221
|
+
st.requirements_hash = requirements_hash
|
|
222
|
+
state_module.save_state(state_path, st)
|
|
223
|
+
|
|
224
|
+
elif run_mode == "delta":
|
|
225
|
+
completed = [c for c in st.cycles if c.status == "completed"]
|
|
226
|
+
if st.mode == "multi-cycle" and completed:
|
|
227
|
+
# Defer hash update to async path — atomicity requires Phase 0
|
|
228
|
+
# to succeed before we store the new hash.
|
|
229
|
+
delta_hash_for_async = requirements_hash
|
|
230
|
+
else:
|
|
231
|
+
# Single mode or no completed cycles: treat as fresh run from Phase 0.
|
|
232
|
+
# Reset mode to "unknown" so Phase 0 re-runs and re-evaluates complexity.
|
|
233
|
+
st.mode = "unknown" # type: ignore[assignment]
|
|
234
|
+
st.requirements_hash = requirements_hash
|
|
235
|
+
state_module.save_state(state_path, st)
|
|
236
|
+
|
|
237
|
+
# Resolve effective mode (honor existing state when --mode auto).
|
|
238
|
+
effective_mode = mode
|
|
239
|
+
if mode == "auto" and st.mode in ("single", "multi-cycle"):
|
|
240
|
+
effective_mode = st.mode
|
|
241
|
+
|
|
242
|
+
# Resolve start_phase and start_cycle.
|
|
243
|
+
# Flag precedence (highest → lowest):
|
|
244
|
+
# 1. --continue → derive start_cycle + start_phase from state (last completed)
|
|
245
|
+
# 2. --phase N → explicit phase; start_cycle stays None (single-mode default)
|
|
246
|
+
# 3. --cycle N → overrides any continue-derived start_cycle; start_phase
|
|
247
|
+
# defaults to 1 unless --phase N is also given
|
|
248
|
+
# 4. (none) → fresh run from phase 1, cycle None
|
|
249
|
+
start_phase = 1
|
|
250
|
+
start_cycle: int | None = None
|
|
251
|
+
|
|
252
|
+
if continue_:
|
|
253
|
+
# Resume from the *next* phase/cycle after the last completed one.
|
|
254
|
+
if effective_mode == "multi-cycle":
|
|
255
|
+
start_cycle, start_phase = resolve_continue_multi_cycle(st)
|
|
256
|
+
else:
|
|
257
|
+
start_phase = next_start_phase(st)
|
|
258
|
+
|
|
259
|
+
elif phase is not None:
|
|
260
|
+
start_phase = phase
|
|
261
|
+
|
|
262
|
+
if cycle is not None:
|
|
263
|
+
# --cycle N overrides any continue-derived start_cycle.
|
|
264
|
+
start_cycle = cycle
|
|
265
|
+
start_phase = phase or 1
|
|
266
|
+
|
|
267
|
+
asyncio.run(
|
|
268
|
+
dispatch_async(
|
|
269
|
+
st,
|
|
270
|
+
project_dir,
|
|
271
|
+
dry_run=dry_run,
|
|
272
|
+
start_phase=start_phase,
|
|
273
|
+
start_cycle=start_cycle,
|
|
274
|
+
mode=effective_mode,
|
|
275
|
+
delta_hash=delta_hash_for_async,
|
|
276
|
+
state_path=state_path,
|
|
277
|
+
)
|
|
278
|
+
)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Resume helpers for the generate command.
|
|
2
|
+
|
|
3
|
+
Derives ``start_phase`` and ``start_cycle`` from persisted state when the user
|
|
4
|
+
passes ``--continue``. These functions are pure (no I/O) so they are easy to
|
|
5
|
+
unit-test in isolation.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
from typing import TYPE_CHECKING
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from code_generator import state as state_module
|
|
15
|
+
|
|
16
|
+
_logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
__all__ = ["next_start_phase", "resolve_continue_multi_cycle"]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def next_start_phase(st: state_module.State) -> int:
|
|
22
|
+
"""Return the phase to resume from after ``--continue``.
|
|
23
|
+
|
|
24
|
+
``state.phase`` records the last *completed* phase, so the next phase to
|
|
25
|
+
run is always ``phase + 1``. When no prior phase is recorded (fresh run),
|
|
26
|
+
start from phase 1.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
st: Root state.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
Phase number to pass as ``start_phase`` to the cycle loop.
|
|
33
|
+
"""
|
|
34
|
+
return (st.phase or 0) + 1
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def resolve_continue_multi_cycle(
|
|
38
|
+
st: state_module.State,
|
|
39
|
+
) -> tuple[int | None, int]:
|
|
40
|
+
"""Return ``(start_cycle, start_phase)`` for a ``--continue`` in multi-cycle mode.
|
|
41
|
+
|
|
42
|
+
Rules:
|
|
43
|
+
- When ``current_cycle`` is *completed* (all phases done), advance to the
|
|
44
|
+
next cycle and restart from phase 1 — the completed cycle must not be
|
|
45
|
+
re-entered.
|
|
46
|
+
- When ``current_cycle`` is still *open* (interrupted mid-cycle), resume
|
|
47
|
+
within that cycle at the next phase.
|
|
48
|
+
- When no ``current_cycle`` is recorded, return ``(None, 1)`` for a full run.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
st: Root state.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Tuple of ``(start_cycle, start_phase)`` to pass to ``run_multi_cycle``.
|
|
55
|
+
"""
|
|
56
|
+
if st.current_cycle is None:
|
|
57
|
+
return None, 1
|
|
58
|
+
|
|
59
|
+
current = next((c for c in st.cycles if c.id == st.current_cycle), None)
|
|
60
|
+
if current is not None and current.status == "completed":
|
|
61
|
+
return st.current_cycle + 1, 1
|
|
62
|
+
|
|
63
|
+
if current is not None:
|
|
64
|
+
cycle_phase = current.phase or 0
|
|
65
|
+
# Phase 7 with non-completed status → retry commit (don't advance beyond 7).
|
|
66
|
+
start_phase = cycle_phase + 1 if cycle_phase < 7 else 7
|
|
67
|
+
return st.current_cycle, start_phase
|
|
68
|
+
|
|
69
|
+
_logger.warning(
|
|
70
|
+
"No cycle record found for current_cycle=%d; defaulting to phase 1.",
|
|
71
|
+
st.current_cycle,
|
|
72
|
+
)
|
|
73
|
+
return st.current_cycle, 1
|