Documentation
¶
Overview ¶
Package okf is the core in-memory model for an OKF bundle. It must not import cobra or any CLI package.
Index ¶
- Constants
- Variables
- func AppendLog(root, message string) error
- func ApplyMove(root string, b *Bundle, old, new string, rewrites []LinkRewrite) error
- func Clone(url, dir string) error
- func GitAvailable() bool
- func GitLastCommitDate(root, relPath string) (time.Time, bool, error)
- func GitLastCommitDateIgnoring(root, relPath string, ignore map[string]bool) (time.Time, string, bool, error)
- func IndexDirs(b *Bundle) []string
- func IndexInSync(b *Bundle) (bool, string)
- func IsGitWorkTree(dir string) bool
- func IsReservedPath(rel string) bool
- func LoadDriftIgnoreRevs(root string) (map[string]bool, error)
- func MigrateApply(root string, b *Bundle, plan MigratePlan) error
- func NewNode(root, relPath, typ, title string) (string, error)
- func NewNodeFromTemplate(root, relPath, typ, title string, t Template) (string, error)
- func ParseFrontmatter(src []byte) (map[string]any, string, error)
- func PlanRemoveOrphans(b *Bundle, path string) ([]string, error)
- func PromotableIndexes(b *Bundle) []string
- func PromoteApply(root string, b *Bundle, changes []PromoteChange) error
- func PullFastForward(dir string) error
- func ReadLog(root string) (string, error)
- func RefreshApply(changes []RefreshChange) error
- func RenderDirIndex(b *Bundle, dir string) string
- func RenderIndex(b *Bundle) string
- func Scaffold(dir string) error
- func TemplateScaffold(t Template) (fields []string, sections []string)
- func Templates(b *Bundle) map[string]Template
- func TouchModifiedFile(abs string, at time.Time) error
- func WriteIndex(b *Bundle) error
- type AccuracyClaim
- type Actor
- type AlignmentCheck
- type AnalyzeNodeRef
- type AnalyzeOptions
- type AnalyzeReport
- type AnalyzeSummary
- type Bundle
- type CalibrationCheck
- type ClusterFinding
- type ConnectivityReport
- type CoverageReport
- type DanglingLink
- type DriftFinding
- type DuplicateGroup
- type EpistemicCount
- type EpistemicReport
- type EvalFinding
- type EvalOptions
- type Finding
- type FreshnessReport
- type GeneratedEdit
- type Generation
- type Graph
- type GraphEdge
- type GraphNode
- type JudgmentItem
- type JudgmentKind
- type LinkRewrite
- type LintFinding
- type LintOptions
- type LoadOption
- type MigratePlan
- type Neighbor
- type NeighborResult
- type Node
- func (n *Node) Epistemic() (string, bool)
- func (n *Node) Generated() (Generation, bool)
- func (n *Node) IsStale(today time.Time) bool
- func (n *Node) SourceCitations() int
- func (n *Node) Sources() []Source
- func (n *Node) StaleAfter() (time.Time, bool)
- func (n *Node) Status() string
- func (n *Node) Tags() []string
- func (n *Node) TrustTier() TrustTier
- func (n *Node) Type() string
- func (n *Node) Verified() []Verification
- type NodeEvalScaffold
- type NodeMigration
- type PromoteChange
- type RefreshChange
- type RefreshGuardResult
- type SampleOptions
- type SearchField
- type SearchResult
- type SemanticIndex
- type SemanticOptions
- type SlugPair
- type Source
- type SourceEdit
- type StaleNode
- type StructureReport
- type Template
- type ThinNode
- type TimeSensitiveNode
- type TrustTier
- type UsageWindow
- type Verification
- type WeaklyLinked
Constants ¶
const DriftIgnoreRevsFile = ".okf-drift-ignore-revs"
DriftIgnoreRevsFile is the bundle-root file naming the mechanical commit SHAs that opt out of git drift. It mirrors the file `git blame --ignore-revs-file` consumes: one SHA per line, blank lines and #-comments ignored. A checked-in list lets a human declare commit INTENT that git itself cannot read — the day-one bulk migration commit that would otherwise collapse a corpus's real authoring history into a single date.
const SpecVersion = "0.2"
SpecVersion is the OKF spec version this build targets.
const StatusStable = "stable"
StatusStable is the default lifecycle status when `status` is absent (§5.4).
const TargetOkfVersion = "0.2"
TargetOkfVersion is the version this migrator upgrades a bundle TO. It is a migrate-local constant, deliberately independent of SpecVersion (the build's pinned version): closing the SpecVersion gap is separate, tracked work.
Variables ¶
var DefaultSkipDirs = map[string]bool{ ".git": true, ".hg": true, ".svn": true, ".okfctl": true, "node_modules": true, ".venv": true, "venv": true, "env": true, "__pycache__": true, ".mypy_cache": true, ".pytest_cache": true, ".tox": true, ".ruff_cache": true, "site-packages": true, "vendor": true, "target": true, "dist": true, "build": true, ".next": true, ".cache": true, ".idea": true, ".vscode": true, }
DefaultSkipDirs is the base-name skip list applied to the bundle walk. These are vendored (third-party, checked-in or installed) and derived (build/tool output) directories whose .md files nobody authored as knowledge: a Python virtualenv or a build-output tree sitting under the bundle root would otherwise become part of the graph. The set is matched by directory BASE NAME at any depth, so tool/.venv and web/node_modules are both skipped. It is a default, not a policy: --no-ignore (WithNoIgnore) restores the full walk, and the skip is never silent (Bundle.SkippedDirs records what was pruned so the caller can announce it). The bundle root itself is never skipped even if its own base name matches.
This deliberately does NOT consult .gitignore (couples curation scope to version-control scope, two different questions) and is a built-in default rather than a required .okfctlignore (the tool must be usable on a real tree with no config); a project-level ignore file composes cleanly on top later.
var LifecycleStatuses = map[string]bool{ "draft": true, "stable": true, "deprecated": true, }
LifecycleStatuses is the §5.4 status lifecycle enum: the only values `status` may hold. Absent ⇒ stable. This is a spec-DEFINED closed set, unlike the open `type` vocabulary (§7.4) or the unknown `epistemic` key (§11): status is named and enumerated by §5.4, so a value outside this set is a genuine defect. It drives lint's status-lifecycle check (soft guidance), never a validate floor rejection — §11 forbids rejecting a bundle for an optional field.
var ReservedFiles = map[string]bool{"index.md": true, "log.md": true}
ReservedFiles are the reserved base names that are not concept nodes. A file named index.md or log.md is reserved at ANY depth: a knowledge base commonly keeps a per-neighborhood index.md and log.md (e.g. wine/index.md, security/auth/log.md), and those are structural files (front door / append-only change history), not concepts. Recognition is by base name, so use isReservedPath to test a bundle-relative path.
Functions ¶
func AppendLog ¶
AppendLog prepends a timestamped entry to log.md (newest-first), creating the file with a heading when absent. A multi-line message is flattened to its first line to keep the log well-formed; an empty message is rejected.
func ApplyMove ¶
func ApplyMove(root string, b *Bundle, old, new string, rewrites []LinkRewrite) error
ApplyMove performs the file move old->new on disk and applies the planned inbound-link rewrites. It is the single writer for a move. root is the bundle root; b is the loaded bundle the plan was computed against.
func Clone ¶
Clone runs `git clone <url> <dir>`, materializing a remote bundle source into a local directory. It returns an error that includes git's own stderr so a failure (bad url, unreachable host, auth) is actionable.
func GitAvailable ¶
func GitAvailable() bool
GitAvailable reports whether the git binary is on PATH.
func GitLastCommitDate ¶
GitLastCommitDate returns the committer date of the most recent commit that touched the bundle-relative file relPath, resolving git from within root.
The three return values distinguish "no answer" from "error": ok=false with a nil error means git could not answer (git binary absent, root is not a git repo, or the file is untracked / has no commits) — a normal, non-fatal state that callers degrade on. A non-nil error is a genuine failure (git present and invoked but failing for an unexpected reason). This keeps the drift check working the same way as `index check`: git is a source of truth when present, and silently unavailable when not, never a crash.
func GitLastCommitDateIgnoring ¶ added in v0.2.0
func GitLastCommitDateIgnoring(root, relPath string, ignore map[string]bool) (time.Time, string, bool, error)
GitLastCommitDateIgnoring returns the committer date AND SHA of the most recent commit that touched relPath whose SHA is NOT in the ignore set, resolving git from within root. It is the drift-comparison primitive that lets a bulk mechanical commit opt out: when the file's last-touching commit is listed in `.okf-drift-ignore-revs` (loaded via LoadDriftIgnoreRevs), the comparison walks back to the prior real commit instead of collapsing the node's authoring history into the migration date. This mirrors the established `git blame --ignore-revs-file` convention.
An ignore entry matches a commit when it equals the full 40-char SHA or is a prefix of it (>= 7 chars), so an abbreviated SHA in the file still opts the commit out — the same spelling tolerance git blame gives.
The four return values distinguish "no answer" from "error": ok=false with a nil error means git could not answer (git binary absent, root is not a git repo, the file is untracked, or EVERY commit that touched it is ignored) — all normal, non-fatal states callers degrade on. A non-nil error is a genuine failure. When ignore is nil or empty this behaves exactly like a `git log -1`.
func IndexDirs ¶
IndexDirs returns the sorted bundle-relative directories that should carry an index.md (OKF §8: an index MAY appear in any directory and enumerates that directory's contents). A directory qualifies when it directly holds a concept node OR is an ancestor of a directory that does — exactly the set a reader traverses for progressive disclosure. The bundle root ("") is always included when the bundle has any concept node. Empty directories, and directories whose entire subtree has no concept, are excluded. This is the single source of truth shared by WriteIndex (build) and IndexInSync (check).
func IndexInSync ¶
IndexInSync reports whether the on-disk nested index tree matches what WriteIndex would generate for the current bundle. It is in sync only when EVERY content-bearing directory has an index.md equal to its RenderDirIndex output AND no orphaned generated index.md exists in a directory that should carry none. A missing, stale, or orphaned index counts as out of sync, and the report names the first offending path.
func IsGitWorkTree ¶
IsGitWorkTree reports whether dir is inside a git work tree. It degrades to false (never an error) when git is absent or dir is not a repo, mirroring the "git as an optional source of truth" discipline in gitmeta.go.
func IsReservedPath ¶
IsReservedPath reports whether a bundle-relative slash path names a reserved file (index.md or log.md) at any depth.
func LoadDriftIgnoreRevs ¶ added in v0.2.0
LoadDriftIgnoreRevs reads DriftIgnoreRevsFile from the bundle root and returns the set of commit SHAs to opt out of drift comparison. The file is OPTIONAL: its absence yields an empty (non-nil) set and no error — the common case.
Format (identical to git's ignore-revs file so users already understand it):
- one SHA per line
- blank lines ignored
- a line beginning with '#' is a comment, ignored
- an inline '#' comment after a SHA is stripped
- surrounding whitespace trimmed
SHAs are lower-cased so matching is case-insensitive against git's %H output.
func MigrateApply ¶ added in v0.2.0
func MigrateApply(root string, b *Bundle, plan MigratePlan) error
MigrateApply applies a plan's deterministic edits to disk, order-preserving and additive-only. root is the bundle root; b is the loaded bundle the plan was computed against. It writes each edited node in place, then bumps the version markers (.okf sidecar and bundle-root index.md). It never touches a judgment item — those stay for the plan's consumer.
func NewNode ¶
NewNode creates a conformant concept node at relPath (bundle-relative) with a required non-empty type (§7.2). It refuses an empty type and refuses to overwrite an existing file. Frontmatter is YAML-marshaled (never concatenated) so a type/title containing newlines or YAML metacharacters is safely quoted and cannot inject additional frontmatter keys. Returns the absolute path written.
NewNode is the no-template path: it delegates to NewNodeFromTemplate with an empty template, so both paths share one containment/marshal/write mechanism.
func NewNodeFromTemplate ¶
NewNodeFromTemplate creates a node conformant to both the spec floor and a governing type template (PRD §9.3). Beyond the required type + title, it stubs the template's required fields (with a "TODO" placeholder so the node starts free of template drift) and recommended fields (empty), and lays down its body_sections as empty `## ` headings. An empty Template scaffolds nothing — that is the plain NewNode path. Existing-file, containment, and empty-type refusals apply. Returns the absolute path written.
func ParseFrontmatter ¶
ParseFrontmatter splits a source file into its YAML frontmatter map and the Markdown body. Missing frontmatter yields an empty (non-nil) map and no error; malformed YAML frontmatter is an error.
func PlanRemoveOrphans ¶
PlanRemoveOrphans returns the bundle-relative nodes that become orphaned (zero inbound links) as a direct consequence of removing path. It is pure.
func PromotableIndexes ¶ added in v0.2.0
PromotableIndexes returns the sorted bundle-relative paths of every NON-ROOT index.md that carries a non-empty frontmatter block — the exact shape validateReserved flags as "index files contain no frontmatter (§8)". The bundle-root index.md is excluded (its §12 okf_version carve-out is legal), and a non-root index with no frontmatter is already conformant and excluded. A non-root index whose frontmatter failed to parse (Frontmatter == nil) is a different failure class and is not promotable — promote does not guess at broken YAML.
func PromoteApply ¶ added in v0.2.0
func PromoteApply(root string, b *Bundle, changes []PromoteChange) error
PromoteApply performs each planned promotion on disk: it applies the inbound link rewrites, writes the promoted directory-concept index into its new sibling concept file with the body preserved VERBATIM and `created` immutable, and removes the old index.md (a clean, frontmatter-free index is regenerated by WriteIndex at the command layer). root is the bundle root; b is the loaded bundle the plan was computed against. It is the single writer for a promotion.
func PullFastForward ¶
PullFastForward runs `git -C <dir> pull --ff-only`, updating an existing checkout without ever rewriting local history. A divergence (local commits that are not on the remote) fails rather than merging.
func RefreshApply ¶
func RefreshApply(changes []RefreshChange) error
RefreshApply writes each planned change to disk, rewriting only the frontmatter `modified` field via the order- and body-preserving writer. `created` is never touched. It is safe to call with an empty plan (no-op).
func RenderDirIndex ¶
RenderDirIndex produces the deterministic index.md body for one directory of the bundle (dir is bundle-relative slash form; "" is the bundle root), per OKF §8: it enumerates ONLY that directory's own immediate contents — its content-bearing child directories (linked dir-relatively as `child/`) under a "Subdirectories" section, and the concept nodes living directly in it (linked by base name) under a "Concepts" section, each carrying the linked concept's description from frontmatter. Links are relative to dir itself, never bundle-relative. Only the bundle-root index carries frontmatter (the §12 okf_version carve-out); every nested index carries none. Output is byte-stable (all ordering via sort) and passes Validate.
func RenderIndex ¶
RenderIndex renders the bundle-ROOT index. It is RenderDirIndex(b, "") — kept as a named helper so existing call sites and tests that mean "the root index" stay expressive.
func Scaffold ¶
Scaffold writes a minimal conformant bundle into dir: a reserved index.md and log.md and an .okf spec pin. The result passes Validate with zero findings (it has no concept nodes yet, so the type floor is vacuously satisfied).
The scaffolded index.md carries NO frontmatter (OKF §8). The bundle's okf_version is pinned by the .okf sidecar; `okfctl index build` surfaces it as the sole permitted index frontmatter key (§12) once the index is regenerated.
func TemplateScaffold ¶
TemplateScaffold returns the required fields to stub as empty frontmatter keys and the body sections to lay down as empty `## ` headings for a node created from t (PRD §9.3). recommended_fields are stubbed alongside required ones.
func Templates ¶
Templates folds every `type: Type Template` node in the bundle into a map keyed by the target_type it governs. A bundle should not ship two templates for one target_type; if it does, the lexicographically-last path wins (stable, since paths are visited in sorted order).
func TouchModifiedFile ¶
TouchModifiedFile refreshes the frontmatter `modified` field of the node at abs to `at` (RFC3339 UTC), writing the file back in place. It is order- and body-preserving: the frontmatter block is round-tripped through a yaml.Node so existing keys keep their order and the Markdown body is preserved verbatim. `created` is never rewritten (only modified is touched); a node without a `modified` key gains one appended to the end of its frontmatter, and one without frontmatter at all gains a minimal block. It never fabricates created.
This is the single, order-preserving writer for a timestamp refresh — it edits the frontmatter block only, so it cannot drop the body the way rewriting a parsed sub-region over the whole file would.
func WriteIndex ¶
WriteIndex regenerates one index.md per content-bearing directory (OKF §8), from the current bundle. It is the single writer for the reserved index (both `index build` and the automatic create/edit/delete/rename maintenance call it) so the two paths cannot diverge on how indexes are produced. Directories are created as needed; a directory that already holds concept files always exists, but IndexDirs may include an ancestor that is only implied by a deeper node.
WriteIndex also self-heals the tree: an index.md left behind in a directory that is no longer content-bearing (e.g. after a node moved or was removed out of it) is pruned, so a subsequent `index check` is clean. This is the stale parent/sibling index class the pre-§8 flat model left behind.
Types ¶
type AccuracyClaim ¶ added in v0.3.0
type AccuracyClaim struct {
Claim string `json:"claim"`
ExpectedGrounding string `json:"expected_grounding"` // filled by the judge
}
AccuracyClaim is one factual claim extracted from a node body, paired with an empty grounding slot for a human or LLM judge to fill in: does the node's cited source actually support this claim? okfctl extracts the claim; it never judges the grounding.
type Actor ¶ added in v0.2.0
type Actor string
Actor is a provenance actor: generated.by / verified[].by (§7). Its recorded form is one of `<producer>/<version>`, `human:<id>`, or `process:<id>`.
type AlignmentCheck ¶ added in v0.3.0
type AlignmentCheck struct {
Question string `json:"question"` // title + description: what the node set out to answer
Answered string `json:"answered"` // filled by the judge: yes/no/partial + why
}
AlignmentCheck asks whether the node answers the question it set out to. The question is the node's title/description; Answered is filled by the judge.
type AnalyzeNodeRef ¶
type AnalyzeNodeRef struct {
Path string `json:"path"`
}
AnalyzeNodeRef is a bare reference to a node by path (used where a finding carries no extra fields).
type AnalyzeOptions ¶
type AnalyzeOptions struct {
// StaleDays: age (days) past which a node's freshness basis date is stale.
StaleDays int
// TimeSensitiveFraction: a time-sensitive node surfaces once its age is
// >= TimeSensitiveFraction * StaleDays; undated marked nodes always surface.
TimeSensitiveFraction float64
// ThinLines: body line count (blank lines excluded) below which a node is
// "thin".
ThinLines int
// ClusterMin: minimum nodes sharing a tag to flag a synthesis cluster.
ClusterMin int
// CoverageThreshold is passed through to Lint's coverage-gap check.
CoverageThreshold int
}
AnalyzeOptions configures the proactive curation report. Zero values are filled with defaults by DefaultAnalyzeOptions / the command layer.
func DefaultAnalyzeOptions ¶
func DefaultAnalyzeOptions() AnalyzeOptions
DefaultAnalyzeOptions returns the report defaults, mirroring the reference okf_analyze.py: 180-day staleness, 0.5 time-sensitive fraction, 15-line thin threshold, 3-node cluster minimum.
type AnalyzeReport ¶
type AnalyzeReport struct {
Summary AnalyzeSummary `json:"summary"`
Coverage CoverageReport `json:"coverage_gaps"`
Epistemic EpistemicReport `json:"epistemic"`
Freshness FreshnessReport `json:"freshness"`
Connectivity ConnectivityReport `json:"connectivity"`
Clusters []ClusterFinding `json:"clusters"`
Structure StructureReport `json:"structure"`
}
AnalyzeReport is the structured curation report across the five dimensions. It is JSON-serializable for the machine path (the curation sweep files research cards from it) and consumed by the human renderer.
func Analyze ¶
func Analyze(b *Bundle, opts AnalyzeOptions) AnalyzeReport
Analyze runs the five-dimension proactive curation report over a loaded bundle. It is READ-ONLY, pure (aside from the package clock for freshness), and deterministic (all output ordered by sort). It NEVER mutates the bundle and NEVER fails on findings — the caller decides exit semantics (report, not gate: exit 0 on a successful analysis regardless of finding count).
type AnalyzeSummary ¶
type AnalyzeSummary struct {
Nodes int `json:"nodes"`
TotalInternalLinks int `json:"total_internal_links"`
StaleThresholdDays int `json:"stale_threshold_days"`
}
AnalyzeSummary carries corpus-level counts and the thresholds in effect.
type Bundle ¶
type Bundle struct {
Root string
Nodes map[string]*Node // concept nodes only (excludes reserved)
Reserved map[string]*Node // index.md, log.md
OkfVersion string // okf_version from the bundle's .okf, or SpecVersion if absent
// SkippedDirs holds the bundle-relative slash paths of directories pruned
// from the walk by the default skip list (see DefaultSkipDirs), sorted.
// Empty when WithNoIgnore was passed or nothing matched. The CLI announces
// these on stderr so an excluded subtree is never a silent omission.
SkippedDirs []string
// contains filtered or unexported fields
}
Bundle is a loaded OKF bundle: concept nodes keyed by bundle-relative path, plus the reserved files, plus the derived link graph.
func Load ¶
func Load(root string, opts ...LoadOption) (*Bundle, error)
Load walks root, parses every .md file, and builds the in-memory graph.
By default the walk prunes vendored and derived directories (DefaultSkipDirs) so content nobody authored as knowledge never enters the graph; pass WithNoIgnore to restore the full walk. Applying the skip once here means every consumer (lint, analyze, validate, search, graph, index) inherits identical scope — divergent per-command scope would be its own bug class.
func (*Bundle) OutboundLinks ¶
OutboundLinks returns the in-bundle nodes that path links to.
type CalibrationCheck ¶ added in v0.3.0
type CalibrationCheck struct {
CurrentGrade string `json:"current_grade"` // the grade being re-checked
HoldsOnRecheck string `json:"holds_on_recheck"` // filled by the judge: yes/no + note
}
CalibrationCheck measures whether a node's grade holds up on re-check, so a periodic sample can tell whether the "VERIFIED" rate is calibrated. CurrentGrade is what okfctl records today; HoldsOnRecheck is filled by the judge.
type ClusterFinding ¶
ClusterFinding is a tag shared by >= ClusterMin nodes with no synthesis node.
type ConnectivityReport ¶
type ConnectivityReport struct {
Orphans []AnalyzeNodeRef `json:"orphans"`
WeaklyLinked []WeaklyLinked `json:"weakly_linked"`
}
ConnectivityReport groups the connectivity findings.
type CoverageReport ¶
type CoverageReport struct {
DanglingLinks []DanglingLink `json:"dangling_links"`
ThinNodes []ThinNode `json:"thin_nodes"`
Uncited []AnalyzeNodeRef `json:"uncited"`
SingleCitation []AnalyzeNodeRef `json:"single_citation"`
KnownGaps []string `json:"known_gaps"`
}
CoverageReport groups the coverage / gap findings.
type DanglingLink ¶
DanglingLink is a body link whose .md target resolves to no node.
type DriftFinding ¶
DriftFinding is a single template-overlay violation (PRD §9.4). It is a warning-class finding, never a spec-floor failure.
func TemplateDrift ¶
func TemplateDrift(b *Bundle) []DriftFinding
TemplateDrift reports where nodes diverge from the template governing their type (PRD §9.4): a required field missing/empty, or a body_section heading absent. recommended_fields are advisory and never reported here. A node whose type has no governing template never drifts (unknown types are fine, §7.4). Output is deterministic (sorted by node path, then finding order).
type DuplicateGroup ¶
type DuplicateGroup struct {
Members []string `json:"members"`
}
DuplicateGroup is a set of node paths whose titles fold to one key.
type EpistemicCount ¶ added in v0.3.0
EpistemicCount is one observed epistemic value and how many nodes carry it.
type EpistemicReport ¶ added in v0.3.0
type EpistemicReport struct {
Distribution []EpistemicCount `json:"distribution"`
Untagged int `json:"untagged"`
}
EpistemicReport surfaces the observed distribution of the `epistemic` grade key (§11 unknown key). It is OBSERVATIONAL, not a gate: analyze recognizes the key and reports whatever values appear so a curator can spot an outlier or typo, but okfctl never enum-checks or rejects a value (over-conformance on an unknown key is a spec violation). Untagged counts nodes with no epistemic key.
type EvalFinding ¶ added in v0.3.0
type EvalFinding struct {
Check string `json:"check"`
Path string `json:"path"`
Message string `json:"message"`
}
EvalFinding is one TACA-Transparency observation about a node's provenance. Like a LintFinding it is curation guidance, never a spec-floor failure: eval is advisory by default and only gates under --strict. The four checks: "grade-missing" | "grade-vocabulary" | "uncited" | "citation-unresolved".
func EvalTransparency ¶ added in v0.3.0
func EvalTransparency(b *Bundle, opts EvalOptions) []EvalFinding
EvalTransparency runs the deterministic, stdlib-only, offline TACA-Transparency checks over a bundle and returns findings sorted by (path, check). It never mutates the bundle and never touches the network — external http(s) citations are deliberately out of scope (that is the eval-sample / human pass, see verifying-citation-link-fit). This is the only TACA dimension okfctl can honestly automate; Accuracy/Alignment/Calibration are scaffolded by EvalSample instead.
type EvalOptions ¶ added in v0.3.0
type EvalOptions struct {
// GradeVocabularyFloor is the minimum number of nodes that must carry a
// given epistemic/authority value for that value to count as part of the
// corpus vocabulary. A value carried by fewer nodes is reported as a
// vocabulary outlier (a likely typo/drift). Zero means the default.
//
// Calibrated against the real cwest/knowledge-base corpus. As measured on
// 2026-08-09 (corpus HEAD 895693b, 246 nodes) the legitimate epistemic
// vocabulary bottoms out at "draft"/"active" (2 nodes each) and authority at
// "SYNTHESIS" (2 nodes); the genuine drift lives at count 1 ("authority:
// high", "authority: DEPRECATED"). A floor of 2 therefore isolates exactly
// the count-1 outliers without false-positiving a small-but-real grade. See
// docs/specs/2026-08-07-taca-eval.md.
GradeVocabularyFloor int
}
EvalOptions configures the Transparency gate. Zero values take the defaults.
type Finding ¶
Finding is a single spec-floor violation. Path is bundle-relative.
func DriftFindings ¶
DriftFindings reports concept nodes whose frontmatter `modified` contradicts the file's git last-commit date — the tool noticing when the hand-maintained field has gone stale (or been bumped ahead of reality). It is READ-ONLY: it never rewrites a node (following the `index check` precedent — report, and let a write command fix it).
It degrades cleanly: outside a git repo, when git is unavailable, or for an untracked file, there is no source of truth to compare against and no finding is produced. A node without a `modified` field cannot contradict anything and is skipped. Findings are returned sorted by path for deterministic output.
func Validate ¶
Validate enforces the OKF spec floor (PRD §6.2, §7.1):
- frontmatter must be parseable (nil frontmatter == parse failure);
- every concept node has a non-empty `type` (§7 rule 2);
- reserved index.md files carry no frontmatter, with the single §12 carve-out for an okf_version-only block on the bundle-root index.
It never enforces a taxonomy of type VALUES (§7.4): unknown types pass. It returns findings; an empty slice means the bundle passes the floor.
The index-frontmatter rule closes the loop: okfctl generates index.md, so its own validator must reject an index that violates §8/§12 — otherwise a generator regression (e.g. re-introducing `type: Index`) passes validation unnoticed, which is the exact defect this floor exists to catch.
type FreshnessReport ¶
type FreshnessReport struct {
Stale []StaleNode `json:"stale"`
TimeSensitive []TimeSensitiveNode `json:"time_sensitive"`
}
FreshnessReport groups the freshness findings.
type GeneratedEdit ¶ added in v0.2.0
GeneratedEdit is the planned §13.1 `timestamp` → `generated { by, at }` rename for one node. At is the legacy timestamp value carried over verbatim; By is the supplied/inferred actor (§7).
type Generation ¶ added in v0.2.0
Generation records how the current content was produced (§5.2): who (an actor) and when (last meaningful change).
type Graph ¶
Graph is a serializable view of a bundle's concept-node link graph. Reserved files (index.md/log.md) are not graph nodes, but their outbound links confer inbound reachability for orphan detection (consistent with lint).
func BuildGraph ¶
BuildGraph derives the serializable graph from a loaded bundle. Nodes are sorted by path; edges by (from, to). The orphan flag reuses inboundCounts — the same inbound source of truth lint uses — so graph and lint can never disagree about what is orphaned.
type GraphNode ¶
type GraphNode struct {
Path string `json:"path"`
Title string `json:"title"`
Type string `json:"type"`
Neighborhood string `json:"neighborhood"`
Orphan bool `json:"orphan"`
}
GraphNode is a single concept node in the graph.
type JudgmentItem ¶ added in v0.2.0
type JudgmentItem struct {
Path string `json:"path"`
Kind JudgmentKind `json:"kind"`
Context string `json:"context"`
}
JudgmentItem is one thing the migrator refuses to guess. Path is the node it belongs to; Context is verbatim material (the citation line, the timestamp value) the consumer needs to resolve it.
type JudgmentKind ¶ added in v0.2.0
type JudgmentKind string
JudgmentKind classifies why an item could not be migrated deterministically.
const ( // JudgmentProseCitation: a `# Citations` item with no follow-able resource // (§5.1 requires `resource`). The consumer must supply a resource, reclassify // it (e.g. to §5.2 `verified`), or drop it. JudgmentProseCitation JudgmentKind = "prose-citation" // JudgmentMissingActor: a `timestamp` rename with no actor supplied and none // inferable (§7). The consumer must supply `generated.by`. JudgmentMissingActor JudgmentKind = "missing-actor" )
type LinkRewrite ¶
type LinkRewrite struct {
NodePath string // bundle-relative path of the node whose body is edited
Old string // exact link target text being replaced (URL + optional title)
New string // replacement target text (same relative form as Old)
}
LinkRewrite is a single planned edit to a node's body: replace the link target text Old with New, preserving the author's relative link form.
func PlanMove ¶
func PlanMove(b *Bundle, old, new string) ([]LinkRewrite, error)
PlanMove computes the inbound-link rewrites needed to move old->new. It is pure (no disk access) and preserves each author's relative link form: a link that resolved root-relative stays root-relative; a link that resolved dir-relative is recomputed relative to the linking node's directory.
type LintFinding ¶
type LintFinding struct {
Check string `json:"check"` // "orphan" | "missing-xref" | "coverage-gap" | "type-hygiene" | "broken-link" | "status-lifecycle" | "spec-version"
Path string `json:"path"` // node path the finding is about ("" for bundle-level findings)
Message string `json:"message"`
}
LintFinding is one judgment-worthy observation about bundle health. Unlike a validate Finding (a spec-floor violation), a lint finding is curation guidance — never a format failure.
func Lint ¶
func Lint(b *Bundle, opts LintOptions) []LintFinding
Lint runs the deterministic, stdlib-only structural checks over a bundle and returns findings sorted by path then check. It never mutates the bundle.
func LintSemantic ¶
func LintSemantic(b *Bundle, idx SemanticIndex, opts SemanticOptions) []LintFinding
LintSemantic runs the similarity-driven curation checks the PRD (§8.6) calls for: pairs that read alike but carry no edge, and nodes with no semantically close kin at all. It is the semantic counterpart to the structural checks in Lint — structural asks "is anything linked to this?", semantic asks "is anything even about the same thing?".
idx supplies the neighbor sets; a node absent from idx is reported once as index drift rather than silently skipped, so a partial answer never reads as a complete one. Findings are sorted by path then check, so the same inputs always produce byte-identical output.
type LintOptions ¶
type LintOptions struct {
// CoverageThreshold is the number of distinct nodes that must mention a
// term (with no node of its own) before it is reported as a coverage gap.
// Zero means the default (3).
CoverageThreshold int
}
LintOptions configures the deterministic structural checks.
type LoadOption ¶ added in v0.2.0
type LoadOption func(*loadConfig)
LoadOption configures Load. The zero set of options is the default behavior: the bundle walk skips vendored/derived directories (DefaultSkipDirs).
func WithNoIgnore ¶ added in v0.2.0
func WithNoIgnore() LoadOption
WithNoIgnore restores the full walk: no directory is skipped, so the loaded graph is byte-identical to the pre-skip-list behavior. It is the escape hatch for a bundle that deliberately authored real content into a directory whose name happens to match the skip list.
type MigratePlan ¶ added in v0.2.0
type MigratePlan struct {
TargetVersion string `json:"target_version"`
Nodes []NodeMigration `json:"nodes"`
Judgment []JudgmentItem `json:"judgment"`
}
MigratePlan is the full, JSON-serializable migration plan: the deterministic per-node edits, the enumerated judgment items, and the target version. It is the review surface — for Casey and for a solo user alike.
func PlanMigration ¶ added in v0.2.0
func PlanMigration(b *Bundle, generatedBy string) (MigratePlan, error)
PlanMigration computes the v0.1 → v0.2 migration plan for a loaded bundle. It is PURE: it reads the bundle and writes nothing. generatedBy is the actor (§7) used for every `timestamp` rename; when empty and no actor is inferable, each such node becomes a JudgmentMissingActor item instead of a guessed rename.
type Neighbor ¶
Neighbor is one ranked semantic neighbor of a node: the neighbor's path and its cosine similarity to the subject node.
type NeighborResult ¶
type NeighborResult struct {
Path string `json:"path"`
Title string `json:"title"`
Type string `json:"type"`
Neighborhood string `json:"neighborhood"`
Depth int `json:"depth"`
}
NeighborResult is one node reached by graph-structural traversal from a start node, along with its hop distance (the start node is depth 0 and is excluded from results).
func Neighborhood ¶
func Neighborhood(b *Bundle, start string, depth int) ([]NeighborResult, bool)
Neighborhood returns the nodes within depth hops of start in the bundle's concept-node link graph, treating edges as UNDIRECTED: a node is a neighbor whether it links to start or start links to it (a reader traverses both ways). The start node itself is excluded. depth < 1 is treated as 1. Results are sorted by (depth, path) so the closest neighbors come first, deterministically. An unknown start path returns (nil, false).
type Node ¶
type Node struct {
Path string // bundle-relative, e.g. "wine/tannin.md"
Frontmatter map[string]any // parsed YAML frontmatter
Body string // markdown after the frontmatter
}
Node is a single OKF concept: its bundle-relative path is its identity, its frontmatter carries typed metadata (type is required, §7), and body holds the Markdown after the frontmatter block.
func (*Node) Epistemic ¶ added in v0.3.0
Epistemic returns the node's `epistemic` grade value, and whether the key is present. `epistemic` is NOT an OKF-defined field: it is an unknown frontmatter key (§11) the corpus carries to preserve the pre-v0.2 conflated grade verbatim (migration 13.1). okfctl RECOGNIZES it — surfacing its value distribution in analyze — but never enum-gates it, because §11 forbids rejecting a bundle for an unknown key or its values. A non-string or empty value reports present=true with the value rendered as text, so a typo is surfaced rather than dropped.
func (*Node) Generated ¶ added in v0.2.0
func (n *Node) Generated() (Generation, bool)
Generated returns how the content was produced (§5.2). §13.1 fallback: when `generated` is absent, fall back to the legacy `timestamp` for `.At` (with an empty By — v0.1 recorded no author). ok is false when neither yields a usable date.
func (*Node) IsStale ¶ added in v0.2.0
IsStale reports whether the node is stale as of `today` (§5.5): stale when today >= stale_after. A node with no (or unparseable) stale_after is never stale.
func (*Node) SourceCitations ¶ added in v0.2.0
SourceCitations returns how many provenance entries a node carries (§13.1 fallback). It reads frontmatter `sources` first; for a v0.1 document with no `sources`, it falls back to counting the legacy body `# Citations` list.
func (*Node) Sources ¶ added in v0.2.0
Sources returns the parsed `sources` list (§5.1). Entries missing the REQUIRED `resource` are dropped — the reader surfaces only well-formed sources. The shared `usage_window` sibling is framed onto every entry; an entry MAY carry its own `usage_window` to override it. Returns an empty slice when `sources` is absent.
func (*Node) StaleAfter ¶ added in v0.2.0
StaleAfter returns the absolute stale-after date (§5.5, YYYY-MM-DD). ok is false when the field is absent or unparseable.
func (*Node) Tags ¶ added in v0.2.0
Tags returns the node's frontmatter tags (§4.1), nil when absent. Tags are an optional YAML list; a scalar tag normalizes to a one-element list and non-string scalars coerce to their string form. This is the exported accessor for callers outside the package (e.g. semantic-search filters) that need a node's tags without reaching into frontmatter directly.
func (*Node) TrustTier ¶ added in v0.2.0
TrustTier derives the trust tier from `verified` (§5.3): no verified ⇒ unverified; non-human actors only ⇒ machine-confirmed; any human:<id> ⇒ human-reviewed. Derived, never stored.
func (*Node) Verified ¶ added in v0.2.0
func (n *Node) Verified() []Verification
Verified returns the verification events (§5.2). §11 MUST: a BARE MAPPING is treated as a one-element list. Returns an empty slice when `verified` is absent.
type NodeEvalScaffold ¶ added in v0.3.0
type NodeEvalScaffold struct {
Path string `json:"path"`
Title string `json:"title"`
Description string `json:"description"`
Epistemic string `json:"epistemic"`
Authority string `json:"authority"`
TrustTier string `json:"trust_tier"`
Sources []string `json:"sources"`
Accuracy []AccuracyClaim `json:"accuracy"`
Alignment AlignmentCheck `json:"alignment"`
Calibration CalibrationCheck `json:"calibration"`
}
NodeEvalScaffold is the per-node eval-set entry the sampler emits for the three un-automatable TACA dimensions. okfctl pre-populates every field it can extract (path, title, description, grades, sources, candidate claims) and leaves the judgment slots empty. It computes NO truth verdict.
func EvalSample ¶ added in v0.3.0
func EvalSample(b *Bundle, opts SampleOptions) []NodeEvalScaffold
EvalSample selects a spot-check sample of nodes and returns an eval-set scaffold per node for the Accuracy/Alignment/Calibration dimensions that okfctl cannot automate. It is deterministic: an explicit Paths set is honored verbatim (sorted); otherwise a seeded pseudo-random Count sample is drawn reproducibly.
type NodeMigration ¶ added in v0.2.0
type NodeMigration struct {
Path string `json:"path"`
Generated *GeneratedEdit `json:"generated,omitempty"`
Sources []SourceEdit `json:"sources,omitempty"`
}
NodeMigration is the set of deterministic frontmatter edits planned for one node. A nil Generated means no timestamp rename is planned; an empty Sources means no citation became a source. Only nodes with at least one edit appear.
type PromoteChange ¶ added in v0.2.0
type PromoteChange struct {
OldPath string // bundle-relative, e.g. "gke-pm-map/index.md"
NewPath string // bundle-relative, e.g. "gke-pm-map/gke-pm-map.md"
Rewrites []LinkRewrite
}
PromoteChange is a single planned promotion of one directory-as-concept index.md into a sibling concept file, together with the inbound-link rewrites that keep every reference to it resolving after the move. OldPath is the non-root index.md that carries frontmatter; NewPath is the sibling concept file it becomes (dir/<basename>.md). Rewrites are the edits to OTHER files' bodies whose links pointed at the old directory-concept in either directory spelling.
func PromotePlan ¶ added in v0.2.0
func PromotePlan(b *Bundle, basename string) ([]PromoteChange, error)
PromotePlan computes the promotion of every directory-as-concept index into a sibling concept file, plus the inbound-link rewrites needed to keep references resolving. basename is the concept file's base name for every promoted node ("" means default to the directory's own base name). It is PURE: it reads the loaded bundle and writes nothing to disk.
A destination that already exists as a concept node is a hard error — promote never overwrites authored content.
type RefreshChange ¶
type RefreshChange struct {
Path string // bundle-relative, e.g. "wine/tannin.md"
AbsPath string // absolute on-disk path to rewrite
OldModified string // current modified, "2006-01-02"
NewModified string // target modified, RFC3339 (e.g. "2026-07-20T00:00:00Z")
Commit string // SHA of the git commit the drift comparison used
}
RefreshChange is a single planned timestamp correction: the drift finding's remediation. AbsPath is the file to rewrite; OldModified is the current frontmatter date (bare-date form, for display); NewModified is the value the refresh will write — the git last-commit calendar day stamped in the corpus's RFC3339-at-midnight-UTC form, so it both resolves the drift and matches the bare-date convention the corpus already uses (minimising the git diff).
func RefreshPlan ¶
func RefreshPlan(b *Bundle) []RefreshChange
RefreshPlan is the read-only remediation companion of DriftFindings: it reports the timestamp correction the refresh would make for every drifting node, in path order. It writes nothing. Outside a git repo the plan is empty (no source of truth, nothing to fix).
func RefreshPlanNode ¶
func RefreshPlanNode(b *Bundle, relPath string) ([]RefreshChange, error)
RefreshPlanNode narrows RefreshPlan to a single bundle-relative path. An honest (non-drifting) node yields an empty plan — a clean no-op, not an error. A path that is not a node in the bundle is a real caller error.
type RefreshGuardResult ¶ added in v0.2.0
type RefreshGuardResult struct {
Triggered bool // the plan is large and dominated by one commit
Commit string // the dominant commit's SHA (empty when not triggered)
Count int // how many of the plan's changes that commit accounts for
Total int // total changes in the plan
}
RefreshGuardResult reports whether a refresh plan looks like the remediation of a bulk mechanical commit, and the evidence for it.
func RefreshGuard ¶ added in v0.2.0
func RefreshGuard(plan []RefreshChange) RefreshGuardResult
RefreshGuard inspects a refresh plan for the bulk-mechanical-commit signature: a large plan in which a single commit accounts for an implausible share of the changes. Such a plan is not an incremental cleanup — running it would flatten every distinct authoring date the commit touched into the migration date. The caller uses the result to refuse (or require explicit confirmation) and to point the user at `.okf-drift-ignore-revs`, which resolves the case without destroying data. RefreshGuard is pure: it reads the plan and writes nothing.
type SampleOptions ¶ added in v0.3.0
type SampleOptions struct {
Paths []string // explicit node paths to scaffold (wins over Count/Seed)
Count int // size of a random sample when Paths is empty
Seed int64 // seed for reproducible sampling (0 ⇒ a fixed default seed)
}
SampleOptions selects which nodes the spot-check scaffold covers.
Precedence: an explicit Paths set wins (used by the --changed-since curation hook, resolved to node paths in the cmd layer). Otherwise a deterministic pseudo-random Count sample seeded by Seed is drawn. Count <= 0 with no Paths yields no scaffolds.
type SearchField ¶
type SearchField string
SearchField names a lexical match surface. A lexical query with no field restriction matches ANY of these; a field-restricted query matches only the named one. This is the core-search surface of PRD §6.3: title, tag, type, and content substring, all case-insensitive.
const ( // FieldAny matches title, tag, type, or body substring (the default). FieldAny SearchField = "" // FieldTitle matches only the node title (frontmatter title, or file base). FieldTitle SearchField = "title" // FieldTag matches only a node's frontmatter tags. FieldTag SearchField = "tag" // FieldType matches only the node's type value. FieldType SearchField = "type" // FieldBody matches only the node's body substring. FieldBody SearchField = "body" )
type SearchResult ¶
type SearchResult struct {
Path string `json:"path"`
Title string `json:"title"`
Type string `json:"type"`
Neighborhood string `json:"neighborhood"`
MatchedOn []string `json:"matched_on"`
}
SearchResult is one lexical hit: the matched node plus the fields the query matched on (sorted, deduped), so a caller can explain WHY a node matched.
func Search ¶
func Search(b *Bundle, query string, field SearchField) []SearchResult
Search runs a case-insensitive lexical query over a bundle's concept nodes. Reserved files (index.md/log.md) are never search results. An empty query returns no results (a lexical search needs a term). Results are sorted by path for deterministic output. field restricts the match surface; FieldAny searches title, tag, type, and body substring together.
type SemanticIndex ¶
SemanticIndex maps a node path to its ranked neighbors (self excluded). It is deliberately a plain data shape rather than a search-package type: the checks below are pure functions over similarity scores, so internal/okf stays free of any dependency on the index format or the embedder that produced it. The caller (cmd) reads the real index and adapts it into this shape.
type SemanticOptions ¶
type SemanticOptions struct {
// SimilarityThreshold is the cosine score at or above which two UNLINKED
// nodes are reported as a possible missing link. Zero means the default.
SimilarityThreshold float64
// IsolationFloor is the score a node's BEST neighbor must reach for the node
// to count as semantically connected. Zero means the default.
IsolationFloor float64
}
SemanticOptions tunes the similarity-driven checks.
type Source ¶ added in v0.2.0
type Source struct {
ID string
Resource string
Title string
Author string // actor (§7); an authority signal
UsageCount *int // adoption/liveness signal; nil when absent
LastModified string // YYYY-MM-DD; recency signal
UsageWindow *UsageWindow // in-effect window (shared or per-entry override)
}
Source is one parsed `sources` entry (§5.1). Resource is REQUIRED within an entry; the other fields are optional credibility signals. UsageWindow is the window in effect for this entry: the shared sibling window framed onto it, or the entry's own override when present.
type SourceEdit ¶ added in v0.2.0
type SourceEdit struct {
Resource string `json:"resource"`
}
SourceEdit is one planned §5.1 `sources` entry derived from a body citation. Resource is REQUIRED and preserves the author's form (absolute URL, or a bundle-relative path kept relative per §6).
type StaleNode ¶
type StaleNode struct {
Path string `json:"path"`
AgeDays *int `json:"age_days"`
Basis string `json:"basis"` // the date string compared, or "(none)"
}
StaleNode is a node whose freshness basis date is older than the threshold, or that carries no basis date at all (AgeDays nil, Basis "(none)").
type StructureReport ¶
type StructureReport struct {
DuplicateTitles []DuplicateGroup `json:"duplicate_titles"`
NearDuplicateSlugs []SlugPair `json:"near_duplicate_slugs"`
}
StructureReport groups the structural findings.
type Template ¶
type Template struct {
TargetType string
RequiredFields []string
RecommendedFields []string
BodySections []string
Path string // bundle-relative path of the template node
}
Template is a parsed type-template node (PRD §9.2). It governs nodes whose type equals TargetType.
type TimeSensitiveNode ¶
type TimeSensitiveNode struct {
Path string `json:"path"`
AgeDays *int `json:"age_days"`
Markers []string `json:"markers"`
}
TimeSensitiveNode is a marked node aged past the time-sensitive gate.
type TrustTier ¶ added in v0.2.0
type TrustTier string
TrustTier is the derived trust level (§5.3), lowest to highest. It is DERIVED, never stored.
type UsageWindow ¶ added in v0.2.0
UsageWindow is the { from, to } date range that frames a usage_count (§5.1).
type Verification ¶ added in v0.2.0
Verification is one verification event (§5.2): an actor and an instant.
type WeaklyLinked ¶
WeaklyLinked is a node with exactly one total in-bundle link.
Source Files
¶
- analyze.go
- authoring.go
- bundle.go
- drift.go
- eval.go
- eval_sample.go
- frontmatter.go
- frontmatter_edit.go
- gitmeta.go
- gitsource.go
- graph.go
- ignorerevs.go
- lint.go
- lint_lifecycle.go
- lint_semantic.go
- migrate.go
- mutate.go
- node.go
- promote.go
- provenance.go
- refresh.go
- refresh_guard.go
- reserved.go
- reserved_lifecycle.go
- search.go
- template.go
- timestamps.go
- validate.go