How Watari maps a support ticket to a file and function — pgvector, tree-sitter, and a Claude rerank
Watari maps each extracted bug to a specific file and function in your GitHub repository by chunking your code with tree-sitter, embedding every chunk with OpenAI text-embedding-3-small, indexing the embeddings in pgvector, and reranking the top-K candidates with Claude before opening a draft pull request.
TL;DR
Watari maps a customer support ticket to a specific file and function in your GitHub repository by chunking your code with tree-sitter, embedding every chunk with OpenAI text-embedding-3-small, indexing the embeddings in pgvector, and reranking the top-twenty candidates with Claude Haiku 4.5 before opening a draft pull request. The pipeline runs in under thirty seconds for a typical SaaS codebase.
Why function-level chunking beats file-level chunking
The single highest-impact decision in the mapping pipeline is the chunking strategy. File-level chunks blur the signal — a 2,000-line controller becomes one embedding that "matches" any bug that touches the controller's domain. Function-level chunks isolate the unit of behaviour that the customer's bug report describes. When a ticket says "the export button hangs after I click it," the relevant chunk is the function bound to that button's handler, not the entire UI module.
Tree-sitter is the right tool for the job because it produces a real AST per language. Watari uses tree-sitter grammars for twelve languages today, including TypeScript, JavaScript, Python, Go, Ruby, Java, and PHP. Each chunk carries the function name, the surrounding class or module context, line numbers, and imports — context the reranker uses later to disambiguate similarly-named functions across files.
Why pgvector instead of a dedicated vector database
pgvector lives in the same Postgres that holds every other row Watari writes — bugs, tickets, organisations, code_chunks, billing counters. Adding a separate vector DB would add a synchronisation layer, a second backup story, a second RLS-equivalent thinking model, a second tier of operational on-call, and a second vendor invoice — for one query type. pgvector's HNSW index hits sub-100ms p95 on indexes up to a few million chunks, which is the scale a typical SaaS codebase produces.
The other reason pgvector wins for this workload: every search is scoped to a single repository, which is a single tenant's data. RLS scoping WHERE repository_id = $1 AND organization_id = $2 lets Postgres prune the index before HNSW traversal even starts. A dedicated vector DB would have to handle multi-tenant filtering inside the vector layer, where it tends to be expensive or constrained.
Why Claude rerank instead of vector-only
A top-1 vector match is often almost right. The function with the highest embedding similarity might share vocabulary with the bug ("export" appears in three different export-related functions) without being the one the customer hit. A rerank pass reads the candidate chunks and the structured bug together, weighs the actual logic against the actual symptoms, and either confirms the top-1 or surfaces a different candidate.
We use Claude Haiku 4.5 for the rerank because it's the right cost-quality point — strong enough to reason about code context, fast enough to keep the pipeline under thirty seconds, cheap enough that reranking twenty chunks per bug is comfortable. The rerank prompt asks for a confidence score per chunk and a short rationale per top candidate; both feed into the Mapped Bug qualification decision.
What stays bundled, what is metered
The mapping step is the meter. Once a bug clears confidence thresholds, the Mapped Bug billing event fires exactly once. Everything downstream — drafting the PR, drafting the customer-facing RCA, syncing to Slack or Linear or Jira — is bundled into your subscription. There is no per-PR fee, no per-RCA fee, no per-sync fee. We bill the moment a bug becomes actionable and stop billing after that.
Failure modes the pipeline handles
| Failure | What Watari does |
|---|---|
| Bug doesn't clear extraction confidence | Marked pending review, surfaced in the Tickets view; no meter |
| Bug extracts but no candidate clears mapping confidence | Marked not fixable with a stage-by-stage explanation; no meter |
| Multiple candidate locations score above threshold | All persisted as code_locations; PR drafting routes per-file via CODEOWNERS |
| Repository hasn't finished indexing | Bug is queued; mapping fires once indexing completes |
| Ticket is a feature request or billing question | Filtered before mapping by the extractor; not a bug, not metered |
What's next
Future posts will cover the embedding cost model (how we keep per-org indexing costs under control as repos grow), the Inngest concurrency patterns that keep the meter idempotent under retry storms, and the Claude prompt design that makes extraction stable across thousand-token tickets. Subscribe to the Watari Blog newsletter — one email when a new engineering deep dive lands.
The Code-to-PR pipeline doc covers what happens after this mapping step — how Watari turns the mapped location into a working pull request.
Get new posts in your inbox
One email when a new post lands. No spam. Unsubscribe in one click.