| 6 | ) |
| 7 | |
| 8 | func TestScanJSONL(t *testing.T) { |
| 9 | // One assistant turn with usage, a flow-show-task bootstrap (resume), |
| 10 | // a flow show reference, a flow transcript cross-task, a kb read, |
| 11 | // an own-task brief read (skipped), and a sibling read (cross-task). |
| 12 | lines := []string{ |
| 13 | `{"type":"assistant","timestamp":"2026-06-01T10:00:00.000Z","message":{"role":"assistant","usage":{"input_tokens":10,"output_tokens":5,"cache_creation_input_tokens":2,"cache_read_input_tokens":100},"content":[{"type":"text","text":"hi"}]}}`, |
| 14 | `{"type":"assistant","timestamp":"2026-06-01T10:00:01.000Z","message":{"role":"assistant","content":[{"type":"tool_use","name":"Bash","input":{"command":"flow show task"}}]}}`, |
| 15 | `{"type":"assistant","timestamp":"2026-06-01T10:00:02.000Z","message":{"role":"assistant","content":[{"type":"tool_use","name":"Bash","input":{"command":"flow show project flow"}}]}}`, |
| 16 | `{"type":"assistant","timestamp":"2026-06-01T10:00:03.000Z","message":{"role":"assistant","content":[{"type":"tool_use","name":"Bash","input":{"command":"flow transcript sibling"}}]}}`, |
| 17 | `{"type":"assistant","timestamp":"2026-06-01T10:00:04.000Z","message":{"role":"assistant","content":[{"type":"tool_use","name":"Read","input":{"file_path":"/Users/x/.flow/kb/org.md"}}]}}`, |
| 18 | `{"type":"assistant","timestamp":"2026-06-01T10:00:05.000Z","message":{"role":"assistant","content":[{"type":"tool_use","name":"Read","input":{"file_path":"/Users/x/.flow/tasks/mine/brief.md"}}]}}`, |
| 19 | `{"type":"assistant","timestamp":"2026-06-01T10:00:06.000Z","message":{"role":"assistant","content":[{"type":"tool_use","name":"Read","input":{"file_path":"/Users/x/.flow/tasks/other/brief.md"}}]}}`, |
| 20 | `garbage-line-should-skip`, |
| 21 | } |
| 22 | roll, err := ScanJSONL(strings.NewReader(strings.Join(lines, "\n")), "mine") |
| 23 | if err != nil { |
| 24 | t.Fatalf("ScanJSONL: %v", err) |
| 25 | } |
| 26 | if roll.Usage.Total() != 117 { |
| 27 | t.Errorf("Usage.Total = %d, want 117", roll.Usage.Total()) |
| 28 | } |
| 29 | counts := map[LookupKind]int{} |
| 30 | for _, l := range roll.Lookups { |
| 31 | counts[l.Kind]++ |
| 32 | } |
| 33 | if counts[LookupResume] != 1 || counts[LookupReference] != 1 || |
| 34 | counts[LookupCrossTask] != 2 || counts[LookupKB] != 1 { |
| 35 | t.Errorf("lookup counts = %v, want resume1 reference1 cross_task2 kb1", counts) |
| 36 | } |
| 37 | if roll.First.IsZero() || roll.Last.Before(roll.First) { |
| 38 | t.Errorf("First/Last not set correctly: %v..%v", roll.First, roll.Last) |
| 39 | } |
| 40 | } |