claude-code-generator 0.2.1__tar.gz → 0.2.2__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.
Files changed (115) hide show
  1. {claude_code_generator-0.2.1/src/claude_code_generator.egg-info → claude_code_generator-0.2.2}/PKG-INFO +1 -1
  2. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/pyproject.toml +1 -1
  3. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2/src/claude_code_generator.egg-info}/PKG-INFO +1 -1
  4. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/__init__.py +1 -1
  5. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/commands/_dispatch.py +58 -0
  6. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/tests/test_generate_resume.py +126 -0
  7. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/LICENSE +0 -0
  8. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/README.md +0 -0
  9. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/setup.cfg +0 -0
  10. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/claude_code_generator.egg-info/SOURCES.txt +0 -0
  11. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/claude_code_generator.egg-info/dependency_links.txt +0 -0
  12. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/claude_code_generator.egg-info/entry_points.txt +0 -0
  13. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/claude_code_generator.egg-info/requires.txt +0 -0
  14. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/claude_code_generator.egg-info/top_level.txt +0 -0
  15. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/agents.py +0 -0
  16. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/cli.py +0 -0
  17. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/commands/__init__.py +0 -0
  18. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/commands/_detect.py +0 -0
  19. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/commands/_resume.py +0 -0
  20. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/commands/generate.py +0 -0
  21. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/commands/init.py +0 -0
  22. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/commands/optimize.py +0 -0
  23. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/commands/review.py +0 -0
  24. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/commands/status.py +0 -0
  25. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/effort.py +0 -0
  26. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/env.py +0 -0
  27. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/gh/__init__.py +0 -0
  28. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/gh/core.py +0 -0
  29. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/gh/issues.py +0 -0
  30. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/gh/labels.py +0 -0
  31. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/gh/milestones.py +0 -0
  32. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/git_ops.py +0 -0
  33. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/logging_setup.py +0 -0
  34. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/orchestrator/__init__.py +0 -0
  35. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/orchestrator/_comments.py +0 -0
  36. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/orchestrator/cycle_loop.py +0 -0
  37. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/orchestrator/phase0_complexity.py +0 -0
  38. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/orchestrator/phase1_plan.py +0 -0
  39. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/orchestrator/phase2_review.py +0 -0
  40. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/orchestrator/phase3_4_implement.py +0 -0
  41. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/orchestrator/phase5_closure.py +0 -0
  42. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/orchestrator/phase6_test.py +0 -0
  43. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/orchestrator/phase7_commit.py +0 -0
  44. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/prompts/__init__.py +0 -0
  45. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/prompts/prompt-optimize-requirements.md +0 -0
  46. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/prompts/prompt-phase-0-complexity.md +0 -0
  47. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/prompts/prompt-phase-1-planning.md +0 -0
  48. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/prompts/prompt-phase-2-issue-review.md +0 -0
  49. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/prompts/prompt-phase-3-implementation.md +0 -0
  50. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/prompts/prompt-phase-5-final-review.md +0 -0
  51. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/prompts/prompt-phase-6-test.md +0 -0
  52. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/prompts/prompt-phase-7-commit.md +0 -0
  53. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/prompts/prompt-review.md +0 -0
  54. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/requirements_structure.py +0 -0
  55. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/runner/__init__.py +0 -0
  56. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/runner/message_parsing.py +0 -0
  57. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/runner/options.py +0 -0
  58. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/runner/protocol.py +0 -0
  59. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/runner/rate_limit.py +0 -0
  60. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/runner/retry.py +0 -0
  61. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/runner/sdk_runner.py +0 -0
  62. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/runner/subprocess_runner.py +0 -0
  63. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/runner/types.py +0 -0
  64. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/runner/utils.py +0 -0
  65. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/state.py +0 -0
  66. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/templates/__init__.py +0 -0
  67. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/templates/angular.md +0 -0
  68. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/templates/base.md +0 -0
  69. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/templates/fastapi.md +0 -0
  70. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/templates/finance.md +0 -0
  71. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/templates/fullstack.md +0 -0
  72. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/templates/nestjs.md +0 -0
  73. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/src/code_generator/templates/python-cli.md +0 -0
  74. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/tests/test_agents.py +0 -0
  75. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/tests/test_comments.py +0 -0
  76. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/tests/test_commit_message.py +0 -0
  77. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/tests/test_cycle_loop.py +0 -0
  78. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/tests/test_cycle_loop_multicycle.py +0 -0
  79. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/tests/test_delta_planning.py +0 -0
  80. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/tests/test_detect.py +0 -0
  81. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/tests/test_effort.py +0 -0
  82. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/tests/test_env.py +0 -0
  83. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/tests/test_generate.py +0 -0
  84. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/tests/test_gh.py +0 -0
  85. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/tests/test_gh_labels.py +0 -0
  86. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/tests/test_gh_milestones.py +0 -0
  87. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/tests/test_gh_submodules.py +0 -0
  88. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/tests/test_git_ops.py +0 -0
  89. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/tests/test_init.py +0 -0
  90. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/tests/test_logging_setup.py +0 -0
  91. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/tests/test_message_parsing.py +0 -0
  92. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/tests/test_optimize.py +0 -0
  93. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/tests/test_options.py +0 -0
  94. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/tests/test_phase0.py +0 -0
  95. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/tests/test_phase1.py +0 -0
  96. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/tests/test_phase2.py +0 -0
  97. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/tests/test_phase3_4.py +0 -0
  98. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/tests/test_phase5.py +0 -0
  99. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/tests/test_phase6.py +0 -0
  100. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/tests/test_phase7.py +0 -0
  101. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/tests/test_phase_token_logging.py +0 -0
  102. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/tests/test_prompts.py +0 -0
  103. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/tests/test_rate_limit.py +0 -0
  104. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/tests/test_requirements_structure.py +0 -0
  105. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/tests/test_retry.py +0 -0
  106. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/tests/test_review.py +0 -0
  107. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/tests/test_runner_protocol.py +0 -0
  108. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/tests/test_runner_protocol_annotations.py +0 -0
  109. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/tests/test_runner_types.py +0 -0
  110. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/tests/test_runner_utils.py +0 -0
  111. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/tests/test_sdk_runner.py +0 -0
  112. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/tests/test_state.py +0 -0
  113. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/tests/test_status.py +0 -0
  114. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/tests/test_subprocess_runner.py +0 -0
  115. {claude_code_generator-0.2.1 → claude_code_generator-0.2.2}/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.2.1
