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.
@@ -0,0 +1,11 @@
1
+ .env
2
+ __pycache__/
3
+ *.pyc
4
+ output/
5
+ .venv/
6
+ venv/
7
+ dist/
8
+ build/
9
+ *.egg-info/
10
+ .pytest_cache/
11
+ .ruff_cache/
@@ -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
+ [![CI](https://github.com/agrvasu05/DevCrew/actions/workflows/ci.yml/badge.svg)](https://github.com/agrvasu05/DevCrew/actions/workflows/ci.yml)
33
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
34
+ ![Python 3.10–3.13](https://img.shields.io/badge/python-3.10%E2%80%933.13-blue.svg)
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
@@ -0,0 +1,114 @@
1
+ # DevCrew — Multi-Agent AI Orchestration System
2
+
3
+ [![CI](https://github.com/agrvasu05/DevCrew/actions/workflows/ci.yml/badge.svg)](https://github.com/agrvasu05/DevCrew/actions/workflows/ci.yml)
4
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
5
+ ![Python 3.10–3.13](https://img.shields.io/badge/python-3.10%E2%80%933.13-blue.svg)
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,3 @@
1
+ """DevCrew — an AI software team in a pipeline: plan -> code -> test -> review."""
2
+
3
+ __version__ = "0.1.0"
@@ -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,7 @@
1
+ import pytest
2
+
3
+
4
+ @pytest.fixture(autouse=True)
5
+ def fake_api_key(monkeypatch):
6
+ """Building agents needs a key to be set; no test ever calls the API."""
7
+ monkeypatch.setenv("OPENAI_API_KEY", "sk-test-not-a-real-key")
@@ -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)