# BugCraft — integration docs for agents

> Machine-readable mirror of the page at `/docs/agents`. Written for the AI
> coding agent doing the integration and the triage — copy-paste the snippets.

BugCraft collects element-anchored UAT feedback (screenshots, console / error /
network diagnostics) from an in-app widget and serves it to Claude Code as an
MCP work queue.

**Tenancy.** Organization → Project (one host app) → Environment (uat, staging,
…). Each environment has a public **ingest key** the widget ships with; the org
has secret **API tokens** for read/triage (REST, MCP, and CLI). A report belongs
to exactly one project + environment; queries never cross tenants.

`<BASE_URL>` below is the service origin (your Railway URL, or
`http://localhost:3000` in dev).

---

## 1. Add the widget to a Vite host

**Install the widget.** `@bugcraft/widget` is a private, self-hosted package —
it is **not on the public npm registry**. Install it straight from the BugCraft
service; the URL is the install source, so no registry auth or repo access is
needed:

```bash
# Self-hosted tarball — not on the public npm registry.
npm install https://bugcraft.betacraft.com/widget/bugcraft-widget-latest.tgz

# Or pin an exact version (same path):
# npm install https://bugcraft.betacraft.com/widget/bugcraft-widget-0.1.0.tgz
```

The `latest` URL always resolves to the currently deployed widget; a pinned
version URL is also available at the same path.

**Wire it into your entry.** Statically import the capture bootstrap at the very
top of your entry and gate it on the env key. The UI is imported dynamically so
it only loads where the key is set. The widget is **UAT-only by construction**:
with no key, the block tree-shakes to zero bytes in production.

```tsx
// src/main.tsx — import the capture bootstrap FIRST (before app code),
// so console/fetch/XHR are wrapped before any library captures references.
import { initCaptureBootstrap } from "@bugcraft/widget/bootstrap";

if (import.meta.env.VITE_BUGCRAFT_KEY) {
  initCaptureBootstrap({
    key: import.meta.env.VITE_BUGCRAFT_KEY,      // per-environment ingest key
    endpoint: import.meta.env.VITE_BUGCRAFT_URL, // bugcraft service origin
    user,                        // optional — see "The user object" below
    appVersion: __APP_VERSION__, // optional — see "Build metadata" below
    commitSha: __COMMIT_SHA__,   // optional
  });
  // Dynamically import the UI (Shadow DOM, picker, snapdom) — lazy, ~50kB gz.
  import("@bugcraft/widget").then((m) => m.mount());
}

// ...then your normal bootstrap: createRoot(el).render(<App />)
```

**The launcher.** The floating button opens the **element picker first** — click
an element or drag a region to anchor the report — then the feedback form opens.
The selected element, both screenshots (element + viewport), and the console /
network / error diagnostics attach automatically. The widget sets `page_url` to
the current `location.href`; ingest requires it to be an **http(s) URL**
(non-http schemes are rejected with a `422`), since the dashboard renders it as
a link.

**The `user` object** (optional). Identifies the tester so reports are attributed
without them typing a name. Pass it from your host app's session — shape
`{ name?: string; email?: string; role?: string }`, every field optional:

```ts
// from your host app's session
const user = { name: session.name, email: session.email, role: session.role };
```

When omitted, the widget asks the tester for a name and remembers it locally.

**Build metadata** (optional). `appVersion` and `commitSha` let the triaging
agent pin the exact build (symbolicate a stack trace, check out the right SHA).
Pass plain strings, or wire the `__APP_VERSION__` / `__COMMIT_SHA__` globals
used above through Vite's `define`:

```ts
// vite.config.ts
import { execSync } from "node:child_process";
import { defineConfig } from "vite";

export default defineConfig({
  define: {
    __APP_VERSION__: JSON.stringify(process.env.npm_package_version ?? "dev"),
    __COMMIT_SHA__: JSON.stringify(
      execSync("git rev-parse --short HEAD").toString().trim(),
    ),
  },
});
```

…and declare them for TypeScript (e.g. `src/vite-env.d.ts`):

```ts
declare const __APP_VERSION__: string;
declare const __COMMIT_SHA__: string;
```

Provide the env vars per environment (get the ingest key from **Settings → your
project → environment**):

```bash
# .env.uat — one ingest key per environment (Railway per-env vars)
VITE_BUGCRAFT_KEY=bc_ing_your_uat_environment_key
VITE_BUGCRAFT_URL=https://your-bugcraft.up.railway.app

# Production build: DO NOT set VITE_BUGCRAFT_KEY.
# The whole block above then tree-shakes out — provably zero bytes shipped.
```

**Origins & serving.** An environment's **allowed origins** must exactly match the
host page's origin — scheme + host + port (e.g. `https://uat.acme.com` or
`http://localhost:5173`; an origin only — no path, no trailing slash). A browser
submission from any other origin is rejected with **403**. Add every origin the
UAT build is served from (dev `localhost` ports included). Server-to-server calls
(curl with no `Origin` header) are exempt.

---

## 2. Register the MCP server

