intentgap

package
v0.5.4 Latest Latest
Warning

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

Go to latest
Published: Jun 13, 2026 License: MIT Imports: 24 Imported by: 0

Documentation

Overview

Package intentgap mirrors the API's canonical payload_hash computation. The upload endpoint recomputes the hash server-side and rejects mismatches, so the CLI and API encoders must stay byte-compatible.

The golden fixture under testdata/payloadhash/ is copied from the API fixture, and the tests compare them when both repositories are present.

Index

Constants

View Source
const (
	// MaxBundleDiffBytes caps the cumulative diff at 96 KiB.
	MaxBundleDiffBytes = 96 * 1024
	// MaxBundleCommits caps the commit list sent to the analyzer.
	MaxBundleCommits = 100
	// MaxBundleTurns caps captured prompts, retaining the most recent entries.
	MaxBundleTurns = 200
)

Bundle size limits keep analyzer input bounded.

View Source
const (
	ProducerStateTransportOnly = "transport_only"
	ProducerStateAnalyzed      = "analyzed"
	ProducerStateErrored       = "errored"
)

Producer states accepted by the intent-gap upload contract.

View Source
const (
	AlgorithmVersionTransport = "0.1.0-transport"
	FindingSchemaVersion      = "1"
	RedactionVersion          = "1"
)

Versions encoded in uploads and canonical payload hashes.

View Source
const AlgorithmVersionAnalyzed = "0.1.0-local-llm"

AlgorithmVersionAnalyzed identifies uploads produced by local LLM analysis.

View Source
const DeviceIDFileName = "device_id"

DeviceIDFileName is the basename of the file under AppConfigDir that holds the stable per-machine random device identifier.

View Source
const PromptTemplateVersion = "0.1.0"

PromptTemplateVersion identifies the prompt used to produce findings. Bump it when prompt changes may affect model output.

Variables

View Source
var (
	// ErrAnalyzerLLMUnavailable wraps the underlying LLM-registry
	// error when no installed writer succeeded.
	ErrAnalyzerLLMUnavailable = errors.New("intentgap: no LLM CLI produced a response")
	// ErrAnalyzerParseFailed signals the LLM responded but neither
	// the original nor the retry response parsed into the expected
	// JSON shape.
	ErrAnalyzerParseFailed = errors.New("intentgap: could not parse findings JSON from LLM output")
	// ErrAnalyzerSchemaFailed signals the LLM output parsed as JSON
	// but failed schema validation.
	ErrAnalyzerSchemaFailed = errors.New("intentgap: LLM findings failed schema validation")
	// ErrAnalyzerInternal wraps unexpected analyzer-side errors
	// (cite-or-drop filter failure, coverage encode failure, etc.)
	// so the reason-code mapping has a stable sentinel to map to.
	ErrAnalyzerInternal = errors.New("intentgap: analyzer internal error")
)

Analyzer errors map to stable upload reason codes.

View Source
var (
	ErrNoOpenPR    = errors.New("intentgap: no open PR for branch")
	ErrAmbiguousPR = errors.New("intentgap: multiple open PRs for branch")
	ErrUnavailable = errors.New("intentgap: PR-context discovery server unavailable")
)

Discovery errors distinguish missing, ambiguous, and unavailable PR context.

View Source
var ErrLineageUnavailable = errors.New("intentgap: lineage data unavailable")

ErrLineageUnavailable means lineage.db exists but cannot be read. Missing capture data is represented by an empty result instead.

View Source
var ErrNoInstalledProvider = errors.New("intentgap: no LLM CLI installed")

ErrNoInstalledProvider indicates that no supported local AI CLI is available.

View Source
var ErrRedactionFailed = errors.New("intentgap: redaction failed for at least one captured turn")

ErrRedactionFailed means a captured prompt could not be safely prepared for analysis. The analysis fails closed instead of omitting the prompt.

View Source
var ErrSkipped = errors.New("intentgap: upload skipped")

ErrSkipped marks a clean skip, such as a disabled setting or a branch with no open PR.

Functions

func BuildAnalyzedBody

func BuildAnalyzedBody(in AnalyzedBodyInput, producedAt time.Time) ([]byte, string, error)

