Documentation
¶
Index ¶
- Constants
- func BuildSynthesisUserMessage(findings []state.DeepFinding, crossCutting []string, projectContext string, ...) string
- func EstimateSynthesisChars(findingCount int) int
- func Export(result *Result, synthesis *SynthesisResult, path string) error
- func ExportJSON(result *Result, synthesis *SynthesisResult, path string) error
- func ExportMarkdown(result *Result, synthesis *SynthesisResult, path string) error
- func IsBinary(content []byte) bool
- func IsTransientUntracked(path string) bool
- func LoadExcludeFile(path string) ([]string, error)
- func LoadSnapshot(repoRoot string) ([]state.DeepFinding, error)
- func MergeBoundaryAOIs(results []security.AOIScanResult, boundaryAOIs []security.AreaOfInterest) []security.AOIScanResult
- func NeedsHierarchical(findingCount int) bool
- func OrderFilesByRecency(repoRoot string, files []string, lookback int) ([]string, error)
- func RenderCategoryChart(findings []state.DeepFinding) string
- func RenderSeverityBar(findings []state.DeepFinding) string
- func RestrictToRecent(repoRoot string, files []string, lookback int) ([]string, error)
- func RunPlain(ctx context.Context, reviewClient ai.Client, aoiClient ai.Client, opts Options, ...) (*Result, *SynthesisResult, error)
- func RunWithUI(ctx context.Context, reviewClient ai.Client, aoiClient ai.Client, opts Options, ...) (*Result, *SynthesisResult, error)
- func SaveSnapshot(repoRoot string, findings []state.DeepFinding) error
- func SetHierarchicalSynthConcurrency(n int)
- func ShouldExcludeFromAudit(path string) bool
- func ShouldExcludeFromAuditWithCustom(path string, customPatterns []string) bool
- func ShouldForceInclude(path string, includePatterns []string) bool
- func SynthesizeBoundaryAOIs(inventory []state.Boundary) []security.AreaOfInterest
- type BoundaryDiscoveryResult
- type CollectStats
- type CompareResult
- type ConcurrencyConfig
- type CostEstimate
- type ModelPricing
- type OnProgress
- type Options
- type PhaseUsage
- type ReportJSON
- type Result
- type SiblingClusterResult
- type SynthesisResult
- func ParseSynthesisResult(raw string) (*SynthesisResult, error)
- func Synthesize(ctx context.Context, client ai.Client, findings []state.DeepFinding, ...) (*SynthesisResult, error)
- func SynthesizeCached(ctx context.Context, client ai.Client, findings []state.DeepFinding, ...) (*SynthesisResult, error)
Constants ¶
const MaxAuditFileBytes = 512 * 1024
MaxAuditFileBytes is the per-file size cap for audit ingestion. Files larger than this are skipped with a warning rather than loaded into memory and shipped to the classifier/AOI scanner — a single 10MB JSON config can balloon prompt sizes and offers no real audit value.
512KB comfortably accommodates hand-written code (the largest .go files in stdlib are ~300KB) while still catching generated artifacts and accidentally-committed data dumps. Audit.Options.MaxFileBytes overrides this at the call site.
Variables ¶
This section is empty.
Functions ¶
func BuildSynthesisUserMessage ¶
func BuildSynthesisUserMessage(findings []state.DeepFinding, crossCutting []string, projectContext string, failedAOICount int) string
BuildSynthesisUserMessage formats findings and context into the user message sent to the LLM for synthesis.
failedAOICount > 0 appends a "## Audit Recall Gap" section asking the model to mention the gap in the executive summary. Synthesis otherwise can produce a confident-sounding summary on top of degraded inputs with no way for the reader to know.
func EstimateSynthesisChars ¶
EstimateSynthesisChars returns the expected total output character count for synthesis given the finding count. Used to drive a truthful streaming progress bar.
The estimate is bounded:
- 3000 chars minimum (clean audit: executive_summary + 1-2 risks)
- 10000 chars maximum (lots of findings hit the prompt's per-item word caps, so output stops growing past a point)
100 chars per finding is the slope: each finding contributes ~1 short risk/pattern/recommendation entry on average (≤100 words ≈ 400 chars per entry, but only a fraction of findings generate one).
The estimate intentionally errs HIGH so the streaming bar fills slower than reality — better to see a bar that holds at 95% for a moment than to claim 100% before the response actually ends.
func Export ¶
func Export(result *Result, synthesis *SynthesisResult, path string) error
Export auto-detects format from file extension (.json or .md/.markdown). synthesis may be nil; the report is still written without the executive summary section in that case.
func ExportJSON ¶
func ExportJSON(result *Result, synthesis *SynthesisResult, path string) error
ExportJSON writes the audit result as structured JSON to the given path. synthesis may be nil when synthesis was disabled or failed.
func ExportMarkdown ¶
func ExportMarkdown(result *Result, synthesis *SynthesisResult, path string) error
ExportMarkdown writes a styled markdown report to the given path. synthesis may be nil when synthesis was disabled or failed.
func IsBinary ¶
IsBinary returns true if content appears to be a binary file. The heuristic: if any of the first binarySniffLimit bytes is NUL (0x00), treat as binary. Valid text encodings (UTF-8, ASCII, ISO-8859-*) never produce 0x00 in real content; binaries almost always have NUL bytes in headers, length-prefixed sections, or padding.
Known false positives: UTF-16/UTF-32 source files (vanishingly rare for code). Users can force-include via --include if needed.
func IsTransientUntracked ¶
IsTransientUntracked reports whether path looks like a locally generated artifact that should probably be in .gitignore. Only meaningful for files that came from `git ls-files --others`.
func LoadExcludeFile ¶
LoadExcludeFile reads a glob-per-line exclude file (like .prr/audit-exclude). Blank lines and lines starting with # are skipped.
func LoadSnapshot ¶
func LoadSnapshot(repoRoot string) ([]state.DeepFinding, error)
LoadSnapshot loads the previous audit's findings snapshot. Returns nil slice (not error) if no previous snapshot exists.
func MergeBoundaryAOIs ¶ added in v1.6.0
func MergeBoundaryAOIs(results []security.AOIScanResult, boundaryAOIs []security.AreaOfInterest) []security.AOIScanResult
MergeBoundaryAOIs inserts synthetic boundary-coverage AOIs into the per-file AOI scan results. AOIs are appended to the matching file's existing AreasOfInterest list (creating a new AOIScanResult when the file isn't in `results` already).
Returns the merged result list. Idempotency: if an AOI with the same ID already exists in the file, the synthetic AOI is dropped rather than duplicated. This keeps the merge safe to re-run on already-cached results.
func NeedsHierarchical ¶
NeedsHierarchical reports whether the given finding count exceeds the hierarchical synthesis threshold.
func OrderFilesByRecency ¶ added in v1.6.0
OrderFilesByRecency reorders files so paths touched in the last `lookback` commits come first (most-recent first), with the rest preserved in stable order behind them. Paths not touched recently are not dropped. When lookback ≤ 0, lookback falls back to recentLookbackDefault.
Best-effort: a `git log` failure is non-fatal — the original order is returned and the caller logs the warning.
func RenderCategoryChart ¶
func RenderCategoryChart(findings []state.DeepFinding) string
RenderCategoryChart returns a horizontal bar chart of findings by category.
func RenderSeverityBar ¶
func RenderSeverityBar(findings []state.DeepFinding) string
RenderSeverityBar returns a single proportional bar showing severity distribution.
func RestrictToRecent ¶ added in v1.6.0
RestrictToRecent returns the subset of files that were touched in the last `lookback` commits, in most-recent-first order. Files not touched in the window are dropped entirely. When lookback ≤ 0, returns files unchanged (no restriction).
Best-effort: a `git log` failure is non-fatal — returns the input list unchanged.
func RunPlain ¶
func RunPlain( ctx context.Context, reviewClient ai.Client, aoiClient ai.Client, opts Options, reviewModel, aoiModel string, noSynthesis bool, ) (*Result, *SynthesisResult, error)
RunPlain executes the audit without the Bubble Tea UI — prints progress to stderr. Used in --debug mode so debug output isn't clobbered by terminal animations.
func RunWithUI ¶
func RunWithUI( ctx context.Context, reviewClient ai.Client, aoiClient ai.Client, opts Options, reviewModel, aoiModel string, noSynthesis bool, ) (*Result, *SynthesisResult, error)
RunWithUI executes the audit with the shared progress TUI. Returns the audit result and synthesis after completion.
func SaveSnapshot ¶
func SaveSnapshot(repoRoot string, findings []state.DeepFinding) error
SaveSnapshot persists current findings as the "last audit" baseline. Stored in .git/pr-tui/audit-snapshot.json
func SetHierarchicalSynthConcurrency ¶
func SetHierarchicalSynthConcurrency(n int)
SetHierarchicalSynthConcurrency sets the max number of per-category synthesis calls run in parallel. Values <= 0 reset to the default. Not safe to call concurrently with synthesis in flight.
func ShouldExcludeFromAudit ¶
ShouldExcludeFromAudit returns true if the given file path should be excluded from audit mode review. Checks both the standard review exclusions and the audit-specific exclusions.
func ShouldExcludeFromAuditWithCustom ¶
ShouldExcludeFromAuditWithCustom checks against standard, audit, and user-provided custom exclusion patterns.
func ShouldForceInclude ¶
ShouldForceInclude returns true if the path matches any --include pattern. Force-included files override exclusion filters.
func SynthesizeBoundaryAOIs ¶ added in v1.6.0
func SynthesizeBoundaryAOIs(inventory []state.Boundary) []security.AreaOfInterest
SynthesizeBoundaryAOIs turns a boundary inventory into a slice of AreaOfInterest entries that ensure every boundary is reviewed for the standard defense classes. The AOIs are deterministic (no LLM) — for each boundary we emit one AOI per defense question appropriate to the kind.
The returned slice groups AOIs by File so the caller can merge them into per-file AOIScanResult records without re-sorting.
Types ¶
type BoundaryDiscoveryResult ¶ added in v1.6.0
type BoundaryDiscoveryResult struct {
// Boundaries is the parsed boundary list. Nil when loaded from
// cache (caller already holds the cached value).
Boundaries []state.Boundary
// InputHash is a SHA-256 hash of the inputs used to generate the
// inventory. Used for cache invalidation.
InputHash string
// FromCache indicates the cached hash matched; Boundaries is
// nil in that case.
FromCache bool
}
BoundaryDiscoveryResult holds the output of one Phase 1.5 pass.
func DiscoverBoundaries ¶ added in v1.6.0
func DiscoverBoundaries( ctx context.Context, client ai.Client, files map[string]string, runtimeModel *state.RuntimeModel, cachedHash string, onProgress func(string), ) (*BoundaryDiscoveryResult, error)
DiscoverBoundaries runs Phase 1.5: one LLM call that takes file header excerpts + the runtime model (when available) and emits a structured list of externally-reachable surfaces.
The runtime model is optional — passing nil falls back to "look at the file headers and classify what you see." Quality is better when the runtime model is present because its entry-point classes anchor the classification.
On LLM/parse failure returns an error; the caller treats Phase 1.5 as best-effort and proceeds without the inventory.
type CollectStats ¶
type CollectStats struct {
// TotalListed is the count of paths returned by `git ls-files`
// (tracked + untracked-non-ignored) before any filter ran.
TotalListed int
// Included is the count of paths that survived all filters.
// Equal to len(files) returned by CollectFiles.
Included int
// Tracked / Untracked break Included down by git origin.
Tracked int
Untracked int
// Exclusion counts, ordered by the precedence applied in
// partitionFiles. Each path increments exactly one of these (or
// makes it into Included).
ExcludedReview int // standard review exclusions (lock/vendor/generated/etc.)
ExcludedAudit int // audit-specific exclusions (docs/build/IDE/etc.)
ExcludedCustom int // user-provided --exclude patterns
// ForceIncluded counts paths that matched --include and bypassed
// exclusion. Subset of Included.
ForceIncluded int
// UntrackedTransients lists untracked files that survived
// filtering AND look like locally generated artifacts (logs,
// debug dumps, state snapshots). The caller surfaces a warning
// suggesting .gitignore updates — we do NOT auto-exclude because
// a legitimately-named file could match these patterns.
UntrackedTransients []string
}
CollectStats summarizes what CollectFiles saw and dropped. Exposed so callers can surface drop reasons in --debug and warn the user about untracked files that look like local tooling output.
func CollectFiles ¶
func CollectFiles(repoRoot string, excludePatterns, includePatterns []string) ([]string, CollectStats, error)
CollectFiles returns the list of files to audit after applying all filters, plus a CollectStats breakdown of what was dropped and why. Runs two `git ls-files` queries so we can distinguish tracked from untracked origin (needed for the transient-file warning).
type CompareResult ¶
type CompareResult struct {
New []state.DeepFinding // findings in current but not previous
Resolved []state.DeepFinding // findings in previous but not current
Persistent []state.DeepFinding // findings in both (may have changed severity etc)
}
CompareResult holds the diff between two audit runs.
func CompareFindings ¶
func CompareFindings(current, previous []state.DeepFinding) CompareResult
CompareFindings compares current findings against a previous set. Matching is done by (File, Category, Subcategory, Title) tuple — line numbers may shift between runs.
func (CompareResult) FormatComparison ¶
func (c CompareResult) FormatComparison() string
FormatComparison returns a human-readable summary like: "3 new findings, 2 resolved, 10 persistent"
type ConcurrencyConfig ¶
type ConcurrencyConfig struct {
// Classify caps Phase 1b classification calls.
Classify int
// AOIScan caps Phase 2 AOI batch calls.
AOIScan int
// DeepReview caps Phase 3 review calls.
DeepReview int
// Recheck caps Phase 3b recheck batch calls.
Recheck int
// HierarchicalSynth caps Phase 4 per-category synthesis calls.
HierarchicalSynth int
}
ConcurrencyConfig holds per-phase concurrency caps. Each field is the maximum number of in-flight LLM calls for that phase. Zero = use the package default.
type CostEstimate ¶
type CostEstimate struct {
TotalCalls int
IndividualCalls int
GroupedCalls int
EstInputTokens int // rough estimate of total input tokens
EstOutputTokens int // rough estimate of total output tokens
EstCostUSD float64
SkippedCalls int // calls that would be skipped by --max-reviews
}
CostEstimate holds pre-execution cost projections for Phase 3.
func EstimateCost ¶
func EstimateCost(routing *review.RouteResult, maxReviews int, pricing ModelPricing) CostEstimate
EstimateCost projects Phase 3 costs from routing results.
func (CostEstimate) FormatEstimate ¶
func (e CostEstimate) FormatEstimate() string
FormatEstimate returns a human-readable cost summary.
type ModelPricing ¶
ModelPricing holds per-1M-token costs.
func DefaultPricing ¶
func DefaultPricing(modelName string) ModelPricing
DefaultPricing returns pricing for a model, looking up from the known models registry.
type OnProgress ¶
OnProgress is called with status updates during the audit.
type Options ¶
type Options struct {
// RepoRoot is the absolute path to the repository root.
RepoRoot string
// Focus is a list of dimension slugs to focus on. Empty = all.
Focus []string
// ExcludePatterns are additional globs to exclude (from --exclude and .prr/audit-exclude).
ExcludePatterns []string
// IncludePatterns are globs to force-include (from --include).
IncludePatterns []string
// MaxReviews caps the number of Phase 3 review calls. 0 = no limit.
MaxReviews int
// NoCache disables incremental caching — re-audit everything.
NoCache bool
// AOIContextLines is the number of context lines for AOI generation.
AOIContextLines int
// Debug enables verbose output of all LLM prompts and responses.
Debug bool
// DebugFile restricts the audit to a single file (path relative to repo root).
DebugFile string
// AuditRecent restricts the audit to files touched in the last
// N commits (and only those — cold files are dropped entirely).
// Zero = no restriction; the audit covers every file that passes
// filtering. Either way, files touched recently are sorted to
// the front so --max-reviews truncates AOIs in cold files first.
AuditRecent int
// SiblingClustering enables Phase 2.5: after Phase 2 AOI
// generation, run an LLM call (or per-category calls when the
// AOI set is large) that groups AOIs by shape and surfaces
// outliers — handlers/call-sites whose pattern disagrees with
// the majority of their siblings. Each outlier becomes an
// individual-urgency AOI for Phase 3.
//
// Gated behind a flag for the first release so precision can be
// measured on real audits before making it default.
SiblingClustering bool
// LargeFileThreshold is the file count above which a warning is
// displayed. Defaults to 200 when zero.
LargeFileThreshold int
// MaxFileBytes caps per-file size during Phase 1 ingestion. Files
// exceeding the cap are skipped with a warning (not loaded into
// prompts). Zero falls back to MaxAuditFileBytes.
MaxFileBytes int
// Concurrency tunes per-phase concurrency caps. Each zero field falls
// back to the package default (currently 5).
Concurrency ConcurrencyConfig
}
Options configures an audit run.
type PhaseUsage ¶
type PhaseUsage struct {
AOI ai.TokenUsage // Phase 2: AOI pre-scan
Review ai.TokenUsage // Phase 3: deep review
Recheck ai.TokenUsage // Phase 3b: recheck/dedup
Synth ai.TokenUsage // Phase 4: synthesis
}
PhaseUsage holds per-phase token usage for cost reporting.
func (PhaseUsage) Total ¶
func (u PhaseUsage) Total() ai.TokenUsage
Total returns aggregate token usage across all phases.
type ReportJSON ¶
type ReportJSON struct {
FilesScanned int `json:"files_scanned"`
AOIsGenerated int `json:"aois_generated"`
ReviewCalls int `json:"review_calls"`
IndividualReviews int `json:"individual_reviews"`
GroupedReviews int `json:"grouped_reviews"`
FailedReviews int `json:"failed_reviews,omitempty"`
Findings []state.DeepFinding `json:"findings"`
Dismissals int `json:"dismissals"`
RecheckDismissals []state.DismissedRecord `json:"recheck_dismissals,omitempty"`
CrossCuttingObservations []string `json:"cross_cutting_observations,omitempty"`
SkippedSubcategories []string `json:"skipped_subcategories,omitempty"`
Synthesis *SynthesisResult `json:"synthesis,omitempty"`
}
ReportJSON is the JSON-serializable form of an audit result.
type Result ¶
type Result struct {
// FilesScanned is the number of files that passed Phase 1 filtering.
FilesScanned int
// AOIsGenerated is the total number of AOIs from Phase 2.
AOIsGenerated int
// ReviewCalls is the number of Phase 3 LLM calls made.
ReviewCalls int
// IndividualReviews is the count of individual review calls.
IndividualReviews int
// GroupedReviews is the count of grouped review calls.
GroupedReviews int
// FailedReviews is the count of Phase 3 review calls that errored and
// produced no findings. The user should know about these — silently
// dropping them means the audit's recall is lower than it appears.
FailedReviews int
// FailedAOIIDs is the list of AOI IDs whose Phase 3 review call
// failed (and therefore produced no finding or dismissal). Carries
// the per-call FailedAOIIDs from review.ExecuteResult so synthesis
// can mention recall gaps in the executive summary and the reporter
// can name exactly which areas of the codebase didn't get reviewed.
FailedAOIIDs []string
// Findings is all confirmed findings from Phase 3.
Findings []state.DeepFinding
// Dismissals is all dismissed AOIs from Phase 3.
Dismissals int
// RecheckDismissals is the Phase 3b dismissal log — every finding
// the recheck pass removed, with the rationale the model gave.
// Separate from Dismissals (which counts Phase 3 AOI-level
// dismissals before any finding was emitted); these are findings
// that survived Phase 3 and were then dropped by recheck.
RecheckDismissals []state.DismissedRecord
// CrossCuttingObservations from grouped reviews.
CrossCuttingObservations []string
// SkippedSubcategories lists subcategories skipped due to --max-reviews.
SkippedSubcategories []string
// Routing is the Phase 3 routing result (for summary display).
Routing *review.RouteResult
// ProjectContext is the Phase 0 project briefing. Captured here so the
// caller can plumb it into Phase 4 synthesis (otherwise the work done in
// Phase 0 would not reach the executive summary).
ProjectContext string
// TokenUsage tracks actual token consumption per phase.
Usage PhaseUsage
}
Result holds the output of an audit run.
type SiblingClusterResult ¶ added in v1.6.0
type SiblingClusterResult struct {
// Outliers is the list of synthesized outlier AOIs (urgency
// "individual", SiblingDeviation populated) ready to merge into
// the per-file scan results.
Outliers []security.AreaOfInterest
// InputHash is a SHA-256 hash of the AOI inputs + runtime model.
InputHash string
// FromCache indicates the cached hash matched; Outliers is nil
// in that case (caller uses the cached value it already holds).
FromCache bool
}
SiblingClusterResult holds the output of one Phase 2.5 pass.
func DiscoverSiblingOutliers ¶ added in v1.6.0
func DiscoverSiblingOutliers( ctx context.Context, client ai.Client, aois []security.AreaOfInterest, cachedHash string, onProgress func(string), ) (*SiblingClusterResult, error)
DiscoverSiblingOutliers runs Phase 2.5: it groups AOIs by shape via LLM clustering, identifies deviants whose pattern disagrees with their siblings, and synthesizes outlier AOIs (urgency individual, SiblingDeviation populated) for Phase 3.
Above siblingClusterGlobalThreshold total AOIs the work splits per category and runs in parallel — one LLM call per category — so no single prompt has to hold the full AOI set. Below the threshold a single global call gives the LLM cross-category visibility.
On LLM/parse failure for any single call this function logs and keeps going with whatever results other calls produced — sibling clustering is a quality enhancement, not a load-bearing input. Returns nil Outliers when no clusters meet the threshold.
type SynthesisResult ¶
type SynthesisResult struct {
// ExecutiveSummary is a 2-3 paragraph overview of audit findings.
ExecutiveSummary string `json:"executive_summary"`
// TopRisks are the most critical issues identified, ranked.
TopRisks []string `json:"top_risks"`
// SystemicPatterns are recurring issues across the codebase.
SystemicPatterns []string `json:"systemic_patterns"`
// Recommendations are prioritized action items.
Recommendations []string `json:"recommendations"`
// RawOutput is the full LLM response.
RawOutput string `json:"-"`
}
SynthesisResult holds the output of Phase 4 synthesis.
func ParseSynthesisResult ¶
func ParseSynthesisResult(raw string) (*SynthesisResult, error)
ParseSynthesisResult extracts a SynthesisResult from the LLM's raw response. Uses ai.ExtractJSON so trailing prose, embedded fences, and multi-round agent output don't break parsing.
Returns errSynthesisParse-wrapped errors on parse-shape failures so callers (and the retry wrapper) can distinguish "model output was malformed" from "transport error" — the former won't be fixed by retrying the same prompt.
func Synthesize ¶
func Synthesize( ctx context.Context, client ai.Client, findings []state.DeepFinding, crossCutting []string, projectContext string, failedAOICount int, onToken func(string), ) (*SynthesisResult, error)
Synthesize runs Phase 4: takes all findings and produces an executive summary. onToken is called for streaming output (can be nil).
failedAOICount is the number of AOIs whose Phase 3 review failed (so they have no finding/dismissal verdict). When > 0, synthesis is told to mention the recall gap in the executive summary — otherwise the user reads a confident summary on top of degraded inputs and has no way to know.
func SynthesizeCached ¶
func SynthesizeCached( ctx context.Context, client ai.Client, findings []state.DeepFinding, crossCutting []string, projectContext string, failedAOICount int, onToken func(string), noCache bool, ) (*SynthesisResult, error)
SynthesizeCached wraps Synthesize with persistent cache lookup against the audit state. Cache misses run synthesis and persist the result. Cache hits are returned without an LLM call. Pass noCache=true to bypass.
The audit state is loaded and (if updated) saved by this function — callers don't need to manage it.