Skip to content

Claude Code session provider #15

Description

@Saber5656

Title

Claude Code session provider

Summary

Implement the Claude Code source: discover session JSONL files under the
projects directory with an mtime prefilter, assemble per-session facts
(span, project, counts, title) with the tolerant scanner (13), and emit
agent-session Events per DESIGN.md §7.3 and the research doc.

Context

~/.claude/projects/<encoded-path>/<uuid>.jsonl is an unofficial,
version-drifting format. The research doc
(docs/research/claude-code-session-format.md) is the normative field
contract: cwd (not the lossy directory name) attributes the project;
summary lines beat first-prompt titles; sidechain lines are counted
separately; prompts are opt-in (include_prompts, ADR-004).

Scope

  • internal/source/claudecode/provider.go, session.go, tests, synthetic
    fixtures under testdata/.

Detailed Requirements

  1. New(cfg config.SourcesClaudeCode) *Provider; ID() =
    model.SourceClaudeCode.
  2. Discovery: <projects_dir>/*/*.jsonl via os.ReadDir two levels (no
    deeper recursion); skip files with mtime < rng.Start − 48h
    (files still being written have fresh mtimes; a session ending after
    rng.End still matters for spanning, so no upper-bound skip). Missing
    projects dir → claudecode_dir_missing warning, zero events, nil error.
    Symlinked project dirs are not followed.
  3. Per file, one streaming jsonlutil.ForEach pass building:
    • first, last — min/max timestamp over lines with a parseable
      timestamp that fall inside rng (per research doc: do not trust
      line order); also track any-line-in-range boolean.
    • counts (computed over in-range lines only — a midnight-spanning
      session reports per-day counts, per the research doc): user lines
      with isSidechain != trueUserMsgs; assistant non-sidechain →
      AgentMsgs; any user/assistant with isSidechain == true
      SidechainMsgs.
    • modal cwd and modal gitBranch over in-range user/assistant lines.
    • title candidates: latest summary-type line's summary string
      (regardless of range — a summary describes the whole session); first
      in-range user line's text when include_prompts (content as a plain
      string, OR the concatenation of all text-type blocks when content is
      a block array — per the research doc; then take the first line and cap
      at 120 runes).
    • version (last seen), sessionId (any line; fallback filename stem).
    • Early stop: none (need min/max over whole file) — but skip files fast
      when the first 50 parseable timestamps are all > rng.End AND mtime
      indicates no rewrite risk? NO — keep it simple and correct: full scan
      per file that passes the mtime prefilter (documented decision).
  4. Emit one Event per session with ≥ 1 in-range user/assistant line:
    Kind KindAgentSession, Start=first, End=last, Project = modal cwd
    (raw path; normalization happens in 18), Ref=sessionId,
    Title = summary → prompt (opt-in) → claude-code session <first 8 of id>,
    Meta: agent="claude-code", user_messages, assistant_messages,
    sidechain_messages, git_branch, cli_version (omit empty values).
  5. Per-file malformed handling: MalformedRatio > 0.20 && Lines ≥ 10
    claudecode_malformed_lines warning naming the file (basename only —
    full paths of other projects would leak into shared reports via the
    warnings footer; DESIGN §13 messages must use basenames). Unreadable
    file (permissions) → claudecode_file_skipped warning, continue.
  6. ctx honored between files and via ForEach.

Acceptance Criteria

  • Fixture session with summary line + user/assistant/sidechain mix →
    one Event with exact counts, summary-based Title, modal cwd Project.
  • include_prompts=false (default): no prompt text anywhere in Event
    (Title falls back when no summary); =true: first-prompt Title,
    first line only, ≤ 120 runes.
  • message.content as array-of-blocks fixture parses (text blocks
    concatenated).
  • Midnight-spanning fixture: session included for both adjacent days;
    Start/End clamp to in-range min/max per day AND the per-day message
    counts differ according to which lines fall in each day (two Collect
    calls asserted, counts hand-computed in the test).
  • Out-of-range session file (old mtime) never opened (assert via a
    fixture with unreadable permissions that would warn if opened — no
    warning appears when mtime is old).
  • Malformed fixture (30% bad lines) → warning with basename, session
    still emitted from good lines.
  • Fixture with ANSI escapes + fake ghp_… token in title text passes
    through raw here (sanitize/redact are 18's job) — asserted so nobody
    "helpfully" sanitizes in the parser and breaks layering.
  • Unknown line types (fixture includes queue-operation,
    last-prompt, an invented type) silently ignored.
  • Read-only proof: fixture tree hash identical before/after.

Validation

go test -race -cover ./internal/source/claudecode/ in PR.

Dependencies

04, 08, 13.

Non-goals

~/.claude/history.jsonl (v2 candidate), automation-noise filtering beyond
sidechain separation (known unknown U2-adjacent), project-name
normalization (18).

Design References

  • docs/research/claude-code-session-format.md (normative)
  • docs/DESIGN.md §7.3, §13
  • docs/decisions/ADR-004-redaction-default-on.md (prompts opt-in)

Source of truth: docs/issues/14-claude-code-provider.md (PR #1, branch docs/v1-design). If this issue and the repo docs disagree, the docs win. Execution order and dependencies: docs/ISSUE_PLAN.md (this is issue 14 of 33).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions