claude-code-generator 0.1.0__py3-none-any.whl

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 (49) hide show
  1. claude_code_generator-0.1.0.dist-info/METADATA +176 -0
  2. claude_code_generator-0.1.0.dist-info/RECORD +49 -0
  3. claude_code_generator-0.1.0.dist-info/WHEEL +5 -0
  4. claude_code_generator-0.1.0.dist-info/entry_points.txt +2 -0
  5. claude_code_generator-0.1.0.dist-info/licenses/LICENSE +21 -0
  6. claude_code_generator-0.1.0.dist-info/top_level.txt +1 -0
  7. code_generator/__init__.py +3 -0
  8. code_generator/agents.py +177 -0
  9. code_generator/cli.py +49 -0
  10. code_generator/commands/__init__.py +1 -0
  11. code_generator/commands/generate.py +252 -0
  12. code_generator/commands/init.py +72 -0
  13. code_generator/commands/review.py +117 -0
  14. code_generator/commands/status.py +83 -0
  15. code_generator/env.py +55 -0
  16. code_generator/gh.py +331 -0
  17. code_generator/logging_setup.py +73 -0
  18. code_generator/orchestrator/__init__.py +4 -0
  19. code_generator/orchestrator/cycle_loop.py +371 -0
  20. code_generator/orchestrator/phase0_complexity.py +159 -0
  21. code_generator/orchestrator/phase1_plan.py +170 -0
  22. code_generator/orchestrator/phase2_review.py +126 -0
  23. code_generator/orchestrator/phase3_4_implement.py +164 -0
  24. code_generator/orchestrator/phase5_closure.py +154 -0
  25. code_generator/orchestrator/phase6_test.py +98 -0
  26. code_generator/orchestrator/phase7_commit.py +167 -0
  27. code_generator/prompts/__init__.py +86 -0
  28. code_generator/prompts/prompt-phase-0-complexity.md +85 -0
  29. code_generator/prompts/prompt-phase-1-planning.md +209 -0
  30. code_generator/prompts/prompt-phase-2-issue-review.md +84 -0
  31. code_generator/prompts/prompt-phase-3-implementation.md +191 -0
  32. code_generator/prompts/prompt-phase-5-final-review.md +135 -0
  33. code_generator/prompts/prompt-phase-6-test.md +102 -0
  34. code_generator/prompts/prompt-phase-7-commit.md +103 -0
  35. code_generator/prompts/prompt-review.md +124 -0
  36. code_generator/runner/__init__.py +26 -0
  37. code_generator/runner/rate_limit.py +113 -0
  38. code_generator/runner/retry.py +165 -0
  39. code_generator/runner/sdk_runner.py +267 -0
  40. code_generator/runner/subprocess_runner.py +200 -0
  41. code_generator/state.py +178 -0
  42. code_generator/templates/__init__.py +1 -0
  43. code_generator/templates/angular.md +12 -0
  44. code_generator/templates/base.md +28 -0
  45. code_generator/templates/fastapi.md +12 -0
  46. code_generator/templates/finance.md +9 -0
  47. code_generator/templates/fullstack.md +24 -0
  48. code_generator/templates/nestjs.md +9 -0
  49. code_generator/templates/python-cli.md +9 -0
