# M0.6 — Claude transcript adapter | Field | Value | |---|---| | Phase | M0 — Read-only spine | | Size | S — under 1 day | | Status | ⬜ Not started | | Flags | — | | Spec | inlined below | | Blocks | M0.5 | ## Goal The second `RecordSource`, which is the one that proves the trait is real. ## Facts (inlined — no spec read needed) ``` ~/.claude/projects/-Users-rockliang-workplace-Poimen/.jsonl └─ cwd, / replaced by -, no wrapping dashes (differs from pi) ``` 15 MB across 7 transcripts on this machine. Record types observed: ``` attachment 150 queue-operation 138 assistant 134 user 81 file-history-snapshot 69 system 69 ai-title 21 last-prompt 21 mode 21 ``` Fields, read from any line: `sessionId`, `cwd`, `gitBranch`. Typed records dispatch on `type`; the ones that matter here: - `user` / `assistant` — content under `message.content` - `system` with `subtype == "api_error"` — a failed turn, worth keeping - `summary` — carries `summary` - everything else — ignore The filename stem is a UUID. The encoding differs from pi (no wrapping `--`), which is exactly why the project key must come from the `cwd` **field**, as in M0.5, and not from the directory name. ## Steps 1. Implement `ClaudeTranscriptSource` in `mem-ingest`, implementing `RecordSource`. 2. Take `cwd` from the first line carrying it; error if no line does. 3. Emit records for `user`, `assistant`, and `system`+`api_error`. 4. Flatten `message.content` across its shapes, as in M0.5. 5. `Provenance` = `claude:` plus line offset. 6. Reuse the content-flattening and malformed-line handling from M0.5 — extract them into a shared helper rather than copying, since divergence between two flatteners is a bug that only shows up on one source. ## Acceptance - Both sources satisfy `RecordSource` with no changes to `mem-chunk`. - Project keys from pi and claude for the same directory resolve to the same `ProjectId`. - `api_error` system records survive into the stream. ## Verify **Harness:** a committed transcript fixture plus a real one, secrets scrubbed. **Integration test** — `tests/it_claude_source.rs`: 1. `a1_project_from_cwd_field` — assert the key comes from `cwd`, not the directory name, using a fixture where they would differ. 2. `a2_same_project_across_sources` — a pi session and a claude transcript for the same directory yield an equal `ProjectId`. This is the assertion that makes cross-source memory possible at all. 3. `a3_role_mapping` — user/assistant/api_error appear; `attachment`, `queue-operation`, `file-history-snapshot` do not. 4. `a4_shared_flattener` — the same content-block fixture flattens identically through both sources (call both, compare strings). **Command:** `cargo test -p mem-ingest claude_source` **False pass:** - Testing each source in isolation. The point of this task is that they agree; assertions 2 and 4 are the only ones that check it, and both are cross-source. ## Traps - Copying the flattener instead of sharing it. The two will drift, and the resulting bug looks like "claude transcripts lose tool output" rather than "there are two flatteners". - Assuming the pi encoding. Claude has no wrapping `--`, so a decoder written against pi silently produces a different project key for the same directory — and cross-source memory quietly splits in two. --- Background: [DESIGN.md](../DESIGN.md) — Context