# 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 ```