# Project export: Canary

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: UC Berkeley AI Hackathon 2026
- Tagline: An autonomous agent that catches the silent bugs your tests and Sentry both miss, then fixes them and writes the regression test.
- Devpost: https://devpost.com/software/agi-14q6d3
- GitHub: https://github.com/vikashftw/Canary
- Result: winner (Best Use of Sentry API)
- Team: 4 GitHub contributor(s) — Claude Opus 4.8 (48 commits), Prajit (40 commits), Vikash Mall (14 commits), Prajit Viswanadha (11 commits)

## Devpost submission (written by the team)

### Inspiration

Every team ships two kinds of bugs: The kind that throws (crashes, 500s, timeouts) The kind that's silently wrong (a total that's off, money leaking on every order) Your tests pass, and your app is still broken. These bugs hide for months until a customer or a finance report finds them for you. That's the gap nobody's watching, so we built Canary to watch it.

### What it does

Canary is a self-healing correctness agent — an agent that catches the bug, root-causes it, fixes it, writes the regression test, proves the fix works, and remembers it, with zero humans in the loop. It runs on two oracles: Sentry watches what throws (errors, 500s, traces) and gives the agent the trace context to reason over. Impact: instant root-cause instead of hours digging through logs. The Computer Use Agent is an agent in a real cloud browser that takes action and watches what's silently wrong. Impact: catches the revenue-leaking bugs your tests and error monitoring both miss. In our demo, Canary caught a checkout charging $26 for two $52 seats (a 50% revenue leak every unit test passed right over) and a silent 54% drop in average order value, then fixed both with no human in the loop. Together they power one agentic, memory-backed loop: detect, fix, verify, remember.

### How we built it

TypeScript end to end. A Next.js app is both the seeded checkout and the live dashboard. Claude (Anthropic) is the brain for exploration, triage, and fix synthesis. The explorer drives a real Browserbase cloud browser over CDP, with a synthetic in-page cursor so every click shows up in the live view and the replay. The Sentry SDK supplies trace correlation, unified fingerprinting, breadcrumb reasoning trails, and the resolved-to-regressed lifecycle. Redis backs the knowledge base. Every external dependency sits behind an interface with a fake implementation, so 226 tests and CI run fully offline and live integrations switch on only when their keys are present.

### Challenges we ran into

Browserbase only records the context it hands you. Spin up a fresh one and the replay is blank, so we drive the recorded page directly. Dev-mode hot reload cannot upgrade through a tunnel, so we serve a production build and run all checkout traffic from inside the cloud page. The Sentry and Browserbase SDKs fought over Node globals until we registered the Browserbase shim first. We refused to label a fix "verified" unless a real verifier actually ran the test, which closed the gap between looking done and being done. A naive threshold detector either misses the drop or cries wolf, so median and MAD plus a drop gate plus a post-resolve cooldown made it fire once, on the real thing.

### Accomplishments we're proud of

A genuinely autonomous loop, no human click, that detects, fixes, verifies, and remembers. Honesty as a feature: verified means a test ran, backed by 226 deterministic offline tests. Every sponsor is load-bearing, not bolted on. Anthropic is the brain, Sentry is the oracle and memory, Browserbase is the body, Redis is the long-term memory. A test suite that grows itself, because every bug found writes the assertion nobody wrote.

### What we learned

Catching silent bugs needs an oracle that declares intent, not one that waits for a crash. Robust statistics matter: median and MAD survive the outliers that sink mean and standard deviation. And observability is strongest as a substrate an agent reasons over, not a dashboard you check after the fact. Sentry is not where bugs die — it is the agent's brain.

### What's next

Point it at any app and auto-derive invariants instead of seeding them. Open each fix as a real pull request, feeding Sentry's Seer instead of fighting it. A long-horizon miner over accumulated Sentry history that surfaces extreme outliers no single run hits. More oracle types: accessibility, performance budgets, and data-integrity checks. Built With: typescript, next.js, anthropic, claude, sentry, browserbase, redis, playwright, vitest, node.js, react

