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.
Files changed (162) hide show
  1. package/.claude/commands/explain.md +114 -0
  2. package/.claude/commands/idea.md +398 -0
  3. package/.claude/commands/my-dna.md +122 -0
  4. package/.claude/commands/prd.md +286 -0
  5. package/.claude/commands/review.md +167 -0
  6. package/.claude/commands/sign.md +32 -0
  7. package/.claude/commands/styleguide.md +450 -0
  8. package/.claude/commands/tour.md +301 -0
  9. package/.claude/commands/vibe-check.md +116 -0
  10. package/.claude/commands/vibe-help.md +47 -0
  11. package/.claude/commands/vibe-list.md +203 -0
  12. package/.pre-commit-hooks.yaml +102 -0
  13. package/LICENSE +21 -0
  14. package/README.md +238 -0
  15. package/bin/agentic-loop.sh +24 -0
  16. package/bin/postinstall.sh +29 -0
  17. package/bin/ralph.sh +171 -0
  18. package/bin/vibe-check.js +19 -0
  19. package/dist/checks/check-any-types.d.ts +6 -0
  20. package/dist/checks/check-any-types.d.ts.map +1 -0
  21. package/dist/checks/check-any-types.js +73 -0
  22. package/dist/checks/check-any-types.js.map +1 -0
  23. package/dist/checks/check-commented-code.d.ts +6 -0
  24. package/dist/checks/check-commented-code.d.ts.map +1 -0
  25. package/dist/checks/check-commented-code.js +81 -0
  26. package/dist/checks/check-commented-code.js.map +1 -0
  27. package/dist/checks/check-console-error.d.ts +6 -0
  28. package/dist/checks/check-console-error.d.ts.map +1 -0
  29. package/dist/checks/check-console-error.js +41 -0
  30. package/dist/checks/check-console-error.js.map +1 -0
  31. package/dist/checks/check-debug-statements.d.ts +6 -0
  32. package/dist/checks/check-debug-statements.d.ts.map +1 -0
  33. package/dist/checks/check-debug-statements.js +120 -0
  34. package/dist/checks/check-debug-statements.js.map +1 -0
  35. package/dist/checks/check-deep-nesting.d.ts +6 -0
  36. package/dist/checks/check-deep-nesting.d.ts.map +1 -0
  37. package/dist/checks/check-deep-nesting.js +116 -0
  38. package/dist/checks/check-deep-nesting.js.map +1 -0
  39. package/dist/checks/check-docker-platform.d.ts +6 -0
  40. package/dist/checks/check-docker-platform.d.ts.map +1 -0
  41. package/dist/checks/check-docker-platform.js +42 -0
  42. package/dist/checks/check-docker-platform.js.map +1 -0
  43. package/dist/checks/check-dry-violations.d.ts +6 -0
  44. package/dist/checks/check-dry-violations.d.ts.map +1 -0
  45. package/dist/checks/check-dry-violations.js +124 -0
  46. package/dist/checks/check-dry-violations.js.map +1 -0
  47. package/dist/checks/check-empty-catch.d.ts +6 -0
  48. package/dist/checks/check-empty-catch.d.ts.map +1 -0
  49. package/dist/checks/check-empty-catch.js +111 -0
  50. package/dist/checks/check-empty-catch.js.map +1 -0
  51. package/dist/checks/check-function-length.d.ts +6 -0
  52. package/dist/checks/check-function-length.d.ts.map +1 -0
  53. package/dist/checks/check-function-length.js +152 -0
  54. package/dist/checks/check-function-length.js.map +1 -0
  55. package/dist/checks/check-hardcoded-ai-models.d.ts +10 -0
  56. package/dist/checks/check-hardcoded-ai-models.d.ts.map +1 -0
  57. package/dist/checks/check-hardcoded-ai-models.js +102 -0
  58. package/dist/checks/check-hardcoded-ai-models.js.map +1 -0
  59. package/dist/checks/check-hardcoded-urls.d.ts +6 -0
  60. package/dist/checks/check-hardcoded-urls.d.ts.map +1 -0
  61. package/dist/checks/check-hardcoded-urls.js +124 -0
  62. package/dist/checks/check-hardcoded-urls.js.map +1 -0
  63. package/dist/checks/check-magic-numbers.d.ts +6 -0
  64. package/dist/checks/check-magic-numbers.d.ts.map +1 -0
  65. package/dist/checks/check-magic-numbers.js +116 -0
  66. package/dist/checks/check-magic-numbers.js.map +1 -0
  67. package/dist/checks/check-secrets.d.ts +6 -0
  68. package/dist/checks/check-secrets.d.ts.map +1 -0
  69. package/dist/checks/check-secrets.js +138 -0
  70. package/dist/checks/check-secrets.js.map +1 -0
  71. package/dist/checks/check-snake-case-ts.d.ts +6 -0
  72. package/dist/checks/check-snake-case-ts.d.ts.map +1 -0
  73. package/dist/checks/check-snake-case-ts.js +78 -0
  74. package/dist/checks/check-snake-case-ts.js.map +1 -0
  75. package/dist/checks/check-todo-fixme.d.ts +6 -0
  76. package/dist/checks/check-todo-fixme.d.ts.map +1 -0
  77. package/dist/checks/check-todo-fixme.js +41 -0
  78. package/dist/checks/check-todo-fixme.js.map +1 -0
  79. package/dist/checks/check-unsafe-html.d.ts +6 -0
  80. package/dist/checks/check-unsafe-html.d.ts.map +1 -0
  81. package/dist/checks/check-unsafe-html.js +101 -0
  82. package/dist/checks/check-unsafe-html.js.map +1 -0
  83. package/dist/checks/index.d.ts +30 -0
  84. package/dist/checks/index.d.ts.map +1 -0
  85. package/dist/checks/index.js +57 -0
  86. package/dist/checks/index.js.map +1 -0
  87. package/dist/cli.d.ts +13 -0
  88. package/dist/cli.d.ts.map +1 -0
  89. package/dist/cli.js +208 -0
  90. package/dist/cli.js.map +1 -0
  91. package/dist/index.d.ts +9 -0
  92. package/dist/index.d.ts.map +1 -0
  93. package/dist/index.js +10 -0
  94. package/dist/index.js.map +1 -0
  95. package/dist/utils/file-reader.d.ts +24 -0
  96. package/dist/utils/file-reader.d.ts.map +1 -0
  97. package/dist/utils/file-reader.js +146 -0
  98. package/dist/utils/file-reader.js.map +1 -0
  99. package/dist/utils/patterns.d.ts +27 -0
  100. package/dist/utils/patterns.d.ts.map +1 -0
  101. package/dist/utils/patterns.js +84 -0
  102. package/dist/utils/patterns.js.map +1 -0
  103. package/dist/utils/reporters.d.ts +21 -0
  104. package/dist/utils/reporters.d.ts.map +1 -0
  105. package/dist/utils/reporters.js +115 -0
  106. package/dist/utils/reporters.js.map +1 -0
  107. package/dist/utils/types.d.ts +71 -0
  108. package/dist/utils/types.d.ts.map +1 -0
  109. package/dist/utils/types.js +5 -0
  110. package/dist/utils/types.js.map +1 -0
  111. package/package.json +83 -0
  112. package/ralph/api.sh +216 -0
  113. package/ralph/backup.sh +838 -0
  114. package/ralph/browser-verify/README.md +135 -0
  115. package/ralph/browser-verify/verify.ts +450 -0
  116. package/ralph/checks/check-fastapi-responses.py +155 -0
  117. package/ralph/hooks/hooks-config.json +72 -0
  118. package/ralph/hooks/inject-context.sh +44 -0
  119. package/ralph/hooks/install.sh +207 -0
  120. package/ralph/hooks/log-tools.sh +45 -0
  121. package/ralph/hooks/protect-prd.sh +27 -0
  122. package/ralph/hooks/save-learnings.sh +36 -0
  123. package/ralph/hooks/warn-debug.sh +54 -0
  124. package/ralph/hooks/warn-empty-catch.sh +63 -0
  125. package/ralph/hooks/warn-secrets.sh +89 -0
  126. package/ralph/hooks/warn-urls.sh +77 -0
  127. package/ralph/init.sh +515 -0
  128. package/ralph/loop.sh +730 -0
  129. package/ralph/playwright.sh +238 -0
  130. package/ralph/prd.sh +295 -0
  131. package/ralph/setup/feature-tour.sh +155 -0
  132. package/ralph/setup/quick-setup.sh +239 -0
  133. package/ralph/setup/tutorial.sh +159 -0
  134. package/ralph/setup/ui.sh +136 -0
  135. package/ralph/setup.sh +401 -0
  136. package/ralph/signs.sh +150 -0
  137. package/ralph/utils.sh +682 -0
  138. package/ralph/verify/browser.sh +324 -0
  139. package/ralph/verify/lint.sh +363 -0
  140. package/ralph/verify/review.sh +152 -0
  141. package/ralph/verify/tests.sh +81 -0
  142. package/ralph/verify.sh +268 -0
  143. package/templates/PROMPT.md +235 -0
  144. package/templates/config/fullstack.json +86 -0
  145. package/templates/config/go.json +81 -0
  146. package/templates/config/minimal.json +76 -0
  147. package/templates/config/node.json +81 -0
  148. package/templates/config/python.json +81 -0
  149. package/templates/config/rust.json +81 -0
  150. package/templates/examples/CLAUDE-django.md +174 -0
  151. package/templates/examples/CLAUDE-fastapi.md +270 -0
  152. package/templates/examples/CLAUDE-fastmcp.md +352 -0
  153. package/templates/examples/CLAUDE-fullstack.md +256 -0
  154. package/templates/examples/CLAUDE-node.md +246 -0
  155. package/templates/examples/CLAUDE-react.md +138 -0
  156. package/templates/optional/cursorrules.template +147 -0
  157. package/templates/optional/eslint.config.js +34 -0
  158. package/templates/optional/lint-staged.config.js +34 -0
  159. package/templates/optional/ruff.toml +125 -0
  160. package/templates/optional/vibe-check.yml +116 -0
  161. package/templates/optional/vscode-settings.json +127 -0
  162. 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