BuildAnalyzedBody builds an analyzed request and its canonical hash.

func BuildErroredBody

func BuildErroredBody(in UploadInput, reason, promptTemplateVersion string, producedAt time.Time) ([]byte, string, error)

BuildErroredBody builds an errored request with no findings.

Errored rows let the server's materializer keep showing the prior analyzed verdict on the check (it filters errored upstream) while dashboard surfaces still see the failure entry for diagnostics.

func BuildTransportOnlyBody

func BuildTransportOnlyBody(in UploadInput, producedAt time.Time) ([]byte, string, error)

BuildTransportOnlyBody builds the request body bytes plus the canonical payload hash for a transport-only upload. Pure: same input bytes always produce the same body bytes and the same hash, which is what the server's recompute relies on for idempotency.

func ComputePayloadHash

func ComputePayloadHash(in PayloadHashInput) (string, []byte, error)

ComputePayloadHash returns the lowercase-hex sha256 of the canonical payload bytes and the bytes themselves.

func IntentGapFindingSchema

func IntentGapFindingSchema() (*jsonschema.Schema, error)

IntentGapFindingSchema returns the compiled Draft 2020-12 schema for a single intent-gap finding. Compiled lazily on first use and cached for the process lifetime.

func LoadOrCreateDeviceID

func LoadOrCreateDeviceID() (string, error)

LoadOrCreateDeviceID returns a stable random identifier for this machine. The identifier is generated once on first call, persisted under the user-global Semantica config directory, and reused on every subsequent call. It is audit/context metadata only - the server intentionally excludes it from upload deduplication and from the canonical payload hash.

If the existing file is unreadable or holds an invalid UUID, a fresh one is generated and persisted in its place.

func MapWriterNameToWire

func MapWriterNameToWire(writerName string) (string, bool)

MapWriterNameToWire returns the API provider value for a writer name.

func ValidateFindings

func ValidateFindings(findings json.RawMessage) error

ValidateFindings checks each element of a findings array against the intent-gap finding schema. Empty / nil input is valid: an analyzed upload with no findings is a legitimate "no gaps found" result.

The CLI calls this before upload so schema violations surface locally with a clear field path; the server's identical validator is the second line of defense.

Types

type AmbiguousPRError

type AmbiguousPRError struct {
	Matches []OpenPR
}

AmbiguousPRError contains all open PRs matching a branch.

func (*AmbiguousPRError) Error

func (e *AmbiguousPRError) Error() string

func (*AmbiguousPRError) Is

func (e *AmbiguousPRError) Is(target error) bool

type AnalysisInput

type AnalysisInput struct {
	Bundle       Bundle
	PRNumber     int32
	RepositoryID string
}

AnalysisInput contains the repository, pull request, and local evidence used to derive findings. RepositoryID and PRNumber namespace finding IDs.

type AnalysisResult

type AnalysisResult struct {
	Findings              json.RawMessage
	CoverageSummary       json.RawMessage
	Provider              string
	Model                 string
	PromptTemplateVersion string
}

AnalysisResult contains validated findings, coverage metadata, and the provider that produced the response.

type AnalyzedBodyInput

type AnalyzedBodyInput struct {
	UploadInput
	PromptTemplateVersion string
	Findings              json.RawMessage
	CoverageSummary       json.RawMessage
}

AnalyzedBodyInput combines upload metadata with analyzer output.

type Bundle

type Bundle struct {
	RepoRoot string
	BaseRef  string
	BaseSHA  string
	HeadSHA  string
	Commits  []BundleCommit
	// Diff is the cumulative unified diff, capped at MaxBundleDiffBytes.
	Diff []byte
	// Turns contains captured user prompts linked to commits in the PR range.
	Turns     []BundleTurn
	Truncated BundleTruncation
}

Bundle contains the local changes and captured prompts analyzed for a PR.

type BundleAssembler

type BundleAssembler interface {
	Assemble(ctx context.Context, in BundleInput) (Bundle, error)
}

BundleAssembler builds analyzer input for a repository revision.

type BundleCommit

type BundleCommit struct {
	Hash    string
	Subject string
}

