agileforagents 0.3.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.
- agileforagents-0.3.0/PKG-INFO +126 -0
- agileforagents-0.3.0/README.md +97 -0
- agileforagents-0.3.0/pyproject.toml +92 -0
- agileforagents-0.3.0/setup.cfg +4 -0
- agileforagents-0.3.0/src/agileforagents/__init__.py +3 -0
- agileforagents-0.3.0/src/agileforagents/backend_client.py +63 -0
- agileforagents-0.3.0/src/agileforagents/environment_layer.py +115 -0
- agileforagents-0.3.0/src/agileforagents/fallback.py +156 -0
- agileforagents-0.3.0/src/agileforagents/feedback_logger.py +36 -0
- agileforagents-0.3.0/src/agileforagents/models.py +56 -0
- agileforagents-0.3.0/src/agileforagents/prompts.py +115 -0
- agileforagents-0.3.0/src/agileforagents/server.py +185 -0
- agileforagents-0.3.0/src/agileforagents.egg-info/PKG-INFO +126 -0
- agileforagents-0.3.0/src/agileforagents.egg-info/SOURCES.txt +18 -0
- agileforagents-0.3.0/src/agileforagents.egg-info/dependency_links.txt +1 -0
- agileforagents-0.3.0/src/agileforagents.egg-info/entry_points.txt +3 -0
- agileforagents-0.3.0/src/agileforagents.egg-info/requires.txt +10 -0
- agileforagents-0.3.0/src/agileforagents.egg-info/top_level.txt +1 -0
- agileforagents-0.3.0/tests/test_layers.py +234 -0
- agileforagents-0.3.0/tests/test_server.py +197 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agileforagents
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Zero-config MCP server that turns prompts into verified agile task boards for AI coding agents
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/FAJU85/AgileForAgents
|
|
7
|
+
Project-URL: Repository, https://github.com/FAJU85/AgileForAgents
|
|
8
|
+
Project-URL: Issues, https://github.com/FAJU85/AgileForAgents/issues
|
|
9
|
+
Keywords: mcp,agile,ai-agents,claude-code,cursor,codex
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
Requires-Dist: mcp>=1.9.0
|
|
21
|
+
Requires-Dist: pydantic>=2.0.0
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
24
|
+
Requires-Dist: pytest-cov>=5.0.0; extra == "dev"
|
|
25
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
|
|
26
|
+
Requires-Dist: ruff>=0.4.0; extra == "dev"
|
|
27
|
+
Requires-Dist: mypy>=1.10.0; extra == "dev"
|
|
28
|
+
Requires-Dist: pip-audit>=2.7.0; extra == "dev"
|
|
29
|
+
|
|
30
|
+
# AgileForAgents
|
|
31
|
+
|
|
32
|
+
**Turn any project prompt into a verified, sprint-ready agile task board — right inside your AI coding agent.**
|
|
33
|
+
|
|
34
|
+
AgileForAgents is a zero-config [MCP](https://modelcontextprotocol.io) server for
|
|
35
|
+
Claude Code, Cursor, Codex CLI, and any MCP-compatible host. You describe a project
|
|
36
|
+
in your IDE as you normally would; AgileForAgents turns it into a structured agile
|
|
37
|
+
board (epics → sprints → user stories → subtasks) where **every subtask has an
|
|
38
|
+
observable done-criteria** and **every assumption is made explicit** — following
|
|
39
|
+
Andrej Karpathy's Spec methodology.
|
|
40
|
+
|
|
41
|
+
## How it works
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
┌──────────────────────────┐ stdio ┌────────────────────────┐ HTTPS ┌──────────────────────────────┐
|
|
45
|
+
│ Your IDE │◀──────────▶│ agileforagents │───────────▶│ Hosted backend (HF Space) │
|
|
46
|
+
│ Claude Code / Cursor / │ MCP tool │ MCP server │ /api/ │ • runs a free HF model │
|
|
47
|
+
│ Codex — you type here │ call │ (stdlib-only client) │ generate │ • holds the token server-side│
|
|
48
|
+
└──────────────────────────┘ └────────────────────────┘◀───────────│ • returns a verified board │
|
|
49
|
+
└───────────────┬──────────────┘
|
|
50
|
+
│ logs metrics
|
|
51
|
+
▼
|
|
52
|
+
┌──────────────────────────┐
|
|
53
|
+
│ Gradio Control Panel │
|
|
54
|
+
│ (monitoring dashboard, │
|
|
55
|
+
│ no prompt input) │
|
|
56
|
+
└──────────────────────────┘
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
- **You never leave your IDE.** Prompts come from your editor through the MCP tool —
|
|
60
|
+
not from a web form.
|
|
61
|
+
- **No API key, no token, no heavy dependencies.** The MCP server installs with just
|
|
62
|
+
the MCP SDK + pydantic. All model inference happens on the hosted backend, which
|
|
63
|
+
holds the Hugging Face token as a server-side secret.
|
|
64
|
+
- **The Gradio Space is a control panel**, not an input form — it shows how the
|
|
65
|
+
generator is performing (volume, average verification score, recent activity).
|
|
66
|
+
- **Works offline, degraded.** If the backend is unreachable, a local heuristic
|
|
67
|
+
fallback still returns a valid scaffold board so the tool never hard-fails.
|
|
68
|
+
|
|
69
|
+
## Install
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pip install agileforagents
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Then register the `agileforagents` stdio server with your MCP host.
|
|
76
|
+
|
|
77
|
+
**Claude Code** (`~/.claude/mcp.json` or project `.mcp.json`):
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"mcpServers": {
|
|
82
|
+
"agileforagents": {
|
|
83
|
+
"command": "agileforagents"
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**Cursor / Codex CLI:** point your MCP config at the `agileforagents` command. No
|
|
90
|
+
environment variables are required.
|
|
91
|
+
|
|
92
|
+
## Tools
|
|
93
|
+
|
|
94
|
+
| Tool | What it does |
|
|
95
|
+
|---|---|
|
|
96
|
+
| `generate_agile_tasks` | Turn a prompt into a verified agile board. Args: `prompt`, `format` (`claude` \| `json` \| `cursor`), `detail_level` (`standard` \| `detailed`). |
|
|
97
|
+
| `get_agent_rules` | Return the AgileForAgents working protocol to paste into `CLAUDE.md` / `.cursorrules`. |
|
|
98
|
+
| `feedback_stats` | Local generation count + backend health. |
|
|
99
|
+
|
|
100
|
+
## Configuration (optional)
|
|
101
|
+
|
|
102
|
+
The MCP server works with no configuration. For self-hosting or testing you can override:
|
|
103
|
+
|
|
104
|
+
| Env var | Purpose | Default |
|
|
105
|
+
|---|---|---|
|
|
106
|
+
| `AFA_BACKEND_URL` | Hosted backend base URL | `https://vooom-agileforagents.hf.space` |
|
|
107
|
+
| `AFA_BACKEND_TIMEOUT` | Request timeout (seconds) | `120` |
|
|
108
|
+
|
|
109
|
+
## Repository layout
|
|
110
|
+
|
|
111
|
+
| Path | Purpose |
|
|
112
|
+
|---|---|
|
|
113
|
+
| `src/agileforagents/` | The installable stdio MCP server (thin client + formatters + fallback). |
|
|
114
|
+
| `space/` | The Hugging Face Space: FastAPI inference backend + Gradio monitoring dashboard. |
|
|
115
|
+
| `tests/` | Unit + E2E tests. |
|
|
116
|
+
|
|
117
|
+
## Development
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
pip install -e ".[dev]"
|
|
121
|
+
make format-check && make lint && make typecheck && make test
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## License
|
|
125
|
+
|
|
126
|
+
MIT
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# AgileForAgents
|
|
2
|
+
|
|
3
|
+
**Turn any project prompt into a verified, sprint-ready agile task board — right inside your AI coding agent.**
|
|
4
|
+
|
|
5
|
+
AgileForAgents is a zero-config [MCP](https://modelcontextprotocol.io) server for
|
|
6
|
+
Claude Code, Cursor, Codex CLI, and any MCP-compatible host. You describe a project
|
|
7
|
+
in your IDE as you normally would; AgileForAgents turns it into a structured agile
|
|
8
|
+
board (epics → sprints → user stories → subtasks) where **every subtask has an
|
|
9
|
+
observable done-criteria** and **every assumption is made explicit** — following
|
|
10
|
+
Andrej Karpathy's Spec methodology.
|
|
11
|
+
|
|
12
|
+
## How it works
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
┌──────────────────────────┐ stdio ┌────────────────────────┐ HTTPS ┌──────────────────────────────┐
|
|
16
|
+
│ Your IDE │◀──────────▶│ agileforagents │───────────▶│ Hosted backend (HF Space) │
|
|
17
|
+
│ Claude Code / Cursor / │ MCP tool │ MCP server │ /api/ │ • runs a free HF model │
|
|
18
|
+
│ Codex — you type here │ call │ (stdlib-only client) │ generate │ • holds the token server-side│
|
|
19
|
+
└──────────────────────────┘ └────────────────────────┘◀───────────│ • returns a verified board │
|
|
20
|
+
└───────────────┬──────────────┘
|
|
21
|
+
│ logs metrics
|
|
22
|
+
▼
|
|
23
|
+
┌──────────────────────────┐
|
|
24
|
+
│ Gradio Control Panel │
|
|
25
|
+
│ (monitoring dashboard, │
|
|
26
|
+
│ no prompt input) │
|
|
27
|
+
└──────────────────────────┘
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
- **You never leave your IDE.** Prompts come from your editor through the MCP tool —
|
|
31
|
+
not from a web form.
|
|
32
|
+
- **No API key, no token, no heavy dependencies.** The MCP server installs with just
|
|
33
|
+
the MCP SDK + pydantic. All model inference happens on the hosted backend, which
|
|
34
|
+
holds the Hugging Face token as a server-side secret.
|
|
35
|
+
- **The Gradio Space is a control panel**, not an input form — it shows how the
|
|
36
|
+
generator is performing (volume, average verification score, recent activity).
|
|
37
|
+
- **Works offline, degraded.** If the backend is unreachable, a local heuristic
|
|
38
|
+
fallback still returns a valid scaffold board so the tool never hard-fails.
|
|
39
|
+
|
|
40
|
+
## Install
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install agileforagents
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Then register the `agileforagents` stdio server with your MCP host.
|
|
47
|
+
|
|
48
|
+
**Claude Code** (`~/.claude/mcp.json` or project `.mcp.json`):
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"mcpServers": {
|
|
53
|
+
"agileforagents": {
|
|
54
|
+
"command": "agileforagents"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Cursor / Codex CLI:** point your MCP config at the `agileforagents` command. No
|
|
61
|
+
environment variables are required.
|
|
62
|
+
|
|
63
|
+
## Tools
|
|
64
|
+
|
|
65
|
+
| Tool | What it does |
|
|
66
|
+
|---|---|
|
|
67
|
+
| `generate_agile_tasks` | Turn a prompt into a verified agile board. Args: `prompt`, `format` (`claude` \| `json` \| `cursor`), `detail_level` (`standard` \| `detailed`). |
|
|
68
|
+
| `get_agent_rules` | Return the AgileForAgents working protocol to paste into `CLAUDE.md` / `.cursorrules`. |
|
|
69
|
+
| `feedback_stats` | Local generation count + backend health. |
|
|
70
|
+
|
|
71
|
+
## Configuration (optional)
|
|
72
|
+
|
|
73
|
+
The MCP server works with no configuration. For self-hosting or testing you can override:
|
|
74
|
+
|
|
75
|
+
| Env var | Purpose | Default |
|
|
76
|
+
|---|---|---|
|
|
77
|
+
| `AFA_BACKEND_URL` | Hosted backend base URL | `https://vooom-agileforagents.hf.space` |
|
|
78
|
+
| `AFA_BACKEND_TIMEOUT` | Request timeout (seconds) | `120` |
|
|
79
|
+
|
|
80
|
+
## Repository layout
|
|
81
|
+
|
|
82
|
+
| Path | Purpose |
|
|
83
|
+
|---|---|
|
|
84
|
+
| `src/agileforagents/` | The installable stdio MCP server (thin client + formatters + fallback). |
|
|
85
|
+
| `space/` | The Hugging Face Space: FastAPI inference backend + Gradio monitoring dashboard. |
|
|
86
|
+
| `tests/` | Unit + E2E tests. |
|
|
87
|
+
|
|
88
|
+
## Development
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
pip install -e ".[dev]"
|
|
92
|
+
make format-check && make lint && make typecheck && make test
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## License
|
|
96
|
+
|
|
97
|
+
MIT
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=45", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "agileforagents"
|
|
7
|
+
version = "0.3.0"
|
|
8
|
+
description = "Zero-config MCP server that turns prompts into verified agile task boards for AI coding agents"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
keywords = ["mcp", "agile", "ai-agents", "claude-code", "cursor", "codex"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 4 - Beta",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.11",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
"Topic :: Software Development :: Build Tools",
|
|
21
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
22
|
+
]
|
|
23
|
+
# The installable MCP server is intentionally dependency-light: only the MCP SDK
|
|
24
|
+
# and pydantic. All LLM work happens on the hosted backend (a Hugging Face Space),
|
|
25
|
+
# reached over the standard library, so users need no API keys or heavy ML deps.
|
|
26
|
+
dependencies = [
|
|
27
|
+
"mcp>=1.9.0",
|
|
28
|
+
"pydantic>=2.0.0",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://github.com/FAJU85/AgileForAgents"
|
|
33
|
+
Repository = "https://github.com/FAJU85/AgileForAgents"
|
|
34
|
+
Issues = "https://github.com/FAJU85/AgileForAgents/issues"
|
|
35
|
+
|
|
36
|
+
[project.scripts]
|
|
37
|
+
agileforagents = "agileforagents.server:main"
|
|
38
|
+
agile-for-agents = "agileforagents.server:main"
|
|
39
|
+
|
|
40
|
+
[project.optional-dependencies]
|
|
41
|
+
dev = [
|
|
42
|
+
"pytest>=8.0.0",
|
|
43
|
+
"pytest-cov>=5.0.0",
|
|
44
|
+
"pytest-asyncio>=0.23.0",
|
|
45
|
+
"ruff>=0.4.0",
|
|
46
|
+
"mypy>=1.10.0",
|
|
47
|
+
"pip-audit>=2.7.0",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
[tool.setuptools.packages.find]
|
|
51
|
+
where = ["src"]
|
|
52
|
+
|
|
53
|
+
[tool.pytest.ini_options]
|
|
54
|
+
testpaths = ["tests"]
|
|
55
|
+
python_files = ["test_*.py"]
|
|
56
|
+
asyncio_mode = "auto"
|
|
57
|
+
|
|
58
|
+
[tool.ruff]
|
|
59
|
+
line-length = 100
|
|
60
|
+
target-version = "py311"
|
|
61
|
+
|
|
62
|
+
[tool.ruff.lint]
|
|
63
|
+
select = ["E", "F", "W", "I", "N", "UP", "B", "T20"]
|
|
64
|
+
# T20 catches print() statements in production code per policy
|
|
65
|
+
ignore = []
|
|
66
|
+
|
|
67
|
+
[tool.mypy]
|
|
68
|
+
python_version = "3.11"
|
|
69
|
+
strict = true
|
|
70
|
+
ignore_missing_imports = true
|
|
71
|
+
files = ["src/"]
|
|
72
|
+
|
|
73
|
+
# ── third-party without stubs ────────────────────────────────────────────────
|
|
74
|
+
[[tool.mypy.overrides]]
|
|
75
|
+
module = [
|
|
76
|
+
"mcp", "mcp.*",
|
|
77
|
+
"pydantic", "pydantic.*",
|
|
78
|
+
]
|
|
79
|
+
ignore_errors = true
|
|
80
|
+
|
|
81
|
+
[[tool.mypy.overrides]]
|
|
82
|
+
module = ["agileforagents.models"]
|
|
83
|
+
disable_error_code = ["misc"]
|
|
84
|
+
|
|
85
|
+
# The mcp SDK's @app.list_tools() / @app.call_tool() decorators are untyped, and
|
|
86
|
+
# which error code mypy emits for them ("untyped-decorator" vs "no-untyped-call")
|
|
87
|
+
# varies by installed mcp version. Disable both here so the gate is deterministic
|
|
88
|
+
# across environments, and turn off unused-ignore warnings for this module.
|
|
89
|
+
[[tool.mypy.overrides]]
|
|
90
|
+
module = ["agileforagents.server"]
|
|
91
|
+
disable_error_code = ["untyped-decorator", "no-untyped-call", "misc"]
|
|
92
|
+
warn_unused_ignores = false
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Backend client — talks to the hosted AgileForAgents inference service.
|
|
2
|
+
|
|
3
|
+
Uses only the Python standard library (``urllib``) so the MCP server installs
|
|
4
|
+
with zero heavy dependencies. The hosted backend (a Hugging Face Space) holds
|
|
5
|
+
the model token server-side, so the end user never supplies any credentials.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
import urllib.error
|
|
13
|
+
import urllib.request
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
# Public Hugging Face Space backend. Overridable for self-hosting / testing.
|
|
17
|
+
DEFAULT_BACKEND_URL = "https://vooom-agileforagents.hf.space"
|
|
18
|
+
BACKEND_URL = os.getenv("AFA_BACKEND_URL", DEFAULT_BACKEND_URL).rstrip("/")
|
|
19
|
+
|
|
20
|
+
# The backend can be slow on a cold start (model spin-up), so allow generous time.
|
|
21
|
+
REQUEST_TIMEOUT_SECONDS = float(os.getenv("AFA_BACKEND_TIMEOUT", "120"))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class BackendError(RuntimeError):
|
|
25
|
+
"""Raised when the hosted backend is unreachable or returns an error."""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def generate_board(prompt: str, detail_level: str = "standard") -> dict[str, Any]:
|
|
29
|
+
"""POST a prompt to the backend and return ``{"board": ..., "verification": ...}``.
|
|
30
|
+
|
|
31
|
+
Raises :class:`BackendError` on any network or protocol failure so the caller
|
|
32
|
+
can fall back to local generation.
|
|
33
|
+
"""
|
|
34
|
+
payload = json.dumps({"prompt": prompt, "detail_level": detail_level}).encode("utf-8")
|
|
35
|
+
request = urllib.request.Request(
|
|
36
|
+
f"{BACKEND_URL}/api/generate",
|
|
37
|
+
data=payload,
|
|
38
|
+
headers={"Content-Type": "application/json", "Accept": "application/json"},
|
|
39
|
+
method="POST",
|
|
40
|
+
)
|
|
41
|
+
try:
|
|
42
|
+
with urllib.request.urlopen(request, timeout=REQUEST_TIMEOUT_SECONDS) as response:
|
|
43
|
+
body = response.read().decode("utf-8")
|
|
44
|
+
except (urllib.error.URLError, urllib.error.HTTPError, TimeoutError, OSError) as exc:
|
|
45
|
+
raise BackendError(f"Backend request failed: {exc}") from exc
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
data: dict[str, Any] = json.loads(body)
|
|
49
|
+
except json.JSONDecodeError as exc:
|
|
50
|
+
raise BackendError(f"Backend returned invalid JSON: {exc}") from exc
|
|
51
|
+
|
|
52
|
+
if "board" not in data:
|
|
53
|
+
raise BackendError(f"Backend response missing 'board': {data}")
|
|
54
|
+
return data
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def health() -> bool:
|
|
58
|
+
"""Return ``True`` if the backend health endpoint responds OK."""
|
|
59
|
+
try:
|
|
60
|
+
with urllib.request.urlopen(f"{BACKEND_URL}/health", timeout=10) as response:
|
|
61
|
+
return bool(response.status == 200)
|
|
62
|
+
except (urllib.error.URLError, urllib.error.HTTPError, TimeoutError, OSError):
|
|
63
|
+
return False
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""Layer 3 — Environment Layer: Formats output and injects agent guardrails."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
from .models import AgileTaskBoard, VerificationResult
|
|
6
|
+
from .prompts import ENVIRONMENT_RULES
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class EnvironmentLayer:
|
|
10
|
+
"""Adapts the verified AgileTaskBoard for different agent environments."""
|
|
11
|
+
|
|
12
|
+
SUPPORTED_FORMATS = ("claude", "json", "cursor")
|
|
13
|
+
|
|
14
|
+
def format(
|
|
15
|
+
self,
|
|
16
|
+
board: AgileTaskBoard,
|
|
17
|
+
verification: VerificationResult,
|
|
18
|
+
output_format: str = "claude",
|
|
19
|
+
) -> str:
|
|
20
|
+
if output_format not in self.SUPPORTED_FORMATS:
|
|
21
|
+
raise ValueError(
|
|
22
|
+
f"Unknown format '{output_format}'. Choose from: {self.SUPPORTED_FORMATS}"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
if output_format == "json":
|
|
26
|
+
return self._format_json(board, verification)
|
|
27
|
+
if output_format == "cursor":
|
|
28
|
+
return self._format_cursor(board)
|
|
29
|
+
return self._format_claude(board, verification)
|
|
30
|
+
|
|
31
|
+
# ------------------------------------------------------------------
|
|
32
|
+
# Private formatters
|
|
33
|
+
# ------------------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
def _format_claude(self, board: AgileTaskBoard, verification: VerificationResult) -> str:
|
|
36
|
+
lines = [ENVIRONMENT_RULES]
|
|
37
|
+
|
|
38
|
+
lines.append(f"## Project Goal\n{board.project_goal}\n")
|
|
39
|
+
lines.append(f"## Tech Stack\n{', '.join(board.tech_stack)}\n")
|
|
40
|
+
|
|
41
|
+
lines.append("## Assumptions")
|
|
42
|
+
for a in board.assumptions:
|
|
43
|
+
lines.append(f"- {a}")
|
|
44
|
+
lines.append("")
|
|
45
|
+
|
|
46
|
+
lines.append("## Epics")
|
|
47
|
+
for e in board.epics:
|
|
48
|
+
lines.append(f"- {e}")
|
|
49
|
+
lines.append("")
|
|
50
|
+
|
|
51
|
+
for sprint in board.sprints:
|
|
52
|
+
lines.append(f"## Sprint {sprint.number}: {sprint.goal}")
|
|
53
|
+
lines.append(f"**Done when:** {sprint.done_criteria}\n")
|
|
54
|
+
|
|
55
|
+
for story in sprint.user_stories:
|
|
56
|
+
lines.append(f"### {story.id} — {story.title} ({story.story_points} pts)")
|
|
57
|
+
lines.append(
|
|
58
|
+
f"As a **{story.as_a}**, I want **{story.i_want}** "
|
|
59
|
+
f"so that **{story.so_that}**.\n"
|
|
60
|
+
)
|
|
61
|
+
lines.append("**Acceptance Criteria:**")
|
|
62
|
+
for ac in story.acceptance_criteria:
|
|
63
|
+
lines.append(f"- {ac}")
|
|
64
|
+
lines.append("")
|
|
65
|
+
lines.append("**Subtasks:**")
|
|
66
|
+
for st in story.subtasks:
|
|
67
|
+
lines.append(f"- [ ] `{st.id}` {st.title} ({st.estimated_hours}h)")
|
|
68
|
+
lines.append(f" - {st.description}")
|
|
69
|
+
lines.append(f" - **Done when:** {st.done_criteria}")
|
|
70
|
+
lines.append("")
|
|
71
|
+
|
|
72
|
+
lines.append("---")
|
|
73
|
+
lines.append(
|
|
74
|
+
f"*Verification score: {verification.score:.0%} — "
|
|
75
|
+
f"{'✓ Passed' if verification.passed else '⚠ Partial'}*"
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
return "\n".join(lines)
|
|
79
|
+
|
|
80
|
+
def _format_json(self, board: AgileTaskBoard, verification: VerificationResult) -> str:
|
|
81
|
+
return json.dumps(
|
|
82
|
+
{
|
|
83
|
+
"board": board.model_dump(),
|
|
84
|
+
"verification": verification.model_dump(),
|
|
85
|
+
},
|
|
86
|
+
indent=2,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
def _format_cursor(self, board: AgileTaskBoard) -> str:
|
|
90
|
+
"""Cursor Rules format: compact task list for .cursorrules injection."""
|
|
91
|
+
lines = [
|
|
92
|
+
"# Project: " + board.project_goal,
|
|
93
|
+
"",
|
|
94
|
+
"## Rules",
|
|
95
|
+
"- Complete one user story at a time before starting the next.",
|
|
96
|
+
"- Every subtask has a done_criteria — self-test against it before marking [x].",
|
|
97
|
+
"- State any new assumptions explicitly before acting on them.",
|
|
98
|
+
"",
|
|
99
|
+
"## Assumptions",
|
|
100
|
+
]
|
|
101
|
+
for a in board.assumptions:
|
|
102
|
+
lines.append(f"- {a}")
|
|
103
|
+
lines.append("")
|
|
104
|
+
|
|
105
|
+
for sprint in board.sprints:
|
|
106
|
+
lines.append(f"## Sprint {sprint.number}: {sprint.goal}")
|
|
107
|
+
for story in sprint.user_stories:
|
|
108
|
+
lines.append(f"\n### {story.id}: {story.title}")
|
|
109
|
+
for st in story.subtasks:
|
|
110
|
+
lines.append(f"- [ ] {st.id}: {st.title} | done: {st.done_criteria}")
|
|
111
|
+
return "\n".join(lines)
|
|
112
|
+
|
|
113
|
+
def get_rules_only(self) -> str:
|
|
114
|
+
"""Returns the injected agent rules without any task content."""
|
|
115
|
+
return ENVIRONMENT_RULES.strip()
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""Local fallback generator — used only when the hosted backend is unreachable.
|
|
2
|
+
|
|
3
|
+
This is a deterministic, dependency-free heuristic decomposition. It is
|
|
4
|
+
intentionally conservative: it produces a valid, well-structured AgileTaskBoard
|
|
5
|
+
so the MCP tool never hard-fails, but it does not attempt LLM-quality planning.
|
|
6
|
+
The hosted backend is always preferred when available.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import re
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from .models import AgileTaskBoard, Sprint, Subtask, UserStory, VerificationResult
|
|
15
|
+
|
|
16
|
+
# Lightweight tech-stack detection so the fallback board reflects the prompt.
|
|
17
|
+
_TECH_KEYWORDS = {
|
|
18
|
+
"python": "Python",
|
|
19
|
+
"fastapi": "FastAPI",
|
|
20
|
+
"django": "Django",
|
|
21
|
+
"flask": "Flask",
|
|
22
|
+
"react": "React",
|
|
23
|
+
"next": "Next.js",
|
|
24
|
+
"vue": "Vue",
|
|
25
|
+
"node": "Node.js",
|
|
26
|
+
"typescript": "TypeScript",
|
|
27
|
+
"postgres": "PostgreSQL",
|
|
28
|
+
"mysql": "MySQL",
|
|
29
|
+
"sqlite": "SQLite",
|
|
30
|
+
"mongo": "MongoDB",
|
|
31
|
+
"redis": "Redis",
|
|
32
|
+
"docker": "Docker",
|
|
33
|
+
"tailwind": "Tailwind CSS",
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _detect_tech_stack(prompt: str) -> list[str]:
|
|
38
|
+
lower = prompt.lower()
|
|
39
|
+
found = [label for key, label in _TECH_KEYWORDS.items() if key in lower]
|
|
40
|
+
return found or ["TBD — confirm with stakeholder"]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _first_sentence(prompt: str) -> str:
|
|
44
|
+
cleaned = prompt.strip().rstrip(".")
|
|
45
|
+
match = re.split(r"[.!?\n]", cleaned, maxsplit=1)
|
|
46
|
+
return match[0].strip() if match and match[0].strip() else cleaned
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def generate_board(prompt: str, detail_level: str = "standard") -> dict[str, Any]:
|
|
50
|
+
"""Return ``{"board": ..., "verification": ...}`` matching the backend contract."""
|
|
51
|
+
goal = _first_sentence(prompt)
|
|
52
|
+
tech_stack = _detect_tech_stack(prompt)
|
|
53
|
+
|
|
54
|
+
board = AgileTaskBoard(
|
|
55
|
+
project_goal=f"Deliver: {goal}.",
|
|
56
|
+
tech_stack=tech_stack,
|
|
57
|
+
assumptions=[
|
|
58
|
+
"Generated locally (hosted backend was unreachable) — review before use.",
|
|
59
|
+
"Single small team; no external compliance constraints assumed.",
|
|
60
|
+
"Scope limited to a demonstrable MVP first.",
|
|
61
|
+
],
|
|
62
|
+
epics=["Foundation & Setup", "Core Feature", "Polish & Ship"],
|
|
63
|
+
sprints=[
|
|
64
|
+
Sprint(
|
|
65
|
+
number=1,
|
|
66
|
+
goal="Stand up a runnable skeleton that proves the core idea.",
|
|
67
|
+
done_criteria="App boots locally and the primary happy path is demonstrable.",
|
|
68
|
+
user_stories=[
|
|
69
|
+
UserStory(
|
|
70
|
+
id="US-1",
|
|
71
|
+
title="Project skeleton",
|
|
72
|
+
as_a="developer",
|
|
73
|
+
i_want="a runnable project skeleton with the chosen stack",
|
|
74
|
+
so_that="feature work has a verified foundation",
|
|
75
|
+
story_points=3,
|
|
76
|
+
acceptance_criteria=[
|
|
77
|
+
"Given the repo is cloned, when setup is run, then the app starts "
|
|
78
|
+
"without errors.",
|
|
79
|
+
],
|
|
80
|
+
subtasks=[
|
|
81
|
+
Subtask(
|
|
82
|
+
id="ST-1",
|
|
83
|
+
title="Initialise project & dependencies",
|
|
84
|
+
description="Scaffold the project structure and install deps.",
|
|
85
|
+
done_criteria="The dev server / entry point runs and prints a "
|
|
86
|
+
"startup message.",
|
|
87
|
+
estimated_hours=2.0,
|
|
88
|
+
),
|
|
89
|
+
Subtask(
|
|
90
|
+
id="ST-2",
|
|
91
|
+
title="Define the core data model",
|
|
92
|
+
description="Model the central entity implied by the prompt.",
|
|
93
|
+
done_criteria="The model is created and a smoke test reads/writes "
|
|
94
|
+
"one record.",
|
|
95
|
+
estimated_hours=3.0,
|
|
96
|
+
),
|
|
97
|
+
],
|
|
98
|
+
)
|
|
99
|
+
],
|
|
100
|
+
),
|
|
101
|
+
Sprint(
|
|
102
|
+
number=2,
|
|
103
|
+
goal="Implement the primary user-facing capability end to end.",
|
|
104
|
+
done_criteria="A user can complete the main task the product exists to serve.",
|
|
105
|
+
user_stories=[
|
|
106
|
+
UserStory(
|
|
107
|
+
id="US-2",
|
|
108
|
+
title="Core capability",
|
|
109
|
+
as_a="end user",
|
|
110
|
+
i_want="to accomplish the product's primary task",
|
|
111
|
+
so_that="the product delivers its core value",
|
|
112
|
+
story_points=5,
|
|
113
|
+
acceptance_criteria=[
|
|
114
|
+
"Given the app is running, when I perform the main action, then I see "
|
|
115
|
+
"a correct, persisted result.",
|
|
116
|
+
],
|
|
117
|
+
subtasks=[
|
|
118
|
+
Subtask(
|
|
119
|
+
id="ST-3",
|
|
120
|
+
title="Implement core action endpoint/handler",
|
|
121
|
+
description="Build the primary action path identified in the goal.",
|
|
122
|
+
done_criteria="The action returns a successful result and persists "
|
|
123
|
+
"state.",
|
|
124
|
+
estimated_hours=4.0,
|
|
125
|
+
),
|
|
126
|
+
Subtask(
|
|
127
|
+
id="ST-4",
|
|
128
|
+
title="Wire up the user interface / interface surface",
|
|
129
|
+
description="Expose the core action to the user.",
|
|
130
|
+
done_criteria="The action is reachable and usable from the "
|
|
131
|
+
"interface.",
|
|
132
|
+
estimated_hours=4.0,
|
|
133
|
+
),
|
|
134
|
+
],
|
|
135
|
+
)
|
|
136
|
+
],
|
|
137
|
+
),
|
|
138
|
+
],
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
verification = VerificationResult(
|
|
142
|
+
passed=False,
|
|
143
|
+
score=0.5,
|
|
144
|
+
issues=["Generated by the offline fallback — not LLM-verified."],
|
|
145
|
+
feedback=(
|
|
146
|
+
"This board was produced locally because the hosted backend was unreachable. "
|
|
147
|
+
"Treat it as a starting scaffold and refine the sprints for your specific project."
|
|
148
|
+
),
|
|
149
|
+
suggested_fixes=["Retry once the backend is reachable for a fully verified board."],
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
"board": board.model_dump(),
|
|
154
|
+
"verification": verification.model_dump(),
|
|
155
|
+
"source": "local-fallback",
|
|
156
|
+
}
|