refactor(backend): split chatTools.ts into per-tool registry
From the PR description
Summary
- Splits the 3,284-line
backend/src/lib/chatTools.tsmonolith into a typed tool registry underbackend/src/lib/tools/. chatTools.ts shrinks to 976 lines (-70%) and contains only orchestration:SYSTEM_PROMPT,runLLMStream, citation parsing, message building, doc/workflow context builders. - Every tool the LLM can call lives in its own file with one
ToolDefinitionexport. Adding a new tool is now one new file + one line intools/registry.ts- no edits to the monolith. - Deletes the four legacy schema arrays (
TOOLS,PROJECT_EXTRA_TOOLS,TABULAR_TOOLS,WORKFLOW_TOOLS) and the giantrunToolCallsif/else chain. Callers inroutes/projectChat.tsandroutes/tabular.tslose theirextraTools:pass-through - the registry'savailableWhen(ctx)predicates handle gating per-tool.
Structure
backend/src/lib/tools/
types.ts ToolDefinition / ToolContext / ToolExecutionResult
+ tool-result types (EditAnnotation, Doc*Result, TurnEditState)
registry.ts TOOL_REGISTRY (as-const literal-name array)
+ buildAvailableToolSchemas + runToolCalls dispatcher
readDocument.ts
findInDocument.ts
editDocument.ts
generateDocx.ts (10 per-tool files)
replicateDocument.ts
listDocuments.ts
fetchDocuments.ts
listWorkflows.ts
readWorkflow.ts
readTableCells.ts
shared/
documentReading.ts Shared by read/find/fetch
generateDocx.ts The docx-builder helper (~510 lines)
runEditDocument.ts Tracked-edit version persistence helper
Behavior preservation
- All 10 tool schemas reproduced verbatim against the originals.
- Side-effect aggregation (docsRead / docsFound / docsCreated / docsReplicated / workflowsApplied / docsEdited) unchanged.
- SSE events emitted identically.
availableWhenpredicates reproduce existing route-shaped gating (list_documents / fetch_documents / replicate_document require projectId; read_table_cells requires tabularStore; edit_document / replicate_document require docIndex).- One micro-cleanup during a helper move: a dead
const colCount = headers.lengthwas removed from generateDocx. Behavior unchanged.
Test plan
-
tsc --noEmitclean -
npm test- 57/57 backend unit tests pass -
npm run test:e2e- 3/4 active specs pass. Caveat: chat/documents/projects/tabular specs are pre-skipped (test.describe.skip) due to drifted UI selectors - they were already skipped on main and would be the actual end-to-end validation of the dispatcher. See follow-up below. - Manual smoke before merge: send a chat message in each context (general chat, project chat, tabular review) that exercises read_document, generate_docx, and edit_document. The unit suite covers the dispatch shape; this catches anything provider-level the unit suite doesn't.
Tech debt added (surfaced, not caused, by this work)
Two e2e findings logged in TECHDEBT.md:
auth › log-out returns the user to the marketing rootnow times out - log-out lands at/logininstead of/. Likely the existing auth-guard race documented just below it in TECHDEBT.- The test Supabase project is missing the
public.user_profilesmigration - signup logs a schema-cache error but still succeeds.
A follow-up task has been spawned to re-enable the four skipped Playwright specs so the e2e suite can validate the dispatcher end-to-end again.
🤖 Generated with Claude Code
Our analysis
Split chatTools monolith into a typed tool registry — read the full analysis →
Think the analysis missed something the PR description covers?
Capture this PR into my fork
Download a Markdown prompt that tells Claude how to port every
commit in this PR into your working tree. Run it via
claude -p < capture-pull-2.md from
inside the repo you want the changes in.