decision

package
v0.12.0 Latest Latest
Warning

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

Go to latest
Published: Jul 19, 2026 License: MIT Imports: 18 Imported by: 0

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

View Source
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.

View Source
const SchemaVersion = "v1"

SchemaVersion stamps the enrich Result. Bump on incompatible shape changes.

Variables

This section is empty.

Functions

func CorpusDetected

func CorpusDetected(gitRoot string) bool

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

func PathMatcher(gitRoot string) func(relPath string) bool

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

func SearchPathPatterns(gitRoot string) []string

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 Amendment

type Amendment struct {
	Date    string `json:"date"`
	Excerpt string `json:"excerpt,omitempty"`
}

Amendment is one dated amendment marker inside a DR.

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.

const (
	BadgeDeterministic BadgeKind = "deterministic"
	BadgeJudgment      BadgeKind = "judgment"
)

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

type Cite struct {
	ProseHint string `json:"prose_hint"`
	Comment   string `json:"comment"`
}

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 DAnchor

type DAnchor struct {
	ID      string `json:"id"` // "D4"
	Heading string `json:"heading,omitempty"`
}

DAnchor is one numbered sub-decision heading inside a DR.

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

func ResolveInput(topic, file string, stdin io.Reader) (Input, error)

ResolveInput builds the enrich Input from --topic, --file, or stdin, in that precedence order.

func (Input) SourceRefs

func (in Input) SourceRefs() []string

SourceRefs extracts every machine citation ref from the input body.

func (Input) Terms

func (in Input) Terms() string

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

func (in Input) VisibleSageoxCredits() int

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

func ParseContent(path, content string) Record

ParseContent extracts a Record from markdown. path may be "" (stdin draft); corpus/mtime/size are stamped by the caller.

func (Record) IsRecord

func (r Record) IsRecord() bool

IsRecord reports whether the parse found something DR-shaped worth cataloging: a number, or a title plus DR metadata (status/date). Plain markdown with neither is skipped silently.

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.

func Enrich

func Enrich(ctx context.Context, in Input, gitRoot string) Result

Enrich runs every registered detector and retriever, FAIL-OPEN: a panic or error in any one is logged and skipped, never aborting the others. Zero LLM or network calls — everything reads local data resolved once into Env.

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".

Jump to

Keyboard shortcuts

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