## README (from the GitHub repository)

# Bloodhound 🐕‍🦺

**Dual-oracle autonomous QA swarm — finds the silent bugs your tests _and_ your
error monitoring miss.**

Bloodhound explores a real app through a headless browser like a chaos-user and
catches failures with **two oracles**:

- **Sentry** catches what *throws* — crashes, errors, slow spans.
- **A browser agent** catches what's *silently wrong* — logic/UI bugs where
  nothing throws (e.g. the cart shows $10 for two $10 items). It declares what it
  *expects* before acting, then flags observed ≠ expected.

Everything logs into **Sentry, our history DB**. When something breaks, a
**triage agent** pulls the correlated Sentry trace (browser = symptom, Sentry =
cause), root-causes it, **fixes the code, and writes a regression test** so it can
never recur.

> Built for the UC Berkeley AI Hackathon 2026. Shared context lives in the
> sibling **team-brain** repo (`../team-brain`).

## This repo

The Bloodhound codebase. Three workstreams build against one set of frozen
contracts:

| Area | Owner | Path |
|------|-------|------|
| Triage & fix engine + frozen contracts | Vikash | `src/contracts/`, `src/triage/`, `src/sentry/`, `src/cache/` |
| Browser explorer + demo app | Shashank | (incoming) |
| Dashboard + memory/persistence + wiring | Prajit | (incoming) |

### The contracts (`src/contracts/`)

The two hand-off shapes that keep three people building one project — mirrors
team-brain `reference/api-contracts.md`:

- **`Finding`** — detection → triage. What was expected, what was observed, how to
  reproduce, and the Sentry `traceId` to correlate.
- **`Resolution`** — triage → dashboard. Confirmed?, root cause, fix diff,
  regression-test path, verified?, status.
- **`signature(finding)` / `SeenStore`** — stable dedup over *detection-time*
  fields only (never `rootCause` — that would be circular), so triage skips
  duplicates.

Sample fixtures for teammates to build against live in `fixtures/`.

## Develop

```bash
npm ci
npm run typecheck
npm test
```

Anything touching Anthropic or Sentry is dependency-injected behind an interface
with a `Fake*` implementation, so the suite runs fully offline. Live
implementations activate only when the relevant API keys are present in the
environment.


## Detected evidence (automated analysis)

