# Flowchart: transcript-watcher-integration
## Sources Consulted
- `src/services/transcripts/watcher.ts:1-242`
- `src/services/transcripts/processor.ts:33-393`
- `src/services/transcripts/config.ts:1-100`
- `src/services/transcripts/types.ts:1-71`
- `src/services/worker-service.ts:91, 164, 466, 614-658`
- `src/services/integrations/CursorHooksInstaller.ts:1-100`
- `src/cli/handlers/observation.ts:1-87`
- `src/services/worker/http/routes/SessionRoutes.ts:378-660`
## Happy Path Description
Worker startup loads transcript-watch config and instantiates `TranscriptWatcher`. `FileTailer` uses `fs.watch()` on each JSONL transcript; on growth, reads new bytes and splits by newline. Each line is `JSON.parse`d and routed to `TranscriptEventProcessor.processEntry()`, which matches schema rules to classify the event (`session_init`, `tool_use`, `tool_result`, `session_end`). Per-session `SessionState` holds `pendingTools` map: `tool_use` stores name+input; `tool_result` retrieves pending, pairs with response, and calls `observationHandler.execute()` — which POSTs to `/api/sessions/observations` (the same endpoint used by lifecycle-hooks). On `session_end`, processor queues summary via `/api/sessions/summarize` and refreshes Cursor context via `/api/context/inject`.
## Mermaid Flowchart
```mermaid
flowchart TD
Start["Worker Start
worker-service.ts:614"] --> Config["loadTranscriptWatchConfig
config.ts:1"]
Config --> Watcher["new TranscriptWatcher
watcher.ts:83-91"]
Watcher --> StartW["watcher.start
watcher.ts:93"]
StartW --> SetupWatch["setupWatch per target
watcher.ts:110-134"]
SetupWatch --> AddTailer["addTailer
watcher.ts:169-210"]
AddTailer --> CreateTailer["new FileTailer
watcher.ts:15-26"]
CreateTailer --> TailerStart["fs.watch filePath
watcher.ts:28"]
TailerStart --> FileChange([File change event])
FileChange --> ReadNewData["readNewData
watcher.ts:40-80"]
ReadNewData --> ParseLine["JSON.parse each line
watcher.ts:220"]
ParseLine --> HandleLine["handleLine
watcher.ts:212-236"]
HandleLine --> ProcessEntry["processor.processEntry
processor.ts:36-46"]
ProcessEntry --> MatchRule["matchesRule
processor.ts:42"]
MatchRule --> HandleEvent["handleEvent
processor.ts:113-169"]
HandleEvent -->|session_init| SI["handleSessionInit
processor.ts:138-142"]
HandleEvent -->|tool_use| TU["handleToolUse
processor.ts:193-221"]
HandleEvent -->|tool_result| TR["handleToolResult
processor.ts:224-246"]
HandleEvent -->|session_end| SE["handleSessionEnd
processor.ts:309-320"]
SI --> SIhttp["POST /api/sessions/init"]
TU --> TUmap["session.pendingTools.set
processor.ts:202"]
TR --> TRlookup["Lookup pending tool
processor.ts:232-236"]
TRlookup --> SendObs["sendObservation
processor.ts:240-244"]
SendObs --> ObsHandler["observationHandler.execute
observation.ts:31-86"]
ObsHandler --> WorkerHttp["POST /api/sessions/observations
observation.ts:77"]
WorkerHttp --> Routes["SessionRoutes.handleObservationsByClaudeId
SessionRoutes.ts:565"]
Routes --> Strip["stripMemoryTagsFromJson
SessionRoutes.ts:627-634"]
Strip --> Queue["sessionManager.queueObservation
SessionRoutes.ts:637"]
Queue --> Gen["ensureGeneratorRunning
SessionRoutes.ts:654"]
SE --> QS["queueSummary
processor.ts:322-344"]
QS --> SumHttp["POST /api/sessions/summarize"]
SE --> UpdateCtx["updateContext
processor.ts:346-392"]
UpdateCtx --> CtxHttp["GET /api/context/inject
processor.ts:377"]
CtxHttp --> WriteAgentsMd["writeAgentsMd
processor.ts:390"]
SE --> ClearState["sessions.delete
processor.ts:319"]
```
## Side Effects
- Byte-offset state persisted to `transcript-watch-state.json`.
- Rescan timer every 5s for new transcript files (watcher.ts:124).
- PendingTools map state cleared after each paired observation.
- `AGENTS.md` context file written by Cursor session_end.
- SSE broadcast via existing pipeline when observations queued.
## External Feature Dependencies
**Calls into:** observationHandler (bridge), `/api/sessions/observations` endpoint (shared with lifecycle-hooks), `/api/sessions/summarize`, `/api/context/inject`. SessionManager processes identically regardless of source.
**Called by:** Worker-service initialization only; not user-invoked.
## Duplication with lifecycle-hooks?
**YES — significant re-implementation.** Both paths ingest observations, but via different capture mechanisms:
| Aspect | lifecycle-hooks | transcript-watcher |
|---|---|---|
| Source | Cursor/Claude Code PostToolUse hook | JSONL file via fs.watch + FileTailer |
| Tool pairing | Hook receives tool_name + response atomically | pendingTools map pairs tool_use + tool_result |
| Session init | observationHandler → sessionInitHandler | processor directly calls sessionInitHandler |
| HTTP transport | observationHandler → `/api/sessions/observations` | observationHandler → `/api/sessions/observations` (same) |
| Exclusion check | observationHandler checks `isProjectExcluded` | processor may skip this check; SessionRoutes enforces privacy |
| Storage convergence | SessionRoutes queue → SessionManager → SDK agent | SessionRoutes queue → SessionManager → SDK agent (same) |
**Conclusion:** transcript-watcher is a **parallel capture path** that re-implements session-init + observation dispatch logic but converges at the same HTTP endpoint. The pendingTools state machine is unique to transcripts. This is the clearest cross-feature duplication in the codebase and a prime target for Phase 3 unification.
## Confidence + Gaps
**High:** TranscriptWatcher → FileTailer → processor → observationHandler → shared HTTP endpoint.
**Medium:** Privacy filter coverage when bypassing observationHandler's exclusion check.
**Gaps:** FileTailer retry strategy on I/O errors; schema FieldSpec coalesce/default evaluation details; updateContext timing relative to sessionCompleteHandler.