3
+ Version: 0.2.2
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "claude-code-generator"
7
- version = "0.2.1"
7
+ version = "0.2.2"
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.2.1
3
+ Version: 0.2.2
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
@@ -1,3 +1,3 @@
1
1
  """code-generator: orchestrator CLI for end-to-end project generation."""
2
2
 
3
- __version__ = "0.2.1"
3
+ __version__ = "0.2.2"
@@ -25,6 +25,56 @@ _logger = logging.getLogger(__name__)
25
25
  __all__ = ["dispatch_async", "dispatch_orchestrator"]
26
26
 
27
27
 
28
+ def _reset_failed_cycles_for_continue(
29
+ st: state_module.State,
30
+ *,
31
+ target_cycle: int | None,
32
+ state_path: Path,
33
+ ) -> None:
34
+ """Reset ``failed`` cycles back to ``open`` on ``--continue``.
35
+
36
+ When a cycle crashes mid-run, ``cycle_loop.run_multi_cycle`` marks it as
37
+ ``status="failed"`` and persists the exception in ``state.last_error``.
38
+ The next ``--continue`` must treat these as retryable, otherwise the user
39
+ has to hand-edit ``state.json`` every time a cycle crashes — which
40
+ defeats the whole point of ``--continue``.
41
+
42
+ When ``target_cycle`` is set (``--cycle N``), only that cycle is reset.
43
+ Otherwise every failed cycle is reset. ``state.last_error`` and
44
+ ``state.rate_limit_type`` are cleared on any successful reset so the
45
+ status line stops showing red for a resolved failure.
46
+
47
+ Args:
48
+ st: Root state (mutated in place).
49
+ target_cycle: When set, only reset this cycle id; otherwise reset all.
50
+ state_path: Path to ``state.json`` for atomic persistence.
51
+ """
52
+ from code_generator import state as _state_module
53
+
54
+ reset_ids: list[int] = []
55
+ for c in st.cycles:
56
+ if c.status != "failed":
57
+ continue
58
+ if target_cycle is not None and c.id != target_cycle:
59
+ continue
60
+ c.status = "open"
61
+ reset_ids.append(c.id)
62
+
63
+ if not reset_ids:
64
+ return
65
+
66
+ if getattr(st, "last_error", None):
67
+ st.last_error = None
68
+ if getattr(st, "rate_limit_type", None):
69
+ st.rate_limit_type = None
70
+
71
+ _state_module.save_state(state_path, st)
72
+ _logger.info(
73
+ "--continue: reset failed cycles to open: %s",
74
+ ", ".join(str(i) for i in reset_ids),
75
+ )
76
+
77
+
28
78
  async def _apply_delta_plan(
29
79
  st: state_module.State,
30
80
  project_dir: Path,
@@ -250,6 +300,14 @@ def dispatch_orchestrator(
250
300
  start_cycle: int | None = None
251
301
 
252
302
  if continue_:
303
+ # A cycle that crashed mid-run is marked "failed" by the exception
304
+ # handler in cycle_loop.run_multi_cycle. Without this reset, `--continue`
305
+ # would skip it forever because `_eligible_cycles` only returns "open"
306
+ # cycles. Semantically, `--continue` means "retry from where we
307
+ # crashed", so flip any failed cycle back to "open" and clear the
308
+ # stale error. Explicit --cycle N also benefits: reset just the target.
309
+ _reset_failed_cycles_for_continue(st, target_cycle=cycle, state_path=state_path)
310
+
253
311
  # Resume from the *next* phase/cycle after the last completed one.
254
312
  if effective_mode == "multi-cycle":
255
313
  start_cycle, start_phase = resolve_continue_multi_cycle(st)
@@ -1337,3 +1337,129 @@ class TestNoOpDetection:
1337
1337
 
1338
1338
  st_after = state_module.load_state(cg / "state.json")
1339
1339
  assert st_after.requirements_hash == new_hash
1340
+
1341
+
1342
+ # ---------------------------------------------------------------------------
1343
+ # --continue resets failed cycles back to open (retry-on-continue)
1344
+ # ---------------------------------------------------------------------------
1345
+
1346
+
1347
+ class TestContinueResetsFailedCycles:
1348
+ """Regression guard: `--continue` must retry cycles marked `status=failed`.
1349
+
1350
+ When a cycle crashes mid-run, `cycle_loop.run_multi_cycle` sets
1351
+ `cycle.status = "failed"` and persists `state.last_error`. Without the
1352
+ reset, `_eligible_cycles` filters out failed cycles and `--continue`
1353
+ prints "all eligible cycles complete" without actually running anything.
1354
+ """
1355
+
1356
+ def test_continue_resets_failed_cycle_to_open(
1357
+ self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
1358
+ ) -> None:
1359
+ """--continue flips cycles[0].status from 'failed' back to 'open' and clears last_error."""
1360
+ monkeypatch.chdir(tmp_path)
1361
+
1362
+ cg = tmp_path / ".code-generator"
1363
+ cg.mkdir(parents=True, exist_ok=True)
1364
+ st = state_module.State(
1365
+ mode="multi-cycle", # type: ignore[arg-type]
1366
+ started_at="2026-01-01T00:00:00Z",
1367
+ updated_at="2026-01-01T00:00:00Z",
1368
+ current_cycle=1,
1369
+ phase=None,
1370
+ cycles=[
1371
+ _make_cycle(cycle_id=1, phase=1, status="failed"),
1372
+ _make_cycle(cycle_id=2, phase=0, status="open"),
1373
+ ],
1374
+ )
1375
+ st.last_error = "SDK session exhausted max_turns budget"
1376
+ state_module.save_state(cg / "state.json", st)
1377
+
1378
+ captured: list[dict] = []
1379
+
1380
+ async def mock_multi(state, *_args, start_cycle=None, start_phase=1, **_kwargs):
1381
+ captured.append({"start_cycle": start_cycle, "start_phase": start_phase})
1382
+ return state
1383
+
1384
+ from code_generator.orchestrator import cycle_loop
1385
+
1386
+ monkeypatch.setattr(cycle_loop, "run_multi_cycle", mock_multi)
1387
+ _mock_phase0_single(monkeypatch)
1388
+
1389
+ result = runner.invoke(app, ["generate", "--continue"])
1390
+ assert result.exit_code == 0, result.output
1391
+
1392
+ st_after = state_module.load_state(cg / "state.json")
1393
+ assert st_after.cycles[0].status == "open"
1394
+ assert st_after.last_error is None
1395
+ assert captured == [{"start_cycle": 1, "start_phase": 2}]
1396
+
1397
+ def test_continue_resets_only_target_cycle_when_cycle_flag_set(
1398
+ self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
1399
+ ) -> None:
1400
+ """--continue --cycle 2 resets cycle 2 only, leaves cycle 1 failed."""
1401
+ monkeypatch.chdir(tmp_path)
1402
+
1403
+ cg = tmp_path / ".code-generator"
1404
+ cg.mkdir(parents=True, exist_ok=True)
1405
+ st = state_module.State(
1406
+ mode="multi-cycle", # type: ignore[arg-type]
1407
+ started_at="2026-01-01T00:00:00Z",
1408
+ updated_at="2026-01-01T00:00:00Z",
1409
+ current_cycle=2,
1410
+ phase=None,
1411
+ cycles=[
1412
+ _make_cycle(cycle_id=1, phase=3, status="failed"),
1413
+ _make_cycle(cycle_id=2, phase=4, status="failed"),
1414
+ ],
1415
+ )
1416
+ state_module.save_state(cg / "state.json", st)
1417
+
1418
+ async def mock_multi(*_args, **_kwargs):
1419
+ return None
1420
+
1421
+ from code_generator.orchestrator import cycle_loop
1422
+
1423
+ monkeypatch.setattr(cycle_loop, "run_multi_cycle", mock_multi)
1424
+ _mock_phase0_single(monkeypatch)
1425
+
1426
+ result = runner.invoke(
1427
+ app, ["generate", "--continue", "--cycle", "2", "--mode", "multi-cycle"]
1428
+ )
1429
+ assert result.exit_code == 0, result.output
1430
+
1431
+ st_after = state_module.load_state(cg / "state.json")
1432
+ assert st_after.cycles[0].status == "failed"
1433
+ assert st_after.cycles[1].status == "open"
1434
+
1435
+ def test_continue_without_failed_cycles_is_noop_on_reset(
1436
+ self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
1437
+ ) -> None:
1438
+ """--continue on a state with no failed cycles does not mutate status."""
1439
+ monkeypatch.chdir(tmp_path)
1440
+
1441
+ cg = tmp_path / ".code-generator"
1442
+ cg.mkdir(parents=True, exist_ok=True)
1443
+ st = state_module.State(
1444
+ mode="multi-cycle", # type: ignore[arg-type]
1445
+ started_at="2026-01-01T00:00:00Z",
1446
+ updated_at="2026-01-01T00:00:00Z",
1447
+ current_cycle=1,
1448
+ phase=None,
1449
+ cycles=[_make_cycle(cycle_id=1, phase=3, status="open")],
1450
+ )
1451
+ state_module.save_state(cg / "state.json", st)
1452
+
1453
+ async def mock_multi(*_args, **_kwargs):
1454
+ return None
1455
+
1456
+ from code_generator.orchestrator import cycle_loop
1457
+
1458
+ monkeypatch.setattr(cycle_loop, "run_multi_cycle", mock_multi)
1459
+ _mock_phase0_single(monkeypatch)
1460
+
1461
+ result = runner.invoke(app, ["generate", "--continue"])
1462
+ assert result.exit_code == 0, result.output
1463
+
1464
+ st_after = state_module.load_state(cg / "state.json")
1465
+ assert st_after.cycles[0].status == "open"