Documentation
¶
Overview ¶
Package intent extracts a short summary of the user's original intent for a code change by reading recent transcripts from local coding agents (Claude Code, Codex CLI, OpenCode, Rovo Dev, Pi, and GitHub Copilot CLI) on the developer's machine.
Given the repo path, the diff filenames, and the commit time window, the package discovers candidate sessions, picks the one with the strongest file-overlap match, drops tool calls, summarizes the remaining user and assistant text via the configured agent, and returns the summary so it can be injected into pipeline step prompts.
Index ¶
Constants ¶
const ClaudeReaderName = "claude"
ClaudeReaderName is the agent name used in cache keys and DB rows.
const CodexReaderName = "codex"
CodexReaderName is the agent name used in cache keys and DB rows.
const CopilotReaderName = "copilot"
CopilotReaderName is the agent name used in cache keys and DB rows.
const OpenCodeReaderName = "opencode"
OpenCodeReaderName is the agent name used in cache keys and DB rows.
const PiReaderName = "pi"
PiReaderName is the agent name used in cache keys and DB rows.
const RovoDevReaderName = "rovodev"
RovoDevReaderName is the agent name used in cache keys and DB rows.
Variables ¶
var ErrDisambiguatorCleanup = errors.New("intent disambiguator cleanup failed")
var ErrNoMatch = errors.New("intent: no matching transcript")
ErrNoMatch indicates no agent transcript matched the change. Callers should treat this as a normal "no intent attached" outcome, not an error.
Functions ¶
func RedactSecrets ¶
RedactSecrets returns text with likely credentials replaced by [REDACTED]. Exported for use at prompt-construction boundaries outside this package (e.g. when injecting cached intent summaries into step prompts) so the same redaction shape applies on the way into the LLM and on the way out.
func StripAdversarial ¶
StripAdversarial removes obvious prompt-injection markers that could try to escape the surrounding instructions. We don't try to be clever; we just neuter common delimiter shapes (ChatML control tokens, role tags, Llama/Mistral instruction delimiters) that an attacker might place in user-controlled text. This is a stop-gap, not a real defense - the real defense is wrapping the text with explicit "this is data, not instructions" framing.
Types ¶
type Cache ¶
type Cache interface {
Get(key string) (string, bool)
Put(key, summary, agentName, sessionID string)
}
Cache abstracts the summarization cache behind a small interface so the extractor can be exercised without a real DB in tests.
func NewMemCache ¶
func NewMemCache() Cache
NewMemCache returns an in-memory Cache. Mainly for tests.
type DisambiguationChoice ¶
type Disambiguator ¶
type Disambiguator interface {
Disambiguate(ctx context.Context, diffFiles []string, candidates []*Match) (DisambiguationChoice, error)
}
Disambiguator chooses among multiple accepted transcript matches when the deterministic file-overlap matcher cannot make a decisive selection.
func NewAgentDisambiguator ¶
func NewAgentDisambiguator(a agent.Agent, cwd string) Disambiguator
NewAgentDisambiguator wraps an agent.Agent as a Disambiguator. The agent is run in cwd so it can inspect changed repository files progressively.
type DiscoverOpts ¶
type DiscoverOpts struct {
// HomeDir overrides the user's home directory. Empty means use os.UserHomeDir.
HomeDir string
// OriginCWD is the user's actual repo directory (NOT the worktree the
// pipeline runs in). Symlinks should already be resolved by the caller.
OriginCWD string
// WindowStart is the earliest LastActivity allowed (inclusive).
WindowStart time.Time
// WindowEnd is the latest StartedAt allowed (inclusive).
WindowEnd time.Time
}
DiscoverOpts narrows the search down to relevant sessions before any expensive body parsing happens.
type ExtractParams ¶
type ExtractParams struct {
// HomeDir overrides the user's home directory. Empty means use os.UserHomeDir.
HomeDir string
// OriginCWD is the user's actual repo directory. The caller is responsible
// for passing the original working path, NOT the no-slop worktree.
OriginCWD string
// DiffFiles is the repo-relative file set used for matching and scoring.
DiffFiles []string
// BaseTime is the committer time of the base SHA.
BaseTime time.Time
// HeadTime is the committer time of the head SHA.
HeadTime time.Time
// SlackDays extends WindowStart backwards. The plan called for 3 days.
SlackDays int
// Threshold is the minimum raw file-overlap score required before applying
// stricter multi-file and stale-partial acceptance rules.
Threshold float64
// Readers are the per-agent transcript readers to consult. Order is
// insignificant; matching accepts plausible candidates, prefers a single
// decisive raw-score match, and otherwise ranks by confidence or an optional
// Disambiguator.
Readers []Reader
// Cache is consulted before summarization. Pass NewMemCache() if no DB.
Cache Cache
// Summarizer turns the chosen session's text into a short summary.
Summarizer Summarizer
// Disambiguator optionally chooses among multiple plausible sessions when
// file-overlap scoring is not decisive enough to pick one safely.
Disambiguator Disambiguator
// Logf receives best-effort accepted candidate diagnostics. Nil disables logging.
Logf func(format string, args ...any)
}
ExtractParams configures a single Extract call.
type Match ¶
type Match struct {
Session *Session
Score float64
// Confidence is the score used to rank accepted candidates after applying
// recency. Score remains the raw file-overlap score surfaced to callers.
Confidence float64
// Overlap lists diff files that appeared in this session. Used purely
// for diagnostics/telemetry.
Overlap []string
}
Match is the chosen session along with its overlap score.
type Message ¶
type Message struct {
Role Role
Text string
FilePaths []string
Timestamp time.Time
// Synthetic marks a message that was inserted by no-slop itself
// (e.g. a "middle messages omitted" notice from clampMessages). The
// transcript serializer renders these without a role prefix so the
// downstream LLM does not mistake them for user or assistant turns.
Synthetic bool
}
Message is a single user or assistant turn extracted from an agent transcript. Tool calls and tool results are deliberately excluded from Text. FilePaths is a best-effort list of file paths the agent referenced via tool inputs or quoted in assistant text - used purely for matching, not for the summary.
type Reader ¶
type Reader interface {
Name() string
// Discover returns candidate sessions matching opts. Implementations must
// only read enough of each transcript to populate metadata; full message
// bodies must wait until Load is called.
Discover(ctx context.Context, opts DiscoverOpts) ([]*Session, error)
// Load populates s.Messages with user/assistant text only. Tool calls and
// tool results must be omitted from Message.Text but file paths they
// reference may be added to Message.FilePaths.
Load(ctx context.Context, s *Session) error
}
Reader is implemented by per-agent transcript readers.
func AllReaders ¶
AllReaders returns the default set of agent transcript readers, minus any the caller has disabled by name. Disabled names are matched case-insensitively against each reader's Name().
func NewClaudeReader ¶
func NewClaudeReader() Reader
NewClaudeReader returns a Reader for Claude Code transcripts.
func NewCodexReader ¶
func NewCodexReader() Reader
NewCodexReader returns a Reader for Codex CLI transcripts.
func NewCopilotReader ¶
func NewCopilotReader() Reader
NewCopilotReader returns a Reader for GitHub Copilot CLI transcripts.
func NewOpenCodeReader ¶
func NewOpenCodeReader() Reader
NewOpenCodeReader returns a Reader for OpenCode transcripts.
func NewPiReader ¶
func NewPiReader() Reader
NewPiReader returns a Reader for Pi coding-agent transcripts.
func NewRovoDevReader ¶
func NewRovoDevReader() Reader
NewRovoDevReader returns a Reader for Rovo Dev transcripts.
type Result ¶
Result is what Extract returns when it successfully attaches an intent to a run. AgentName/SessionID/Score are surfaced for telemetry and DB persistence; callers store the Summary onto db.Run.Intent.
func Extract ¶
func Extract(ctx context.Context, p ExtractParams) (*Result, error)
Extract runs the discover -> match -> optional disambiguate -> cache -> summarize pipeline and returns the final intent. It returns ErrNoMatch when no session satisfies the matcher's threshold, overlap, and freshness acceptance rules. Disambiguation failures fall back to the deterministic match, except cleanup failures are returned because worktree side effects may remain.
type Session ¶
type Session struct {
AgentName string
SessionID string
CWD string
StartedAt time.Time
LastActivity time.Time
// LastMsgKey is a per-reader stable identifier for the last message
// (uuid where available, otherwise a timestamp). Used as the cache key
// input so repeat extractions on an unchanged session hit the cache.
LastMsgKey string
// Messages holds the user/assistant turns. Populated by Reader.Load,
// not by Discover, since most candidates will be filtered out.
Messages []Message
// contains filtered or unexported fields
}
Session is one transcript candidate from one agent.
type Summarizer ¶
Summarizer turns a session's user/assistant text into a 2-6 sentence description of the user's intent.
func NewAgentSummarizer ¶
func NewAgentSummarizer(a agent.Agent, cwd string) Summarizer
NewAgentSummarizer wraps an agent.Agent as a Summarizer. cwd should be the worktree the pipeline will run in.