scope

package
v0.0.11 Latest Latest
Warning

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

Go to latest
Published: Sep 24, 2026 License: MIT Imports: 26 Imported by: 0

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

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

View Source
const (
	DecisionInclude = "include"
	DecisionExclude = "exclude"
	DecisionUnknown = "unknown"
)

Screening file decision names used when a row carries no explicit one.

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

View Source
const (
	BandKept       = "kept"
	BandReview     = "review"
	BandQuarantine = "quarantine"
	BandUndecided  = "undecided"
)

Preview bands of one document.

View Source
const ConflictThreshold = "contract_conflict"

ConflictThreshold names the self-check threshold (default 0.7).

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

View Source
const RootFolder = "raw"

RootFolder names sources stored directly under raw/ (no subfolder).

Variables

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

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

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

func CleanDraft(c *contract.Contract) (*contract.Contract, []string)

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

func Folder(topicRel string) string

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

func ImportClaude(topicRoot string) (*contract.Contract, error)

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

func IsScreeningFile(name string) bool

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

func ScopeLine(claudeMarkdown string) string

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

func Stratify(docs []*corpus.Document, n int) []*corpus.Document

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

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 Conflict

type Conflict struct {
	Pair
	P float64 `json:"p"`
}

Conflict is a pair whose P(conflict) reached the threshold.

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.

func (Conflicts) Blocking

func (c Conflicts) Blocking() bool

Blocking reports whether the self-check blocks acceptance.

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

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

type FolderCount struct {
	Folder string `json:"folder"`
	Files  int    `json:"files"`
}

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

func BuildPairs(c *contract.Contract) []Pair

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

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

type ReasonCount struct {
	Reason string `json:"reason"`
	Count  int    `json:"count"`
}

ReasonCount is one distinct decision reason and how many rows carry it.

type SampleSource

type SampleSource struct {
	Title  string `json:"title"`
	Folder string `json:"folder"`
}

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.

Jump to

Keyboard shortcuts

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