digest

package
v0.20.2 Latest Latest
Warning

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

Go to latest
Published: Jul 17, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Index

Constants

View Source
const (
	TagMilestone = "MILESTONE"
	TagFinding   = "FINDING"
	TagActivity  = "ACTIVITY"
)

Variables

This section is empty.

Functions

func Classify added in v0.6.0

func Classify(event map[string]any) string

Classify returns a tag (MILESTONE, FINDING, or ACTIVITY) for a parsed event. The event map is what DigestLine has already unmarshaled.

Rules (in priority order):

  1. MILESTONE: session.end, permission.request, and tool.call / tool.call_update where kind=="commit" or kind=="task" with status=="completed", or where the title/command contains "git commit".

  2. FINDING: agent.message_chunk, agent.thought_chunk, user.message_chunk where the content text contains a deliberate finding signal (confidence ≥60, explicit [finding] prefix, or multi-word phrase: "reviewer flagged", "correction needed", "failed test").

  3. ACTIVITY: everything else.

func DigestLine

func DigestLine(raw []byte) (line string, event map[string]any, err error)

DigestLine parses a single raw JSON line and returns the formatted plain-text line, the parsed event map, and any error.

When the JSON has no "event" field the line should be skipped: the returned string is empty and event is nil (no error). Callers that only need the string can ignore the map; callers that also need classification (e.g. streamLine) can pass the returned map directly to Classify without re-parsing the bytes.

func ExtractLoopMarker added in v0.9.0

func ExtractLoopMarker(text string) (directive, label string, ok bool)

func ExtractStatusAngle added in v0.19.0

func ExtractStatusAngle(text string) (phase, label string, ok bool)

ExtractStatusAngle scans text for a full-line <|status: phase|> or <|status: phase | label|> angle-token. Returns the normalised phase, trimmed label, and ok=true when a known phase is found. Unlike the legacy bracket syntax, angle tokens must occupy their own line.

func ExtractStatusMarker added in v0.7.2

func ExtractStatusMarker(text string) (phase, label string, ok bool)

ExtractStatusMarker scans text for the first [status: <phase>] or [status: <phase> | <label>] marker (inline, legacy). Returns the normalised phase, trimmed label, and ok=true when a known phase is found. Unknown phase words are ignored (ok=false) to avoid spurious events from unrelated bracket text.

Known phases: thinking, working, waiting, done.

func Stream

func Stream(in io.Reader, out io.Writer, opts Options) error

Types

type Accumulator added in v0.13.1

type Accumulator struct {
	// contains filtered or unexported fields
}

Accumulator buffers consecutive chunk events (agent.message_chunk, agent.thought_chunk, user.message_chunk) with the same session ID and event type, then flushes them as a single human-readable line when the event type or session changes.

func NewAccumulator added in v0.13.1

func NewAccumulator() *Accumulator

func (*Accumulator) FlushRemaining added in v0.13.1

func (a *Accumulator) FlushRemaining(classify bool) []string

FlushRemaining returns formatted lines for any buffered chunks that haven't been flushed yet. Call this at EOF to avoid losing trailing accumulated text.

func (*Accumulator) Process added in v0.13.1

func (a *Accumulator) Process(raw []byte, classify bool) ([]string, error)

Process ingests a raw event line. It returns zero or more formatted lines that the caller should write to output. For chunk events that match the current buffer key, nothing is returned (the text is accumulated). When a chunk event triggers a flush (different key), the flushed lines are returned but the current event's text is buffered for next time. For non-chunk events, all accumulated buffers are flushed plus the current event is formatted.

Empty lines and events with no "event" field are silently ignored.

type ChunkBuffer added in v0.11.0

type ChunkBuffer struct {
	// contains filtered or unexported fields
}

ChunkBuffer accumulates text from streaming chunks and provides a sliding window for marker extraction. It prevents unbounded growth and deduplicates status markers so that the same status is not re-emitted on every subsequent chunk.

func NewChunkBuffer added in v0.11.0

func NewChunkBuffer() *ChunkBuffer

NewChunkBuffer creates a buffer with the default maximum size.

func NewChunkBufferLen added in v0.11.0

func NewChunkBufferLen(maxLen int) *ChunkBuffer

NewChunkBufferLen creates a buffer with a custom maximum size.

func (*ChunkBuffer) Append added in v0.11.0

func (cb *ChunkBuffer) Append(text string)

Append adds text to the buffer and trims the oldest bytes when the total exceeds maxLen.

func (*ChunkBuffer) ScanLoopMarker added in v0.11.0

func (cb *ChunkBuffer) ScanLoopMarker() (directive, label string, ok bool)

ScanLoopMarker runs ExtractLoopMarker over the accumulated buffer. Note: unlike ScanStatusMarker, this does NOT deduplicate. In the WaitForSession loop the severity guard (abort > exit > continue) prevents incorrect behaviour even if a loop marker is re-found while still in the buffer.

func (*ChunkBuffer) ScanStatusAngle added in v0.19.0

func (cb *ChunkBuffer) ScanStatusAngle() (phase, label string, ok bool)

ScanStatusAngle runs ExtractStatusAngle over the accumulated buffer. It returns ok=false for a status angle token that was already returned by a previous call, preventing duplicate emissions.

func (*ChunkBuffer) ScanStatusMarker added in v0.11.0

func (cb *ChunkBuffer) ScanStatusMarker() (phase, label string, ok bool)

ScanStatusMarker runs ExtractStatusMarker over the accumulated buffer. It returns ok=false for a status marker that was already returned by a previous call, preventing duplicate emissions when the same text remains in the buffer across multiple chunks.

func (*ChunkBuffer) Text added in v0.11.0

func (cb *ChunkBuffer) Text() string

Text returns the accumulated buffer contents. Used in unit tests for assertions.

type Options

type Options struct {
	Follow       bool
	PollInterval time.Duration
	Format       string
	// Classify, when true, prefixes each plain-format line with a tag:
	// MILESTONE, FINDING, or ACTIVITY. For JSON format, a top-level
	// "classify" field is injected into each emitted object.
	Classify bool
	// Accumulate, when true in plain format, buffers consecutive chunk
	// events (thought/message chunks) with the same session ID and event
	// type, flushing them as a single human-readable line when the event
	// type or session changes. Ignored in json format.
	Accumulate bool

	// CursorPath, when non-empty, causes Stream to atomically rewrite the
	// cursor file after processing. The caller is responsible for seeking the
	// underlying reader to the offset recorded in the cursor file before
	// calling Stream; CursorStartOffset must be set to that same offset so
	// Stream can compute the absolute file position when writing the cursor.
	//
	// Design note: we keep Stream's signature as io.Reader (rather than
	// *os.File) so tests can pass bytes.Buffer or strings.Reader without
	// touching the filesystem. The caller in watch.go handles the actual
	// os.File seek; we track bytes consumed here via a counting wrapper.
	CursorPath        string
	CursorStartOffset int64
}

Jump to

Keyboard shortcuts

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