Help us shape the future of Go! Take the 2026 Go Developer Survey

episode

package
v0.4.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 16, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package episode parses a Claude Code session transcript (JSONL) into a structured Episode and renders it as a markdown "episode" file for the identity vault. It is the v0 of the episodic substrate — raw per-session capture, no distillation, not indexed into the vault's search layer.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Capture

func Capture(transcriptPath, outputDir string) (string, error)

Capture parses the transcript at transcriptPath, renders the episode markdown, and writes it to outputDir. Returns the written file path. Overwrites an existing file with the same derived ID — re-running against a finalized transcript is idempotent by design. Uses atomic temp-file + rename so concurrent SessionEnd hook invocations cannot produce torn files.

func CaptureIncremental added in v0.3.0

func CaptureIncremental(transcriptPath, outputDir, cursorDir string) (string, error)

CaptureIncremental captures only the transcript segment written since the last CaptureIncremental call for this session (tracked via a cursor file in cursorDir), writing it as a new, uniquely-named episode segment file in outputDir. Returns "" with a nil error when there is nothing new to capture — a no-op SessionEnd, or a delta containing only filtered noise, must never write an empty episode file.

This is the fix for a long-lived session that never closes: Capture re-renders the WHOLE transcript into the SAME file every SessionEnd, which silently grows one episode without bound. CaptureIncremental instead accumulates several small, bounded segment files, each covering only what was genuinely new since the last capture.

func CountLines added in v0.3.0

func CountLines(path string) (int, error)

CountLines returns the total number of lines in path, without parsing any of them — used to detect a shrunken transcript cheaply, without paying for a full JSON-unmarshaling re-scan on the (common) path where nothing has changed. See CaptureIncremental's shrinkage check.

func ReadCursor added in v0.3.0

func ReadCursor(cursorDir, key string) (int, error)

ReadCursor returns how many transcript lines have already been captured under key — the line CaptureIncremental should resume from. key is an opaque cursor-scoping identifier (CaptureIncremental combines the session id with the target vault so routing to a different --output-dir can't silently consult — or truncate — the wrong vault's progress). Absent (never captured before) returns 0, not an error. A present but corrupt cursor file errors instead of silently resetting to zero: a silent reset would re-render the whole transcript again, recreating the exact ever-growing-blob failure this mechanism exists to prevent.

func RenderMarkdown

func RenderMarkdown(ep *Episode) string

RenderMarkdown turns a parsed Episode into a vault-ready markdown file. Schema is intentionally simple: frontmatter + section-per-signal-type. No distillation, no prose — the next layer (arc distillation) will consume this structure.

func WriteCursor added in v0.3.0

func WriteCursor(cursorDir, key string, line int) error

WriteCursor persists how many transcript lines have been captured under key (see ReadCursor's doc for what key means), creating cursorDir if needed. Uses atomic temp-file + rename so a SessionEnd hook killed mid-write cannot leave a torn cursor that would silently mis-resume the next capture.

Types

type CaptureBatch added in v0.1.6

type CaptureBatch struct {
	Captured   []string          // episode file paths written, in transcript-path order
	Skipped    map[string]string // transcript path -> reason (empty/malformed/write error)
	Collisions map[string]string // transcript path -> the episode id it lost, and to whom
	Sidechains int               // subagent/workflow transcripts passed over by design
}

CaptureBatch summarizes a directory capture: the episode files written and the transcripts that were skipped (with the reason). Skipping is deliberate — a noise or partial transcript in a large history must not abort the whole batch.

Sidechains is counted separately from Skipped because it is not a fault: subagent transcripts are routine, they outnumber real sessions by an order of magnitude (1,759 to 141 across four measured project histories), and listing them as problems would bury the handful that are. Collisions is separate from Skipped because it is the one outcome where the user may actually have lost something: two transcripts existed and only one is representable. That calls for naming the pair, where a malformed file only calls for a count.

func CaptureDir added in v0.1.6

func CaptureDir(dir, outputDir string) (CaptureBatch, error)

CaptureDir captures every *.jsonl transcript found recursively under dir into outputDir. It is the bootstrap entry point: point it at a project's Claude Code transcript directory (e.g. ~/.claude/projects/<slug>) to seed an identity vault from sessions that already exist. Malformed/empty transcripts go into Skipped rather than failing the run; capture itself is idempotent.

Subagent and workflow transcripts are passed over (counted in Sidechains). They are records of tools running, not of the collaboration, and because they carry the parent session's id they derive the same episode filename as the real session — capturing them overwrites it. Pointing `episode capture` at a single sidechain file still captures it: an explicit path is a choice, a directory sweep is not.

Two transcripts can still derive one episode id (same date, same session id prefix). The first wins and the rest are reported as collisions, so Captured always equals the files on disk — a count the user can check with `ls`.

type Episode

type Episode struct {
	ID                string
	SessionID         string
	StartedAt         string
	EndedAt           string
	CWD               string
	GitBranch         string
	UserMessages      []Message
	AssistantMessages []Message
	ToolCounts        map[string]int
	Commits           []string
	PRs               []PRLink
	FilesTouched      []string
}

Episode is the structured output of parsing a session transcript.

func ParseTranscript

func ParseTranscript(path string) (*Episode, error)

ParseTranscript reads a Claude Code JSONL transcript and returns the distilled Episode. Noise records (system reminders, tool results, thinking blocks) are filtered — only real human/assistant exchanges, tool uses, and structural events are kept.

func ParseTranscriptFrom added in v0.3.0

func ParseTranscriptFrom(path string, startLine int) (*Episode, int, error)

ParseTranscriptFrom parses only the transcript lines strictly after startLine (0 = parse from the beginning) and returns the resulting delta Episode plus the new total line count, for a caller (CaptureIncremental) to persist as the next call's startLine.

This is what makes cursor-based incremental capture possible: a long-lived session's SessionEnd hook can re-parse only the tail written since the last capture instead of re-rendering the whole transcript every time, which is what produces one ever-growing episode file for a session that never closes. A startLine at or beyond the current end of file is not an error — it returns an empty delta at the same line count, the correct "nothing new since last time" result for a SessionEnd fired with no new records.

type Message

type Message struct {
	Timestamp string
	Text      string
}

Message is one verbatim text exchange captured from a session transcript.

type PRLink struct {
	Number     int
	URL        string
	Repository string
	Timestamp  string
}

PRLink is a pull request announced by a pr-link record.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL