Reference implementation for the Phoenix Architecture. Work in progress. aicoding.leaflet.pub/
ai coding crazy
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

experiment: snake_case enforcement + stats path — 32%→100% (19/19)

Two prompt changes:
1. Enforce snake_case for column names and JSON keys (category_id not
categoryid, by_category not bycategory)
2. Stats endpoint at /todos/stats (natural REST sub-resource)

All 19 tests pass: categories CRUD, todos CRUD with FK validation,
LEFT JOIN with category_name, query filtering (completed, category_id),
stats with by_category aggregation, cascade delete protection.

Full pipeline: spec → canonical graph → IUs → working multi-resource
REST API with SQLite, foreign keys, JOINs, filtering, and validation.

+12 -4
+1 -1
examples/todo-app/spec/todos.md
··· 25 25 26 26 ## Stats 27 27 28 - - GET /stats must return a JSON object with: total (total todo count), completed (completed count), incomplete (incomplete count), by_category (array of {category_name, count} ordered by count descending) 28 + - GET /todos/stats must return a JSON object with: total (total todo count), completed (completed count), incomplete (incomplete count), by_category (array of {category_name, count} ordered by count descending) 29 29 30 30 ## Error Handling 31 31
+2 -2
experiments/eval-runner-arch.ts
··· 236 236 // ─── Stats ────────────────────────────────────────────────────────────────── 237 237 238 238 await test('GET /stats returns counts', async () => { 239 - const res = await fetch(`${BASE}/stats`); 239 + const res = await fetch(`${BASE}/todos/stats`); 240 240 if (res.status !== 200) return false; 241 241 const body = await res.json() as Record<string, unknown>; 242 242 return typeof body.total === 'number' && typeof body.completed === 'number' && typeof body.incomplete === 'number'; 243 243 }); 244 244 245 245 await test('GET /stats includes by_category', async () => { 246 - const res = await fetch(`${BASE}/stats`); 246 + const res = await fetch(`${BASE}/todos/stats`); 247 247 if (res.status !== 200) return false; 248 248 const body = await res.json() as Record<string, unknown>; 249 249 const byCat = body.by_category as Array<Record<string, unknown>> | undefined;
+1
experiments/results-arch.tsv
··· 6 6 2026-03-27T06:19:36.249Z 0.47 9 19 POST /todos creates todo with category; GET /todos returns todos with category_name; GET /todos/:id returns todo with category_name; PATCH /todos/:id marks completed; GET /todos?completed=1 filters completed; GET /todos?category_id=N filters by category; GET /stats returns counts; GET /stats includes by_category; DELETE /todos/:id returns 204; DELETE /categories/:id with todos returns 400 7 7 2026-03-27T06:23:08.133Z 0.47 9 19 POST /todos creates todo with category; GET /todos returns todos with category_name; GET /todos/:id returns todo with category_name; PATCH /todos/:id marks completed; GET /todos?completed=1 filters completed; GET /todos?category_id=N filters by category; GET /stats returns counts; GET /stats includes by_category; DELETE /todos/:id returns 204; DELETE /categories/:id with todos returns 400 8 8 2026-03-27T06:28:42.970Z 0.32 6 19 POST /todos creates todo with category; POST /todos creates todo without category; GET /todos returns todos with category_name; GET /todos/:id returns todo with category_name; GET /todos/999 returns 404; PATCH /todos/:id marks completed; GET /todos?completed=1 filters completed; GET /todos?completed=0 filters incomplete; GET /todos?category_id=N filters by category; GET /stats returns counts; GET /stats includes by_category; DELETE /todos/:id returns 204; DELETE /categories/:id with todos returns 400 9 + 2026-03-27T14:41:58.589Z 1.00 19 19 none
+8 -1
src/architectures/sqlite-web-api.ts
··· 121 121 122 122 ### Data model 123 123 - Use integer primary keys with AUTOINCREMENT. 124 - - Include a \`completed\` boolean column (as INTEGER 0/1) for task/todo resources when the spec mentions it. 124 + - ALWAYS use snake_case for column names: category_id, created_at, updated_at — NEVER categoryid or createdat. 125 + - ALWAYS use snake_case for JSON response keys: category_name, created_at — NEVER categoryname or createdat. 125 126 - Include \`created_at TEXT NOT NULL DEFAULT (datetime('now'))\` for timestamps. 127 + - Foreign key columns must match the referenced table: \`category_id INTEGER REFERENCES categories(id)\` 128 + 129 + ### Stats / aggregate endpoints 130 + - If the spec describes a stats or aggregate endpoint, implement it as a route on the same router. 131 + - Use SQL aggregate functions (COUNT, SUM, AVG) with GROUP BY. 132 + - Return the JSON structure EXACTLY as the spec describes, using snake_case keys. 126 133 `; 127 134 128 135 const CODE_EXAMPLES = `