Signal/docs/superpowers/specs/2026-06-22-build-spec-phase4.md

10 KiB

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

{
  "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

#!/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

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:
*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:
---
## [METHOD] /api/endpoint-name

**Summary:** description

**Error codes:**

| Code | Description |
|------|-------------|
| 200  | Success     |
| 4xx  | Error type  |

Verification

# 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

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