@@ -10,7 +10,16 @@ import (
1010// Agent defines the interface for interacting with a coding agent.
1111// Each agent implementation (Claude Code, Cursor, Aider, etc.) converts its
1212// native format to the normalized types defined in this package.
13+ //
14+ // The interface is organized into four groups:
15+ //
16+ // - Identity (5 methods): Name, Type, Description, DetectPresence, ProtectedDirs
17+ // - Event Mapping (2 methods): HookNames, ParseHookEvent
18+ // - Transcript Storage (3 methods): ReadTranscript, ChunkTranscript, ReassembleTranscript
19+ // - Legacy (8 methods): Will be moved to optional interfaces or removed in a future phase
1320type Agent interface {
21+ // --- Identity ---
22+
1423 // Name returns the agent registry key (e.g., "claude-code", "gemini")
1524 Name () AgentName
1625
@@ -24,46 +33,64 @@ type Agent interface {
2433 // DetectPresence checks if this agent is configured in the repository
2534 DetectPresence () (bool , error )
2635
27- // GetHookConfigPath returns path to hook config file (empty if none)
36+ // ProtectedDirs returns repo-root-relative directories that should never be
37+ // modified or deleted during rewind or other destructive operations.
38+ // Examples: [".claude"] for Claude, [".gemini"] for Gemini.
39+ ProtectedDirs () []string
40+
41+ // --- Event Mapping ---
42+
43+ // HookNames returns the hook verbs this agent supports.
44+ // These become subcommands under `entire hooks <agent>`.
45+ // e.g., ["stop", "user-prompt-submit", "session-start", "session-end"]
46+ HookNames () []string
47+
48+ // ParseHookEvent translates an agent-native hook into a normalized lifecycle Event.
49+ // Returns nil if the hook has no lifecycle significance (e.g., pass-through hooks).
50+ // This is the core contribution surface for new agent implementations.
51+ ParseHookEvent (hookName string , stdin io.Reader ) (* Event , error )
52+
53+ // --- Transcript Storage ---
54+
55+ // ReadTranscript reads the raw transcript bytes for a session.
56+ ReadTranscript (sessionRef string ) ([]byte , error )
57+
58+ // ChunkTranscript splits a transcript into chunks if it exceeds maxSize.
59+ // Returns a slice of chunks. If the transcript fits in one chunk, returns single-element slice.
60+ // The chunking is format-aware: JSONL splits at line boundaries, JSON splits message arrays.
61+ ChunkTranscript (content []byte , maxSize int ) ([][]byte , error )
62+
63+ // ReassembleTranscript combines chunks back into a single transcript.
64+ // Handles format-specific reassembly (JSONL concatenation, JSON message merging).
65+ ReassembleTranscript (chunks [][]byte ) ([]byte , error )
66+
67+ // --- Legacy methods (will move to optional interfaces in Phase 4) ---
68+
69+ // GetHookConfigPath returns path to hook config file (empty if none).
2870 GetHookConfigPath () string
2971
30- // SupportsHooks returns true if agent supports lifecycle hooks
72+ // SupportsHooks returns true if agent supports lifecycle hooks.
3173 SupportsHooks () bool
3274
33- // ParseHookInput parses hook callback input from stdin
75+ // ParseHookInput parses hook callback input from stdin.
3476 ParseHookInput (hookType HookType , reader io.Reader ) (* HookInput , error )
3577
36- // GetSessionID extracts session ID from hook input
78+ // GetSessionID extracts session ID from hook input.
3779 GetSessionID (input * HookInput ) string
3880
39- // ProtectedDirs returns repo-root-relative directories that should never be
40- // modified or deleted during rewind or other destructive operations.
41- // Examples: [".claude"] for Claude, [".gemini"] for Gemini.
42- ProtectedDirs () []string
43-
4481 // GetSessionDir returns where agent stores session data for this repo.
45- // Examples:
46- // Claude: ~/.claude/projects/<sanitized-repo-path>/
47- // Aider: current working directory (returns repoPath)
48- // Cursor: ~/Library/Application Support/Cursor/User/globalStorage/
4982 GetSessionDir (repoPath string ) (string , error )
5083
51- // ResolveSessionFile returns the path to the session transcript file for a given
52- // agent session ID. Agents use different naming conventions:
53- // Claude: <sessionDir>/<id>.jsonl
54- // Gemini: <sessionDir>/session-<date>-<shortid>.json (searches for existing file)
55- // If no existing file is found, returns a sensible default path.
84+ // ResolveSessionFile returns the path to the session transcript file.
5685 ResolveSessionFile (sessionDir , agentSessionID string ) string
5786
5887 // ReadSession reads session data from agent's storage.
59- // Handles different formats: JSONL (Claude), SQLite (Cursor), Markdown (Aider)
6088 ReadSession (input * HookInput ) (* AgentSession , error )
6189
6290 // WriteSession writes session data for resumption.
63- // Agent handles format conversion (JSONL, SQLite, etc.)
6491 WriteSession (session * AgentSession ) error
6592
66- // FormatResumeCommand returns command to resume a session
93+ // FormatResumeCommand returns command to resume a session.
6794 FormatResumeCommand (sessionID string ) string
6895}
6996
@@ -90,18 +117,12 @@ type HookSupport interface {
90117}
91118
92119// HookHandler is implemented by agents that define their own hook vocabulary.
93- // Each agent defines its own hook names (verbs) which become subcommands
94- // under `entire hooks <agent>`. The actual handling is done by handlers
95- // registered in the CLI package to avoid circular dependencies.
96- //
97- // This allows different agents to have completely different hook vocabularies
98- // (e.g., Claude Code has "stop", Cursor might have "completion").
120+ // HookNames() is now part of the core Agent interface.
121+ // This interface is kept for backward compatibility during migration.
99122type HookHandler interface {
100123 Agent
101124
102125 // GetHookNames returns the hook verbs this agent supports.
103- // These are the subcommand names that will appear under `entire hooks <agent>`.
104- // e.g., ["stop", "user-prompt-submit", "pre-task", "post-task", "post-todo"]
105126 GetHookNames () []string
106127}
107128
@@ -118,16 +139,17 @@ type FileWatcher interface {
118139 OnFileChange (path string ) (* SessionChange , error )
119140}
120141
121- // TranscriptAnalyzer is implemented by agents that support transcript analysis.
122- // This allows agent-agnostic detection of work done between checkpoints.
142+ // TranscriptAnalyzer provides format-specific transcript parsing.
143+ // Agents that implement this get richer checkpoints (transcript-derived file lists,
144+ // prompts, summaries). Agents that don't still participate in the checkpoint lifecycle
145+ // via git-status-based file detection and raw transcript storage.
123146type TranscriptAnalyzer interface {
124147 Agent
125148
126149 // GetTranscriptPosition returns the current position (length) of a transcript.
127150 // For JSONL formats (Claude Code), this is the line count.
128151 // For JSON formats (Gemini CLI), this is the message count.
129152 // Returns 0 if the file doesn't exist or is empty.
130- // Use this to efficiently check if the transcript has grown since last checkpoint.
131153 GetTranscriptPosition (path string ) (int , error )
132154
133155 // ExtractModifiedFilesFromOffset extracts files modified since a given offset.
@@ -138,20 +160,46 @@ type TranscriptAnalyzer interface {
138160 // - currentPosition: the current position (line count or message count)
139161 // - error: any error encountered during reading
140162 ExtractModifiedFilesFromOffset (path string , startOffset int ) (files []string , currentPosition int , err error )
163+
164+ // ExtractPrompts extracts user prompts from the transcript starting at the given offset.
165+ ExtractPrompts (sessionRef string , fromOffset int ) ([]string , error )
166+
167+ // ExtractSummary extracts a summary of the session from the transcript.
168+ ExtractSummary (sessionRef string ) (string , error )
141169}
142170
143- // TranscriptChunker is implemented by agents that support transcript chunking.
144- // This allows agents to split large transcripts into chunks for storage (GitHub has
145- // a 100MB blob limit) and reassemble them when reading .
146- type TranscriptChunker interface {
171+ // TranscriptPreparer is called before ReadTranscript to handle agent-specific
172+ // flush/sync requirements (e.g., Claude Code's async transcript writing).
173+ // The framework calls PrepareTranscript before ReadTranscript if implemented .
174+ type TranscriptPreparer interface {
147175 Agent
148176
149- // ChunkTranscript splits a transcript into chunks if it exceeds maxSize .
150- // Returns a slice of chunks. If the transcript fits in one chunk, returns single-element slice .
151- // The chunking is format-aware: JSONL splits at line boundaries, JSON splits message arrays.
152- ChunkTranscript ( content [] byte , maxSize int ) ([][] byte , error )
177+ // PrepareTranscript ensures the transcript is ready to read .
178+ // For Claude Code, this waits for the async transcript flush to complete .
179+ PrepareTranscript ( sessionRef string ) error
180+ }
153181
154- // ReassembleTranscript combines chunks back into a single transcript.
155- // Handles format-specific reassembly (JSONL concatenation, JSON message merging).
156- ReassembleTranscript (chunks [][]byte ) ([]byte , error )
182+ // TokenCalculator provides token usage calculation for a session.
183+ // The framework calls this during step save and checkpoint if implemented.
184+ type TokenCalculator interface {
185+ Agent
186+
187+ // CalculateTokenUsage computes token usage from the transcript starting at the given offset.
188+ CalculateTokenUsage (sessionRef string , fromOffset int ) (* TokenUsage , error )
189+ }
190+
191+ // SubagentAwareExtractor provides methods for extracting files and tokens including subagents.
192+ // Agents that support spawning subagents (like Claude Code's Task tool) should implement this
193+ // to ensure subagent contributions are included in checkpoints.
194+ type SubagentAwareExtractor interface {
195+ Agent
196+
197+ // ExtractAllModifiedFiles extracts files modified by both the main agent and any spawned subagents.
198+ // The subagentsDir parameter specifies where subagent transcripts are stored.
199+ // Returns a deduplicated list of all modified file paths.
200+ ExtractAllModifiedFiles (sessionRef string , fromOffset int , subagentsDir string ) ([]string , error )
201+
202+ // CalculateTotalTokenUsage computes token usage including all spawned subagents.
203+ // The subagentsDir parameter specifies where subagent transcripts are stored.
204+ CalculateTotalTokenUsage (sessionRef string , fromOffset int , subagentsDir string ) (* TokenUsage , error )
157205}
0 commit comments