BundleCommit identifies one commit in merge-base..HEAD.

type BundleInput

type BundleInput struct {
	RepoRoot string
	// Base is the ref to diff against. Empty means "auto-detect".
	Base    string
	HeadSHA string
}

BundleInput identifies the repository range to assemble.

type BundleTruncation

type BundleTruncation struct {
	DiffBytesDropped int
	CommitsDropped   int
	TurnsDropped     int
}

BundleTruncation records input omitted by bundle size limits.

type BundleTurn

type BundleTurn struct {
	TurnID            string
	CommitHash        string
	TS                int64
	PromptExcerpt     string
	PromptExcerptHash string
}

BundleTurn is a captured user prompt associated with a commit. The analyzer correlates these prompts with the pull request diff to detect:

  • under_impl: a turn whose intent was not fully implemented
  • unrequested: a region whose intent does not match any captured turn
  • deferred: a turn whose intent was added then removed (trajectory)

PromptExcerpt and PromptExcerptHash are verified citation anchors.

type CiteOrDropResult

type CiteOrDropResult struct {
	Findings       json.RawMessage
	AcceptedCount  int
	DroppedCount   int
	DroppedReasons map[string]int
}

CiteOrDropResult reports accepted findings and rejection reasons.

func FilterFindingsByCitations

func FilterFindingsByCitations(findings json.RawMessage, bundle Bundle) (CiteOrDropResult, error)

FilterFindingsByCitations drops findings whose prompt or diff citations cannot be verified against the local bundle.

Prompt citations must match captured turns, and diff citations must match changed files and line ranges. Rejection counts are returned as metadata.

type CommitMetaBetween

type CommitMetaBetween struct {
	Hash    string
	Subject string
}

CommitMetaBetween describes a commit in the analyzed range.

type GitBundleAssembler

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

GitBundleAssembler combines Git history with captured prompts.

func NewGitBundleAssembler

func NewGitBundleAssembler(opener GitRepoOpener, turns TurnLoader) *GitBundleAssembler

NewGitBundleAssembler constructs a Git-backed bundle assembler.

func (*GitBundleAssembler) Assemble

func (a *GitBundleAssembler) Assemble(ctx context.Context, in BundleInput) (Bundle, error)

Assemble builds a bounded bundle for the requested revision range.

type GitRepo

type GitRepo interface {
	DefaultBaseRef(ctx context.Context) (string, error)
	MergeBase(ctx context.Context, a, b string) (string, error)
	DiffBetween(ctx context.Context, base, head string) ([]byte, error)
	CommitSummariesBetween(ctx context.Context, base, head string, limit int) ([]CommitMetaBetween, error)
	CountCommitsBetween(ctx context.Context, base, head string) (int, error)
}

GitRepo is the subset of git.Repo the assembler consumes.

type GitRepoOpener

type GitRepoOpener func(repoPath string) (GitRepo, error)

GitRepoOpener opens the Git operations required by bundle assembly.

type InstalledProvider

type InstalledProvider struct {
	Name  string
	Model string
}

InstalledProvider identifies the selected writer using API wire names.

func PickInstalledProvider

func PickInstalledProvider(reg *llm.WriterRegistry) (InstalledProvider, error)

PickInstalledProvider returns the first installed, API-supported writer.

type IntentGapAnalyzer

type IntentGapAnalyzer interface {
	Analyze(ctx context.Context, in AnalysisInput) (AnalysisResult, error)
}

IntentGapAnalyzer produces findings for one pull request bundle.

type LLMAnalyzer

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

LLMAnalyzer runs the local provider fallback chain and validates its output.

func NewLLMAnalyzer

func NewLLMAnalyzer(runner LLMRunner) *LLMAnalyzer

NewLLMAnalyzer constructs an analyzer backed by runner.

func (*LLMAnalyzer) Analyze

Analyze produces validated findings. It retries once when the first response cannot be parsed as JSON.

type LLMRunner

type LLMRunner interface {
	GenerateText(ctx context.Context, prompt string) (*llm.GenerateTextResult, error)
}

LLMRunner is the analyzer's interface to the local provider registry.

