Documentation
¶
Overview ¶
Package scope orchestrates the selection-contract workflow of `kb topic contract` (spec §5.1): drafting a contract with the generation model, importing one from the topic CLAUDE.md, the self-check of kept lines against out-of-scope lines, the impact preview (a shadow gate run of the draft over the topic's own sources) and activation after an explicit confirmation.
internal/contract stays free of the decision stack (it only parses, validates, hashes and writes); everything that needs a session, the gate judgment or the review ledgers lives here.
Index ¶
- Constants
- Variables
- func CleanDraft(c *contract.Contract) (*contract.Contract, []string)
- func Folder(topicRel string) string
- func FolderCountsLine(folders []FolderCount) string
- func ImportClaude(topicRoot string) (*contract.Contract, error)
- func IsScreeningFile(name string) bool
- func ScopeLine(claudeMarkdown string) string
- func Stratify(docs []*corpus.Document, n int) []*corpus.Document
- type AcceptOptions
- type AcceptReport
- type BandCounts
- type Conflict
- type Conflicts
- type DecisionSummary
- type DraftInputs
- type DraftOptions
- type FolderBands
- type FolderCount
- type LabelScore
- type Pair
- type PreviewOptions
- type PreviewReport
- type ReasonCount
- type SampleSource
- type ScreeningSummary
- type TopDocument
Constants ¶
const ( // DefaultSourceSample is the number of source titles given to the draft // (spec §5.1: a sample of 60, stratified by raw/ subfolder). DefaultSourceSample = 60 // DefaultScreeningReasons caps the distinct reasons listed per decision of // a curation or screening file. DefaultScreeningReasons = 8 // GenerationKind is the receipts kind of the draft request. GenerationKind = "contract_draft" )
Draft input limits.
const ( DecisionInclude = "include" DecisionExclude = "exclude" DecisionUnknown = "unknown" )
Screening file decision names used when a row carries no explicit one.
const ( // DefaultSampleSize is the source count above which the preview judges a // stratified sample. DefaultSampleSize = 500 // DefaultTopN is the length of the highest-P(off_topic) list. DefaultTopN = 20 )
Preview defaults (spec §5.1).
const ( BandKept = "kept" BandReview = "review" BandQuarantine = "quarantine" BandUndecided = "undecided" )
Preview bands of one document.
const ConflictThreshold = "contract_conflict"
ConflictThreshold names the self-check threshold (default 0.7).
const CostPerDocumentUSD = 0.0003
CostPerDocumentUSD is the measured decision cost of one gate judgment (spec §5.1: about US$ 0.0003 per document), used for estimates only.
const RootFolder = "raw"
RootFolder names sources stored directly under raw/ (no subfolder).
Variables ¶
var ( // ErrNoDraft is returned by Accept when topic.yaml has no contract_draft. ErrNoDraft = errors.New("scope: topic.yaml has no contract_draft") // ErrConflicts is returned by Accept when the self-check found conflicts // and Force is off. ErrConflicts = errors.New("scope: the self-check found conflicting contract lines") // ErrNotConfirmed is returned by Accept when the activation was not // confirmed by typing the topic slug (and Yes is off). ErrNotConfirmed = errors.New("scope: contract not activated: confirmation did not match the topic slug") // ErrDraftChanged is returned by Accept when contract_draft changed while // the preview ran. ErrDraftChanged = errors.New("scope: contract_draft changed during the preview; re-run --accept") )
Acceptance errors.
var DraftRules = []string{
"Describe what this topic COLLECTED ON PURPOSE, as shown by the folders, source titles and screening decisions below, not a narrower ideal of what the topic should be about.",
"`out_of_scope` lists only material that clearly serves none of the topic's purposes, and each item is qualified so it cannot match a kept line (write \"equity studies with no microstructure, execution or forecasting method\", not \"equity studies\").",
"When you are unsure whether a subject belongs, put it in `adjacent` with the reason it is kept, never in `out_of_scope`.",
"No dates, market claims or narrative: each line says what a document must discuss.",
}
DraftRules are the four drafting rules of spec §5.1, stated in every draft prompt.
var ErrNoClaudeContract = errors.New("scope: the CLAUDE.md `## Selection contract` section has no contract text")
ErrNoClaudeContract is returned by ImportClaude when CLAUDE.md has a `## Selection contract` section without any contract text.
Functions ¶
func CleanDraft ¶
CleanDraft normalizes a generated contract and removes what code can reject with certainty: duplicate lines inside a list (case-insensitive), path globs that do not compile, and out_of_scope lines equal to a core or adjacent line. It returns the cleaned contract and a note per removal.
func Folder ¶
Folder returns the `raw/<subfolder>` stratum of a topic-relative source path, or RootFolder for a file directly under raw/.
func FolderCountsLine ¶
func FolderCountsLine(folders []FolderCount) string
FolderCountsLine renders folder counts as one summary line: "sources per raw/ folder: raw/articles 12, raw/youtube 3".
func ImportClaude ¶
ImportClaude parses the `## Selection contract` section of <topicRoot>/CLAUDE.md (spec §5.1 import) and saves it under topic.yaml `contract_draft:`. It never activates the contract; the returned draft may still fail contract.Validate, which --accept enforces.
func IsScreeningFile ¶
IsScreeningFile reports whether a file under outputs/ is a curation or screening file (spec §12.2): `*screening*.jsonl`, `curation-decisions.json` or `exclusions.jsonl`.
func ScopeLine ¶
ScopeLine returns the text of the `Topic scope:` (or `Scope:`/`Escopo:`) line of a CLAUDE.md, or "" when missing or still the template placeholder.
func Stratify ¶
Stratify returns a deterministic sample of at most n documents, stratified by raw/ subfolder: every folder gets a quota proportional to its size (at least one per folder while n allows it, largest remainders first, ties by folder name) and each quota is filled with evenly spaced documents in path order. The result is sorted by path. When len(docs) ≤ n (or n ≤ 0) every document is returned.
Types ¶
type AcceptOptions ¶
type AcceptOptions struct {
// Force accepts despite self-check conflicts.
Force bool
// Yes activates without the interactive confirmation (scripts).
Yes bool
// Confirm asks the user to type the topic slug; it returns the typed
// text. Nil without Yes means the draft is never activated.
Confirm func(prompt string) (string, error)
// Preview configures the impact preview.
Preview PreviewOptions
// OnSelfCheck and OnPreview, when set, receive each step's outcome as
// soon as it is known (before the confirmation prompt).
OnSelfCheck func(Conflicts)
OnPreview func(PreviewReport)
}
AcceptOptions configures Accept.
type AcceptReport ¶
type AcceptReport struct {
Draft *contract.Contract `json:"draft"`
SelfCheck Conflicts `json:"self_check"`
// Forced reports conflicts accepted through Force.
Forced bool `json:"forced,omitempty"`
// Preview is nil when the self-check blocked.
Preview *PreviewReport `json:"preview,omitempty"`
Activated bool `json:"activated"`
}
AcceptReport is the outcome of Accept.
func Accept ¶
func Accept(ctx context.Context, s *session.Session, opts AcceptOptions) (AcceptReport, error)
Accept runs the acceptance of spec §5.1: the self-check (conflicts block unless Force), then the impact preview of the draft, then activation only after the topic slug is typed through Confirm (or Yes). Activation moves contract_draft to contract in topic.yaml and re-renders the CLAUDE.md `## Selection contract` section. The report is returned alongside every error with what was done.
type BandCounts ¶
type BandCounts struct {
Kept int `json:"kept"`
Review int `json:"review"`
Quarantine int `json:"quarantine"`
Undecided int `json:"undecided"`
}
BandCounts counts documents per preview band.
type Conflicts ¶
type Conflicts struct {
Pairs int `json:"pairs"`
Threshold float64 `json:"threshold"`
Conflicts []Conflict `json:"conflicts"`
// Undecided lists pairs without a usable answer ("conflict_<n>:<reason>").
Undecided []string `json:"undecided,omitempty"`
}
Conflicts is the self-check outcome.
func SelfCheck ¶
func SelfCheck(ctx context.Context, s *session.Session, draft *contract.Contract) (Conflicts, error)
SelfCheck asks, in one request (the engine splits it over several when there are more than 48 pairs), whether a document that fits each kept line also fits each out_of_scope line: bank `contract_check`, questions `conflict_<n>`, state `{pairs: [{n, kept, excluded}]}`. Pairs at P ≥ contract_conflict (0.7) are conflicts. The draft's hash keys the receipts.
type DecisionSummary ¶
type DecisionSummary struct {
Decision string `json:"decision"`
Count int `json:"count"`
Reasons []ReasonCount `json:"reasons,omitempty"`
Examples []string `json:"examples,omitempty"`
}
DecisionSummary summarizes the rows of a screening file with one decision.
type DraftInputs ¶
type DraftInputs struct {
Title string `json:"title"`
Domain string `json:"domain"`
// ScopeText is the `Topic scope` line of CLAUDE.md, when set.
ScopeText string `json:"scope_text,omitempty"`
// ExistingContract is the parsed `## Selection contract` section of
// CLAUDE.md, when it carries text.
ExistingContract *contract.Contract `json:"existing_contract,omitempty"`
Articles []string `json:"articles"`
// Sources is the total number of sources; SourceSample holds the
// stratified sample of their titles.
Sources int `json:"sources"`
SourceSample []SampleSource `json:"source_sample"`
Folders []FolderCount `json:"folders"`
Screening []ScreeningSummary `json:"screening,omitempty"`
// Dropped lists what code validation removed from the generated draft
// (duplicate lines, invalid globs, out_of_scope lines equal to a kept
// line).
Dropped []string `json:"dropped,omitempty"`
}
DraftInputs are the facts the contract draft is asked from (spec §5.1).
func CollectDraftInputs ¶
func CollectDraftInputs(topicRoot, title, domain string, c *corpus.Corpus, opts DraftOptions) (DraftInputs, error)
CollectDraftInputs gathers the draft inputs of a topic: title and domain, the CLAUDE.md scope line and contract section, article titles, a stratified sample of source titles, source counts per raw/ subfolder and the summaries of the topic's curation and screening files.
func Draft ¶
func Draft(ctx context.Context, s *session.Session, opts DraftOptions) (*contract.Contract, DraftInputs, error)
Draft asks the generation model for a selection contract draft from the topic's own collection (spec §5.1), validates it by code, saves it under topic.yaml `contract_draft:` and returns it with the inputs it was asked from. The active contract is never touched.
func (DraftInputs) Prompt ¶
func (in DraftInputs) Prompt() string
Prompt renders the draft prompt: the task, the four drafting rules, the field guide and every input.
type DraftOptions ¶
type DraftOptions struct {
// SourceSample is the number of source titles in the prompt (default 60).
SourceSample int
// ScreeningReasons caps the distinct reasons per screening decision
// (default 8).
ScreeningReasons int
// Exclude are topic.yaml `decisions.exclude` globs: matching files
// (CLAUDE.md, screening files; the corpus already drops documents) never
// enter the draft prompt (spec §14). Draft sets it from the session.
Exclude []string
}
DraftOptions configures Draft.
type FolderBands ¶
type FolderBands struct {
Folder string `json:"folder"`
Judged int `json:"judged"`
BandCounts
}
FolderBands are the band counts of one raw/ subfolder.
type FolderCount ¶
FolderCount is the number of sources under one raw/ subfolder.
func FolderCounts ¶
func FolderCounts(sources []*corpus.Document) []FolderCount
FolderCounts counts sources per raw/ subfolder, largest first (ties by name).
func SourceFolderCounts ¶
func SourceFolderCounts(topicRoot string) ([]FolderCount, error)
SourceFolderCounts counts the sources on disk per raw/ subfolder (quarantined and codebase snapshot files excluded, decisions.exclude ignored: this is a file count, not a decision input). Quarantine and restore print it so the agent can update prose counts in CLAUDE.md or a dashboard (spec §7.1 "Not handled").
type LabelScore ¶
type LabelScore struct {
// Labels counts the labeled subjects that were judged with a decided
// role; Negatives counts the negative ones among them.
Labels int `json:"labels"`
Negatives int `json:"negatives"`
// Predicted counts labeled subjects at P(off_topic) ≥ relevance_quarantine.
Predicted int `json:"predicted"`
TruePositive int `json:"true_positive"`
Precision float64 `json:"precision"`
Recall float64 `json:"recall"`
}
LabelScore is the precision and recall of the would-be relevance quarantine against the topic's relevance labels (negative label = should be quarantined).
type Pair ¶
type Pair struct {
// N numbers the pair from 1; the question id is `conflict_<N>`.
N int `json:"n"`
// KeptField is "core" or "adjacent".
KeptField string `json:"kept_field"`
Kept string `json:"kept"`
Excluded string `json:"excluded"`
}
Pair is one (kept line, out_of_scope line) pair of the self-check.
func BuildPairs ¶
BuildPairs returns one pair per (core or adjacent line, out_of_scope line) of the contract, numbered from 1 in contract order (core first).
type PreviewOptions ¶
type PreviewOptions struct {
// SampleSize caps the judged sources (default 500); larger topics are
// sampled with Stratify.
SampleSize int
// TopN is the length of the highest-P(off_topic) list (default 20).
TopN int
// OnEstimate, when set, is called with the planned judgments and their
// estimated cost before any call is made.
OnEstimate func(documents int, estimateUSD float64)
}
PreviewOptions configures Preview.
type PreviewReport ¶
type PreviewReport struct {
// Contract is the hash of the previewed draft.
Contract string `json:"contract"`
// Sources is the topic's source count; Judged the documents judged.
Sources int `json:"sources"`
Judged int `json:"judged"`
Sampled bool `json:"sampled"`
// RelevanceOff reports topic.yaml decisions.relevance: off (quality only).
RelevanceOff bool `json:"relevance_off,omitempty"`
Thresholds map[string]float64 `json:"thresholds"`
Bands BandCounts `json:"bands"`
Folders []FolderBands `json:"folders"`
Top []TopDocument `json:"top"`
Labels *LabelScore `json:"labels,omitempty"`
Undecided map[string]int `json:"undecided_reasons,omitempty"`
EstimateUSD float64 `json:"estimate_usd"`
CostUSD float64 `json:"cost_usd"`
// contains filtered or unexported fields
}
PreviewReport is the outcome of an impact preview.
func Preview ¶
func Preview(ctx context.Context, s *session.Session, draft *contract.Contract, opts PreviewOptions) (PreviewReport, error)
Preview runs the impact preview of a draft (spec §5.1): the shared gate judgment (classify.JudgeGate with GateOptions.Contract = draft, so the receipts are reused by `kb classify` once the draft is accepted unchanged) over every source, or a stratified sample of opts.SampleSize by raw/ subfolder. Each document lands in one band: quarantine (P(off_topic) ≥ relevance_quarantine or a quality reason at quality_apply, code flags included), review (P(off_topic) ≥ relevance_review or a quality reason at quality_review), undecided (an answer is missing and nothing fired) or kept. When the topic has relevance labels, the would-be relevance quarantine is scored against them. The preview is recorded in previews.jsonl (the top list plus every label used) so a later contract change demotes those labels to dev.
type ReasonCount ¶
ReasonCount is one distinct decision reason and how many rows carry it.
type SampleSource ¶
SampleSource is one sampled source title.
type ScreeningSummary ¶
type ScreeningSummary struct {
File string `json:"file"`
Rows int `json:"rows"`
Decisions []DecisionSummary `json:"decisions"`
}
ScreeningSummary summarizes one curation or screening file of the topic (spec §12.2): row count and, per decision, its reasons and example titles.
func SummarizeScreening ¶
func SummarizeScreening(topicRoot string, maxReasons int, exclude ...string) ([]ScreeningSummary, error)
SummarizeScreening finds the topic's curation and screening files under outputs/ and summarizes each: rows, and per decision its count, up to maxReasons distinct reasons (most frequent first) and a few example titles. Files that do not parse, and files matching an exclude glob (topic-relative, decisions.exclude), are skipped.
type TopDocument ¶
type TopDocument struct {
Path string `json:"path"`
Title string `json:"title"`
POffTopic float64 `json:"p_off_topic"`
Band string `json:"band"`
// Reason is the quality reason when quality drove the band.
Reason string `json:"reason,omitempty"`
}
TopDocument is one of the highest-P(off_topic) documents.