agentic-loop 1.0.0
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.
- package/.claude/commands/explain.md +114 -0
- package/.claude/commands/idea.md +398 -0
- package/.claude/commands/my-dna.md +122 -0
- package/.claude/commands/prd.md +286 -0
- package/.claude/commands/review.md +167 -0
- package/.claude/commands/sign.md +32 -0
- package/.claude/commands/styleguide.md +450 -0
- package/.claude/commands/tour.md +301 -0
- package/.claude/commands/vibe-check.md +116 -0
- package/.claude/commands/vibe-help.md +47 -0
- package/.claude/commands/vibe-list.md +203 -0
- package/.pre-commit-hooks.yaml +102 -0
- package/LICENSE +21 -0
- package/README.md +238 -0
- package/bin/agentic-loop.sh +24 -0
- package/bin/postinstall.sh +29 -0
- package/bin/ralph.sh +171 -0
- package/bin/vibe-check.js +19 -0
- package/dist/checks/check-any-types.d.ts +6 -0
- package/dist/checks/check-any-types.d.ts.map +1 -0
- package/dist/checks/check-any-types.js +73 -0
- package/dist/checks/check-any-types.js.map +1 -0
- package/dist/checks/check-commented-code.d.ts +6 -0
- package/dist/checks/check-commented-code.d.ts.map +1 -0
- package/dist/checks/check-commented-code.js +81 -0
- package/dist/checks/check-commented-code.js.map +1 -0
- package/dist/checks/check-console-error.d.ts +6 -0
- package/dist/checks/check-console-error.d.ts.map +1 -0
- package/dist/checks/check-console-error.js +41 -0
- package/dist/checks/check-console-error.js.map +1 -0
- package/dist/checks/check-debug-statements.d.ts +6 -0
- package/dist/checks/check-debug-statements.d.ts.map +1 -0
- package/dist/checks/check-debug-statements.js +120 -0
- package/dist/checks/check-debug-statements.js.map +1 -0
- package/dist/checks/check-deep-nesting.d.ts +6 -0
- package/dist/checks/check-deep-nesting.d.ts.map +1 -0
- package/dist/checks/check-deep-nesting.js +116 -0
- package/dist/checks/check-deep-nesting.js.map +1 -0
- package/dist/checks/check-docker-platform.d.ts +6 -0
- package/dist/checks/check-docker-platform.d.ts.map +1 -0
- package/dist/checks/check-docker-platform.js +42 -0
- package/dist/checks/check-docker-platform.js.map +1 -0
- package/dist/checks/check-dry-violations.d.ts +6 -0
- package/dist/checks/check-dry-violations.d.ts.map +1 -0
- package/dist/checks/check-dry-violations.js +124 -0
- package/dist/checks/check-dry-violations.js.map +1 -0
- package/dist/checks/check-empty-catch.d.ts +6 -0
- package/dist/checks/check-empty-catch.d.ts.map +1 -0
- package/dist/checks/check-empty-catch.js +111 -0
- package/dist/checks/check-empty-catch.js.map +1 -0
- package/dist/checks/check-function-length.d.ts +6 -0
- package/dist/checks/check-function-length.d.ts.map +1 -0
- package/dist/checks/check-function-length.js +152 -0
- package/dist/checks/check-function-length.js.map +1 -0
- package/dist/checks/check-hardcoded-ai-models.d.ts +10 -0
- package/dist/checks/check-hardcoded-ai-models.d.ts.map +1 -0
- package/dist/checks/check-hardcoded-ai-models.js +102 -0
- package/dist/checks/check-hardcoded-ai-models.js.map +1 -0
- package/dist/checks/check-hardcoded-urls.d.ts +6 -0
- package/dist/checks/check-hardcoded-urls.d.ts.map +1 -0
- package/dist/checks/check-hardcoded-urls.js +124 -0
- package/dist/checks/check-hardcoded-urls.js.map +1 -0
- package/dist/checks/check-magic-numbers.d.ts +6 -0
- package/dist/checks/check-magic-numbers.d.ts.map +1 -0
- package/dist/checks/check-magic-numbers.js +116 -0
- package/dist/checks/check-magic-numbers.js.map +1 -0
- package/dist/checks/check-secrets.d.ts +6 -0
- package/dist/checks/check-secrets.d.ts.map +1 -0
- package/dist/checks/check-secrets.js +138 -0
- package/dist/checks/check-secrets.js.map +1 -0
- package/dist/checks/check-snake-case-ts.d.ts +6 -0
- package/dist/checks/check-snake-case-ts.d.ts.map +1 -0
- package/dist/checks/check-snake-case-ts.js +78 -0
- package/dist/checks/check-snake-case-ts.js.map +1 -0
- package/dist/checks/check-todo-fixme.d.ts +6 -0
- package/dist/checks/check-todo-fixme.d.ts.map +1 -0
- package/dist/checks/check-todo-fixme.js +41 -0
- package/dist/checks/check-todo-fixme.js.map +1 -0
- package/dist/checks/check-unsafe-html.d.ts +6 -0
- package/dist/checks/check-unsafe-html.d.ts.map +1 -0
- package/dist/checks/check-unsafe-html.js +101 -0
- package/dist/checks/check-unsafe-html.js.map +1 -0
- package/dist/checks/index.d.ts +30 -0
- package/dist/checks/index.d.ts.map +1 -0
- package/dist/checks/index.js +57 -0
- package/dist/checks/index.js.map +1 -0
- package/dist/cli.d.ts +13 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +208 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/utils/file-reader.d.ts +24 -0
- package/dist/utils/file-reader.d.ts.map +1 -0
- package/dist/utils/file-reader.js +146 -0
- package/dist/utils/file-reader.js.map +1 -0
- package/dist/utils/patterns.d.ts +27 -0
- package/dist/utils/patterns.d.ts.map +1 -0
- package/dist/utils/patterns.js +84 -0
- package/dist/utils/patterns.js.map +1 -0
- package/dist/utils/reporters.d.ts +21 -0
- package/dist/utils/reporters.d.ts.map +1 -0
- package/dist/utils/reporters.js +115 -0
- package/dist/utils/reporters.js.map +1 -0
- package/dist/utils/types.d.ts +71 -0
- package/dist/utils/types.d.ts.map +1 -0
- package/dist/utils/types.js +5 -0
- package/dist/utils/types.js.map +1 -0
- package/package.json +83 -0
- package/ralph/api.sh +216 -0
- package/ralph/backup.sh +838 -0
- package/ralph/browser-verify/README.md +135 -0
- package/ralph/browser-verify/verify.ts +450 -0
- package/ralph/checks/check-fastapi-responses.py +155 -0
- package/ralph/hooks/hooks-config.json +72 -0
- package/ralph/hooks/inject-context.sh +44 -0
- package/ralph/hooks/install.sh +207 -0
- package/ralph/hooks/log-tools.sh +45 -0
- package/ralph/hooks/protect-prd.sh +27 -0
- package/ralph/hooks/save-learnings.sh +36 -0
- package/ralph/hooks/warn-debug.sh +54 -0
- package/ralph/hooks/warn-empty-catch.sh +63 -0
- package/ralph/hooks/warn-secrets.sh +89 -0
- package/ralph/hooks/warn-urls.sh +77 -0
- package/ralph/init.sh +515 -0
- package/ralph/loop.sh +730 -0
- package/ralph/playwright.sh +238 -0
- package/ralph/prd.sh +295 -0
- package/ralph/setup/feature-tour.sh +155 -0
- package/ralph/setup/quick-setup.sh +239 -0
- package/ralph/setup/tutorial.sh +159 -0
- package/ralph/setup/ui.sh +136 -0
- package/ralph/setup.sh +401 -0
- package/ralph/signs.sh +150 -0
- package/ralph/utils.sh +682 -0
- package/ralph/verify/browser.sh +324 -0
- package/ralph/verify/lint.sh +363 -0
- package/ralph/verify/review.sh +152 -0
- package/ralph/verify/tests.sh +81 -0
- package/ralph/verify.sh +268 -0
- package/templates/PROMPT.md +235 -0
- package/templates/config/fullstack.json +86 -0
- package/templates/config/go.json +81 -0
- package/templates/config/minimal.json +76 -0
- package/templates/config/node.json +81 -0
- package/templates/config/python.json +81 -0
- package/templates/config/rust.json +81 -0
- package/templates/examples/CLAUDE-django.md +174 -0
- package/templates/examples/CLAUDE-fastapi.md +270 -0
- package/templates/examples/CLAUDE-fastmcp.md +352 -0
- package/templates/examples/CLAUDE-fullstack.md +256 -0
- package/templates/examples/CLAUDE-node.md +246 -0
- package/templates/examples/CLAUDE-react.md +138 -0
- package/templates/optional/cursorrules.template +147 -0
- package/templates/optional/eslint.config.js +34 -0
- package/templates/optional/lint-staged.config.js +34 -0
- package/templates/optional/ruff.toml +125 -0
- package/templates/optional/vibe-check.yml +116 -0
- package/templates/optional/vscode-settings.json +127 -0
- package/templates/signs.json +46 -0
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
# CLAUDE.md - FastMCP Server
|
|
2
|
+
|
|
3
|
+
## Naming Conventions
|
|
4
|
+
- **Files**: `snake_case.py` — e.g., `search_tool.py`, `db_resource.py`
|
|
5
|
+
- **Functions/Variables**: `snake_case` — e.g., `search_files`, `is_valid`
|
|
6
|
+
- **Classes**: `PascalCase` — e.g., `SearchResult`, `FileResource`
|
|
7
|
+
- **Tool names**: `snake_case` — e.g., `search_files`, `read_database`
|
|
8
|
+
- **Resource URIs**: `kebab-case` — e.g., `file://project-files`, `db://user-data`
|
|
9
|
+
- **Constants**: `SCREAMING_SNAKE` — e.g., `MAX_RESULTS`, `DEFAULT_TIMEOUT`
|
|
10
|
+
|
|
11
|
+
## Project Overview
|
|
12
|
+
|
|
13
|
+
This is an MCP (Model Context Protocol) server built with FastMCP. It exposes tools, resources, and prompts to LLM clients.
|
|
14
|
+
|
|
15
|
+
## Tech Stack
|
|
16
|
+
|
|
17
|
+
- **Framework**: FastMCP 2.0
|
|
18
|
+
- **Protocol**: Model Context Protocol (MCP)
|
|
19
|
+
- **Validation**: Pydantic v2
|
|
20
|
+
- **Async**: anyio
|
|
21
|
+
|
|
22
|
+
## Project Structure
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
server/
|
|
26
|
+
├── main.py # FastMCP app entry point
|
|
27
|
+
├── tools/ # Tool implementations
|
|
28
|
+
│ ├── __init__.py
|
|
29
|
+
│ └── search.py
|
|
30
|
+
├── resources/ # Resource handlers
|
|
31
|
+
│ ├── __init__.py
|
|
32
|
+
│ └── data.py
|
|
33
|
+
├── prompts/ # Prompt templates
|
|
34
|
+
│ ├── __init__.py
|
|
35
|
+
│ └── analysis.py
|
|
36
|
+
├── dependencies.py # Dependency injection
|
|
37
|
+
└── config.py # Settings
|
|
38
|
+
tests/
|
|
39
|
+
├── conftest.py
|
|
40
|
+
└── test_tools.py
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Commands
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# Development
|
|
47
|
+
fastmcp dev server/main.py
|
|
48
|
+
|
|
49
|
+
# Run server
|
|
50
|
+
python server/main.py
|
|
51
|
+
|
|
52
|
+
# Testing
|
|
53
|
+
pytest
|
|
54
|
+
|
|
55
|
+
# Install to Claude Desktop
|
|
56
|
+
fastmcp install server/main.py --name "My Server"
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Code Standards
|
|
60
|
+
|
|
61
|
+
### Basic Server Setup
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
# Good - clear name, proper structure
|
|
65
|
+
from fastmcp import FastMCP
|
|
66
|
+
|
|
67
|
+
mcp = FastMCP("My Server")
|
|
68
|
+
|
|
69
|
+
@mcp.tool
|
|
70
|
+
def search(query: str) -> list[dict]:
|
|
71
|
+
"""Search for items matching the query."""
|
|
72
|
+
return [{"id": 1, "name": "Result"}]
|
|
73
|
+
|
|
74
|
+
if __name__ == "__main__":
|
|
75
|
+
mcp.run()
|
|
76
|
+
|
|
77
|
+
# Bad - no name, no types
|
|
78
|
+
from fastmcp import FastMCP
|
|
79
|
+
mcp = FastMCP()
|
|
80
|
+
|
|
81
|
+
@mcp.tool
|
|
82
|
+
def search(query):
|
|
83
|
+
return [{"id": 1}]
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Tools
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from typing import Annotated
|
|
90
|
+
from pydantic import Field
|
|
91
|
+
from fastmcp import FastMCP
|
|
92
|
+
from fastmcp.exceptions import ToolError
|
|
93
|
+
|
|
94
|
+
mcp = FastMCP("My Server")
|
|
95
|
+
|
|
96
|
+
# Good - typed parameters, validation, docstring, error handling
|
|
97
|
+
@mcp.tool
|
|
98
|
+
async def search_products(
|
|
99
|
+
query: Annotated[str, Field(min_length=1, description="Search query")],
|
|
100
|
+
max_results: Annotated[int, Field(ge=1, le=100, description="Max results")] = 10,
|
|
101
|
+
category: str | None = None,
|
|
102
|
+
) -> list[dict]:
|
|
103
|
+
"""Search the product catalog.
|
|
104
|
+
|
|
105
|
+
Returns matching products with id, name, and price.
|
|
106
|
+
"""
|
|
107
|
+
if not query.strip():
|
|
108
|
+
raise ToolError("Query cannot be empty")
|
|
109
|
+
|
|
110
|
+
results = await fetch_products(query, max_results, category)
|
|
111
|
+
return results
|
|
112
|
+
|
|
113
|
+
# Bad - no types, no validation, no docstring
|
|
114
|
+
@mcp.tool
|
|
115
|
+
def search_products(query, max_results=10, **kwargs):
|
|
116
|
+
return fetch_products(query, max_results)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Resources
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
from fastmcp import FastMCP
|
|
123
|
+
from fastmcp.resources import TextResource, FileResource
|
|
124
|
+
from fastmcp.exceptions import ResourceError
|
|
125
|
+
from pathlib import Path
|
|
126
|
+
|
|
127
|
+
mcp = FastMCP("My Server")
|
|
128
|
+
|
|
129
|
+
# Good - static resource with proper URI and MIME type
|
|
130
|
+
config_resource = TextResource(
|
|
131
|
+
uri="config://app/settings",
|
|
132
|
+
name="App Settings",
|
|
133
|
+
text='{"theme": "dark", "version": "1.0"}',
|
|
134
|
+
mime_type="application/json"
|
|
135
|
+
)
|
|
136
|
+
mcp.add_resource(config_resource)
|
|
137
|
+
|
|
138
|
+
# Good - dynamic resource with template
|
|
139
|
+
@mcp.resource("users://{user_id}/profile")
|
|
140
|
+
async def get_user_profile(user_id: str) -> dict:
|
|
141
|
+
"""Get user profile by ID."""
|
|
142
|
+
user = await fetch_user(user_id)
|
|
143
|
+
if not user:
|
|
144
|
+
raise ResourceError(f"User {user_id} not found")
|
|
145
|
+
return user
|
|
146
|
+
|
|
147
|
+
# Bad - no URI scheme, no error handling
|
|
148
|
+
@mcp.resource("user")
|
|
149
|
+
def get_user(id):
|
|
150
|
+
return fetch_user(id)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Prompts
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
from fastmcp import FastMCP
|
|
157
|
+
from fastmcp.prompts import PromptMessage
|
|
158
|
+
|
|
159
|
+
mcp = FastMCP("My Server")
|
|
160
|
+
|
|
161
|
+
# Good - typed parameters, clear docstring, structured return
|
|
162
|
+
@mcp.prompt
|
|
163
|
+
def analyze_code(
|
|
164
|
+
code: str,
|
|
165
|
+
language: str = "python",
|
|
166
|
+
focus: str = "security"
|
|
167
|
+
) -> list[PromptMessage]:
|
|
168
|
+
"""Analyze code for issues.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
code: The code to analyze
|
|
172
|
+
language: Programming language
|
|
173
|
+
focus: Analysis focus (security, performance, style)
|
|
174
|
+
"""
|
|
175
|
+
return [
|
|
176
|
+
PromptMessage(
|
|
177
|
+
role="user",
|
|
178
|
+
content=f"Analyze this {language} code for {focus} issues:\n\n```{language}\n{code}\n```"
|
|
179
|
+
)
|
|
180
|
+
]
|
|
181
|
+
|
|
182
|
+
# Bad - no types, returns plain string
|
|
183
|
+
@mcp.prompt
|
|
184
|
+
def analyze(code):
|
|
185
|
+
return f"Analyze: {code}"
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Dependency Injection
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
from fastmcp import FastMCP
|
|
192
|
+
from fastmcp.server.dependencies import get_context
|
|
193
|
+
|
|
194
|
+
mcp = FastMCP("My Server")
|
|
195
|
+
|
|
196
|
+
# Good - hide runtime values from LLM
|
|
197
|
+
def get_api_key() -> str:
|
|
198
|
+
return os.environ["API_KEY"]
|
|
199
|
+
|
|
200
|
+
def get_db() -> Database:
|
|
201
|
+
return Database(os.environ["DATABASE_URL"])
|
|
202
|
+
|
|
203
|
+
@mcp.tool
|
|
204
|
+
async def fetch_data(
|
|
205
|
+
query: str,
|
|
206
|
+
api_key: str = Depends(get_api_key), # Hidden from schema
|
|
207
|
+
db: Database = Depends(get_db), # Hidden from schema
|
|
208
|
+
) -> dict:
|
|
209
|
+
"""Fetch data from external API."""
|
|
210
|
+
return await db.query(query)
|
|
211
|
+
|
|
212
|
+
# Bad - exposing secrets in schema
|
|
213
|
+
@mcp.tool
|
|
214
|
+
def fetch_data(query: str, api_key: str) -> dict:
|
|
215
|
+
return call_api(query, api_key)
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Error Handling
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
from fastmcp.exceptions import ToolError, ResourceError
|
|
222
|
+
|
|
223
|
+
# Good - specific errors with helpful messages
|
|
224
|
+
@mcp.tool
|
|
225
|
+
def divide(a: float, b: float) -> float:
|
|
226
|
+
"""Divide two numbers."""
|
|
227
|
+
if b == 0:
|
|
228
|
+
raise ToolError("Cannot divide by zero")
|
|
229
|
+
return a / b
|
|
230
|
+
|
|
231
|
+
@mcp.resource("data://{id}")
|
|
232
|
+
def get_data(id: str) -> dict:
|
|
233
|
+
"""Get data by ID."""
|
|
234
|
+
data = fetch_data(id)
|
|
235
|
+
if not data:
|
|
236
|
+
raise ResourceError(f"Data with ID '{id}' not found")
|
|
237
|
+
return data
|
|
238
|
+
|
|
239
|
+
# Bad - generic exceptions
|
|
240
|
+
@mcp.tool
|
|
241
|
+
def divide(a, b):
|
|
242
|
+
return a / b # ZeroDivisionError not handled
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Async Best Practices
|
|
246
|
+
|
|
247
|
+
```python
|
|
248
|
+
import anyio
|
|
249
|
+
|
|
250
|
+
# Good - async for I/O operations
|
|
251
|
+
@mcp.tool
|
|
252
|
+
async def fetch_url(url: str) -> str:
|
|
253
|
+
"""Fetch content from URL."""
|
|
254
|
+
async with httpx.AsyncClient() as client:
|
|
255
|
+
response = await client.get(url)
|
|
256
|
+
return response.text
|
|
257
|
+
|
|
258
|
+
# Good - wrap CPU-bound sync code
|
|
259
|
+
@mcp.tool
|
|
260
|
+
async def process_image(image_data: bytes) -> bytes:
|
|
261
|
+
"""Process image (CPU-intensive)."""
|
|
262
|
+
return await anyio.to_thread.run_sync(
|
|
263
|
+
lambda: heavy_image_processing(image_data)
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
# Bad - blocking call in async context
|
|
267
|
+
@mcp.tool
|
|
268
|
+
async def fetch_url(url: str) -> str:
|
|
269
|
+
return requests.get(url).text # Blocks event loop!
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## Do NOT
|
|
273
|
+
|
|
274
|
+
- Use `*args` or `**kwargs` in tools - FastMCP needs complete parameter schemas
|
|
275
|
+
- Expose secrets as tool parameters - use `Depends()` for injection
|
|
276
|
+
- Use blocking I/O in async functions - wrap with `anyio.to_thread.run_sync()`
|
|
277
|
+
- Skip type hints - they generate the JSON schema for clients
|
|
278
|
+
- Catch and silence exceptions - use `ToolError`/`ResourceError` with helpful messages
|
|
279
|
+
- Use generic names like `data` or `process` - be specific
|
|
280
|
+
|
|
281
|
+
## Do
|
|
282
|
+
|
|
283
|
+
- Add docstrings to all tools, resources, and prompts
|
|
284
|
+
- Use `Annotated` with `Field` for parameter validation and descriptions
|
|
285
|
+
- Use async for all I/O operations
|
|
286
|
+
- Use `Depends()` to inject runtime values (API keys, DB connections)
|
|
287
|
+
- Use specific error types (`ToolError`, `ResourceError`)
|
|
288
|
+
- Add proper MIME types to resources
|
|
289
|
+
- Use URI schemes (`data://`, `config://`, `file://`)
|
|
290
|
+
- Test tools with `mcp.call_tool()` in pytest
|
|
291
|
+
|
|
292
|
+
## Testing
|
|
293
|
+
|
|
294
|
+
```python
|
|
295
|
+
import pytest
|
|
296
|
+
from server.main import mcp
|
|
297
|
+
|
|
298
|
+
@pytest.mark.asyncio
|
|
299
|
+
async def test_search_tool():
|
|
300
|
+
result = await mcp.call_tool("search_products", {"query": "test"})
|
|
301
|
+
assert isinstance(result, list)
|
|
302
|
+
|
|
303
|
+
@pytest.mark.asyncio
|
|
304
|
+
async def test_search_empty_query():
|
|
305
|
+
with pytest.raises(ToolError, match="cannot be empty"):
|
|
306
|
+
await mcp.call_tool("search_products", {"query": ""})
|
|
307
|
+
|
|
308
|
+
@pytest.mark.asyncio
|
|
309
|
+
async def test_user_resource():
|
|
310
|
+
result = await mcp.read_resource("users://123/profile")
|
|
311
|
+
assert "name" in result
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
## Configuration
|
|
315
|
+
|
|
316
|
+
```python
|
|
317
|
+
from pydantic_settings import BaseSettings
|
|
318
|
+
|
|
319
|
+
class Settings(BaseSettings):
|
|
320
|
+
api_key: str
|
|
321
|
+
database_url: str
|
|
322
|
+
debug: bool = False
|
|
323
|
+
|
|
324
|
+
model_config = {"env_file": ".env"}
|
|
325
|
+
|
|
326
|
+
settings = Settings()
|
|
327
|
+
|
|
328
|
+
# Use in server
|
|
329
|
+
mcp = FastMCP(
|
|
330
|
+
"My Server",
|
|
331
|
+
debug=settings.debug,
|
|
332
|
+
mask_error_details=not settings.debug, # Hide errors in production
|
|
333
|
+
)
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
## Deployment
|
|
337
|
+
|
|
338
|
+
```bash
|
|
339
|
+
# FastMCP Cloud (free for personal)
|
|
340
|
+
fastmcp deploy server/main.py
|
|
341
|
+
|
|
342
|
+
# Install to Claude Desktop
|
|
343
|
+
fastmcp install server/main.py --name "My Server"
|
|
344
|
+
|
|
345
|
+
# HTTP server
|
|
346
|
+
uvicorn server.main:mcp.http_app --host 0.0.0.0 --port 8000
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
## Resources
|
|
350
|
+
|
|
351
|
+
- [FastMCP Docs](https://gofastmcp.com)
|
|
352
|
+
- [MCP Specification](https://modelcontextprotocol.io)
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
# Project Instructions for AI Coding Agents
|
|
2
|
+
|
|
3
|
+
## Naming Conventions
|
|
4
|
+
|
|
5
|
+
### Frontend (React/TypeScript)
|
|
6
|
+
- **Files**: `PascalCase.tsx` for components, `camelCase.ts` for utilities
|
|
7
|
+
- **Components**: `PascalCase` — e.g., `UserProfile`, `AuthProvider`
|
|
8
|
+
- **Hooks**: `useCamelCase` — e.g., `useAuth`, `useUserData`
|
|
9
|
+
- **Functions/Variables**: `camelCase` — e.g., `handleSubmit`, `isLoading`
|
|
10
|
+
|
|
11
|
+
### Backend (Django/Python)
|
|
12
|
+
- **Files**: `snake_case.py` — e.g., `user_views.py`
|
|
13
|
+
- **Functions/Variables**: `snake_case` — e.g., `get_user_by_id`
|
|
14
|
+
- **Classes**: `PascalCase` — e.g., `UserViewSet`, `UserSerializer`
|
|
15
|
+
|
|
16
|
+
### Shared
|
|
17
|
+
- **API endpoints**: `kebab-case` — e.g., `/api/user-profile/`
|
|
18
|
+
- **Database tables**: `snake_case` — e.g., `user_sessions`
|
|
19
|
+
- **Constants**: `SCREAMING_SNAKE` — e.g., `MAX_RETRIES`
|
|
20
|
+
|
|
21
|
+
## Tech Stack
|
|
22
|
+
- **Frontend**: React 18, TypeScript, Vite, TailwindCSS
|
|
23
|
+
- **Backend**: Django 5, Django REST Framework
|
|
24
|
+
- **Database**: PostgreSQL
|
|
25
|
+
- **Cache/Queue**: Redis, Celery
|
|
26
|
+
- **Testing**: pytest (backend), Vitest (frontend), Playwright (E2E)
|
|
27
|
+
|
|
28
|
+
## Architecture Overview
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
|
32
|
+
│ React │────▶│ Django │────▶│ PostgreSQL │
|
|
33
|
+
│ Frontend │ │ REST API │ │ Database │
|
|
34
|
+
└─────────────┘ └─────────────┘ └─────────────┘
|
|
35
|
+
│
|
|
36
|
+
▼
|
|
37
|
+
┌─────────────┐
|
|
38
|
+
│ Redis │
|
|
39
|
+
│ + Celery │
|
|
40
|
+
└─────────────┘
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Code Quality Standards
|
|
44
|
+
|
|
45
|
+
### API Contract
|
|
46
|
+
- Frontend and backend share type definitions
|
|
47
|
+
- Use consistent naming (camelCase in TS, snake_case in Python)
|
|
48
|
+
- API transforms snake_case responses to camelCase
|
|
49
|
+
- Document API changes before implementing
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
// Frontend: types/api.ts
|
|
53
|
+
interface User {
|
|
54
|
+
id: number;
|
|
55
|
+
email: string;
|
|
56
|
+
firstName: string; // Transformed from first_name
|
|
57
|
+
lastName: string;
|
|
58
|
+
createdAt: string;
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
# Backend: serializers.py
|
|
64
|
+
class UserSerializer(serializers.ModelSerializer):
|
|
65
|
+
class Meta:
|
|
66
|
+
model = User
|
|
67
|
+
fields = ['id', 'email', 'first_name', 'last_name', 'created_at']
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Frontend Standards
|
|
71
|
+
|
|
72
|
+
**Components**
|
|
73
|
+
- Use functional components with TypeScript
|
|
74
|
+
- Keep components under 100 lines
|
|
75
|
+
- Extract logic to custom hooks
|
|
76
|
+
- Handle loading/error/empty states
|
|
77
|
+
|
|
78
|
+
**State Management**
|
|
79
|
+
- React Query for server state
|
|
80
|
+
- Zustand for UI state only
|
|
81
|
+
- Don't duplicate server state
|
|
82
|
+
|
|
83
|
+
**API Calls**
|
|
84
|
+
```typescript
|
|
85
|
+
// services/api.ts
|
|
86
|
+
const api = {
|
|
87
|
+
users: {
|
|
88
|
+
getAll: () => fetch<User[]>('/api/users/'),
|
|
89
|
+
getById: (id: number) => fetch<User>(`/api/users/${id}/`),
|
|
90
|
+
create: (data: CreateUserInput) => post<User>('/api/users/', data),
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Backend Standards
|
|
96
|
+
|
|
97
|
+
**Views**
|
|
98
|
+
- Use DRF ViewSets for CRUD
|
|
99
|
+
- Apply proper permissions
|
|
100
|
+
- Return appropriate status codes
|
|
101
|
+
- Filter querysets by user ownership
|
|
102
|
+
|
|
103
|
+
**Models**
|
|
104
|
+
- Add explicit `related_name`
|
|
105
|
+
- Include `__str__` method
|
|
106
|
+
- Index frequently queried fields
|
|
107
|
+
- Use model managers for complex queries
|
|
108
|
+
|
|
109
|
+
**Queries**
|
|
110
|
+
- Avoid N+1 with select_related/prefetch_related
|
|
111
|
+
- Use pagination for list endpoints
|
|
112
|
+
- Add database indexes
|
|
113
|
+
|
|
114
|
+
### Shared Patterns
|
|
115
|
+
|
|
116
|
+
**Authentication**
|
|
117
|
+
- JWT tokens in httpOnly cookies
|
|
118
|
+
- CSRF protection for state-changing requests
|
|
119
|
+
- Refresh tokens for long sessions
|
|
120
|
+
|
|
121
|
+
**Error Handling**
|
|
122
|
+
```typescript
|
|
123
|
+
// Frontend
|
|
124
|
+
try {
|
|
125
|
+
await api.users.create(data);
|
|
126
|
+
} catch (error) {
|
|
127
|
+
if (error instanceof ApiError) {
|
|
128
|
+
showToast(error.message);
|
|
129
|
+
} else {
|
|
130
|
+
showToast('Something went wrong');
|
|
131
|
+
logger.error(error);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
# Backend
|
|
138
|
+
try:
|
|
139
|
+
user = create_user(data)
|
|
140
|
+
except ValidationError as e:
|
|
141
|
+
raise DRFValidationError(e.messages)
|
|
142
|
+
except IntegrityError:
|
|
143
|
+
raise DRFValidationError({'email': 'Email already exists'})
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Testing Strategy
|
|
147
|
+
|
|
148
|
+
**Unit Tests**
|
|
149
|
+
- Frontend: Test hooks, utilities, complex components
|
|
150
|
+
- Backend: Test services, serializers, model methods
|
|
151
|
+
|
|
152
|
+
**Integration Tests**
|
|
153
|
+
- Backend: Test API endpoints with pytest
|
|
154
|
+
- Frontend: Test API integration with MSW
|
|
155
|
+
|
|
156
|
+
**E2E Tests (Playwright)**
|
|
157
|
+
- Test critical user flows
|
|
158
|
+
- Use TDD workflow (test first, then implement)
|
|
159
|
+
- Include SCENARIO/EXPECTED/FAILURE documentation
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
test('user can complete checkout', async ({ page }) => {
|
|
163
|
+
/**
|
|
164
|
+
* SCENARIO: Logged-in user adds item and completes checkout
|
|
165
|
+
* EXPECTED: Order confirmation shown, order in database
|
|
166
|
+
* FAILURE: Stuck at any step, error shown
|
|
167
|
+
*/
|
|
168
|
+
await loginAsTestUser(page);
|
|
169
|
+
await page.goto('/products/1');
|
|
170
|
+
await page.click('[data-testid="add-to-cart"]');
|
|
171
|
+
await page.click('[data-testid="checkout"]');
|
|
172
|
+
await page.fill('[name="address"]', '123 Main St');
|
|
173
|
+
await page.click('[data-testid="place-order"]');
|
|
174
|
+
|
|
175
|
+
await expect(page.getByText('Order Confirmed')).toBeVisible();
|
|
176
|
+
});
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## File Structure
|
|
180
|
+
|
|
181
|
+
```
|
|
182
|
+
project/
|
|
183
|
+
├── frontend/
|
|
184
|
+
│ ├── src/
|
|
185
|
+
│ │ ├── components/
|
|
186
|
+
│ │ ├── hooks/
|
|
187
|
+
│ │ ├── pages/
|
|
188
|
+
│ │ ├── services/
|
|
189
|
+
│ │ ├── stores/
|
|
190
|
+
│ │ └── types/
|
|
191
|
+
│ └── e2e/ # Playwright tests
|
|
192
|
+
├── backend/
|
|
193
|
+
│ ├── config/ # Django settings
|
|
194
|
+
│ ├── apps/
|
|
195
|
+
│ │ ├── users/
|
|
196
|
+
│ │ └── orders/
|
|
197
|
+
│ └── tests/
|
|
198
|
+
├── docker-compose.yml
|
|
199
|
+
└── Makefile
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Environment Variables
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
# .env.example
|
|
206
|
+
|
|
207
|
+
# Database
|
|
208
|
+
DATABASE_URL=postgresql://user:pass@localhost:5432/dbname
|
|
209
|
+
|
|
210
|
+
# Django
|
|
211
|
+
SECRET_KEY=your-secret-key
|
|
212
|
+
DEBUG=True
|
|
213
|
+
ALLOWED_HOSTS=localhost,127.0.0.1
|
|
214
|
+
|
|
215
|
+
# Frontend
|
|
216
|
+
VITE_API_URL=http://localhost:8000/api
|
|
217
|
+
|
|
218
|
+
# Redis
|
|
219
|
+
REDIS_URL=redis://localhost:6379
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## Pre-commit Hooks
|
|
223
|
+
This project uses agentic-loop hooks. Run `/vibe-check` before committing.
|
|
224
|
+
|
|
225
|
+
## Common Commands
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
# Development
|
|
229
|
+
make up # Start all services (Docker)
|
|
230
|
+
make frontend # Run frontend dev server
|
|
231
|
+
make logs # View backend logs
|
|
232
|
+
|
|
233
|
+
# Database
|
|
234
|
+
make migrate # Run migrations
|
|
235
|
+
make makemigrations # Create migrations
|
|
236
|
+
|
|
237
|
+
# Testing
|
|
238
|
+
make test # Run all tests
|
|
239
|
+
make test-backend # Run backend tests
|
|
240
|
+
make test-frontend # Run frontend tests
|
|
241
|
+
make test-e2e # Run Playwright E2E tests
|
|
242
|
+
|
|
243
|
+
# Code Quality
|
|
244
|
+
make lint # Run all linters
|
|
245
|
+
make format # Format all code
|
|
246
|
+
make typecheck # Check TypeScript types
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## Workflow
|
|
250
|
+
|
|
251
|
+
1. **Idea** - Run `/idea "feature description"` to brainstorm
|
|
252
|
+
2. **Approve** - Review the idea file, then approve
|
|
253
|
+
3. **PRD** - Review generated stories in `.ralph/prd.json`
|
|
254
|
+
4. **Run** - Execute `ralph run` for autonomous coding
|
|
255
|
+
5. **Audit** - Run `/vibe-check` before shipping
|
|
256
|
+
6. **Commit** - Pre-commit hooks catch remaining issues
|