feat(build-spec): Phase 4 — support bot foundation spec + trust fixes applied
This commit is contained in:
parent
25746de7b3
commit
9a274f6fee
1 changed files with 316 additions and 0 deletions
316
docs/superpowers/specs/2026-06-22-build-spec-phase4.md
Normal file
316
docs/superpowers/specs/2026-06-22-build-spec-phase4.md
Normal file
|
|
@ -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.<method>` decorators and `@router.<method>` 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
|
||||||
|
```
|
||||||
Loading…
Reference in a new issue