diff --git a/docs/superpowers/specs/2026-06-22-build-spec-phase4.md b/docs/superpowers/specs/2026-06-22-build-spec-phase4.md new file mode 100644 index 0000000..4306bab --- /dev/null +++ b/docs/superpowers/specs/2026-06-22-build-spec-phase4.md @@ -0,0 +1,316 @@ +# Build Spec Phase 4 — Support Bot Foundation + +**Date:** 2026-06-22 +**Design:** Pi +**Build:** Claude Code +**Est:** 1 session +**Depends on:** Phase 3 (startup ritual) complete — shared-context pointers wired + +--- + +## Overview + +Phase 4 builds the foundation for a support bot by generating a machine-readable API reference from the backend source and cross-referencing it with the existing support manual. This enables automated troubleshooting support and sets up the data layer the bot will query. + +Two substeps: + +- **4.1** — Write `gen-api-ref.py` script to parse FastAPI routes from `main.py` +- **4.2** — Cross-reference `support-manual.md` with generated ref, fix gaps + +--- + +## 4.1 Generate support-api-ref.json + +### File to create + +`signal/scripts/gen-api-ref.py` + +### What to build + +A Python script that: + +1. Reads `python-backend/api/main.py` +2. Parses the AST for FastAPI route decorators (`@app.get`, `@app.post`, `@app.put`, `@app.delete`, `@app.patch`, `@router.*`) +3. Extracts for each route: + - Path (e.g., `/api/upload-csv`) + - HTTP method (GET, POST, etc.) + - Summary (first line of function docstring) + - Parameters (from function signature — path params, query params, body params) + - Response codes and descriptions (from `raise HTTPException(...)` calls in function body, plus the implicit 200) +4. Writes `signal/docs/support-api-ref.json` + +### AST parsing approach + +Use Python's `ast` module (stdlib — no external dependencies). Parse for: + +- `@app.` decorators and `@router.` decorators +- Extract the route string from the decorator argument +- Get the function name from the decorated `FunctionDef` +- Extract docstring first line for summary +- Scan `raise` statements inside the function body for `HTTPException(status_code=...)` +- Detect `Request` and `UploadFile` params for the parameters list + +### Expected output + +```json +{ + "generated": "2026-06-22", + "source": "python-backend/api/main.py", + "endpoints": [ + { + "path": "/api/health", + "method": "GET", + "summary": "Health check endpoint", + "parameters": [], + "responses": [ + {"code": 200, "description": "Service healthy"} + ] + }, + { + "path": "/api/upload-csv", + "method": "POST", + "summary": "Upload and process a CSV file", + "parameters": [ + {"name": "file", "in": "formData", "type": "file", "required": true} + ], + "responses": [ + {"code": 200, "description": "CSV processed successfully"}, + {"code": 400, "description": "Invalid CSV format or missing columns"}, + {"code": 401, "description": "Invalid or missing API key"}, + {"code": 500, "description": "Server error processing CSV"} + ] + } + ] +} +``` + +### Script structure + +```python +#!/usr/bin/env python3 +"""Generate support-api-ref.json from FastAPI backend source.""" +import ast +import json +from pathlib import Path + +BACKEND_DIR = Path(__file__).parent.parent / "python-backend" +OUTPUT = Path(__file__).parent.parent / "docs" / "support-api-ref.json" +MAIN_FILE = BACKEND_DIR / "api" / "main.py" + +# Route decorator prefixes to match +HTTP_METHODS = {"get", "post", "put", "delete", "patch", "options", "head"} + +# Known module names that may contain route definitions +SHARED_ROUTER_MODULES = [ + "api.routes", +] + + +def extract_route(node): + """Given a decorator Call node, return (method, path) or None.""" + # Handle @app.get, @router.post, etc. + if (isinstance(node.func, ast.Attribute) + and isinstance(node.func.value, ast.Name) + and node.func.attr in HTTP_METHODS): + path = node.args[0].value if node.args else "" + return node.func.attr.upper(), path + return None + + +def extract_summary(docstring): + """Get the first line of a docstring.""" + if not docstring: + return "" + lines = docstring.strip().split("\n") + return lines[0].strip() + + +def extract_error_codes(body): + """Scan a function body for HTTPException raises and extract status codes.""" + codes = [{"code": 200, "description": "Success"}] + for node in ast.walk(body): + if (isinstance(node, ast.Raise) + and isinstance(node.exc, ast.Call) + and isinstance(node.exc.func, ast.Name) + and node.exc.func.id == "HTTPException"): + for kw in node.exc.keywords: + if kw.arg == "status_code" and isinstance(kw.value, ast.Constant): + detail = "" + for kw2 in node.exc.keywords: + if kw2.arg == "detail" and isinstance(kw2.value, ast.Constant): + detail = kw2.value.value + break + codes.append({ + "code": kw.value.value, + "description": detail or f"HTTP {kw.value.value}" + }) + return codes + + +def extract_function_parameters(func_def): + """Extract parameter info from a function definition.""" + params = [] + for arg in func_def.args.args: + arg_name = arg.arg + if arg_name == "request": + params.append({"name": arg_name, "in": "path", "type": "Request", "required": True}) + elif arg_name in ("file", "csv_file"): + params.append({"name": arg_name, "in": "formData", "type": "file", "required": True}) + else: + params.append({"name": arg_name, "in": "query", "type": "string", "required": False}) + return params + + +def parse_file(filepath): + """ + Parse a single Python file for FastAPI route decorators. + Returns list of endpoint dicts. + """ + endpoints = [] + tree = ast.parse(filepath.read_text()) + + for node in ast.walk(tree): + if not isinstance(node, ast.FunctionDef): + continue + + func_def = node + for decorator in func_def.decorator_list: + route = extract_route(decorator) + if route: + method, path = route + endpoints.append({ + "path": path, + "method": method, + "summary": extract_summary(ast.get_docstring(func_def)), + "parameters": extract_function_parameters(func_def), + "responses": extract_error_codes(func_def), + }) + + return endpoints + + +def main(): + endpoints = parse_file(MAIN_FILE) + + # Optional: scan shared router modules + # for module_name in SHARED_ROUTER_MODULES: + # module_path = BACKEND_DIR / module_name.replace(".", "/") + ".py" + # if module_path.exists(): + # endpoints.extend(parse_file(module_path)) + + output = { + "generated": "2026-06-22", + "source": str(MAIN_FILE.relative_to(BACKEND_DIR.parent)), + "endpoints": endpoints, + } + + OUTPUT.parent.mkdir(parents=True, exist_ok=True) + OUTPUT.write_text(json.dumps(output, indent=2) + "\n") + print(f"Generated {len(endpoints)} endpoints → {OUTPUT}") + + +if __name__ == "__main__": + main() +``` + +### Verification + +```bash +python3 signal/scripts/gen-api-ref.py +cat signal/docs/support-api-ref.json | python3 -c " +import json,sys; d=json.load(sys.stdin) +print(f'{len(d[\"endpoints\"])} endpoints extracted from {d[\"source\"]}') +for ep in d['endpoints']: + codes = [str(r['code']) for r in ep['responses']] + print(f' {ep[\"method\"]:6} {ep[\"path\"]:30} → {ep[\"summary\"][:40]} [{','.join(codes)}]') +" +``` + +### Acceptance + +- [ ] Script runs without external dependencies (stdlib only) +- [ ] Output file written to `signal/docs/support-api-ref.json` +- [ ] All real API endpoints extracted (health, upload, export, etc.) +- [ ] Error codes from `raise HTTPException(...)` captured +- [ ] Parameter lists populated + +--- + +## 4.2 Cross-reference support-manual.md with API ref + +### What to do + +1. Read both `signal/docs/support-manual.md` and the generated `support-api-ref.json` +2. For each endpoint in the API ref, check the support manual for: + - Does the manual mention this endpoint? If not, add a section or a link + - Do the error messages in the manual's tables match the actual HTTPException codes from the API ref? + - Do the parameter descriptions match what the backend actually accepts? +3. Add a note at the top of `support-manual.md` noting the generation timestamp: + +```markdown +*Auto-generated API reference: signal/docs/support-api-ref.json (generated YYYY-MM-DD)* +``` + +1. Add any missing endpoints to the manual as a section: + +```markdown +--- +## [METHOD] /api/endpoint-name + +**Summary:** description + +**Error codes:** + +| Code | Description | +|------|-------------| +| 200 | Success | +| 4xx | Error type | +``` + +### Verification + +```bash +# Count endpoints in API ref +ENDPOINTS=$(python3 -c "import json; d=json.load(open('signal/docs/support-api-ref.json')); print(len(d['endpoints']))") +# Count sections in support manual that mention endpoints +MANUAL_COVERAGE=$(grep -c '/api/' signal/docs/support-manual.md) +echo "API ref: $ENDPOINTS endpoints, Manual covers: $MANUAL_COVERAGE /api/ references" +``` + +If `MANUAL_COVERAGE` is significantly less than `ENDPOINTS`, more manual sections are needed. + +### Acceptance + +- [ ] support-manual.md has a generation timestamp note at the top +- [ ] All API ref endpoints have a corresponding section in the manual +- [ ] Error code tables in the manual match the actual codes from the API ref +- [ ] No stale/incorrect error descriptions in the manual + +--- + +## Implementation Order + +1. Write `signal/scripts/gen-api-ref.py` +2. Run it to generate `signal/docs/support-api-ref.json` +3. Read the generated ref alongside the manual +4. Update `signal/docs/support-manual.md`: + - Add generation timestamp note + - Add missing endpoint sections + - Fix any error code mismatches +5. Commit both files + +## Completion Report Template + +```markdown +### Phase 4 (Support Bot Foundation) + +#### 4.1 API ref generation +- [ ] gen-api-ref.py written to signal/scripts/ +- [ ] Runs with stdlib only (no pip install) +- [ ] support-api-ref.json generated with all N endpoints + +#### 4.2 Manual cross-reference +- [ ] Generation timestamp added to support-manual.md +- [ ] All endpoints cross-referenced +- [ ] Error codes match between manual and API ref +```