@@ -0,0 +1,176 @@
1
+ Metadata-Version: 2.4
2
+ Name: claude-code-generator
3
+ Version: 0.1.0
4
+ Summary: Orchestrator CLI that drives Claude Code end-to-end to generate whole projects from a requirements.md file.
5
+ Author: Silvio Baratto
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/SilvioBaratto/code-generator
8
+ Project-URL: Repository, https://github.com/SilvioBaratto/code-generator
9
+ Project-URL: Issues, https://github.com/SilvioBaratto/code-generator/issues
10
+ Keywords: claude,claude-code,code-generation,llm,cli,orchestrator
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Software Development :: Code Generators
20
+ Requires-Python: >=3.11
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: claude-agent-sdk
24
+ Requires-Dist: typer
25
+ Requires-Dist: rich
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest; extra == "dev"
28
+ Requires-Dist: pytest-asyncio; extra == "dev"
29
+ Requires-Dist: ruff; extra == "dev"
30
+ Dynamic: license-file
31
+
32
+ # code-generator
33
+
34
+ A Python CLI that orchestrates Claude Code end-to-end to generate a whole project from a `requirements.md` file. Three commands — `init`, `generate`, `review` — plus `status`. Works for any project type: Python CLI, FastAPI, Angular, NestJS, full-stack, finance, BAML/LLM.
35
+
36
+ > **Max subscription only.** This tool uses **exclusively** the Claude Max subscription. It strips every environment variable that could route a call through the API and aborts on startup if any are still present. There is no path to API credit billing — see [Safety constraints](#safety-constraints) below.
37
+
38
+ ## Install
39
+
40
+ ```bash
41
+ pipx install code-generator # recommended
42
+ # or
43
+ pip install code-generator # user-site
44
+ # or, from source
45
+ git clone git@github.com:SilvioBaratto/code-generator.git
46
+ cd code-generator && pip install -e ".[dev]"
47
+ ```
48
+
49
+ ## Authentication
50
+
51
+ ```bash
52
+ # Claude Code (Max subscription)
53
+ claude auth login
54
+
55
+ # GitHub (SSH protocol)
56
+ gh auth login -h github.com -p ssh
57
+ gh config set git_protocol ssh
58
+ ```
59
+
60
+ ## Commands
61
+
62
+ ### `code-generator init [--template TYPE] [--force]`
63
+
64
+ Scaffold `.code-generator/` in the current directory with a ready-to-fill `requirements.md`. Templates: `fastapi`, `angular`, `nestjs`, `fullstack`, `python-cli`, `finance`. Refuses to clobber an existing `.code-generator/` without `--force`.
65
+
66
+ ```bash
67
+ code-generator init --template fastapi
68
+ $EDITOR .code-generator/requirements.md
69
+ ```
70
+
71
+ ### `code-generator generate [flags]`
72
+
73
+ Read `.code-generator/requirements.md` and orchestrate Claude Code through the 0→7 phase pipeline:
74
+
75
+ | Phase | Action | Model |
76
+ |---|---|---|
77
+ | 0 | Complexity analysis (single vs multi-cycle) | Opus 4.6 |
78
+ | 1 | Planning — creates GitHub issues + milestone | Opus 4.6 |
79
+ | 2 | Per-issue review | Opus 4.6 |
80
+ | 3-4 | Implementation, fresh SDK session per issue (TDD) | Sonnet 4.6 |
81
+ | 5 | Closure + cross-module review | Opus 4.6 |
82
+ | 6 | Test suite, max 3 retries on failure | Sonnet 4.6 |
83
+ | 7 | Commit message + git add/commit/push | Haiku 4.5 |
84
+
85
+ Flags:
86
+
87
+ | Flag | Description | Default |
88
+ |---|---|---|
89
+ | `--dry-run` | Run only Phase 0 + Phase 1 (planning) | false |
90
+ | `--phase N` | Resume from phase N | — |
91
+ | `--continue` | Resume from the last completed phase (reads `state.json`) | false |
92
+ | `--mode auto\|single\|multi-cycle` | Force a mode (default `auto` runs Phase 0 first) | auto |
93
+ | `--cycle N` | Resume from cycle N (multi-cycle only) | — |
94
+
95
+ In multi-cycle mode each cycle is a GitHub Milestone and produces a committable, working increment. Each cycle starts a fresh SDK session and re-reads the committed codebase, so the model never relies on stale conversational context.
96
+
97
+ If the Max subscription hits a rate limit, the tool **pauses** — it persists the session id and `paused_until` timestamp atomically to `.code-generator/state.json`, sleeps until the window resets, and resumes with `--resume <session_id>`. A killed process will resume on its own when re-run.
98
+
99
+ ### `code-generator review [--create-issues] [--severity LEVEL]`
100
+
101
+ Run a standalone Opus 4.6 review of the current codebase against the design principles in `requirements.md` §4 (SOLID, Clean Code, TDD, YAGNI/KISS/DRY). With `--create-issues`, every finding becomes a GitHub Issue.
102
+
103
+ ### `code-generator status`
104
+
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 the last error.
106
+
107
+ ## Safety constraints
108
+
109
+ The tool enforces eight non-negotiables (see `CLAUDE.md` for the full list):
110
+
111
+ 1. **Max subscription only, zero API credits.** Before any SDK or subprocess call, the tool strips `ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN`, `ANTHROPIC_BEDROCK_API_KEY`, `ANTHROPIC_VERTEX_PROJECT_ID`, `CLAUDE_CODE_USE_BEDROCK`, `CLAUDE_CODE_USE_VERTEX` from the environment. A startup check refuses to run if any are present.
112
+ 2. **Never `--bare`.** The CLI flag `--bare` skips OAuth and forces API credits — it is **never** passed by this tool. If Anthropic ever makes `--bare` the default for `claude -p`, pin the CLI version or pass the opposite flag explicitly.
113
+ 3. **YOLO mode always.** Every SDK call uses `permission_mode="bypassPermissions"`; the subprocess fallback uses `--dangerously-skip-permissions`.
114
+ 4. **Overage protection.** On every `RateLimitEvent`, the tool checks `info.overage_status` and aborts immediately if it is anything other than `None` or `"disabled"`. Reference incident: [anthropics/claude-code#37686](https://github.com/anthropics/claude-code/issues/37686) — a user burned $1,800 in two days because billing overage was silently enabled.
115
+ 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
+ 6. **Fresh SDK session per issue.** The implementation phase never reuses conversational context across issues — each issue is a `/clear`-equivalent fresh session.
117
+ 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; Haiku 4.5 for phase 7's commit message.
119
+
120
+ ## Troubleshooting
121
+
122
+ **Startup fails with "dangerous environment variables present".** A previous shell session exported `ANTHROPIC_API_KEY` (or another billing-routing variable). `unset` it and try again — the tool is intentionally strict so a stray export never costs you money.
123
+
124
+ **Rate limit reached.** Run `code-generator status` to see how many minutes remain on the current pause. The next `code-generator generate --continue` will wait out the rest of the window and resume the same session automatically.
125
+
126
+ **`gh issue create` fails.** Confirm `gh auth status` reports an authenticated SSH session for `github.com`, and that the current directory is inside a repo with a default remote configured.
127
+
128
+ **Tests don't pass after Phase 6.** The orchestrator skips Phase 7 (commit) when the test runner reports persistent failures. Fix the failing tests manually, then run `code-generator generate --continue` to re-enter from where it stopped.
129
+
130
+ ## Project layout
131
+
132
+ ```
133
+ src/code_generator/
134
+ ├── cli.py # Typer entry point: init, generate, review, status
135
+ ├── env.py # DANGEROUS_VARS, build_agent_env, assert_safe_environment
136
+ ├── state.py # State dataclasses + atomic load/save
137
+ ├── prompts/ # Packaged prompt-phase-*.md files + load_prompt
138
+ ├── templates/ # Project templates for `init`
139
+ ├── logging_setup.py # Per-phase log files under .code-generator/logs/
140
+ ├── gh.py # Subprocess wrapper for issues, milestones, labels
141
+ ├── agents.py # Tech-stack detector + agent selection matrix
142
+ ├── runner/
143
+ │ ├── sdk_runner.py # claude-agent-sdk wrapper, RateLimitEvent handling
144
+ │ ├── subprocess_runner.py # CLI fallback, stream-json parser
145
+ │ ├── rate_limit.py # Wait-and-resume main loop
146
+ │ └── retry.py # Backoff + circuit breaker
147
+ ├── orchestrator/
148
+ │ ├── phase0_complexity.py # single vs multi-cycle decision
149
+ │ ├── phase1_plan.py # milestone + issues
150
+ │ ├── phase2_review.py # per-issue review
151
+ │ ├── phase3_4_implement.py # TDD loop, fresh session per issue
152
+ │ ├── phase5_closure.py # closure + fix-issue feedback loop
153
+ │ ├── phase6_test.py # test runner, max 3 retries
154
+ │ ├── phase7_commit.py # Haiku commit message + push with rebase retry
155
+ │ └── cycle_loop.py # multi-cycle driver
156
+ └── commands/ # init, status, generate, review (Typer commands)
157
+ ```
158
+
159
+ ## Development
160
+
161
+ ```bash
162
+ git clone git@github.com:SilvioBaratto/code-generator.git
163
+ cd code-generator
164
+ python -m venv .venv && source .venv/bin/activate
165
+ pip install -e ".[dev]"
166
+ pytest -q
167
+ ruff check src tests
168
+ ```
169
+
170
+ ## Reference
171
+
172
+ The full specification lives in [`requirements.md`](requirements.md). Each prompt file (`prompt-phase-*.md`, `prompt-review.md`) is loaded verbatim by the orchestrator with `{NAME}` placeholders substituted at runtime — edit the prompts there, not inline in Python.
173
+
174
+ ## License
175
+
176
+ MIT.
@@ -0,0 +1,49 @@
1
+ claude_code_generator-0.1.0.dist-info/licenses/LICENSE,sha256=Pmk91Ef-CmCxp3mvYU1DWeLRGKEC3HFitMjivm45BIY,1071
2
+ code_generator/__init__.py,sha256=_ZMXTePzmM8wjXWqBXJ1w9EezQ3zEOZhdbwAC1qiCfc,97
3
+ code_generator/agents.py,sha256=6D-BORzWkHYs2V4O0Z4RK8Hwsgkn5zqD-ZyWBKuOjX0,5251
4
+ code_generator/cli.py,sha256=JMU7wVpztnavkTkoGXrvEmMqOuEuwaxa-WH-wuWrANk,1244
5
+ code_generator/env.py,sha256=e-wsII0H7IB0rwJB4SlqmpfSG6asqBLA7rpjCU2fpBQ,1760
6
+ code_generator/gh.py,sha256=Iw3mIPNZ6l8gqLUMjwbAcR9bD_EKLz7im1ajpAniYsY,8951
7
+ code_generator/logging_setup.py,sha256=pu5c8I4BrFlSU80QMDVUVKf67q_UwGtQRe8hYI975fc,2295
8
+ code_generator/state.py,sha256=O_qd7eSMpd2J9KtkHobDC6VICCjyViSW5K32DGSwYHU,5220
9
+ code_generator/commands/__init__.py,sha256=UgM_pau4ln3VOxO6p3ElNrFF_kGxpNNYGlw04FHt1ho,64
10
+ code_generator/commands/generate.py,sha256=2TP6X94gmBNrZURzDwIftA6PoGq2PDfgYCyGgl9ax7c,7611
11
+ code_generator/commands/init.py,sha256=9faehMIm_5F4MytOcej0JjFzduVhz-oVc3bb1_-1_Nk,2110
12
+ code_generator/commands/review.py,sha256=U0c_xiMLHYgfVwiPTOGXgyvNDWj7m0y1xO5JImuI7UY,3434
13
+ code_generator/commands/status.py,sha256=T7-kf9EsrP9zOOGrR_9BqnE036J9px3mRabZ6LJxzr0,2566
14
+ code_generator/orchestrator/__init__.py,sha256=B_bjMB96SZPeNAJ07DSNY25c8UDMz_eqqsel-QtU_C4,129
15
+ code_generator/orchestrator/cycle_loop.py,sha256=LbM7mrRWAL2OWLe0WbaFa2c4tsAWs2uNaVONj9Tws7s,12971
16
+ code_generator/orchestrator/phase0_complexity.py,sha256=D0reZQMQJqGg1V7kNEA52TUZ497gcOMh0FCVAWYyd5o,4983
17
+ code_generator/orchestrator/phase1_plan.py,sha256=ZwqwlHf10Ay6wvAnwRTI6lqQAhkSF2biBoKVDLvGMWs,5433
18
+ code_generator/orchestrator/phase2_review.py,sha256=_OGWi11pzt9AHSvYk0uBjeWt52C3KRyZI0eUI3-S1Pw,3937
19
+ code_generator/orchestrator/phase3_4_implement.py,sha256=iLoOdq9bMbRe9st2BZnk6LESsyGJKTeYpcmB6vW9mC8,5523
20
+ code_generator/orchestrator/phase5_closure.py,sha256=Ak6RSVRop5b4D8NWqeBZ06y5DZRQHAOMYtax9kMC2fE,4841
21
+ code_generator/orchestrator/phase6_test.py,sha256=3leV1K3vGP9arfretKPZg4d-ApWIDR5s_TNMvR5f-1U,2924
22
+ code_generator/orchestrator/phase7_commit.py,sha256=HzZt40s-W9vbaolmpo7mAXWE624ei1tByM_7bnKNQ3k,5052
23
+ code_generator/prompts/__init__.py,sha256=uME1HtGcZQuGahcCRdoN2CmVBk7SIfS6U1PdIYJN_ZY,3118
24
+ code_generator/prompts/prompt-phase-0-complexity.md,sha256=ffZMcqJMeXPUjKVTfLVM_Pj_NF7ofEVf6fa9SWAJp2A,4290
25
+ code_generator/prompts/prompt-phase-1-planning.md,sha256=aRILIz162x10TXsk2Zk4-apbbJBgdkizibvI2iX41OE,11523
26
+ code_generator/prompts/prompt-phase-2-issue-review.md,sha256=xvn1VSzV19mgEZrZG5D2V-1HUfDv9ZTjFf_BOiQCqcE,3400
27
+ code_generator/prompts/prompt-phase-3-implementation.md,sha256=FEqH6b5k-jZblYt2ydAIdigoipsBxX63ExkgrxC_moE,10955
28
+ code_generator/prompts/prompt-phase-5-final-review.md,sha256=kU8Mb3YfiK5c07G5MEoHfNZu68C9697Uv5lqZZMAs7A,6047
29
+ code_generator/prompts/prompt-phase-6-test.md,sha256=8St1Z_b5A38oZkd2Zq8OZRmHZ_tFyW1AgCbRbd3ciC8,4799
30
+ code_generator/prompts/prompt-phase-7-commit.md,sha256=yx7d-DaJgoyYYcpf3gnUIvcXoZQBch-BMdyHY_zB9-c,3057
31
+ code_generator/prompts/prompt-review.md,sha256=KP3c2QpLNpv4RvIuMVJAL-YU-JZkJ9MvcY3C1HHCuLM,4441
32
+ code_generator/runner/__init__.py,sha256=kZ5vJ0DfHOVufBgni7_bxKfHOeu6npx7XGDTOpP1-Nc,662
33
+ code_generator/runner/rate_limit.py,sha256=7oKuRMEkRrjp-rXFxxn7Fj5hNqdv9tHkMPOHElDYCZ8,3882
34
+ code_generator/runner/retry.py,sha256=j4O9uRhm_DMsd1O0eyJyuWqOIc-_uFL0HyV1-_qkIZo,5621
35
+ code_generator/runner/sdk_runner.py,sha256=5pgmiENy_nee2VMivGnRlEBUUsSjndVJuTmXWfFw6NU,9353
36
+ code_generator/runner/subprocess_runner.py,sha256=E3LK_jLT50qwBVh5NDWubqN_neA13zphBTB2HNjYxJU,6571
37
+ code_generator/templates/__init__.py,sha256=HbGi1Gw9KI0QWKlYT-PGPVcQF4YfQzE35V07F7BIOlM,48
38
+ code_generator/templates/angular.md,sha256=ey8aQJSaWPKNhue-ZOIzZygpOw3udRVFTThxE21doG4,306
39
+ code_generator/templates/base.md,sha256=CsliWXVVHGg_HfK0Ls2F6zocYAsHQPt4uVPRPkzekF4,792
40
+ code_generator/templates/fastapi.md,sha256=N1loHS9IWGOmvMA2Op-EY1Ys4Igpt2sxsIei0YV3qa0,304
41
+ code_generator/templates/finance.md,sha256=sqfS0HQJB05sSsWACtj5JPDm2GGNxd3yEOw9G3y7faE,222
42
+ code_generator/templates/fullstack.md,sha256=ub6pfQU2Cz9x29BJExgxMKb_c-9ev4briEzI0fYON8g,610
43
+ code_generator/templates/nestjs.md,sha256=-ENYbwCOScFJ-9Tvl8R0QEfLDe9bj7YLgoW_HIHVKhc,182
44
+ code_generator/templates/python-cli.md,sha256=g6pcznCbc1f11LeKuEnGu74YHM-qCgtYlza9j5QuukU,200
45
+ claude_code_generator-0.1.0.dist-info/METADATA,sha256=1Dzr76VnIWs9C0tbnGQ8BTvexodWdFhI_GLidoDBTJ8,9673
46
+ claude_code_generator-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
47
+ claude_code_generator-0.1.0.dist-info/entry_points.txt,sha256=Tg2EXnr2LvXDTR5_9ArK5HUyUCzLIycIjNbx3I2lmw4,58
48
+ claude_code_generator-0.1.0.dist-info/top_level.txt,sha256=FYLzn8l0Cgl2tARhLjBAQNS3manGG-zM_rd_1v7C8HQ,15
49
+ claude_code_generator-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ code-generator = code_generator.cli:app
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Silvio Baratto
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ code_generator
@@ -0,0 +1,3 @@
1
+ """code-generator: orchestrator CLI for end-to-end project generation."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,177 @@
1
+ """Tech-stack detector and agent selection matrix (requirements.md §7).
2
+
3
+ Parses the ``## Tech Stack`` section of a requirements file and returns
4
+ an :class:`AgentSelection` dataclass describing which agents and skills
5
+ to activate for the project.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from dataclasses import dataclass
11
+ from typing import TYPE_CHECKING
12
+
13
+ if TYPE_CHECKING:
14
+ from pathlib import Path
15
+
16
+
17
+ @dataclass(frozen=True)
18
+ class AgentSelection:
19
+ """Immutable record of chosen agents and skills for a project.
20
+
21
+ Attributes:
22
+ primary: Tuple of primary agent names to assign as implementers.
23
+ support: Tuple of support/advisory agent names.
24
+ skills: Tuple of skill names to auto-load.
25
+ """
26
+
27
+ primary: tuple[str, ...]
28
+ support: tuple[str, ...]
29
+ skills: tuple[str, ...]
30
+
31
+
32
+ # ---------------------------------------------------------------------------
33
+ # Fallback / defaults
34
+ # ---------------------------------------------------------------------------
35
+
36
+ _GENERIC_FALLBACK = AgentSelection(
37
+ primary=("python-pro",),
38
+ support=(),
39
+ skills=("solid-principles",),
40
+ )
41
+
42
+ # ---------------------------------------------------------------------------
43
+ # Matrix definitions — ordered from most-specific to least-specific
44
+ # ---------------------------------------------------------------------------
45
+
46
+ _ANGULAR_FASTAPI = AgentSelection(
47
+ primary=("frontend-developer", "python-pro", "fastapi-expert-agent"),
48
+ support=("typescript-pro", "tailwind-patterns", "ui-ux-designer", "supabase-connection-expert"),
49
+ skills=("solid-principles", "python-expert"),
50
+ )
51
+
52
+ _ANGULAR_NESTJS = AgentSelection(
53
+ primary=("frontend-developer", "nestjs-expert"),
54
+ support=("typescript-pro", "tailwind-patterns", "ui-ux-designer", "supabase-connection-expert"),
55
+ skills=("solid-principles",),
56
+ )
57
+
58
+ _ANGULAR_ONLY = AgentSelection(
59
+ primary=("frontend-developer",),
60
+ support=("typescript-pro", "tailwind-patterns", "ui-ux-designer"),
61
+ skills=("solid-principles",),
62
+ )
63
+
64
+ _FASTAPI = AgentSelection(
65
+ primary=("python-pro", "fastapi-expert-agent"),
66
+ support=("supabase-connection-expert",),
67
+ skills=("solid-principles", "python-expert"),
68
+ )
69
+
70
+ _NESTJS = AgentSelection(
71
+ primary=("nestjs-expert",),
72
+ support=("supabase-connection-expert",),
73
+ skills=("solid-principles",),
74
+ )
75
+
76
+ _PORTFOLIO = AgentSelection(
77
+ primary=("riskfolio-expert", "python-pro"),
78
+ support=(),
79
+ skills=("skfolio", "yfinance"),
80
+ )
81
+
82
+ _BAML = AgentSelection(
83
+ primary=("baml-expert-agent", "python-pro"),
84
+ support=(),
85
+ skills=("solid-principles",),
86
+ )
87
+
88
+ _PYTHON_CLI = AgentSelection(
89
+ primary=("python-pro",),
90
+ support=(),
91
+ skills=("solid-principles", "python-expert"),
92
+ )
93
+
94
+
95
+ # ---------------------------------------------------------------------------
96
+ # Public API
97
+ # ---------------------------------------------------------------------------
98
+
99
+
100
+ def _extract_tech_stack_block(text: str) -> str:
101
+ """Return the text between ``## Tech Stack`` and the next ``## `` heading.
102
+
103
+ Args:
104
+ text: Full content of a requirements file.
105
+
106
+ Returns:
107
+ Lowercased content of the Tech Stack section, or an empty string
108
+ when the section is absent.
109
+ """
110
+ marker = "## tech stack"
111
+ lower = text.lower()
112
+ start = lower.find(marker)
113
+ if start == -1:
114
+ return ""
115
+
116
+ block_start = start + len(marker)
117
+ # Find the next level-2 heading after the marker.
118
+ next_heading = lower.find("\n## ", block_start)
119
+ block = lower[block_start:] if next_heading == -1 else lower[block_start:next_heading]
120
+
121
+ return block
122
+
123
+
124
+ def detect(requirements_path: Path) -> AgentSelection:
125
+ """Parse a requirements file and return the matching AgentSelection.
126
+
127
+ Detection follows the §7 matrix in requirements.md. Full-stack
128
+ combos are checked before single-stack patterns. Falls back to the
129
+ generic ``python-pro`` + ``solid-principles`` selection when the file
130
+ is missing or no keyword matches.
131
+
132
+ Args:
133
+ requirements_path: Path to a ``requirements.md`` file (need not exist).
134
+
135
+ Returns:
136
+ The most-specific :class:`AgentSelection` that matches the stack,
137
+ or the generic fallback when no match is found.
138
+ """
139
+ if not requirements_path.exists():
140
+ return _GENERIC_FALLBACK
141
+
142
+ text = requirements_path.read_text(encoding="utf-8")
143
+ block = _extract_tech_stack_block(text)
144
+
145
+ if not block:
146
+ return _GENERIC_FALLBACK
147
+
148
+ has_angular = "angular" in block or " ng " in block or " ng," in block
149
+ has_fastapi = "fastapi" in block or "python api" in block
150
+ has_nestjs = "nestjs" in block or "nest" in block
151
+
152
+ # Full-stack combos checked first (most specific).
153
+ if has_angular and has_fastapi:
154
+ return _ANGULAR_FASTAPI
155
+
156
+ if has_angular and has_nestjs:
157
+ return _ANGULAR_NESTJS
158
+
159
+ if has_angular:
160
+ return _ANGULAR_ONLY
161
+
162
+ if has_fastapi:
163
+ return _FASTAPI
164
+
165
+ if has_nestjs:
166
+ return _NESTJS
167
+
168
+ if "portfolio" in block or "riskfolio" in block or "skfolio" in block:
169
+ return _PORTFOLIO
170
+
171
+ if "baml" in block or "llm function" in block:
172
+ return _BAML
173
+
174
+ if "typer" in block or "click" in block or "python cli" in block:
175
+ return _PYTHON_CLI
176
+
177
+ return _GENERIC_FALLBACK
code_generator/cli.py ADDED
@@ -0,0 +1,49 @@
1
+ """Entry-point CLI for code-generator.
2
+
3
+ Exposes a Typer application with --version and subcommands:
4
+ init, status, generate, review.
5
+ """
6
+
7
+ from typing import Annotated
8
+
9
+ import typer
10
+
11
+ import code_generator
12
+ from code_generator.commands.generate import generate_command
13
+ from code_generator.commands.init import init_command
14
+ from code_generator.commands.review import review_command
15
+ from code_generator.commands.status import status_command
16
+
17
+ app = typer.Typer(
18
+ name="code-generator",
19
+ help="Orchestrate Claude Code to generate whole projects from a requirements.md.",
20
+ no_args_is_help=True,
21
+ )
22
+
23
+
24
+ def _version_callback(value: bool) -> None:
25
+ if value:
26
+ typer.echo(f"code-generator {code_generator.__version__}")
27
+ raise typer.Exit()
28
+
29
+
30
+ @app.callback()
31
+ def root(
32
+ version: Annotated[
33
+ bool | None,
34
+ typer.Option(
35
+ "--version",
36
+ "-V",
37
+ help="Show version and exit.",
38
+ callback=_version_callback,
39
+ is_eager=True,
40
+ ),
41
+ ] = None,
42
+ ) -> None:
43
+ """code-generator CLI root."""
44
+
45
+
46
+ app.command(name="init")(init_command)
47
+ app.command(name="status")(status_command)
48
+ app.command(name="generate")(generate_command)
49
+ app.command(name="review")(review_command)
@@ -0,0 +1 @@
1
+ """Typer command implementations, one module per subcommand."""