crewforge 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.
Files changed (68) hide show
  1. crewforge-0.1.0/LICENSE +21 -0
  2. crewforge-0.1.0/PKG-INFO +372 -0
  3. crewforge-0.1.0/README.md +325 -0
  4. crewforge-0.1.0/pyproject.toml +88 -0
  5. crewforge-0.1.0/setup.cfg +4 -0
  6. crewforge-0.1.0/src/crewforge/__init__.py +3 -0
  7. crewforge-0.1.0/src/crewforge/__main__.py +4 -0
  8. crewforge-0.1.0/src/crewforge/cli.py +128 -0
  9. crewforge-0.1.0/src/crewforge/cli_support.py +109 -0
  10. crewforge-0.1.0/src/crewforge/core/__init__.py +54 -0
  11. crewforge-0.1.0/src/crewforge/core/agent.py +118 -0
  12. crewforge-0.1.0/src/crewforge/core/client.py +253 -0
  13. crewforge-0.1.0/src/crewforge/core/config.py +181 -0
  14. crewforge-0.1.0/src/crewforge/core/crew.py +144 -0
  15. crewforge-0.1.0/src/crewforge/core/errors.py +106 -0
  16. crewforge-0.1.0/src/crewforge/core/parsing.py +110 -0
  17. crewforge-0.1.0/src/crewforge/core/providers.py +131 -0
  18. crewforge-0.1.0/src/crewforge/core/usage.py +163 -0
  19. crewforge-0.1.0/src/crewforge/py.typed +0 -0
  20. crewforge-0.1.0/src/crewforge/recipes/__init__.py +1 -0
  21. crewforge-0.1.0/src/crewforge/recipes/api/__init__.py +38 -0
  22. crewforge-0.1.0/src/crewforge/recipes/api/agents.py +523 -0
  23. crewforge-0.1.0/src/crewforge/recipes/api/analytics.py +99 -0
  24. crewforge-0.1.0/src/crewforge/recipes/api/appcheck.py +73 -0
  25. crewforge-0.1.0/src/crewforge/recipes/api/cli.py +127 -0
  26. crewforge-0.1.0/src/crewforge/recipes/api/formatters.py +32 -0
  27. crewforge-0.1.0/src/crewforge/recipes/api/generator.py +37 -0
  28. crewforge-0.1.0/src/crewforge/recipes/api/pipeline.py +86 -0
  29. crewforge-0.1.0/src/crewforge/recipes/api/scaffold.py +68 -0
  30. crewforge-0.1.0/src/crewforge/recipes/api/templates/docker-compose.j2 +9 -0
  31. crewforge-0.1.0/src/crewforge/recipes/api/templates/dockerfile.j2 +7 -0
  32. crewforge-0.1.0/src/crewforge/recipes/api/templates/env.example.j2 +2 -0
  33. crewforge-0.1.0/src/crewforge/recipes/api/templates/fastapi_main.j2 +10 -0
  34. crewforge-0.1.0/src/crewforge/recipes/api/templates/model.j2 +4 -0
  35. crewforge-0.1.0/src/crewforge/recipes/api/templates/requirements.j2 +6 -0
  36. crewforge-0.1.0/src/crewforge/recipes/api/templates/route.j2 +4 -0
  37. crewforge-0.1.0/src/crewforge/recipes/api/templates/test.j2 +3 -0
  38. crewforge-0.1.0/src/crewforge/recipes/api/utils.py +134 -0
  39. crewforge-0.1.0/src/crewforge/recipes/content/__init__.py +20 -0
  40. crewforge-0.1.0/src/crewforge/recipes/content/agents.py +735 -0
  41. crewforge-0.1.0/src/crewforge/recipes/content/cli.py +128 -0
  42. crewforge-0.1.0/src/crewforge/recipes/content/export.py +84 -0
  43. crewforge-0.1.0/src/crewforge/recipes/content/pipeline.py +222 -0
  44. crewforge-0.1.0/src/crewforge/recipes/content/settings.py +63 -0
  45. crewforge-0.1.0/src/crewforge/recipes/content/text.py +57 -0
  46. crewforge-0.1.0/src/crewforge/recipes/docs/__init__.py +48 -0
  47. crewforge-0.1.0/src/crewforge/recipes/docs/agents.py +426 -0
  48. crewforge-0.1.0/src/crewforge/recipes/docs/cli.py +156 -0
  49. crewforge-0.1.0/src/crewforge/recipes/docs/export.py +118 -0
  50. crewforge-0.1.0/src/crewforge/recipes/docs/index.py +117 -0
  51. crewforge-0.1.0/src/crewforge/recipes/docs/ingest.py +212 -0
  52. crewforge-0.1.0/src/crewforge/recipes/docs/pipeline.py +223 -0
  53. crewforge-0.1.0/src/crewforge/recipes/docs/retrieve.py +70 -0
  54. crewforge-0.1.0/src/crewforge/recipes/docs/settings.py +54 -0
  55. crewforge-0.1.0/src/crewforge/recipes/review/__init__.py +1 -0
  56. crewforge-0.1.0/src/crewforge/recipes/review/agents.py +331 -0
  57. crewforge-0.1.0/src/crewforge/recipes/review/cli.py +144 -0
  58. crewforge-0.1.0/src/crewforge/recipes/review/pipeline.py +125 -0
  59. crewforge-0.1.0/src/crewforge/recipes/review/report.py +103 -0
  60. crewforge-0.1.0/src/crewforge/recipes/review/sources.py +89 -0
  61. crewforge-0.1.0/src/crewforge.egg-info/PKG-INFO +372 -0
  62. crewforge-0.1.0/src/crewforge.egg-info/SOURCES.txt +66 -0
  63. crewforge-0.1.0/src/crewforge.egg-info/dependency_links.txt +1 -0
  64. crewforge-0.1.0/src/crewforge.egg-info/entry_points.txt +2 -0
  65. crewforge-0.1.0/src/crewforge.egg-info/requires.txt +21 -0
  66. crewforge-0.1.0/src/crewforge.egg-info/top_level.txt +1 -0
  67. crewforge-0.1.0/tests/test_honest_failure_e2e.py +59 -0
  68. crewforge-0.1.0/tests/test_readme.py +36 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Muhammad Aiman
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,372 @@
1
+ Metadata-Version: 2.4
2
+ Name: crewforge
3
+ Version: 0.1.0
4
+ Summary: One crew of LLM agents, four recipes: code review, document Q&A, API scaffolding and content — on any OpenAI-compatible provider.
5
+ Author: Muhammad Aiman
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/aimanmalib/crewforge
8
+ Project-URL: Repository, https://github.com/aimanmalib/crewforge
9
+ Project-URL: Issues, https://github.com/aimanmalib/crewforge/issues
10
+ Project-URL: Changelog, https://github.com/aimanmalib/crewforge/blob/main/CHANGELOG.md
11
+ Keywords: llm,agents,multi-agent,openai,code-review,rag,fastapi,content-generation,cli
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
23
+ Classifier: Topic :: Software Development :: Quality Assurance
24
+ Classifier: Typing :: Typed
25
+ Requires-Python: >=3.10
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Requires-Dist: httpx>=0.27
29
+ Requires-Dist: pydantic>=2.5
30
+ Requires-Dist: click>=8.1
31
+ Requires-Dist: rich>=13.0
32
+ Requires-Dist: pyyaml>=6.0
33
+ Requires-Dist: jinja2>=3.1
34
+ Provides-Extra: docs
35
+ Requires-Dist: pymupdf>=1.24; extra == "docs"
36
+ Requires-Dist: python-docx>=1.1; extra == "docs"
37
+ Provides-Extra: api
38
+ Requires-Dist: fastapi>=0.110; extra == "api"
39
+ Provides-Extra: dev
40
+ Requires-Dist: pytest>=8.0; extra == "dev"
41
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
42
+ Requires-Dist: pytest-cov>=5.0; extra == "dev"
43
+ Requires-Dist: ruff>=0.6; extra == "dev"
44
+ Requires-Dist: fastapi>=0.110; extra == "dev"
45
+ Requires-Dist: httpx>=0.27; extra == "dev"
46
+ Dynamic: license-file
47
+
48
+ # crewforge
49
+
50
+ **One crew of LLM agents, four recipes — code review, document Q&A, API scaffolding and content — on any OpenAI-compatible provider, and it fails loudly instead of pretending.**
51
+
52
+ [![CI](https://github.com/aimanmalib/crewforge/actions/workflows/ci.yml/badge.svg)](https://github.com/aimanmalib/crewforge/actions/workflows/ci.yml)
53
+ ![Python 3.10–3.13](https://img.shields.io/badge/python-3.10%E2%80%933.13-blue)
54
+ ![License: MIT](https://img.shields.io/badge/license-MIT-green)
55
+
56
+ ```bash
57
+ crewforge review src/ # six reviewers + a summary
58
+ crewforge ask handbook.pdf -q "How many leave days?" --verify
59
+ crewforge scaffold "Todo API: list, create and delete todos" -o todo-api
60
+ crewforge write "Why unit tests matter" -f markdown -f html
61
+ ```
62
+
63
+ <p align="center">
64
+ <img src="https://raw.githubusercontent.com/aimanmalib/crewforge/main/docs/demo/review.gif" alt="Terminal demo: crewforge review on a login function finds the SQL injection (CWE-89) as critical and requests changes" width="900">
65
+ </p>
66
+ <p align="center"><sub>A real run of <code>crewforge review</code>: seven agents, ~25 s, provider-reported tokens. Recorded with <a href="https://github.com/charmbracelet/vhs">VHS</a> from <a href="https://github.com/aimanmalib/crewforge/blob/main/docs/demo/review.tape"><code>docs/demo/review.tape</code></a> using Gemini 3.8 Flash through an OpenAI-compatible gateway (the <code>custom</code> provider). The wait for the model is shown in full.</sub></p>
67
+
68
+ ## Why one crew, four recipes
69
+
70
+ crewforge started as four separate command-line tools —
71
+ [codeguard](https://github.com/aimanmalib/codeguard),
72
+ [documind](https://github.com/aimanmalib/documind),
73
+ [apiforge](https://github.com/aimanmalib/apiforge) and
74
+ [contentforge](https://github.com/aimanmalib/contentforge). Each had its own copy
75
+ of the same scaffold: provider presets, an HTTP client, an agent base class, an
76
+ asyncio pipeline and a token tracker. Running all four against a real model
77
+ showed the copies had drifted and that each hid its failures differently:
78
+
79
+ - codeguard reported success when every review came back empty;
80
+ - documind's `ask` command was a stub;
81
+ - apiforge scored a spec with **no endpoints** 100/100;
82
+ - contentforge wrote the real API key into its YAML file, and sent `${OPENAI_API_KEY}` literally as the key.
83
+
84
+ crewforge keeps one core — so a fix lands once — and turns each tool into a
85
+ *recipe*: a set of agents plus the steps that connect them. The same core rules
86
+ apply to every recipe: non-streaming requests, bounded retries, usage numbers
87
+ taken only from the provider, keys only from the environment, and no run that
88
+ reports success when an agent produced nothing.
89
+
90
+ ## 60-second quickstart
91
+
92
+ ```bash
93
+ pip install crewforge
94
+ # optional extras: PDF/DOCX ingestion, and the FastAPI app check in `validate`
95
+ pip install "crewforge[docs,api]"
96
+ # or the latest main: pip install "crewforge @ git+https://github.com/aimanmalib/crewforge.git"
97
+
98
+ export OPENAI_API_KEY=sk-... # or any provider below
99
+ crewforge providers # list presets and the variable each one reads
100
+ crewforge review path/to/file.py
101
+ ```
102
+
103
+ Any provider works the same way:
104
+
105
+ ```bash
106
+ export GROQ_API_KEY=...
107
+ crewforge --provider groq review src/
108
+
109
+ # any other OpenAI-compatible server (vLLM, LM Studio, a gateway, ...)
110
+ crewforge --provider custom --base-url http://localhost:8000/v1 --model my-model ask notes.md -q "..."
111
+ ```
112
+
113
+ `crewforge init` writes a `crewforge.yaml` you can pass with `--config`; the key
114
+ in it is a `${VAR}` reference, never the value.
115
+
116
+ ## Recipes
117
+
118
+ Each example below was run against a real model (a Gemini Flash model behind an
119
+ OpenAI-compatible router) while building crewforge. Output is abbreviated; token
120
+ tables are trimmed to their totals.
121
+
122
+ | Command | Agents | What you get |
123
+ |---|---|---|
124
+ | `review PATH` | security, style, complexity, logic, testing, documentation (parallel) → summary | Findings by severity, a decision and a score; `-f json` for CI |
125
+ | `ingest FILES` | ingester, indexer (no LLM) | How each file was parsed and chunked |
126
+ | `ask FILES -q Q [--verify] [--rerank]` | retriever → answerer → fact-checker | An answer with `[Source N]` citations and a claim-by-claim verdict |
127
+ | `summarize FILES` | summarizer | Summary and key points per document |
128
+ | `scaffold "DESCRIPTION"` | parser → schema → router, model, auth, test_gen, doc_gen (parallel) → validator | A runnable FastAPI project with `openapi.json`, tests and docs |
129
+ | `validate PATH` | validator (no LLM) | Spec check; for a project directory, also checks the app serves every spec operation |
130
+ | `write "TOPIC"` | research → outline → writer → SEO → editor ⇄ quality → translator → publisher | Article as Markdown/HTML/JSON, social snippets, a usage file |
131
+
132
+ ### `review`
133
+
134
+ ```text
135
+ $ crewforge review app.py -f markdown -o review.md # app.py builds SQL with an f-string
136
+ # crewforge review
137
+
138
+ **Decision:** REQUEST_CHANGES · **Quality score:** 25/100
139
+
140
+ ### Top issues
141
+ 1. Critical SQL injection vulnerability caused by string interpolation in database query (CWE-89)
142
+ 2. Missing unit tests covering basic retrieval, missing records, and inputs with quotes
143
+ 3. Missing docstring and incomplete type annotations for the 'name' parameter and return value
144
+
145
+ ### Critical (3)
146
+ - **SQL Injection via string interpolation** (line 4) [CWE-89] — _security_: ...
147
+ - Fix: conn.execute('SELECT * FROM users WHERE name = ?', (name,)).fetchall()
148
+ ...
149
+ total 7 calls 22,366 tokens (provider-reported)
150
+ ```
151
+
152
+ ### `ask` and `summarize`
153
+
154
+ ```text
155
+ $ crewforge ask kestrel.md -q "How many sensors does Project Kestrel use, and who leads it?" --verify --export session.md
156
+ ✓ ingested kestrel.md (1 chunks)
157
+ Q: How many sensors does Project Kestrel use, and who leads it?
158
+ Project Kestrel uses 42 water-level sensors deployed along the Sarawak River, and the project is
159
+ led by Nadia Rahman [Source 1].
160
+ Confidence: high
161
+ Verification: verified (2/2 claims verified)
162
+ Exported to: session.md
163
+ total 2 calls 5,053 tokens (provider-reported)
164
+
165
+ $ crewforge summarize kestrel.md --length short
166
+ Summary: Project Kestrel is a river-monitoring pilot initiative launched in March 2025 by the
167
+ Kuching office under the leadership of Nadia Rahman. ...
168
+ total 1 call 2,821 tokens (provider-reported)
169
+ ```
170
+
171
+ ### `scaffold` and `validate`
172
+
173
+ ```text
174
+ $ crewforge scaffold "Todo API: list todos, create a todo with a title, mark a todo done, and delete a todo. Bearer auth." -o todo-api
175
+ Wrote 11 files
176
+ todo-api/
177
+ .env.example API.md Dockerfile auth.py main.py models.py openapi.json
178
+ requirements.txt routes.py tests/conftest.py tests/test_api.py
179
+ Validation: valid · 100/100 (4 operation(s) checked, 0 error(s), 0 warning(s))
180
+ App check: OK — app serves 5 route(s)
181
+ total 5 calls 20,711 tokens (provider-reported)
182
+
183
+ $ cd todo-api && pytest -q tests
184
+ 4 passed
185
+
186
+ $ crewforge validate todo-api # offline, no key needed
187
+ Validation: valid · 100/100 (4 operation(s) checked, 0 error(s), 0 warning(s))
188
+ App check: OK — app serves 5 route(s)
189
+ ```
190
+
191
+ The first real run of this recipe produced a project whose own smoke test failed
192
+ (`DELETE /todos/1` correctly answered 404 and the generated test did not allow
193
+ it); that was fixed with a regression test before this output was taken.
194
+
195
+ ### `write`
196
+
197
+ ```text
198
+ $ crewforge write "Why unit tests matter" --words 300 --no-seo --max-iter 1 -f markdown -f html -o article
199
+ ╭──────────── crewforge write — Why unit tests matter ────────────╮
200
+ │ Article: 711 words · quality score 88/100 │
201
+ │ Files: │
202
+ │ article/why-unit-tests-matter.md │
203
+ │ article/why-unit-tests-matter.html │
204
+ │ article/why-unit-tests-matter.publisher.json │
205
+ │ Metrics: article/metrics/20261020_095052.json │
206
+ ╰──────────────────────────────────────────────────────────────────╯
207
+ total 6 calls 35,351 tokens (provider-reported)
208
+ ```
209
+
210
+ Two honest caveats from that run: the model wrote more than twice the requested
211
+ length (`--words` is a target given to the agents, not a hard limit), and the
212
+ research agent has no web access, so statistics it quotes come from the model's
213
+ training data. Review articles before publishing them.
214
+
215
+ ## Providers
216
+
217
+ The table is checked against the code by `tests/test_readme.py`.
218
+
219
+ | Name | Provider | Base URL | Default model | Key variable | Auth |
220
+ |---|---|---|---|---|---|
221
+ | `openai` | OpenAI | `https://api.openai.com/v1` | `gpt-4o-mini` | `OPENAI_API_KEY` | Bearer |
222
+ | `openrouter` | OpenRouter | `https://openrouter.ai/api/v1` | `openai/gpt-4o-mini` | `OPENROUTER_API_KEY` | Bearer |
223
+ | `groq` | Groq | `https://api.groq.com/openai/v1` | `llama-3.3-70b-versatile` | `GROQ_API_KEY` | Bearer |
224
+ | `deepseek` | DeepSeek | `https://api.deepseek.com/v1` | `deepseek-chat` | `DEEPSEEK_API_KEY` | Bearer |
225
+ | `together` | Together AI | `https://api.together.xyz/v1` | `meta-llama/Llama-3.3-70B-Instruct-Turbo` | `TOGETHER_API_KEY` | Bearer |
226
+ | `mistral` | Mistral | `https://api.mistral.ai/v1` | `mistral-small-latest` | `MISTRAL_API_KEY` | Bearer |
227
+ | `ollama` | Ollama (local) | `http://localhost:11434/v1` | `llama3.1` | `OLLAMA_API_KEY` (optional) | Bearer |
228
+ | `llamacpp` | llama.cpp server (local) | `http://localhost:8080/v1` | `local` | `LLAMACPP_API_KEY` (optional) | Bearer |
229
+ | `mimo` | Xiaomi MiMo | `https://token-plan-sgp.xiaomimimo.com/v1` | `mimo-v2.5-pro` | `MIMO_API_KEY` | `api-key` header |
230
+ | `custom` | Any OpenAI-compatible endpoint | set `--base-url` / `CREWFORGE_BASE_URL` | set `--model` / `CREWFORGE_MODEL` | `CREWFORGE_API_KEY` (optional) | Bearer |
231
+
232
+ Resolution order for the key, base URL and model: command-line flag or
233
+ `crewforge.yaml` → `CREWFORGE_API_KEY` / `CREWFORGE_BASE_URL` / `CREWFORGE_MODEL`
234
+ → the provider's own variables (e.g. `OPENAI_BASE_URL`) → the preset default.
235
+ Pick the provider with `--provider` or `CREWFORGE_PROVIDER`; the default is `openai`.
236
+ An unknown provider name is a configuration error, not a silent fallback.
237
+
238
+ ## Architecture
239
+
240
+ ```mermaid
241
+ flowchart LR
242
+ CLI["crewforge CLI<br/>review · ask · summarize · ingest<br/>scaffold · validate · write"] --> R
243
+
244
+ subgraph R["Recipes (src/crewforge/recipes)"]
245
+ RV[review]
246
+ DC[docs]
247
+ AP[api]
248
+ CT[content]
249
+ end
250
+
251
+ R --> CREW
252
+
253
+ subgraph CORE["Core (src/crewforge/core)"]
254
+ CREW["Crew<br/>step() · parallel()<br/>names failed agents"]
255
+ AG["Agent<br/>ask() · ask_json()<br/>rejects empty output"]
256
+ PA["parsing<br/>tolerant JSON + schema"]
257
+ CL["LLMClient<br/>stream=false · retries 429/5xx only"]
258
+ US["UsageTracker<br/>provider-reported tokens"]
259
+ CF["LLMConfig + provider presets<br/>keys from env only"]
260
+ CREW --> AG
261
+ AG --> PA
262
+ AG --> CL
263
+ AG --> US
264
+ CL --> CF
265
+ end
266
+
267
+ CL -->|"/chat/completions"| P[("Any OpenAI-compatible provider")]
268
+ ```
269
+
270
+ - **`core/client.py`** — one async client. It always sends `"stream": false`,
271
+ retries only HTTP 429, 5xx and network/timeout errors (with capped exponential
272
+ backoff that honours `Retry-After`), and fails on the first 401/403/4xx with a
273
+ message naming the variable to fix. The key is only ever a request header.
274
+ - **`core/agent.py`** — an agent is a role, a system prompt and per-agent
275
+ sampling defaults. `ask()` records usage and raises if the reply is blank;
276
+ `ask_json()` additionally requires parseable JSON (optionally a pydantic schema).
277
+ - **`core/crew.py`** — runs agents as sequential steps or parallel stages and
278
+ turns any failure into a `CrewFailedError` that lists every failed agent.
279
+ - **`core/usage.py`** — per-agent calls, tokens, retries and latency, taken from
280
+ the provider's `usage` object. Calls without usage are counted as "not reported",
281
+ never estimated.
282
+
283
+ Use the core directly:
284
+
285
+ ```python
286
+ import asyncio
287
+ from crewforge.core import Agent, Crew, LLMConfig
288
+
289
+
290
+ class Haiku(Agent):
291
+ name = "poet"
292
+ system_prompt = "You write one haiku. Nothing else."
293
+
294
+
295
+ async def main():
296
+ async with Crew("demo", LLMConfig(provider="groq")) as crew:
297
+ reply = await crew.step("poet", crew.agent(Haiku).ask("autumn in Kuching"))
298
+ print(reply.content, crew.tracker.one_line())
299
+
300
+
301
+ asyncio.run(main())
302
+ ```
303
+
304
+ ## Honest failure
305
+
306
+ A run whose agents return empty or unparseable output, or a validator that
307
+ checked nothing, **fails with exit code 1 and names the agent**. It never prints
308
+ a success message over a hollow result.
309
+
310
+ ```text
311
+ $ crewforge ask kestrel.md -q "Who leads Project Kestrel?" # with a wrong key
312
+ error: docs failed
313
+ ✗ agent 'answerer' (http): HTTP 401 from <base-url> (authentication failed — check OPENAI_API_KEY): API key required for remote API access
314
+
315
+ $ crewforge validate empty-spec.json
316
+ Validation: INVALID · 0/100 (0 operation(s) checked, 1 error(s), 0 warning(s))
317
+ ERROR: No endpoints defined (paths is empty)
318
+ ```
319
+
320
+ | Situation | Before (the four tools) | crewforge |
321
+ |---|---|---|
322
+ | Every reviewer returns nothing | "Review Complete!", exit 0 | exit 1, names each reviewer |
323
+ | A reviewer answers in prose | treated as a review | exit 1, `agent 'style' (parse)` |
324
+ | Parser returns no endpoints | empty spec scored 100/100 | exit 1, names `parser` |
325
+ | Spec has paths but no operations | scored | score 0, invalid, exit 1 |
326
+ | Quality agent answers in prose | score 0, loop burns every round | exit 1 after one round, names `quality` |
327
+ | Quality never reaches the threshold | article published | exit 1 unless `--accept-below-threshold` |
328
+ | Question matches no passage | model asked to answer from nothing | exit 1, names `retriever`, no LLM call |
329
+ | Generated code does not compile | template silently reused | exit 1, names the agent |
330
+
331
+ Exit codes: `0` success · `1` run failed (agent, validator or provider) · `2`
332
+ usage or configuration error (bad flag, unknown provider, missing key).
333
+
334
+ The guards are covered by tests that were checked by removing each guard and
335
+ watching them fail; see [docs/porting.md](https://github.com/aimanmalib/crewforge/blob/main/docs/porting.md#honest-failure-guards-and-the-sabotage-check).
336
+
337
+ ## How this was built
338
+
339
+ crewforge was built with AI coding assistance (Claude Code) under the
340
+ direction of its author, Muhammad Aiman. It consolidates four earlier tools by
341
+ the same author — [codeguard](https://github.com/aimanmalib/codeguard),
342
+ [documind](https://github.com/aimanmalib/documind),
343
+ [apiforge](https://github.com/aimanmalib/apiforge) and
344
+ [contentforge](https://github.com/aimanmalib/contentforge) — whose test suites
345
+ were carried over into this repository (see [docs/porting.md](https://github.com/aimanmalib/crewforge/blob/main/docs/porting.md)
346
+ for what was ported, re-targeted at the shared core, or dropped and why).
347
+
348
+ The evidence that it works is meant to be checkable: the test suite (run
349
+ `pytest`; it needs no key and no network), CI on Python 3.10–3.13, and the
350
+ real-provider runs shown above, two of which found bugs that were fixed with
351
+ tests before the output was recorded.
352
+
353
+ ## Contributing
354
+
355
+ See [CONTRIBUTING.md](https://github.com/aimanmalib/crewforge/blob/main/CONTRIBUTING.md). In short: `pip install -e ".[dev,docs]"`,
356
+ `pytest`, `ruff check . && ruff format --check .`, and every new failure path
357
+ needs a test that fails if the guard is removed.
358
+
359
+ ## Security
360
+
361
+ Please report vulnerabilities privately through
362
+ [GitHub private vulnerability reporting](https://github.com/aimanmalib/crewforge/security/advisories/new).
363
+ See [SECURITY.md](https://github.com/aimanmalib/crewforge/blob/main/SECURITY.md) for scope notes (what is sent to your provider,
364
+ and what `validate` executes).
365
+
366
+ ## License
367
+
368
+ [MIT](https://github.com/aimanmalib/crewforge/blob/main/LICENSE) © Muhammad Aiman
369
+
370
+ ---
371
+
372
+ Built by [Sasa Teguh](https://sasateguh.com) · Malaysia