Read, query, validate and edit Open Knowledge Format (OKF) bundles
| Package | Contents | Targets |
|---|---|---|
| moonrockz/okf | Document model, frontmatter, links, link graph, indexes, logs, queries, search, validation, profiles | all |
| moonrockz/okf/edit | Text edits that keep every other byte, and edit plans | all |
| moonrockz/okf/provider | Async DocumentStore, IndexProvider and SearchProvider traits, loading and plan application | all (async: native, wasm, js) |
| moonrockz/okf/memory | In-memory store, index and search providers | all (async: native, wasm, js) |
| moonrockz/okf/conformance | Conformance checks for new providers | native, wasm, js |
| moonrockz/okf/fs | File system store | native, wasm |
| moonrockz/okf/fs/node | File system store over node:fs/promises (Node.js 20.16+, Bun, Deno) | js |
| moonrockz/okf/fs/browser | Store over the File System Access API | js |
| moonrockz/okf/idb | Store over IndexedDB, for pages and workers in every browser | js |
| moonrockz/okf/openspec | OpenSpec profile, structured view, import and export | all |
let set = @okf.DocumentSet::of_texts([
("kb/index.md", "---\nokf_version: \"0.2\"\n---\n"),
("kb/tables/orders.md", "---\ntype: BigQuery Table\ntitle: Orders\n---\nSee [customers](customers.md).\n"),
])
let knowledge = @okf.discover(set) // every directory whose index.md has okf_version
let bundle = knowledge.bundles()[0]
let orders = bundle.concept(@okf.ConceptId("tables/orders")).unwrap()
orders.concept_type() // Some(ConceptType "BigQuery Table")
orders.links() // body links with their origin and kind
let graph = @okf.Graph::build(knowledge) // outbound links, backlinks, orphans, broken links
knowledge.filter([TypeIs(@okf.ConceptType("metric")), HasTag("finance")])
knowledge.facet(Type) // or Tags, Bundles, Field(@okf.FieldKey("owner"))
@okf.search_knowledge(knowledge, @okf.SearchRequest("revenue", limit=@okf.SearchLimit(10)))| Type | Rule |
|---|---|
| BundleLabel | Segments joined with /; no segment is empty or has : or white space |
| StoreDir, StorePath, BundlePath, EntryName | No segment escapes the root; an entry name is one segment |
| ConceptType | Not blank; equal without letter case |
| FieldKey | A plain frontmatter key that edits can write |
| RuleName | Lower-case kebab case |
| SearchLimit | 1 to 1000 |
| LogDate | A real calendar date, YYYY-MM-DD |
| RegisterName, StateName, RecordId, IdWidth | Not blank; an ID width is 1 to 9 |
let report = @okf.validate(
knowledge,
[@okf.okf_v02_conformance(), @okf.advisory()],
@okf.FixedClock::at("2026-10-23T00:00:00Z"),
config=@okf.ValidationConfig(
overrides=[(@okf.RuleName("link-broken"), Error)],
disabled=[@okf.RuleName("duplicate-title")],
),
)
report.findings() // sorted by severity, subject, line and rule
report.fails(strict=true)@edit.set_key(text, @okf.FieldKey("status"), "deprecated")
@edit.add_index_entry(index_text, @edit.SectionName("Metric"), @edit.IndexEntryLine("Revenue", "/metrics/revenue.md"))
@edit.add_log_entry(log_text, @okf.LogDate("2026-10-23"), "Added revenue.", label="Creation")
let plan = @edit.plan_add_concept(
bundle, "/metrics/revenue.md", @okf.ConceptType("Metric"), "Revenue",
"Net revenue per day", @okf.LogDate("2026-10-23"),
)
plan.render() // a dry run| Runtime | Store |
|---|---|
| Tests, wasm-gc, any target | @memory.MemoryStore (its snapshot() is synchronous) |
| Native and wasm | @fs.FsStore(@provider.StoreRoot(root)) |
| Node.js, Bun, Deno | @node.NodeStore(@provider.StoreRoot(root)) |
| Browser, origin private file system | @browser.BrowserStore::origin_private(path=@okf.StoreDir("kb")) |
| Browser, a directory that the user selects | @browser.BrowserStore(@browser.pick_directory()) |
| Browser or worker, IndexedDB | @idb.IdbStore::open(database=@idb.DatabaseName("kb")) |
| Method | Adds |
|---|---|
| vocabulary, bundle_keys | Known frontmatter keys |
| derive_metadata | Metadata for a document without frontmatter |
| registers | Typed views of one concept type: record IDs, states, transitions with obligations, supersession |
| reference_fields | Fields whose values name documents or records |
| templates | Bodies for new concepts |
| rule_sets, severities | Validation rules and their preferred severities |
| relations | Links that a document implies |
| options | Preferred options |
# openspec/ears.yaml
name: my-project
systems: [Mission Control, Text comparison]
lead_ins: [for each]pub(open) trait Profile {
fn name(Self) -> String
fn vocabulary(Self) -> Array[KeyDefinition] raise ValueError = _
fn bundle_keys(Self) -> Array[KeyDefinition] raise ValueError = _
fn derive_metadata(Self, Document) -> Fields? = _
fn registers(Self) -> Array[Register] raise ValueError = _
fn reference_fields(Self) -> Array[ReferenceField] raise ValueError = _
fn templates(Self) -> Array[Template] raise ValueError = _
fn rule_sets(Self) -> Array[RuleSet] raise ValueError = _
fn options(Self) -> Options? = _
fn relations(Self, Bundle, Document) -> Array[Link] = _
fn severities(Self) -> Array[(RuleName, Severity)] raise ValueError = _
}pub(all) suberror DocRefError {
NotADocRef(String)
BadLabel(text~ : String, error~ : ValueError)
BadPath(text~ : String, error~ : PathError)
} derive(Eq, Debug)pub(all) suberror ValueError {
InvalidLabel(String)
BlankConceptType(String)
InvalidFieldKey(String)
InvalidRuleName(String)
InvalidSearchLimit(Int)
InvalidDate(String)
InvalidInstant(String)
BlankName(String)
InvalidIdWidth(Int)
UnknownState(register~ : String, state~ : String)
UnknownRegister(register~ : String)
} derive(Eq, Debug)type Bundleimpl Compare for BundleLabelimpl Show for BundleLabelimpl Compare for BundlePathimpl Show for BundlePathimpl Compare for ConceptTypeimpl Eq for ConceptTypeimpl Hash for ConceptTypeimpl Show for ConceptTypetype Documenttype DocumentSetimpl LinkResolver for DocumentSetResolverpub(all) enum Filter {
TypeIs(ConceptType)
StatusIs(Status)
HasTag(String)
InBundle(BundleLabel)
FieldIs(FieldKey, String)
} derive(Eq, Debug)pub struct Finding {
severity : Severity
rule : RuleName
subject : FindingSubject
line : Int?
message : String
hint : String?
} derive(Eq, Debug)pub(all) enum FindingSubject {
InBundle(BundleLabel, BundlePath)
Stray(StorePath)
ProfileSubject(String)
ConfigSubject
} derive(Eq, Debug)impl Clock for FixedClocktype Graphpub(all) struct Header {
shape : HeaderShape
body : String
body_offset : Int
body_line : Int
bom : Bool
} derive(Eq, Debug)pub struct IndexSection {
heading : IndexHeading
line : Int
entries : ReadOnlyArray[IndexEntry]
text : ReadOnlyArray[String]
} derive(Eq, Debug)pub struct Issue {
subject : FindingSubject
line : Int?
message : String
hint : String?
} derive(Eq, Debug)fn KeyDefinition::KeyDefinition(key : FieldKey, values? : ArrayView[String], description? : String) -> KeyDefinitiontype Knowledgeimpl LinkResolver for KnowledgeResolverpub struct Link {
text : String
destination : String
origin : LinkOrigin
kind : LinkKind
} derive(Eq, Debug)impl Show for LogHeadingpub struct LogSection {
heading : LogHeading
line : Int
entries : ReadOnlyArray[LogEntry]
} derive(Eq, Debug)pub(all) struct Options {
link_base : LinkBase
status_default : Status?
nested_logs : NestedLogs
date_only_instants : Bool
v01_fields : Bool
index_links_count : Bool
} derive(Eq, Debug)type PathListResolverimpl LinkResolver for PathListResolverpub(all) enum ProfileConflict {
KeyConflict(key~ : FieldKey, first~ : String, second~ : String)
RegisterConflict(concept_type~ : ConceptType, first~ : String, second~ : String)
ProfileInvalid(profile~ : String, error~ : ValueError)
} derive(Eq, Debug)pub struct Record {
register : RegisterName
bundle : BundleLabel
id : RecordId?
state : StateName?
document : Document
}pub(all) enum ReferenceKind {
DocumentRef
RecordRef(register~ : RegisterName)
PathRef
} derive(Eq, Debug)type Registerfn Register::Register(name : RegisterName, concept_type : ConceptType, id_rule? : IdRule, state_key? : FieldKey, states? : ArrayView[StateName], transitions? : ArrayView[Transition], supersedes_key? : FieldKey, superseded_by_key? : FieldKey, superseded_states? : ArrayView[StateName], bundle_flag? : FieldKey, fields? : ArrayView[KeyDefinition]) -> Register raise ValueErrorfn Register::check_transition(self : Register, record : Record, to : StateName) -> Result[Unit, TransitionDenial]impl Show for RegisterNametype Reporttype Ruletype RuleSetpub struct SearchRequest {
query : String
filters : ReadOnlyArray[Filter]
limit : SearchLimit
} derive(Eq, Debug)fn SearchRequest::SearchRequest(query : String, filters? : ArrayView[Filter], limit? : SearchLimit) -> SearchRequestpub(all) struct Source {
resource : String
id : String?
title : String?
author : String?
usage_count : Int?
last_modified : String?
usage_window : UsageWindow?
} derive(Eq, Debug)type Transitionfn Transition::Transition(to : StateName, from? : ArrayView[StateName], obligations? : ArrayView[Obligation]) -> Transitionimpl Show for UnknownStatustype ValidationConfigfn ValidationConfig::ValidationConfig(overrides? : ArrayView[(RuleName, Severity)], disabled? : ArrayView[RuleName]) -> ValidationConfigtype Vocabularyfn is_markdown_name(name : String) -> Boolfn lexical_compare(a : String, b : String) -> Intfn load_bundle(set : DocumentSet, root? : StoreDir, label? : BundleLabel, options? : Options, profiles? : ArrayView[&Profile]) -> Bundlefn marker_comment(name : String) -> Stringfn normalize_description(text : String) -> Stringlet okf_concept_keys : ReadOnlyArray[String]fn resolve_field_path(bundle : Bundle, doc : Document, value : String, options? : Options) -> Resolutionfn slugify(text : String) -> Stringfn typed_references(knowledge : Knowledge, bundle : Bundle, doc : Document, profiles? : ArrayView[&Profile], resolver? : &LinkResolver) -> Array[TypedReference]fn validate(knowledge : Knowledge, rule_sets : ArrayView[RuleSet], clock : &Clock, config? : ValidationConfig, resolver? : &LinkResolver, profiles? : ArrayView[&Profile]) -> ReportInstall
Download zipRead, query, validate and edit Open Knowledge Format (OKF) bundles