devcrew 0.1.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.
- devcrew-0.1.0/.gitignore +11 -0
- devcrew-0.1.0/CHANGELOG.md +32 -0
- devcrew-0.1.0/LICENSE +21 -0
- devcrew-0.1.0/PKG-INFO +143 -0
- devcrew-0.1.0/README.md +114 -0
- devcrew-0.1.0/devcrew/__init__.py +3 -0
- devcrew-0.1.0/devcrew/agents.py +80 -0
- devcrew-0.1.0/devcrew/cli.py +71 -0
- devcrew-0.1.0/devcrew/crew.py +33 -0
- devcrew-0.1.0/devcrew/schemas.py +66 -0
- devcrew-0.1.0/devcrew/tasks.py +74 -0
- devcrew-0.1.0/main.py +14 -0
- devcrew-0.1.0/pyproject.toml +58 -0
- devcrew-0.1.0/sample_output/01_plan.json +31 -0
- devcrew-0.1.0/sample_output/02_code.py +64 -0
- devcrew-0.1.0/sample_output/03_test_report.json +54 -0
- devcrew-0.1.0/sample_output/04_review.json +23 -0
- devcrew-0.1.0/tests/conftest.py +7 -0
- devcrew-0.1.0/tests/test_cli.py +45 -0
- devcrew-0.1.0/tests/test_crew.py +53 -0
- devcrew-0.1.0/tests/test_schemas.py +55 -0
devcrew-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here.
|
|
4
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project uses
|
|
5
|
+
[Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.1.0] - 2026-10-21
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- Installable package with a `devcrew` command (`pip install -e .`).
|
|
13
|
+
- `--output-dir` and `--model` options, plus `--version`.
|
|
14
|
+
- Unit tests that check pipeline wiring and validate `sample_output/` against the schemas,
|
|
15
|
+
without calling an LLM.
|
|
16
|
+
- CI on Python 3.10–3.13 (lint, tests, package build) and a PyPI release workflow.
|
|
17
|
+
- MIT license, contributing guide, security policy, issue and PR templates.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- Schemas now constrain QA verdicts, review severities, review verdicts and 1–10 scores, so
|
|
21
|
+
malformed agent output is rejected instead of passed downstream.
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
- `--output-dir` refuses absolute paths and `..`. CrewAI only writes to relative paths and would
|
|
25
|
+
otherwise silently turn `/tmp/out` into `./tmp/out`.
|
|
26
|
+
|
|
27
|
+
## 0.0.1 - 2026-08-18
|
|
28
|
+
|
|
29
|
+
- First version: Project Manager → Developer → QA Tester → Code Reviewer pipeline on CrewAI.
|
|
30
|
+
|
|
31
|
+
[Unreleased]: https://github.com/agrvasu05/DevCrew/compare/v0.1.0...HEAD
|
|
32
|
+
[0.1.0]: https://github.com/agrvasu05/DevCrew/releases/tag/v0.1.0
|
devcrew-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vasu Agrawal
|
|
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.
|
devcrew-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: devcrew
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: An AI software team in a pipeline: four agents plan, code, test and review a project from a one-line idea.
|
|
5
|
+
Project-URL: Homepage, https://github.com/agrvasu05/DevCrew
|
|
6
|
+
Project-URL: Issues, https://github.com/agrvasu05/DevCrew/issues
|
|
7
|
+
Project-URL: Changelog, https://github.com/agrvasu05/DevCrew/blob/main/CHANGELOG.md
|
|
8
|
+
Author: Vasu Agrawal
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: agents,ai,code-generation,code-review,crewai,llm,multi-agent
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
21
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
22
|
+
Requires-Python: <3.14,>=3.10
|
|
23
|
+
Requires-Dist: crewai>=1.0.0
|
|
24
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
27
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# DevCrew — Multi-Agent AI Orchestration System
|
|
31
|
+
|
|
32
|
+
[](https://github.com/agrvasu05/DevCrew/actions/workflows/ci.yml)
|
|
33
|
+
[](LICENSE)
|
|
34
|
+

|
|
35
|
+
|
|
36
|
+
An AI software team in a pipeline. You give it a one-line project idea; four specialised LLM agents plan it, code it, test it, and review it — each handing structured output to the next.
|
|
37
|
+
|
|
38
|
+
Built with **CrewAI** on the OpenAI API.
|
|
39
|
+
|
|
40
|
+
## How it works
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
idea ─→ ┌─────────────────┐ ┌─────────────────┐
|
|
44
|
+
│ Project Manager │ ───→ │ Developer │
|
|
45
|
+
│ plan.json │ │ code.py │
|
|
46
|
+
└─────────────────┘ └───────┬─────────┘
|
|
47
|
+
│
|
|
48
|
+
▼
|
|
49
|
+
┌─────────────────┐ ┌─────────────────┐
|
|
50
|
+
│ Code Reviewer │ ←─── │ QA Tester │
|
|
51
|
+
│ review.json │ │ test_report.json│
|
|
52
|
+
└─────────────────┘ └─────────────────┘
|
|
53
|
+
(sees code + QA report)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
1. **Project Manager** turns the idea into a 3–6 task plan (JSON).
|
|
57
|
+
2. **Developer** implements the plan as one runnable Python file.
|
|
58
|
+
3. **QA Tester** designs edge-case tests against the code and honestly marks which would fail (JSON).
|
|
59
|
+
4. **Code Reviewer** reads the code *and* the QA report, scores it, and gives a verdict (JSON).
|
|
60
|
+
|
|
61
|
+
## Design decisions
|
|
62
|
+
|
|
63
|
+
- **Role-scoped context, not one giant prompt.** Each agent only receives what it needs (the developer sees the plan, not the user's raw message). Narrow prompts hallucinate less and stay on task.
|
|
64
|
+
- **Structured JSON outputs via Pydantic.** Every non-code output is bound to a schema (`devcrew/schemas.py`), so one agent's output is machine-parseable input for the next — no regex, no parsing failures.
|
|
65
|
+
- **Sequential process.** A software pipeline has a natural order; sequential orchestration keeps the run deterministic and debuggable. Every stage is written to `output/` so you can inspect exactly where a run went wrong.
|
|
66
|
+
- **Adversarial QA.** The tester is prompted to be sceptical and mark failures honestly, and the reviewer sees the QA report — so the review verdict is grounded in found bugs, not vibes.
|
|
67
|
+
|
|
68
|
+
## Run it
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
git clone https://github.com/agrvasu05/DevCrew && cd DevCrew
|
|
72
|
+
pip install -e .
|
|
73
|
+
cp .env.example .env # add your OPENAI_API_KEY
|
|
74
|
+
|
|
75
|
+
devcrew "Build a CLI expense tracker with categories and monthly totals"
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
`python main.py "..."` still works if you'd rather not install the package.
|
|
79
|
+
|
|
80
|
+
| Option | Default | What it does |
|
|
81
|
+
|---|---|---|
|
|
82
|
+
| `-o`, `--output-dir` | `output` | Where results go (a path relative to the current directory) |
|
|
83
|
+
| `-m`, `--model` | `$MODEL` or `gpt-4o-mini` | Which LLM the agents use |
|
|
84
|
+
| `--version` | | Print the version |
|
|
85
|
+
|
|
86
|
+
Outputs land in the output directory:
|
|
87
|
+
|
|
88
|
+
| File | Agent | Contents |
|
|
89
|
+
|---|---|---|
|
|
90
|
+
| `01_plan.json` | Project Manager | Task breakdown |
|
|
91
|
+
| `02_code.py` | Developer | Runnable Python file |
|
|
92
|
+
| `03_test_report.json` | QA Tester | Test cases + bugs found |
|
|
93
|
+
| `04_review.json` | Code Reviewer | Scores, comments, verdict |
|
|
94
|
+
|
|
95
|
+
A full example run is committed in [`sample_output/`](sample_output/).
|
|
96
|
+
|
|
97
|
+
> DevCrew never runs the code it generates. Read `02_code.py` before you execute it.
|
|
98
|
+
|
|
99
|
+
## Development
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
pip install -e ".[dev]"
|
|
103
|
+
pytest # no API key or network needed
|
|
104
|
+
ruff check .
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The tests check how the pipeline is wired (which agent sees which output) and validate
|
|
108
|
+
`sample_output/` against the schemas, without calling an LLM. CI runs them on Python 3.10–3.13.
|
|
109
|
+
|
|
110
|
+
## Roadmap
|
|
111
|
+
|
|
112
|
+
- Feed the reviewer's verdict back to the Developer for a fix-up round when changes are requested
|
|
113
|
+
- Actually run the generated code against the QA test cases in a sandbox
|
|
114
|
+
- Support projects that span more than one file
|
|
115
|
+
- A small eval set of project ideas to catch prompt regressions in CI
|
|
116
|
+
|
|
117
|
+
Ideas and PRs are welcome — see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
118
|
+
|
|
119
|
+
## Stack
|
|
120
|
+
|
|
121
|
+
Python · CrewAI · OpenAI API · Pydantic
|
|
122
|
+
|
|
123
|
+
## Project structure
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
devcrew/
|
|
127
|
+
├── main.py # thin wrapper around devcrew.cli
|
|
128
|
+
├── devcrew/
|
|
129
|
+
│ ├── cli.py # `devcrew` command-line entry point
|
|
130
|
+
│ ├── agents.py # 4 agents with role-scoped prompts
|
|
131
|
+
│ ├── tasks.py # pipeline tasks + context hand-offs
|
|
132
|
+
│ ├── schemas.py # Pydantic schemas for structured JSON
|
|
133
|
+
│ └── crew.py # crew assembly (sequential process)
|
|
134
|
+
├── sample_output/ # committed example run
|
|
135
|
+
├── tests/ # offline unit tests
|
|
136
|
+
├── pyproject.toml
|
|
137
|
+
├── requirements.txt
|
|
138
|
+
└── .env.example
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## License
|
|
142
|
+
|
|
143
|
+
[MIT](LICENSE) © Vasu Agrawal
|
devcrew-0.1.0/README.md
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# DevCrew — Multi-Agent AI Orchestration System
|
|
2
|
+
|
|
3
|
+
[](https://github.com/agrvasu05/DevCrew/actions/workflows/ci.yml)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
An AI software team in a pipeline. You give it a one-line project idea; four specialised LLM agents plan it, code it, test it, and review it — each handing structured output to the next.
|
|
8
|
+
|
|
9
|
+
Built with **CrewAI** on the OpenAI API.
|
|
10
|
+
|
|
11
|
+
## How it works
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
idea ─→ ┌─────────────────┐ ┌─────────────────┐
|
|
15
|
+
│ Project Manager │ ───→ │ Developer │
|
|
16
|
+
│ plan.json │ │ code.py │
|
|
17
|
+
└─────────────────┘ └───────┬─────────┘
|
|
18
|
+
│
|
|
19
|
+
▼
|
|
20
|
+
┌─────────────────┐ ┌─────────────────┐
|
|
21
|
+
│ Code Reviewer │ ←─── │ QA Tester │
|
|
22
|
+
│ review.json │ │ test_report.json│
|
|
23
|
+
└─────────────────┘ └─────────────────┘
|
|
24
|
+
(sees code + QA report)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
1. **Project Manager** turns the idea into a 3–6 task plan (JSON).
|
|
28
|
+
2. **Developer** implements the plan as one runnable Python file.
|
|
29
|
+
3. **QA Tester** designs edge-case tests against the code and honestly marks which would fail (JSON).
|
|
30
|
+
4. **Code Reviewer** reads the code *and* the QA report, scores it, and gives a verdict (JSON).
|
|
31
|
+
|
|
32
|
+
## Design decisions
|
|
33
|
+
|
|
34
|
+
- **Role-scoped context, not one giant prompt.** Each agent only receives what it needs (the developer sees the plan, not the user's raw message). Narrow prompts hallucinate less and stay on task.
|
|
35
|
+
- **Structured JSON outputs via Pydantic.** Every non-code output is bound to a schema (`devcrew/schemas.py`), so one agent's output is machine-parseable input for the next — no regex, no parsing failures.
|
|
36
|
+
- **Sequential process.** A software pipeline has a natural order; sequential orchestration keeps the run deterministic and debuggable. Every stage is written to `output/` so you can inspect exactly where a run went wrong.
|
|
37
|
+
- **Adversarial QA.** The tester is prompted to be sceptical and mark failures honestly, and the reviewer sees the QA report — so the review verdict is grounded in found bugs, not vibes.
|
|
38
|
+
|
|
39
|
+
## Run it
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
git clone https://github.com/agrvasu05/DevCrew && cd DevCrew
|
|
43
|
+
pip install -e .
|
|
44
|
+
cp .env.example .env # add your OPENAI_API_KEY
|
|
45
|
+
|
|
46
|
+
devcrew "Build a CLI expense tracker with categories and monthly totals"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`python main.py "..."` still works if you'd rather not install the package.
|
|
50
|
+
|
|
51
|
+
| Option | Default | What it does |
|
|
52
|
+
|---|---|---|
|
|
53
|
+
| `-o`, `--output-dir` | `output` | Where results go (a path relative to the current directory) |
|
|
54
|
+
| `-m`, `--model` | `$MODEL` or `gpt-4o-mini` | Which LLM the agents use |
|
|
55
|
+
| `--version` | | Print the version |
|
|
56
|
+
|
|
57
|
+
Outputs land in the output directory:
|
|
58
|
+
|
|
59
|
+
| File | Agent | Contents |
|
|
60
|
+
|---|---|---|
|
|
61
|
+
| `01_plan.json` | Project Manager | Task breakdown |
|
|
62
|
+
| `02_code.py` | Developer | Runnable Python file |
|
|
63
|
+
| `03_test_report.json` | QA Tester | Test cases + bugs found |
|
|
64
|
+
| `04_review.json` | Code Reviewer | Scores, comments, verdict |
|
|
65
|
+
|
|
66
|
+
A full example run is committed in [`sample_output/`](sample_output/).
|
|
67
|
+
|
|
68
|
+
> DevCrew never runs the code it generates. Read `02_code.py` before you execute it.
|
|
69
|
+
|
|
70
|
+
## Development
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pip install -e ".[dev]"
|
|
74
|
+
pytest # no API key or network needed
|
|
75
|
+
ruff check .
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The tests check how the pipeline is wired (which agent sees which output) and validate
|
|
79
|
+
`sample_output/` against the schemas, without calling an LLM. CI runs them on Python 3.10–3.13.
|
|
80
|
+
|
|
81
|
+
## Roadmap
|
|
82
|
+
|
|
83
|
+
- Feed the reviewer's verdict back to the Developer for a fix-up round when changes are requested
|
|
84
|
+
- Actually run the generated code against the QA test cases in a sandbox
|
|
85
|
+
- Support projects that span more than one file
|
|
86
|
+
- A small eval set of project ideas to catch prompt regressions in CI
|
|
87
|
+
|
|
88
|
+
Ideas and PRs are welcome — see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
89
|
+
|
|
90
|
+
## Stack
|
|
91
|
+
|
|
92
|
+
Python · CrewAI · OpenAI API · Pydantic
|
|
93
|
+
|
|
94
|
+
## Project structure
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
devcrew/
|
|
98
|
+
├── main.py # thin wrapper around devcrew.cli
|
|
99
|
+
├── devcrew/
|
|
100
|
+
│ ├── cli.py # `devcrew` command-line entry point
|
|
101
|
+
│ ├── agents.py # 4 agents with role-scoped prompts
|
|
102
|
+
│ ├── tasks.py # pipeline tasks + context hand-offs
|
|
103
|
+
│ ├── schemas.py # Pydantic schemas for structured JSON
|
|
104
|
+
│ └── crew.py # crew assembly (sequential process)
|
|
105
|
+
├── sample_output/ # committed example run
|
|
106
|
+
├── tests/ # offline unit tests
|
|
107
|
+
├── pyproject.toml
|
|
108
|
+
├── requirements.txt
|
|
109
|
+
└── .env.example
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## License
|
|
113
|
+
|
|
114
|
+
[MIT](LICENSE) © Vasu Agrawal
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""The four specialised agents.
|
|
2
|
+
|
|
3
|
+
Each agent gets a narrow role, goal and backstory ("role-scoped context").
|
|
4
|
+
This is deliberate: a focused prompt hallucinates less than one giant
|
|
5
|
+
prompt asking a single LLM to plan, code, test and review all at once.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
|
|
10
|
+
from crewai import LLM, Agent
|
|
11
|
+
|
|
12
|
+
DEFAULT_MODEL = "gpt-4o-mini"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_llm(model: str | None = None) -> LLM:
|
|
16
|
+
"""One shared LLM config. Model comes from the argument, then the MODEL env var."""
|
|
17
|
+
return LLM(
|
|
18
|
+
model=model or os.getenv("MODEL", DEFAULT_MODEL),
|
|
19
|
+
temperature=0.3, # low temp = more deterministic, parseable output
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def project_manager(llm: LLM) -> Agent:
|
|
24
|
+
return Agent(
|
|
25
|
+
role="Project Manager",
|
|
26
|
+
goal="Break the user's idea into a small, concrete development plan.",
|
|
27
|
+
backstory=(
|
|
28
|
+
"You are a pragmatic software project manager. You take vague "
|
|
29
|
+
"one-line ideas and turn them into 3-6 unambiguous tasks a "
|
|
30
|
+
"developer can implement in a single Python file. You never "
|
|
31
|
+
"invent features the user did not ask for."
|
|
32
|
+
),
|
|
33
|
+
llm=llm,
|
|
34
|
+
verbose=True,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def developer(llm: LLM) -> Agent:
|
|
39
|
+
return Agent(
|
|
40
|
+
role="Python Developer",
|
|
41
|
+
goal="Implement the project plan as clean, working Python code.",
|
|
42
|
+
backstory=(
|
|
43
|
+
"You are a senior Python developer. You write a single, "
|
|
44
|
+
"self-contained, runnable Python file with functions, docstrings "
|
|
45
|
+
"and a __main__ demo block. You implement exactly the tasks in "
|
|
46
|
+
"the plan — nothing more. You only use the standard library."
|
|
47
|
+
),
|
|
48
|
+
llm=llm,
|
|
49
|
+
verbose=True,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def qa_tester(llm: LLM) -> Agent:
|
|
54
|
+
return Agent(
|
|
55
|
+
role="QA Tester",
|
|
56
|
+
goal="Find bugs and untested edge cases in the developer's code.",
|
|
57
|
+
backstory=(
|
|
58
|
+
"You are a sceptical QA engineer. You read code line by line "
|
|
59
|
+
"looking for edge cases: empty inputs, wrong types, negative "
|
|
60
|
+
"numbers, duplicates. You design test cases and honestly mark "
|
|
61
|
+
"which ones the current code would fail."
|
|
62
|
+
),
|
|
63
|
+
llm=llm,
|
|
64
|
+
verbose=True,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def code_reviewer(llm: LLM) -> Agent:
|
|
69
|
+
return Agent(
|
|
70
|
+
role="Code Reviewer",
|
|
71
|
+
goal="Judge the code on readability and correctness, using the QA report.",
|
|
72
|
+
backstory=(
|
|
73
|
+
"You are a strict but fair code reviewer. You consider both the "
|
|
74
|
+
"code and the QA report, score readability and correctness out "
|
|
75
|
+
"of 10, and give a final verdict: approved or changes_requested. "
|
|
76
|
+
"You point at specific functions, never vague advice."
|
|
77
|
+
),
|
|
78
|
+
llm=llm,
|
|
79
|
+
verbose=True,
|
|
80
|
+
)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Command-line entry point: `devcrew "your project idea"`."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
from dotenv import load_dotenv
|
|
8
|
+
|
|
9
|
+
from . import __version__
|
|
10
|
+
from .agents import DEFAULT_MODEL
|
|
11
|
+
|
|
12
|
+
OUTPUT_FILES = (
|
|
13
|
+
("01_plan.json", "Project Manager's plan"),
|
|
14
|
+
("02_code.py", "Developer's code"),
|
|
15
|
+
("03_test_report.json", "QA Tester's report"),
|
|
16
|
+
("04_review.json", "Code Reviewer's verdict"),
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
21
|
+
parser = argparse.ArgumentParser(
|
|
22
|
+
prog="devcrew",
|
|
23
|
+
description="Run an AI software team (plan -> code -> test -> review) on a one-line idea.",
|
|
24
|
+
)
|
|
25
|
+
parser.add_argument("idea", help='what to build, e.g. "a CLI expense tracker"')
|
|
26
|
+
parser.add_argument(
|
|
27
|
+
"-o",
|
|
28
|
+
"--output-dir",
|
|
29
|
+
default="output",
|
|
30
|
+
help="folder for results, relative to the current directory (default: output)",
|
|
31
|
+
)
|
|
32
|
+
parser.add_argument(
|
|
33
|
+
"-m", "--model", default=None, help=f"LLM to use (default: $MODEL or {DEFAULT_MODEL})"
|
|
34
|
+
)
|
|
35
|
+
parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
|
|
36
|
+
return parser
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def main(argv: list[str] | None = None) -> int:
|
|
40
|
+
parser = build_parser()
|
|
41
|
+
args = parser.parse_args(argv)
|
|
42
|
+
|
|
43
|
+
# CrewAI only writes task output to relative paths: it rejects ".." and "~",
|
|
44
|
+
# and silently drops the leading "/" of an absolute path.
|
|
45
|
+
if (
|
|
46
|
+
os.path.isabs(args.output_dir)
|
|
47
|
+
or ".." in args.output_dir.split(os.sep)
|
|
48
|
+
or (args.output_dir.startswith(("~", "$")))
|
|
49
|
+
):
|
|
50
|
+
parser.error("--output-dir must be a relative path inside the current directory")
|
|
51
|
+
|
|
52
|
+
load_dotenv()
|
|
53
|
+
|
|
54
|
+
if not os.getenv("OPENAI_API_KEY"):
|
|
55
|
+
sys.exit("Missing OPENAI_API_KEY. Copy .env.example to .env and add your key.")
|
|
56
|
+
|
|
57
|
+
os.makedirs(args.output_dir, exist_ok=True)
|
|
58
|
+
|
|
59
|
+
# Imported here so `--help` and `--version` stay fast.
|
|
60
|
+
from .crew import run
|
|
61
|
+
|
|
62
|
+
print(f"\n🚀 DevCrew starting on: {args.idea}\n")
|
|
63
|
+
run(args.idea, output_dir=args.output_dir, model=args.model)
|
|
64
|
+
print(f"\n✅ Done. Check the {args.output_dir}/ folder:")
|
|
65
|
+
for name, what in OUTPUT_FILES:
|
|
66
|
+
print(f" {name:<20}— {what}")
|
|
67
|
+
return 0
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
if __name__ == "__main__":
|
|
71
|
+
sys.exit(main())
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Assemble the crew and run the pipeline end-to-end."""
|
|
2
|
+
|
|
3
|
+
from crewai import Crew, Process
|
|
4
|
+
|
|
5
|
+
from . import agents, tasks
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def build_crew(output_dir: str = "output", model: str | None = None) -> Crew:
|
|
9
|
+
llm = agents.get_llm(model)
|
|
10
|
+
|
|
11
|
+
pm = agents.project_manager(llm)
|
|
12
|
+
dev = agents.developer(llm)
|
|
13
|
+
qa = agents.qa_tester(llm)
|
|
14
|
+
reviewer = agents.code_reviewer(llm)
|
|
15
|
+
|
|
16
|
+
t_plan = tasks.plan_task(pm, output_dir=output_dir)
|
|
17
|
+
t_code = tasks.code_task(dev, plan=t_plan, output_dir=output_dir)
|
|
18
|
+
t_test = tasks.test_task(qa, code=t_code, output_dir=output_dir)
|
|
19
|
+
t_review = tasks.review_task(reviewer, code=t_code, tests=t_test, output_dir=output_dir)
|
|
20
|
+
|
|
21
|
+
return Crew(
|
|
22
|
+
agents=[pm, dev, qa, reviewer],
|
|
23
|
+
tasks=[t_plan, t_code, t_test, t_review],
|
|
24
|
+
process=Process.sequential, # strict order: plan -> code -> test -> review
|
|
25
|
+
verbose=True,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def run(idea: str, output_dir: str = "output", model: str | None = None) -> str:
|
|
30
|
+
"""Run the full pipeline for a one-line project idea."""
|
|
31
|
+
crew = build_crew(output_dir=output_dir, model=model)
|
|
32
|
+
result = crew.kickoff(inputs={"idea": idea})
|
|
33
|
+
return str(result)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Pydantic schemas that force each agent to return structured JSON.
|
|
2
|
+
|
|
3
|
+
Why this matters: LLM agents tend to ramble. By binding each task to a
|
|
4
|
+
schema, the output of one agent becomes machine-parseable input for the
|
|
5
|
+
next agent in the pipeline — no regex, no guessing.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Literal
|
|
9
|
+
|
|
10
|
+
from pydantic import BaseModel, Field
|
|
11
|
+
|
|
12
|
+
# ---------- Project Manager output ----------
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class PlanItem(BaseModel):
|
|
16
|
+
id: int = Field(description="Sequential task id, starting at 1")
|
|
17
|
+
title: str = Field(description="Short task title")
|
|
18
|
+
description: str = Field(description="What exactly needs to be built")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ProjectPlan(BaseModel):
|
|
22
|
+
project_name: str
|
|
23
|
+
summary: str = Field(description="One-paragraph summary of the project")
|
|
24
|
+
tasks: list[PlanItem] = Field(description="3-6 concrete development tasks")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# ---------- QA Tester output ----------
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class TestCase(BaseModel):
|
|
31
|
+
name: str
|
|
32
|
+
scenario: str = Field(description="What is being tested")
|
|
33
|
+
expected: str = Field(description="Expected behaviour")
|
|
34
|
+
verdict: Literal["pass", "fail"] = Field(
|
|
35
|
+
description="'pass' or 'fail' based on reading the code"
|
|
36
|
+
)
|
|
37
|
+
notes: str = Field(default="", description="Why it passes/fails")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class TestReport(BaseModel):
|
|
41
|
+
total_cases: int
|
|
42
|
+
passed: int
|
|
43
|
+
failed: int
|
|
44
|
+
test_cases: list[TestCase]
|
|
45
|
+
bugs_found: list[str] = Field(description="Concrete bugs or edge cases the code misses")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# ---------- Code Reviewer output ----------
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class ReviewComment(BaseModel):
|
|
52
|
+
severity: Literal["critical", "major", "minor"] = Field(
|
|
53
|
+
description="'critical', 'major' or 'minor'"
|
|
54
|
+
)
|
|
55
|
+
location: str = Field(description="Function or section of code")
|
|
56
|
+
comment: str
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class ReviewReport(BaseModel):
|
|
60
|
+
verdict: Literal["approved", "changes_requested"] = Field(
|
|
61
|
+
description="'approved' or 'changes_requested'"
|
|
62
|
+
)
|
|
63
|
+
readability_score: int = Field(ge=1, le=10, description="1-10")
|
|
64
|
+
correctness_score: int = Field(ge=1, le=10, description="1-10")
|
|
65
|
+
comments: list[ReviewComment]
|
|
66
|
+
summary: str = Field(description="Final review summary in 2-3 sentences")
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""The pipeline: Plan -> Code -> Test -> Review.
|
|
2
|
+
|
|
3
|
+
The hand-off between agents happens through CrewAI's `context` parameter:
|
|
4
|
+
each task receives the *output* of the previous task(s) as input. That is
|
|
5
|
+
the "orchestrated hand-off" — no agent sees the whole conversation, only
|
|
6
|
+
what it needs.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
|
|
11
|
+
from crewai import Agent, Task
|
|
12
|
+
|
|
13
|
+
from .schemas import ProjectPlan, ReviewReport, TestReport
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def plan_task(agent: Agent, output_dir: str = "output") -> Task:
|
|
17
|
+
return Task(
|
|
18
|
+
description=(
|
|
19
|
+
"The user wants to build: '{idea}'.\n"
|
|
20
|
+
"Create a development plan with 3-6 concrete tasks. The whole "
|
|
21
|
+
"project must fit in ONE Python file using only the standard "
|
|
22
|
+
"library."
|
|
23
|
+
),
|
|
24
|
+
expected_output="A structured project plan as JSON.",
|
|
25
|
+
agent=agent,
|
|
26
|
+
output_json=ProjectPlan,
|
|
27
|
+
output_file=os.path.join(output_dir, "01_plan.json"),
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def code_task(agent: Agent, plan: Task, output_dir: str = "output") -> Task:
|
|
32
|
+
return Task(
|
|
33
|
+
description=(
|
|
34
|
+
"Implement the project plan you received as ONE self-contained "
|
|
35
|
+
"Python file. Include docstrings and a small demo under "
|
|
36
|
+
"`if __name__ == '__main__':`. Output ONLY the raw Python code, "
|
|
37
|
+
"no markdown fences, no explanations."
|
|
38
|
+
),
|
|
39
|
+
expected_output="A single runnable Python file.",
|
|
40
|
+
agent=agent,
|
|
41
|
+
context=[plan], # hand-off: developer only sees the plan
|
|
42
|
+
output_file=os.path.join(output_dir, "02_code.py"),
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_task(agent: Agent, code: Task, output_dir: str = "output") -> Task:
|
|
47
|
+
return Task(
|
|
48
|
+
description=(
|
|
49
|
+
"Read the Python code you received and design 5-8 test cases "
|
|
50
|
+
"covering normal use and edge cases (empty input, invalid input, "
|
|
51
|
+
"duplicates, boundaries). For each, decide honestly whether the "
|
|
52
|
+
"CURRENT code would pass or fail, and list concrete bugs."
|
|
53
|
+
),
|
|
54
|
+
expected_output="A structured QA test report as JSON.",
|
|
55
|
+
agent=agent,
|
|
56
|
+
context=[code], # hand-off: tester only sees the code
|
|
57
|
+
output_json=TestReport,
|
|
58
|
+
output_file=os.path.join(output_dir, "03_test_report.json"),
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def review_task(agent: Agent, code: Task, tests: Task, output_dir: str = "output") -> Task:
|
|
63
|
+
return Task(
|
|
64
|
+
description=(
|
|
65
|
+
"Review the code, taking the QA report into account. Score "
|
|
66
|
+
"readability and correctness out of 10, leave specific comments "
|
|
67
|
+
"with severity levels, and give a final verdict."
|
|
68
|
+
),
|
|
69
|
+
expected_output="A structured code review as JSON.",
|
|
70
|
+
agent=agent,
|
|
71
|
+
context=[code, tests], # hand-off: reviewer sees code + QA report
|
|
72
|
+
output_json=ReviewReport,
|
|
73
|
+
output_file=os.path.join(output_dir, "04_review.json"),
|
|
74
|
+
)
|
devcrew-0.1.0/main.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""DevCrew — run an AI software team on a one-line project idea.
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
python main.py "Build a CLI expense tracker"
|
|
5
|
+
|
|
6
|
+
Once installed (`pip install -e .`), the same thing is available as `devcrew "..."`.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
from devcrew.cli import main
|
|
12
|
+
|
|
13
|
+
if __name__ == "__main__":
|
|
14
|
+
sys.exit(main())
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "devcrew"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "An AI software team in a pipeline: four agents plan, code, test and review a project from a one-line idea."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
authors = [{ name = "Vasu Agrawal" }]
|
|
13
|
+
requires-python = ">=3.10,<3.14"
|
|
14
|
+
dependencies = [
|
|
15
|
+
"crewai>=1.0.0",
|
|
16
|
+
"python-dotenv>=1.0.0",
|
|
17
|
+
]
|
|
18
|
+
keywords = ["ai", "agents", "multi-agent", "crewai", "llm", "code-generation", "code-review"]
|
|
19
|
+
classifiers = [
|
|
20
|
+
"Development Status :: 3 - Alpha",
|
|
21
|
+
"Environment :: Console",
|
|
22
|
+
"Intended Audience :: Developers",
|
|
23
|
+
"Programming Language :: Python :: 3",
|
|
24
|
+
"Programming Language :: Python :: 3.10",
|
|
25
|
+
"Programming Language :: Python :: 3.11",
|
|
26
|
+
"Programming Language :: Python :: 3.12",
|
|
27
|
+
"Programming Language :: Python :: 3.13",
|
|
28
|
+
"Topic :: Software Development :: Code Generators",
|
|
29
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
dev = ["pytest>=8", "ruff>=0.6"]
|
|
34
|
+
|
|
35
|
+
[project.scripts]
|
|
36
|
+
devcrew = "devcrew.cli:main"
|
|
37
|
+
|
|
38
|
+
[project.urls]
|
|
39
|
+
Homepage = "https://github.com/agrvasu05/DevCrew"
|
|
40
|
+
Issues = "https://github.com/agrvasu05/DevCrew/issues"
|
|
41
|
+
Changelog = "https://github.com/agrvasu05/DevCrew/blob/main/CHANGELOG.md"
|
|
42
|
+
|
|
43
|
+
[tool.hatch.version]
|
|
44
|
+
path = "devcrew/__init__.py"
|
|
45
|
+
|
|
46
|
+
[tool.hatch.build.targets.sdist]
|
|
47
|
+
include = ["devcrew", "tests", "sample_output", "main.py", "README.md", "LICENSE", "CHANGELOG.md"]
|
|
48
|
+
|
|
49
|
+
[tool.ruff]
|
|
50
|
+
line-length = 100
|
|
51
|
+
target-version = "py310"
|
|
52
|
+
extend-exclude = ["sample_output"]
|
|
53
|
+
|
|
54
|
+
[tool.ruff.lint]
|
|
55
|
+
select = ["E", "F", "I", "B", "UP"]
|
|
56
|
+
|
|
57
|
+
[tool.pytest.ini_options]
|
|
58
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"project_name": "CLI Expense Tracker",
|
|
3
|
+
"summary": "A command-line interface (CLI) application that allows users to track their expenses by categories and calculate monthly totals, all implemented in a single Python file using only the standard library.",
|
|
4
|
+
"tasks": [
|
|
5
|
+
{
|
|
6
|
+
"id": 1,
|
|
7
|
+
"title": "Define Expense Data Structure",
|
|
8
|
+
"description": "Create a data structure to hold expense entries, including fields for amount, category, and date."
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"id": 2,
|
|
12
|
+
"title": "Implement Expense Entry Functionality",
|
|
13
|
+
"description": "Write a function to allow users to input new expenses, specifying the amount, category, and date, and store these in the defined data structure."
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"id": 3,
|
|
17
|
+
"title": "Create Category Management",
|
|
18
|
+
"description": "Implement functionality to define and manage expense categories, allowing users to view and add categories."
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"id": 4,
|
|
22
|
+
"title": "Calculate Monthly Totals",
|
|
23
|
+
"description": "Develop a function to calculate and display the total expenses for each category on a monthly basis."
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"id": 5,
|
|
27
|
+
"title": "Build CLI Interface",
|
|
28
|
+
"description": "Create a command-line interface that allows users to interact with the expense tracker, including options to add expenses, view totals, and manage categories."
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
|
|
4
|
+
class Expense:
|
|
5
|
+
"""Class to represent an expense entry."""
|
|
6
|
+
def __init__(self, amount, category, date):
|
|
7
|
+
self.amount = amount
|
|
8
|
+
self.category = category
|
|
9
|
+
self.date = datetime.strptime(date, '%Y-%m-%d')
|
|
10
|
+
|
|
11
|
+
class ExpenseTracker:
|
|
12
|
+
"""Class to manage expenses and categories."""
|
|
13
|
+
def __init__(self):
|
|
14
|
+
self.expenses = []
|
|
15
|
+
self.categories = set()
|
|
16
|
+
|
|
17
|
+
def add_expense(self, amount, category, date):
|
|
18
|
+
"""Add a new expense to the tracker."""
|
|
19
|
+
if category not in self.categories:
|
|
20
|
+
print(f"Category '{category}' not found. Please add it first.")
|
|
21
|
+
return
|
|
22
|
+
expense = Expense(amount, category, date)
|
|
23
|
+
self.expenses.append(expense)
|
|
24
|
+
|
|
25
|
+
def add_category(self, category):
|
|
26
|
+
"""Add a new category to the tracker."""
|
|
27
|
+
self.categories.add(category)
|
|
28
|
+
|
|
29
|
+
def calculate_monthly_totals(self, year, month):
|
|
30
|
+
"""Calculate total expenses for each category for a given month."""
|
|
31
|
+
monthly_totals = {}
|
|
32
|
+
for expense in self.expenses:
|
|
33
|
+
if expense.date.year == year and expense.date.month == month:
|
|
34
|
+
if expense.category not in monthly_totals:
|
|
35
|
+
monthly_totals[expense.category] = 0
|
|
36
|
+
monthly_totals[expense.category] += expense.amount
|
|
37
|
+
return monthly_totals
|
|
38
|
+
|
|
39
|
+
def view_categories(self):
|
|
40
|
+
"""View all expense categories."""
|
|
41
|
+
return list(self.categories)
|
|
42
|
+
|
|
43
|
+
def main():
|
|
44
|
+
tracker = ExpenseTracker()
|
|
45
|
+
|
|
46
|
+
# Add categories
|
|
47
|
+
tracker.add_category("Food")
|
|
48
|
+
tracker.add_category("Transport")
|
|
49
|
+
tracker.add_category("Utilities")
|
|
50
|
+
|
|
51
|
+
# Add expenses
|
|
52
|
+
tracker.add_expense(50, "Food", "2023-10-01")
|
|
53
|
+
tracker.add_expense(20, "Transport", "2023-10-02")
|
|
54
|
+
tracker.add_expense(100, "Utilities", "2023-10-03")
|
|
55
|
+
tracker.add_expense(30, "Food", "2023-10-04")
|
|
56
|
+
|
|
57
|
+
# Calculate monthly totals
|
|
58
|
+
totals = tracker.calculate_monthly_totals(2023, 10)
|
|
59
|
+
print("Monthly Totals for October 2023:")
|
|
60
|
+
for category, total in totals.items():
|
|
61
|
+
print(f"{category}: ${total}")
|
|
62
|
+
|
|
63
|
+
if __name__ == '__main__':
|
|
64
|
+
main()
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"total_cases": 6,
|
|
3
|
+
"passed": 3,
|
|
4
|
+
"failed": 3,
|
|
5
|
+
"test_cases": [
|
|
6
|
+
{
|
|
7
|
+
"name": "Valid Expense Addition",
|
|
8
|
+
"scenario": "Adding an expense with a valid category and date.",
|
|
9
|
+
"expected": "Expense is added successfully.",
|
|
10
|
+
"verdict": "pass",
|
|
11
|
+
"notes": "The expense is added correctly when the category exists."
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"name": "Invalid Category Expense Addition",
|
|
15
|
+
"scenario": "Adding an expense with a non-existent category.",
|
|
16
|
+
"expected": "Error message is printed, and expense is not added.",
|
|
17
|
+
"verdict": "fail",
|
|
18
|
+
"notes": "The code prints an error but does not prevent adding the expense if the category is invalid."
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"name": "Empty Category Addition",
|
|
22
|
+
"scenario": "Adding an empty category.",
|
|
23
|
+
"expected": "Category is added as an empty string.",
|
|
24
|
+
"verdict": "pass",
|
|
25
|
+
"notes": "The code allows adding an empty string as a category."
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"name": "Duplicate Category Addition",
|
|
29
|
+
"scenario": "Adding a category that already exists.",
|
|
30
|
+
"expected": "Category remains unchanged, no error.",
|
|
31
|
+
"verdict": "pass",
|
|
32
|
+
"notes": "The set data structure prevents duplicates, which is expected behavior."
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"name": "Calculate Monthly Totals with No Expenses",
|
|
36
|
+
"scenario": "Calculating totals for a month with no expenses.",
|
|
37
|
+
"expected": "Returns an empty dictionary.",
|
|
38
|
+
"verdict": "pass",
|
|
39
|
+
"notes": "The method correctly returns an empty dictionary when there are no expenses."
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"name": "Invalid Date Format",
|
|
43
|
+
"scenario": "Adding an expense with an invalid date format.",
|
|
44
|
+
"expected": "Error raised due to invalid date format.",
|
|
45
|
+
"verdict": "fail",
|
|
46
|
+
"notes": "The code does not handle invalid date formats and will raise a ValueError."
|
|
47
|
+
}
|
|
48
|
+
],
|
|
49
|
+
"bugs_found": [
|
|
50
|
+
"The add_expense method does not prevent adding an expense if the category is invalid.",
|
|
51
|
+
"The code does not handle invalid date formats, which can cause runtime errors.",
|
|
52
|
+
"Adding an empty string as a category is allowed, which may not be intended."
|
|
53
|
+
]
|
|
54
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"verdict": "changes_requested",
|
|
3
|
+
"readability_score": 7,
|
|
4
|
+
"correctness_score": 5,
|
|
5
|
+
"comments": [
|
|
6
|
+
{
|
|
7
|
+
"severity": "major",
|
|
8
|
+
"location": "add_expense",
|
|
9
|
+
"comment": "The method does not prevent adding an expense if the category is invalid, which can lead to incorrect data being stored."
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"severity": "critical",
|
|
13
|
+
"location": "Expense.__init__",
|
|
14
|
+
"comment": "The code does not handle invalid date formats, which can cause runtime errors when an invalid date is provided."
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"severity": "minor",
|
|
18
|
+
"location": "add_category",
|
|
19
|
+
"comment": "Allowing an empty string as a category may not be intended behavior and could lead to confusion."
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"summary": "The code has good readability but contains critical issues that affect correctness. Specifically, it does not handle invalid categories or date formats properly, which must be addressed before approval."
|
|
23
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from devcrew import __version__, cli
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_version(capsys):
|
|
7
|
+
with pytest.raises(SystemExit) as exc:
|
|
8
|
+
cli.main(["--version"])
|
|
9
|
+
assert exc.value.code == 0
|
|
10
|
+
assert __version__ in capsys.readouterr().out
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_idea_is_required():
|
|
14
|
+
with pytest.raises(SystemExit) as exc:
|
|
15
|
+
cli.main([])
|
|
16
|
+
assert exc.value.code == 2
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_missing_api_key_exits_with_hint(monkeypatch):
|
|
20
|
+
monkeypatch.delenv("OPENAI_API_KEY")
|
|
21
|
+
monkeypatch.setattr(cli, "load_dotenv", lambda: None)
|
|
22
|
+
with pytest.raises(SystemExit) as exc:
|
|
23
|
+
cli.main(["a todo app"])
|
|
24
|
+
assert "OPENAI_API_KEY" in str(exc.value.code)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@pytest.mark.parametrize("bad", ["/tmp/out", "../out", "~/out"])
|
|
28
|
+
def test_output_dir_must_be_relative(bad):
|
|
29
|
+
with pytest.raises(SystemExit) as exc:
|
|
30
|
+
cli.main(["a todo app", "-o", bad])
|
|
31
|
+
assert exc.value.code == 2
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_runs_pipeline_with_options(monkeypatch, tmp_path):
|
|
35
|
+
calls = {}
|
|
36
|
+
|
|
37
|
+
def fake_run(idea, output_dir, model):
|
|
38
|
+
calls.update(idea=idea, output_dir=output_dir, model=model)
|
|
39
|
+
return "ok"
|
|
40
|
+
|
|
41
|
+
monkeypatch.setattr("devcrew.crew.run", fake_run)
|
|
42
|
+
monkeypatch.chdir(tmp_path)
|
|
43
|
+
assert cli.main(["a todo app", "-o", "runs/todo", "-m", "gpt-4o"]) == 0
|
|
44
|
+
assert calls == {"idea": "a todo app", "output_dir": "runs/todo", "model": "gpt-4o"}
|
|
45
|
+
assert (tmp_path / "runs" / "todo").is_dir()
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""The pipeline is wired as plan -> code -> test -> review, without calling an LLM."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from crewai import Process
|
|
6
|
+
|
|
7
|
+
from devcrew import schemas
|
|
8
|
+
from devcrew.crew import build_crew
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_crew_has_four_agents_in_order():
|
|
12
|
+
crew = build_crew()
|
|
13
|
+
assert [a.role for a in crew.agents] == [
|
|
14
|
+
"Project Manager",
|
|
15
|
+
"Python Developer",
|
|
16
|
+
"QA Tester",
|
|
17
|
+
"Code Reviewer",
|
|
18
|
+
]
|
|
19
|
+
assert crew.process == Process.sequential
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_each_task_only_sees_what_it_needs():
|
|
23
|
+
plan, code, test, review = build_crew().tasks
|
|
24
|
+
assert not isinstance(plan.context, list) # no upstream task
|
|
25
|
+
assert code.context == [plan]
|
|
26
|
+
assert test.context == [code]
|
|
27
|
+
assert review.context == [code, test]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_structured_outputs_are_bound_to_schemas():
|
|
31
|
+
plan, code, test, review = build_crew().tasks
|
|
32
|
+
assert plan.output_json is schemas.ProjectPlan
|
|
33
|
+
assert code.output_json is None
|
|
34
|
+
assert test.output_json is schemas.TestReport
|
|
35
|
+
assert review.output_json is schemas.ReviewReport
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_output_dir_is_configurable():
|
|
39
|
+
out = os.path.join("runs", "todo-app")
|
|
40
|
+
tasks = build_crew(output_dir=out).tasks
|
|
41
|
+
assert [os.path.basename(t.output_file) for t in tasks] == [
|
|
42
|
+
"01_plan.json",
|
|
43
|
+
"02_code.py",
|
|
44
|
+
"03_test_report.json",
|
|
45
|
+
"04_review.json",
|
|
46
|
+
]
|
|
47
|
+
assert {os.path.dirname(t.output_file) for t in tasks} == {out}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_model_can_be_overridden(monkeypatch):
|
|
51
|
+
monkeypatch.setenv("MODEL", "gpt-4o")
|
|
52
|
+
assert build_crew().agents[0].llm.model == "gpt-4o"
|
|
53
|
+
assert build_crew(model="gpt-4.1-mini").agents[0].llm.model == "gpt-4.1-mini"
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""The committed sample run must match the schemas agents are bound to."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
from pydantic import ValidationError
|
|
8
|
+
|
|
9
|
+
from devcrew import schemas
|
|
10
|
+
|
|
11
|
+
SAMPLE = Path(__file__).resolve().parent.parent / "sample_output"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def load(name: str) -> dict:
|
|
15
|
+
return json.loads((SAMPLE / name).read_text())
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_sample_plan_is_valid():
|
|
19
|
+
plan = schemas.ProjectPlan.model_validate(load("01_plan.json"))
|
|
20
|
+
assert 3 <= len(plan.tasks) <= 6
|
|
21
|
+
assert [t.id for t in plan.tasks] == list(range(1, len(plan.tasks) + 1))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_sample_test_report_is_valid():
|
|
25
|
+
report = schemas.TestReport.model_validate(load("03_test_report.json"))
|
|
26
|
+
assert report.total_cases == len(report.test_cases)
|
|
27
|
+
assert report.passed + report.failed == report.total_cases
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_sample_review_is_valid():
|
|
31
|
+
schemas.ReviewReport.model_validate(load("04_review.json"))
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_sample_code_compiles():
|
|
35
|
+
compile((SAMPLE / "02_code.py").read_text(), "02_code.py", "exec")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@pytest.mark.parametrize("score", [0, 11])
|
|
39
|
+
def test_review_scores_are_bounded(score):
|
|
40
|
+
review = load("04_review.json") | {"readability_score": score}
|
|
41
|
+
with pytest.raises(ValidationError):
|
|
42
|
+
schemas.ReviewReport.model_validate(review)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_unknown_verdict_is_rejected():
|
|
46
|
+
review = load("04_review.json") | {"verdict": "looks fine"}
|
|
47
|
+
with pytest.raises(ValidationError):
|
|
48
|
+
schemas.ReviewReport.model_validate(review)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_unknown_severity_is_rejected():
|
|
52
|
+
review = load("04_review.json")
|
|
53
|
+
review["comments"][0]["severity"] = "blocker"
|
|
54
|
+
with pytest.raises(ValidationError):
|
|
55
|
+
schemas.ReviewReport.model_validate(review)
|