Add BugCraft as an HTTP MCP server. `<BASE_URL>` is the service origin (your
Railway URL, or `http://localhost:3000` in dev); `$BUGCRAFT_TOKEN` is an org API
token from Settings.

```bash
claude mcp add --transport http bugcraft <BASE_URL>/mcp \
  --header "Authorization: Bearer $BUGCRAFT_TOKEN"
```

### Tools (5)

| Tool | Description | Token budget |
|---|---|---|
| `list_reports` | Compact summaries, oldest-first. Filters: project, environment, status, kind, since, limit, cursor. Never returns attachments. | text ≤ ~6k tokens/page |
| `get_report` | One report: metadata, element anchor (incl. React component path), description, console-error tail, + the screenshot as a native image block. | image ≈1.5–2k, tail ≈2k, metadata <500 |
| `get_console_log` | Full console / error / network log, 50 entries per page (pass the cursor back). | text ≤ ~6k tokens/page |
| `update_report_status` | Transition status (matrix-validated); optional note becomes an agent comment; optional commit_sha is stored. | confirmation <200 |
| `get_report_stats` | One-call triage overview: counts by status / kind / environment. | <500 tokens |

### The triage loop

1. `list_reports(status="new")` — oldest first.
2. `get_report(id)` — one at a time; read the screenshot, element anchor, and console-error tail.
3. Reproduce and fix in the repo (use the commit SHA to symbolicate).
4. `update_report_status(id, "fixed", commit_sha)` when the fix lands.

### Install the /bugcraft skill

Rather than wire the loop by hand, drop the ready-made `/bugcraft` Claude Code
skill into your project's `.claude/skills/` with one line:

```bash
mkdir -p .claude/skills/bugcraft && curl -fsSL \
  <BASE_URL>/skill/bugcraft/SKILL.md \
  -o .claude/skills/bugcraft/SKILL.md
```

Then run `/bugcraft`: it lists open reports oldest-first, reads each report's
element anchor, screenshot, and console-error tail, fixes it in your repo, and
closes it with the commit SHA — over the registered `bugcraft` MCP server, or the
CLI when `BUGCRAFT_URL` / `BUGCRAFT_TOKEN` are set. (On the hosted service the
path is `https://bugcraft.betacraft.com/skill/bugcraft/SKILL.md`.)

---

## 3. REST v1 (source of truth)

Every route takes `Authorization: Bearer <org token>`. Reports are ordered
oldest-first with an opaque keyset cursor (`next_cursor`). A cross-org id returns
`404`, never 403.

```bash
# List new reports, oldest first (the agent work-queue order)
curl -s "<BASE_URL>/api/v1/reports?project=nexdigm-v2&status=new" \
  -H "Authorization: Bearer $BUGCRAFT_TOKEN"

# One report (metadata + element anchor + console-error tail)
curl -s "<BASE_URL>/api/v1/reports/<id>" \
  -H "Authorization: Bearer $BUGCRAFT_TOKEN"

# Close it with the fix's commit SHA
curl -s -X PATCH "<BASE_URL>/api/v1/reports/<id>" \
  -H "Authorization: Bearer $BUGCRAFT_TOKEN" \
  -H "content-type: application/json" \
  -d '{"status":"fixed","commit_sha":"<sha>","note":"Fixed the null deref"}'
```

Uniform error shape:

```json
{ "error": { "code": "not_found", "message": "Report not found" } }
```

Codes: `unauthorized`(401) `forbidden`(403) `not_found`(404) `validation`(422)
`bad_request`(400) `payload_too_large`(413) `rate_limited`(429).

Endpoints:

- `GET /api/v1/reports` — list (filters: project, environment, status, kind, since, limit, cursor)
- `GET /api/v1/reports/:id` — full detail
- `GET /api/v1/reports/:id/console` — paginated log
- `GET /api/v1/reports/:id/screenshot?which=element|viewport` — raw PNG
- `PATCH /api/v1/reports/:id` — status / note / commit_sha
- `POST /api/v1/reports/:id/comments` — add a comment
- `GET /api/v1/stats?project=<slug>` — counts

---

## 4. CLI (token-efficient alternative)

The `@bugcraft/cli` package (bin `bugcraft`) wraps REST v1. Same org token.
Screenshots arrive as native image blocks over MCP (~1.6k tokens); the CLI
writes them to a **file for you to Read** instead.

```bash
export BUGCRAFT_URL=<BASE_URL>
export BUGCRAFT_TOKEN=bc_tok_...   # org API token (same one MCP uses)
```

```bash
bugcraft reports [--project <slug>] [--env <slug>] [--status <s>] \
                 [--kind <k>] [--since <ISO>] [--limit 20] [--cursor <c>] [--json]
bugcraft report <id>                       # full metadata + element + console tail
bugcraft console <id>                      # paginated console / error / network log
bugcraft screenshot <id> [--which element|viewport] [-o out.png]  # writes a PNG file
bugcraft status <id> <status> [--note <text>] [--sha <commit>]
bugcraft comment <id> <body> [--question]
bugcraft stats [--project <slug>]
```