Indexed codebase: 148 recognized source files, 902 KB.
- CSS (language) — detected in the code
- Next.js (technology) — detected in the code
- React (technology) — detected in the code
- Redis (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- Anthropic (technology) — claimed on Devpost, not found in the code
- Node.js (technology) — claimed on Devpost, not found in the code
- AI coding agent: Claude Code — evidence: config files committed to the repository; commit authorship or trailers
- AI coding agent: Codex — evidence: config files committed to the repository

## Codebase structure (from repository index)

### Files (120 of 167)

```
.github/workflows/ci.yml
.github/workflows/web-ci.yml
.gitignore
AGENTS.md
CLAUDE.md
explorer/.gitignore
explorer/browser.ts
explorer/cursor.ts
explorer/env.ts
explorer/loop.ts
explorer/package.json
explorer/run-cloud.sh
explorer/run.ts
explorer/sentry-breadcrumbs.ts
explorer/smoke-cloud.ts
explorer/stagehand-run.ts
explorer/tsconfig.json
explorer/verify.ts
findings/find_shashank-drop-demo.json
fixtures/sample-finding.json
fixtures/sample-resolution.json
fixtures/traces/a1b2c3d4e5f60718293a4b5c6d7e8f90.json
next-env.d.ts
package.json
README.md
scripts/demo-preflight.sh
scripts/demo-reset.sh
src/cache/canonical.ts
src/cache/index.ts
src/cli.ts
src/contracts/finding.ts
src/contracts/index.ts
src/contracts/resolution.ts
src/contracts/seen.ts
src/contracts/signature.ts
src/index.ts
src/memory/embedder.ts
src/memory/finding-store.ts
src/memory/index.ts
src/memory/redis-client.ts
src/memory/seen-store.ts
src/memory/similar-findings.ts
src/memory/vector-index.ts
src/sentry/index.ts
src/sentry/trace-client.ts
src/sentry/trace.ts
src/triage/engine.ts
src/triage/fixer.ts
src/triage/index.ts
src/triage/judge.ts
src/triage/llm.ts
src/triage/prompts.ts
src/triage/regression.ts
src/triage/seer.ts
src/triage/verifier.ts
tests/cache-canonical.test.ts
tests/case2-traffic.test.ts
tests/cli.test.ts
tests/contracts.test.ts
tests/detector.test.ts
tests/explorer-breadcrumbs.test.ts
tests/integration/contract-conformance.test.ts
tests/integration/engine-offline-smoke.test.ts
tests/integration/invariants.test.ts
tests/memory-dedup.test.ts
tests/memory-fakes.test.ts
tests/memory-redis-integration.test.ts
tests/memory-rootcause-pin.test.ts
tests/memory-shared-redis.test.ts
tests/memory-similar-findings.test.ts
tests/sentry-trace.test.ts
tests/triage-engine.test.ts
tests/triage-fix.test.ts
tests/triage-judge.test.ts
tests/triage-verifier-seer.test.ts
tsconfig.json
vitest.config.ts
web/.env.example
web/.gitignore
web/AGENTS.md
web/CLAUDE.md
web/eslint.config.mjs
web/instrumentation-client.ts
web/instrumentation.ts
web/next.config.ts
web/package.json
web/postcss.config.mjs
web/README.md
web/sentry.edge.config.ts
web/sentry.server.config.ts
web/src/app/_components/DiffView.tsx
web/src/app/_components/LiveAgentPanel.tsx
web/src/app/_components/LoopStepper.tsx
web/src/app/_components/MemoryPanel.tsx
web/src/app/_components/redesign/AgentActivityFeed.tsx
web/src/app/_components/redesign/AuditLog.tsx
web/src/app/_components/redesign/AutonomyBrowser.tsx
web/src/app/_components/redesign/AutonomyExploration.tsx
web/src/app/_components/redesign/AutonomyKeyframes.tsx
web/src/app/_components/redesign/AutonomyMemory.tsx
web/src/app/_components/redesign/AutonomySpine.tsx
web/src/app/_components/redesign/AutonomyTopBar.tsx
web/src/app/_components/redesign/AutonomyTriage.tsx
web/src/app/_components/redesign/BrowserPlayback.tsx
web/src/app/_components/redesign/Case1Invariant.tsx
web/src/app/_components/redesign/Dashboard.tsx
web/src/app/_components/redesign/DashboardFooter.tsx
web/src/app/_components/redesign/DESIGN.md
web/src/app/_components/redesign/LoopSpine.tsx
web/src/app/_components/redesign/MemoryTimeline.tsx
web/src/app/_components/redesign/RedisRecall.tsx
web/src/app/_components/redesign/RegressionPanel.tsx
web/src/app/_components/redesign/TabbedDashboard.tsx
web/src/app/_components/redesign/theme.ts
web/src/app/_components/redesign/TopBar.tsx
web/src/app/_components/redesign/TrendChart.tsx
web/src/app/_components/redesign/TriagePanel.tsx
web/src/app/_components/redesign/types.ts
web/src/app/_components/redesign/useAutonomy.ts
web/src/app/_components/redesign/useDashboardRun.ts
[47 more files omitted for size]
```

### Dependencies

- explorer/package.json: @browserbasehq/sdk@^2.0.0, @browserbasehq/stagehand@^3.6.0, @sentry/node@^8.47.0, playwright@^1.49.0, tsx@^4.19.2
- package.json: @types/node@^22.10.0, redis@^4.7.1, tsx@^4.19.2, typescript@^5.7.2, vitest@^4.1.9
- web/package.json: @sentry/nextjs@^10.59.0, @tailwindcss/postcss@^4, @types/node@^20, @types/react@^19, @types/react-dom@^19, eslint@^9, eslint-config-next@16.2.9, next@16.2.9, react@19.2.4, react-dom@19.2.4, redis@^4.7.1, tailwindcss@^4, typescript@^5

### Recent commits (newest first)

- done
- update loggging
- some working shit
- Merge remote-tracking branch 'origin/demo/autonomy'
- feat(autonomy): reframe Redis as company bug knowledge base (novelty/recurrence, not skip/dedup)
- feat(engine): real `verified` on runEngine + APP_URL-robust cache cutover (verify-lane #3 + seam #2) (#30)
- feat(ui): enrich Case-1 + light-mode the demo checkout
- Merge remote-tracking branch 'origin/main' into demo/autonomy-ui
- chore: take in teammate work — explorer/cursor.ts (synthetic cursor) wired into stagehand-run.ts + redesign/DESIGN.md handoff
- fix(autonomy): dramatic ~54% AOV drop (team-seats traffic) + fixed-baseline gate + post-resolve cooldown
- feat(ui): Case 1 | Case 2 tabbed dashboard in the clay-monochrome design
- fix(ui): AutonomySpine circle uses non-shorthand border (no React shorthand/borderStyle mix warning)
- feat(autonomy): real Case-2 (seeded traffic AOV + median/MAD detector) + fix Open-in-Sentry link
- feat(autonomy): populate exploration walk (the moat) + honest verified/test-pass labels
- feat(autonomy): long-horizon autonomous agent — brain loop, dashboard, explorer state-walker, Sentry unify, cache reliability
- fix(explorer): mkdir findings dir before disk mirror so TRIGGER_TRIAGE fires
- Merge pull request #29 from vikashftw/shashank/dashboard-redesign
- Merge remote-tracking branch 'origin/main' into shashank/dashboard-redesign
- Merge pull request #28 from vikashftw/demo/stagehand-live
- fix(checkout): keep cached-canonical guard green after bug-toggle merge

## Key source files (fetched from GitHub, selected and truncated for size)

### CLAUDE.md

```markdown
@../team-brain/context/active.md
@../team-brain/AGENTS.md

# Bloodhound (repo: `21`)

Dual-oracle autonomous QA swarm. This repo holds the code; **team-brain** (sibling
folder) holds the shared brain — read its `context/active.md` first.

## Stack & commands
- TypeScript (ESM, Node ≥ 20), tested with Vitest.
- install: `npm ci`
- typecheck: `npm run typecheck`
- test: `npm test`

## Ownership (see team-brain `context/workstreams.md`)
- **Vikash — Triage & Fix (this code's core):** `src/contracts/` (frozen, shared),
  `src/triage/`, `src/sentry/`, `src/cache/`, plus the reference buggy app.
- Shashank — explorer + demo app (emits `Finding`).
- Prajit — dashboard + memory/persistence + e2e wiring (consumes `Resolution`).

## Conventions (only the non-obvious)
- `src/contracts/` is a **frozen interface** mirroring team-brain
  `reference/api-contracts.md`. Don't change a shape without all-three sign-off +
  a `decisions.md` entry.
- Everything that touches Anthropic or Sentry is **dependency-injected** behind an
  interface with a `Fake*` impl, so tests + CI run offline. Live impls activate
  only when the relevant env vars are present.

```

### AGENTS.md

```markdown
# AGENTS.md — Bloodhound code repo (`21`)

> Shared brain is the sibling **team-brain** repo. Read `../team-brain/context/active.md`
> first, then `../team-brain/AGENTS.md`. This file is repo-local conventions only.

## Commands
- install: `npm ci`
- typecheck: `npm run typecheck`
- test: `npm test` (Vitest, runs offline)

## Layout
- `src/contracts/` — FROZEN hand-off shapes (`Finding`, `Resolution`, `signature`,
  `SeenStore`). Mirror of team-brain `reference/api-contracts.md`. Don't break
  without all-three sign-off + a `decisions.md` entry.
- `src/triage/`, `src/sentry/`, `src/cache/` — the triage & fix engine (Vikash).
- `fixtures/` — sample `Finding` / `Resolution` so teammates aren't blocked.
- `tests/` — Vitest unit/e2e (`*.test.ts`); Playwright regression specs land as
  `*.spec.ts` (written by the engine).

## Rules
- External services (Anthropic, Sentry, browser) are injected behind interfaces
  with `Fake*` impls. Tests must never need network or secrets.
- Live impls read env vars (`ANTHROPIC_API_KEY`, `SENTRY_AUTH_TOKEN`, …) and are
  selected at the edge; the core stays pure and testable.
- Keep CI green: `npm run typecheck && npm test` must pass before every merge.

```

### package.json

```
{
  "name": "bloodhound",
  "version": "0.1.0",
  "private": true,
  "type": "module",
  "description": "Bloodhound — dual-oracle autonomous QA swarm. This package: the triage & fix engine + frozen contracts.",
  "engines": {
    "node": ">=20"
  },
  "scripts": {
    "typecheck": "tsc --noEmit",
    "test": "vitest run",
    "test:watch": "vitest",
    "triage": "tsx src/cli.ts",
    "lint": "tsc --noEmit"
  },
  "devDependencies": {
    "@types/node": "^22.10.0",
    "tsx": "^4.19.2",
    "typescript": "^5.7.2",
    "vitest": "^4.1.9"
  },
  "dependencies": {
    "redis": "^4.7.1"
  }
}

```

### web/package.json

```
{
  "name": "web",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "eslint"
  },
  "dependencies": {
    "@sentry/nextjs": "^10.59.0",
    "next": "16.2.9",
    "react": "19.2.4",
    "react-dom": "19.2.4",
    "redis": "^4.7.1"
  },
  "devDependencies": {
    "@tailwindcss/postcss": "^4",
    "@types/node": "^20",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "eslint": "^9",
    "eslint-config-next": "16.2.9",
    "tailwindcss": "^4",
    "typescript": "^5"
  }
}

```

### explorer/package.json

```
{
  "name": "bloodhound-explorer",
  "version": "0.1.0",
  "private": true,
  "type": "module",
  "description": "Bloodhound browser explorer — drives the checkout app, asserts the cart-total invariant, and files a Finding on violation.",
  "engines": {
    "node": ">=20"
  },
  "scripts": {
    "explore": "tsx run.ts",
    "loop": "tsx loop.ts",
    "agent": "tsx stagehand-run.ts",
    "verify": "tsx verify.ts"
  },
  "dependencies": {
    "@browserbasehq/sdk": "^2.0.0",
    "@browserbasehq/stagehand": "^3.6.0",
    "@sentry/node": "^8.47.0",
    "playwright": "^1.49.0",
    "tsx": "^4.19.2"
  }
}

```

### src/index.ts

```typescript
/**
 * Bloodhound triage engine — public API (Vikash).
 *
 * Consumable as `@engine` from the dashboard / Band adapter, or as a library:
 *   import { triage, createTraceClient, createLLMClient, InMemorySeenStore } from "@engine";
 *
 * For a subprocess entrypoint, use the CLI: `npx tsx src/cli.ts <finding.json>`.
 */
export * from "./contracts/index";
export * from "./sentry/index";
export * from "./triage/index";
export * from "./cache/index";

```

### src/cli.ts

```typescript
#!/usr/bin/env node
/**
 * Bloodhound triage CLI — the engine's subprocess entrypoint.
 *
 *   npx tsx src/cli.ts <finding.json> [--write] [--out <dir>]
 *   cat finding.json | npx tsx src/cli.ts --stdin
 *
 * Reads a `Finding` (JSON), runs the triage engine, prints the `Resolution` JSON
 * to stdout (exit 0). This is how the dashboard or a Band.ai adapter invokes
 * triage — pure JSON in, pure JSON out (codeband's shell-out-to-CLI pattern).
 */

import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
import { basename, dirname, resolve, sep } from "node:path";
import { pathToFileURL } from "node:url";

import { isFinding, type Finding } from "./contracts/index";
import { createTraceClient } from "./sentry/index";
import { createLLMClient } from "./triage/llm";
import { triage, type TriageDeps } from "./triage/engine";
import { createSeenStore } from "./memory/seen-store";
import { createVerifier, toVerifierHook } from "./triage/verifier";

const USAGE = `bloodhound-triage — run triage on a Finding

Usage:
  bloodhound-triage <finding.json> [--write] [--out <dir>]
  bloodhound-triage --stdin [--write] [--out <dir>]

Options:
  --write        persist the regression test + Resolution to disk
  --out <dir>    base dir for the written Resolution (default: .bloodhound)
  -h, --help     show this help

Output: the Resolution as JSON on stdout.
`;

export interface CliOptions {
  /** Inject engine deps (tests / custom wiring). Default: built from env. */
  deps?: TriageDeps;
  env?: NodeJS.ProcessEnv;
  cwd?: string;
  readFileFn?: (path: string) => string;
  stdinReadFn?: () => string;
  writeFileFn?: (path: string, data: string) => void;
  mkdirFn?: (path: string) => void;
}

export interface CliOutcome {
  code: number;
  stdout: string;
  stderr: string;
}

interface ParsedArgs {
  input?: string;
  stdin: boolean;
  write: boolean;
  outDir: string;
  help: boolean;
}

function parseArgs(argv: string[]): ParsedArgs {
  const args: ParsedArgs = { stdin: false, write: false, outDir: ".bloodhound", help: false };
  for (let i = 0; i < argv.length; i++) {
    const a = argv[i];
    if (a === "--stdin") args.stdin = true;
    else if (a === "--write") args.write = true;
    else if (a === "--out") {
      const next = argv[i + 1];
      if (next && !next.startsWith("-")) args.outDir = next, i++;
    }
    else if (a === "-h" || a === "--help") args.help = true;
    else if (a && !a.startsWith("-")) args.input ??= a;
  }
  return args;
}

const msg = (e: unknown): string => (e instanceof Error ? e.message : String(e));

/** True if `target` is inside (or equal to) `base` — guards against path traversal. */
function contained(base: string, target: string): boolean {
  return target === base || target.startsWith(base + sep);
}

export async function runTriageCli(argv: string[], opts: CliOptions = {}): Promise<CliOutcome> {
  const args = parseArgs(argv);
  if (args.help) return { code: 0, stdout: USAGE, stderr: "" };
  if (!args.input && !args.stdin) return { code: 2, stdout: "", stderr: USAGE };

  const cwd = opts.cwd ?? process.cwd();
  const readFileFn = opts.readFileFn ?? ((p: string) => readFileSync(p, "utf8"));
  const stdinReadFn = opts.stdinReadFn ?? (() => readFileSync(0, "utf8")); // fd 0 — portable

  let raw: string;
  try {
    raw = args.stdin ? stdinReadFn() : readFileFn(resolve(cwd, args.input!));
  } catch (e) {
    return { code: 1, stdout: "", stderr: `cannot read finding: ${msg(e)}` };
  }

  let parsed: unknown;
  try {
    parsed = JSON.parse(raw);
  } catch (e) {
    return { code: 1, stdout: "", stderr: `invalid JSON: ${msg(e)}` };
  }
  if (!isFinding(parsed)) {
    return { code: 1, stdout: "", stderr: "input is not a valid Finding (see contracts)" };
  }

  const deps = opts.deps ?? (await buildDeps(opts.env ?? process.env, cwd));
  try {
    const result = await triage(parsed as Finding, deps);

    if (result.kind === "duplicate") {
      const out = { kind: "duplicate", findingId: result.findingId, signature: result.signature };
      return { code: 0, stdout: JSON.stringify(out, null, 2) + "\n", stderr: "" };
    }

    if (args.write) {
      const writeFileFn = opts.writeFileFn ?? ((p: string, d: string) => writeFileSync(p, d));
      const mkdirFn = opts.mkdirFn ?? ((p: string) => void mkdirSync(p, { recursive: true }));
      const cwdBase = resolve(cwd);
      const outBase = resolve(cwd, args.outDir);
      try {
        if (result.regressionTest) {
          // Contain inside the project — never let a derived test path escape via "..".
          const testPath = resolve(cwd, result.regressionTest.path);
          if (!contained(cwdBase, testPath)) {
            return { code: 1, stdout: "", stderr: "refusing to write a regression test outside the project" };
          }
          mkdirFn(dirname(testPath));
          writeFileFn(testPath, result.regressionTest.content);
        }
        // findingId is attacker-controlled (the contract only checks it's a string),
        // so sanitize to a basename + safe charset before using it as a filename.
        const safeId = basename(result.resolution.findingId).replace(/[^a-zA-Z0-9._-]/g, "_") || "finding";
        const resPath = resolve(outBase, `${safeId}.resolution.json`);
        if (!contained(outBase, resPath)) {
          return { code: 1, stdout: "", stderr: "refusing to write the Resolution outside the out dir" };
        }
        mkdirFn(dirname(resPath));
        writeFileFn(resPath, JSON.stringify(result.resolution, null, 2) + "\n");
      } catch (e) {
        return { code: 1, stdout: "", stderr: `write failed: ${msg(e)}` };
      }
    }

    return { code: 0, stdout: JSON.stringify(result.resolution, null, 2) + "\n", stderr: "" };
  } finally {
    // Best-effort teardown of a seen-store WE built (release a live Redis client)
    // so an in-process caller of runTriageCli doesn't leak a handle / keep the event
    // loop alive. No-op until the memory layer's store exposes close() (flagg
[truncated — 1895 more characters]
```

### src/cache/index.ts

```typescript
/**
 * Cached canonical fixes — the demo's guaranteed fallback (Vikash). Keyed by the
 * Finding dedup signature; plugs into the engine via `CachedFixStrategy`.
 */
export type { CachedEntry } from "./canonical";
export {
  SEEDED_SIGNATURE,
  REAL_SIGNATURE,
  REAL_CHECKOUT_DETECTION,
  EXPLORER_LIVE_SIGNATURE,
  cachedEntryFor,
  cachedRegressionTestFor,
  checkoutTotalCanonicalFix,
  CachedFixStrategy,
} from "./canonical";

```

### src/sentry/index.ts

```typescript
/**
 * Sentry trace access for triage — pull a trace by id to correlate the browser
 * symptom (Finding) with the backend cause. Offline `Fake*` + live API impls.
 */
export type { TraceData, TraceSpan, TraceError } from "./trace";
export { traceHasErrors, findSpan, findSpansByKeyword, summarizeTrace } from "./trace";

export type { TraceClient, SentryApiOptions, CreateTraceClientOptions } from "./trace-client";
export {
  FakeTraceClient,
  SentryApiTraceClient,
  mapSentryTraceResponse,
  createTraceClient,
} from "./trace-client";

```

### src/contracts/index.ts

```typescript
/**
 * Bloodhound frozen contracts — the hand-off shapes that keep three people
 * building ONE project. Detection (Shashank/miner) → triage (Vikash) → dashboard
 * (Prajit). See team-brain `reference/api-contracts.md`.
 */
export type { Finding, FindingSource, SeverityHint } from "./finding";
export { isFinding } from "./finding";

export type { Resolution, ResolutionStatus } from "./resolution";
export { isResolution } from "./resolution";

export { signature } from "./signature";
export type { SeenStore } from "./seen";
export { InMemorySeenStore, checkSeen } from "./seen";

```

[138 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]