type NoopTurnLoader

type NoopTurnLoader struct{}

NoopTurnLoader returns no captured turns.

func (NoopTurnLoader) LoadTurnsForCommits

func (NoopTurnLoader) LoadTurnsForCommits(context.Context, []string) ([]BundleTurn, error)

type OpenPR

type OpenPR struct {
	PRNumber   int32  `json:"pr_number"`
	State      string `json:"state"`
	Title      string `json:"title,omitempty"`
	HeadSHA    string `json:"head_sha,omitempty"`
	HeadBranch string `json:"head_branch,omitempty"`
	BaseBranch string `json:"base_branch,omitempty"`
	IsDraft    bool   `json:"is_draft"`
}

OpenPR contains the PR fields needed for local analysis.

func LookupOpenPRByBranch

func LookupOpenPRByBranch(
	ctx context.Context,
	httpClient *http.Client,
	endpoint, token, repoID, branch string,
) (*OpenPR, error)

LookupOpenPRByBranch returns the single open PR for a short branch name. It uses http.DefaultClient when httpClient is nil and applies a 10-second timeout.

type PayloadHashInput

type PayloadHashInput struct {
	RepositoryID          string
	PRNumber              int32
	HeadSHA               string
	BaseSHA               string
	AlgorithmVersion      string
	PromptTemplateVersion string
	FindingSchemaVersion  string
	RedactionVersion      string
	Provider              string
	Model                 string
	ProducerState         string
	// CoverageSummary and Findings carry raw JSON. nil or empty input
	// is treated as {} / [] so callers that omit the fields hash the
	// same as ones that send empty literals.
	CoverageSummary json.RawMessage
	Findings        json.RawMessage
}

PayloadHashInput contains the fields included in the canonical upload hash.

type ReasonCode

type ReasonCode string

ReasonCode is a sanitized failure label suitable for upload. Detailed errors remain in the local activity log.

const (
	ReasonBundleFailed       ReasonCode = "bundle_failed"
	ReasonLineageUnavailable ReasonCode = "lineage_unavailable"
	ReasonRedactionFailed    ReasonCode = "redaction_failed"
	ReasonLLMUnavailable     ReasonCode = "llm_unavailable"
	ReasonParseFailed        ReasonCode = "parse_failed"
	ReasonSchemaFailed       ReasonCode = "schema_failed"
	ReasonAnalyzerInternal   ReasonCode = "analyzer_internal"
)

func ReasonCodeFor

func ReasonCodeFor(err error) ReasonCode

ReasonCodeFor maps an error to a stable upload label.

type SkipReason

type SkipReason struct {
	Reason string
}

SkipReason wraps a skip cause with the ErrSkipped sentinel so errors.Is(err, ErrSkipped) holds for any skip outcome.

func (*SkipReason) Error

func (e *SkipReason) Error() string

func (*SkipReason) Is

func (e *SkipReason) Is(target error) bool

type TurnLoader

type TurnLoader interface {
	LoadTurnsForCommits(ctx context.Context, commitHashes []string) ([]BundleTurn, error)
}

TurnLoader loads captured turns for a chronological list of commits.

type UploadInput

type UploadInput struct {
	RepositoryID     string
	PRNumber         int32
	HeadSHA          string
	BaseSHA          string
	Provider         string
	Model            string
	ProducerDeviceID string
}

UploadInput contains repository and producer metadata for an upload.

type UploadResponse

type UploadResponse struct {
	UploadID   string `json:"upload_id"`
	ReceivedAt string `json:"received_at"`
}

UploadResponse is the API's success body for both 201 (fresh) and 200 (idempotent duplicate).

type UploadResult

type UploadResult struct {
	StatusCode int
	UploadID   string
	ReceivedAt string
}

UploadResult contains the accepted upload identity and HTTP status.

func PostUpload

func PostUpload(
	ctx context.Context,
	httpClient *http.Client,
	endpoint, token string,
	in UploadInput,
	body []byte,
	idempotencyKey string,
) (*UploadResult, error)

PostUpload sends a prepared body. Both fresh and duplicate responses are successful outcomes.

Jump to

Keyboard shortcuts

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