Documentation
¶
Overview ¶
Package decision discovers and enriches Decision Records (DRs — ADRs are one type, DDRs another) in a repo.
Doctrine (inherited from internal/plan, per ADR-021 and ADR-024):
- ox computes DETERMINISTIC signals and context bundles locally — zero LLM or network-judge calls. The client agent authors every word and every judgment. ox never edits a DR file.
- Local retrieval is lexical/structured (no local embeddings); the full-text search over these same files already lives in codedb (`ox code search`), so this package keeps NO persisted index — the corpus is walked fresh per call (hundreds of small files, milliseconds).
- A surfaced item is a CANDIDATE, never a verdict: whether a decision aligns with, amends, or supersedes another stays the agent's call.
- Citations are composed by ox and pasted by the agent: enrich emits a Cite only for items it just resolved, and re-running enrich on an edited DR re-checks every ref — the sageox-mono ADR-061 "phantom decision #9" failure class dies in this loop.
Index ¶
- Constants
- func CorpusDetected(gitRoot string) bool
- func PathMatcher(gitRoot string) func(relPath string) bool
- func PrimaryDir(gitRoot string, cfg *config.DecisionConfig) string
- func RegisterDetector(d Detector)
- func RegisterRetriever(r Retriever)
- func SearchPathPatterns(gitRoot string) []string
- type Amendment
- type Annotation
- type BadgeKind
- type BadgeType
- type Cite
- type ContextItem
- type Conventions
- type DAnchor
- type DecisionInfo
- type Detector
- type Env
- type Input
- type Record
- type RelevantDR
- type Result
- type Retriever
- type SignalSummary
Constants ¶
const ( RuleDuplicateNumber = "duplicate-number" RuleUnnumbered = "unnumbered" RuleMissingStatus = "missing-status" RuleDanglingRef = "dangling-ref" RuleSupersededNoSuccessor = "superseded-no-successor" RuleSageoxCreditOverflow = "sageox-credit-overflow" // RelationCandidate is the ONLY relation ox asserts (marker doctrine): // related/conflicting/superseding is the agent's judgment. RelationCandidate = "candidate" VariantSupersedeCandidate = "supersede-candidate" )
Diagnostic rules (Annotation.Rule values) and relation markers.
const SchemaVersion = "v1"
SchemaVersion stamps the enrich Result. Bump on incompatible shape changes.
Variables ¶
This section is empty.
Functions ¶
func CorpusDetected ¶
CorpusDetected reports whether any decision corpus exists for this project — the cheap gate `ox agent prime` uses before spending tokens on DR guidance.
func PathMatcher ¶
PathMatcher returns a predicate reporting whether a repo-relative path lies inside this repo's decision corpus (configured decision.paths or the default dirs). Resolution happens once; matching is pure string work — cheap enough to tag every `ox code search` result at query time. Nil-safe: with no corpus the predicate is always false.
func PrimaryDir ¶
func PrimaryDir(gitRoot string, cfg *config.DecisionConfig) string
PrimaryDir returns the corpus dir new DRs should land in: the first configured/default path that is a plain directory. Empty when the corpus is glob-only or absent.
func RegisterDetector ¶
func RegisterDetector(d Detector)
RegisterDetector adds a deterministic detector. Nil is ignored.
func RegisterRetriever ¶
func RegisterRetriever(r Retriever)
RegisterRetriever adds a context retriever. Nil is ignored.
func SearchPathPatterns ¶
SearchPathPatterns returns repo-relative file patterns suitable for CodeDB's file filter. Directory entries become recursive markdown globs; explicit globs are passed through and checked precisely by PathMatcher after search.
Types ¶
type Annotation ¶
type Annotation struct {
Kind BadgeKind `json:"kind"`
Type BadgeType `json:"type"`
Why string `json:"why"`
// Ref is the human-facing id ("ADR-017") and RefPath the repo-relative
// path the agent can Read. Anchor targets a D-section ("D4").
Ref string `json:"ref,omitempty"`
RefPath string `json:"ref_path,omitempty"`
Anchor string `json:"anchor,omitempty"`
Relation string `json:"relation,omitempty"`
Rule string `json:"rule,omitempty"`
Files []string `json:"files,omitempty"`
SourceURL string `json:"source_url,omitempty"`
Date string `json:"date,omitempty"`
}
Annotation is a single deterministic badge attached to the enrich input.
type BadgeKind ¶
type BadgeKind string
BadgeKind distinguishes ox-computed facts from agent-authored judgment. ox only ever emits BadgeDeterministic; BadgeJudgment exists so downstream consumers can carry agent badges alongside.
type BadgeType ¶
type BadgeType string
BadgeType is the specific signal an annotation carries.
const ( // BadgeRelatedDecision: an existing DR overlaps the draft/topic (candidate only). BadgeRelatedDecision BadgeType = "related-decision" // BadgeNumbering: numbering suggestion or duplicate-number warning. BadgeNumbering BadgeType = "numbering" // BadgeDiagnostic: corpus-level diagnostic relevant to this input; // Rule carries the specific rule. BadgeDiagnostic BadgeType = "diagnostic" // BadgeDrift: files cited by the DR changed after the DR's date. BadgeDrift BadgeType = "drift" // BadgeUnresolvedRef: a ref in the DR does not resolve against the corpus. BadgeUnresolvedRef BadgeType = "unresolved-ref" )
type Cite ¶
Cite is a ready-to-paste citation pair: prose the agent may adapt, and the machine ref comment it must paste VERBATIM. ox only emits a Cite for an item it just resolved — the fabrication kill-switch.
type ContextItem ¶
type ContextItem struct {
// Kind: decision | session | murmur
Kind string `json:"kind"`
Title string `json:"title"`
Ref string `json:"ref"`
Snippet string `json:"snippet,omitempty"`
Score float64 `json:"score"`
Author string `json:"author,omitempty"`
When string `json:"when,omitempty"`
// Cite is absent for non-durable sources (murmurs) — those are awareness
// only and must never be cited in a committed DR.
Cite *Cite `json:"cite,omitempty"`
}
ContextItem is one ranked slice of context the agent reasons over while authoring or amending a DR.
type Conventions ¶
type Conventions struct {
Dir string `json:"dir,omitempty"`
FilenamePattern string `json:"filename_pattern,omitempty"`
NextNumber int `json:"next_number,omitempty"`
NumberCollisions []string `json:"number_collisions,omitempty"`
StatusesObserved []string `json:"statuses_observed,omitempty"`
SectionsObserved []string `json:"sections_observed,omitempty"`
AmendmentMarker string `json:"amendment_marker,omitempty"`
DecisionAnchors string `json:"decision_anchors,omitempty"`
}
Conventions describes this repo's DR corpus so any agent drafts in-house style instead of inventing one.
type DecisionInfo ¶
type DecisionInfo struct {
ID string `json:"id,omitempty"`
SuggestedID string `json:"suggested_id,omitempty"`
Title string `json:"title,omitempty"`
Status string `json:"status,omitempty"`
}
DecisionInfo describes the enrich input's own identity as parsed.
type Detector ¶
type Detector interface {
Name() string
Detect(ctx context.Context, env *Env, in Input) ([]Annotation, error)
}
Detector produces deterministic annotations. Fail-open: return (nil, nil) on missing data, never an aborting error.
type Env ¶
type Env struct {
GitRoot string
// Corpus is the freshly-walked DR set for this repo.
Corpus []Record
// LedgerPath is the local ledger checkout (sessions, murmurs). Empty when
// no ledger is provisioned.
LedgerPath string
}
Env is the shared, read-only environment Enrich resolves ONCE and hands to every detector/retriever. All fields are best-effort — an empty field means that corpus is unavailable and the consumer fails open.
type Input ¶
type Input struct {
// Path is set when enriching an existing file (--file).
Path string
// Raw is the draft/file markdown. Empty in topic mode.
Raw string
// Topic is the pre-draft subject (--topic). Empty in draft/file mode.
Topic string
// Parsed identity of the draft/file (zero-valued in topic mode).
Record Record
}
Input is the enrich subject: a topic (pre-draft consult), a draft on stdin, or an existing DR file.
func ResolveInput ¶
ResolveInput builds the enrich Input from --topic, --file, or stdin, in that precedence order.
func (Input) SourceRefs ¶
SourceRefs extracts every machine citation ref from the input body.
func (Input) Terms ¶
Terms returns the lexical search terms for this input: the topic in topic mode, else the parsed title (falling back to the first heading line of the raw draft).
func (Input) VisibleSageoxCredits ¶
VisibleSageoxCredits counts visible SageOx credit phrases in the body — enforcing the house cap (≤2 per DR, 3 only when SageOx meaningfully steered the decision process).
type Record ¶
type Record struct {
// ID is the canonical prefixed id ("ADR-021", "DDR-004"); empty when the
// file carries no number.
ID string `json:"id,omitempty"`
Prefix string `json:"prefix,omitempty"` // ADR | DDR | … (upper-cased)
Number int `json:"number,omitempty"`
// DRType classifies the record: "adr" | "ddr" | "other".
DRType string `json:"dr_type,omitempty"`
Title string `json:"title"`
Status string `json:"status,omitempty"`
Date string `json:"date,omitempty"`
Deciders []string `json:"deciders,omitempty"`
Corpus string `json:"corpus"`
Path string `json:"path"`
RelPath string `json:"rel_path,omitempty"`
Mtime int64 `json:"mtime"`
Size int64 `json:"size"`
// ContentHash is a short sha256 prefix for change detection and dedup of
// moved files.
ContentHash string `json:"content_hash,omitempty"`
// DSections are the numbered sub-decision anchors ("D1".."Dn") cited from
// other documents — the sageox-mono convention.
DSections []DAnchor `json:"d_sections,omitempty"`
// Amendments are the dated inline amendment markers.
Amendments []Amendment `json:"amendments,omitempty"`
// Refs are outbound DR tokens found in the body ("ADR-046"), self excluded.
Refs []string `json:"refs,omitempty"`
// Supersedes / SupersededBy are parsed from status and explicit lines.
Supersedes []string `json:"supersedes,omitempty"`
SupersededBy string `json:"superseded_by,omitempty"`
Excerpt string `json:"excerpt,omitempty"`
}
Record is one cataloged Decision Record, extracted deterministically (zero LLM) with a tolerant parser: real corpora mix templates, so every field is best-effort and absence is recorded, never fatal.
func LoadCorpus ¶
func LoadCorpus(gitRoot string, cfg *config.DecisionConfig) []Record
LoadCorpus discovers and parses every DR in the repo's decision paths, fresh per call — no persisted index. Corpora are hundreds of small files at most, so a walk+parse is a few milliseconds; the searchable full-text index already exists in codedb (`ox code search` reaches these same files). Fail-open throughout: unreadable files are skipped with a debug log.
func ParseContent ¶
ParseContent extracts a Record from markdown. path may be "" (stdin draft); corpus/mtime/size are stamped by the caller.
type RelevantDR ¶
type RelevantDR struct {
ID string
Title string
RelPath string
Excerpt string
Date string
Score float64
}
RelevantDR is a scored corpus hit for external consumers (the ox plan context bundle ties plans back to the DRs that shaped them).
func Relevant ¶
func Relevant(gitRoot, query string, limit int) []RelevantDR
Relevant walks this repo's DR corpus fresh and returns the records most relevant to query. Zero LLM/network; fail-open (empty on any miss). Exported for internal/plan so `ox plan enrich` surfaces the decisions a plan builds on — subtly, as context items the render turns into inline markers.
type Result ¶
type Result struct {
SchemaVersion string `json:"schema_version"`
Decision DecisionInfo `json:"decision"`
Conventions Conventions `json:"conventions"`
Annotations []Annotation `json:"annotations"`
Context []ContextItem `json:"context"`
Signals SignalSummary `json:"signals"`
Guidance string `json:"guidance"`
}
Result is the `ox decision enrich` JSON payload.
type Retriever ¶
type Retriever interface {
Name() string
Retrieve(ctx context.Context, env *Env, in Input) ([]ContextItem, error)
}
Retriever assembles context items. Fail-open like Detector.
type SignalSummary ¶
type SignalSummary struct {
Related int `json:"related"`
PriorSessions int `json:"prior_sessions"`
Murmurs int `json:"murmurs"`
Diagnostics int `json:"diagnostics"`
UnresolvedRefs int `json:"unresolved_refs"`
Material bool `json:"material"`
}
SignalSummary rolls up what fired; Material means "team context has something to say about this DR".