OKF v0.2 frontmatter 規範及兩種資料夾檢查方法
OKF v0.2 frontmatter: what the spec requires, and two ways to check a folder
OKF v0.2 要求每個非保留名稱的 .md 檔具備可解析的 YAML frontmatter,並含非空的 type;規範見 https://github.com/GoogleCloudPlatform/open-knowledge-format/blob/main/SPEC.md。
I make JidoSeal. Free and local; the scan engine is proprietary; JidoSeal also sells optional certificates.
If you keep notes, docs or a knowledge base as markdown with YAML frontmatter, and you point an AI tool at them (Claude Code, Cursor, Copilot, Codex, Gemini CLI, Zed, a local model), Google Cloud's Open Knowledge Format (OKF) is worth ten minutes. It is a small, open spec for exactly that kind of folder: "a directory of markdown files with YAML frontmatter", meant to be "authored by people, generated by agents... and consumed by both". This post covers what v0.2 actually requires, which is less than most people assume, and then two ways to check a folder against it.
What OKF v0.2 requires
The spec is short. You can read all of it at https://github.com/GoogleCloudPlatform/open-knowledge-format/blob/main/SPEC.md. Here is the part that matters for frontmatter.
A bundle is just a folder. Every .md file in it is a "concept document", except two reserved filenames: index.md (a directory listing) and log.md (an update history). Subfolders are fine, and git is the recommended way to ship it.
One field is required: type. Section 4.1 lists it as the only required key, and says "a concept carrying just type is fully conformant". Its values are free text that you choose (policy, procedure, Metric, Playbook...). Nobody registers them centrally, and consumers must handle types they don't recognise.
So this file is valid OKF v0.2:
---
type: policy
---
# Leave policy
Employees accrue leave monthly.
Four fields are recommended: title, description (one sentence), resource (a URI for the thing the note describes, if there is one), and tags (a YAML list). If title is missing, a consumer may derive it from the filename.
Optional families, if you want more:
-
Trust:
generated: { by, at }records who or what wrote the content.verifiedis a list of{ by, at }entries. A consumer derives a trust tier from it: noverifiedmeans unverified, a non-human verifier means machine-confirmed, andby: human:<id>means human-reviewed. -
Lifecycle:
statusisdraft,stableordeprecated, and defaults tostable.stale_afteris an absolute instant after which the note counts as stale. -
Provenance:
sourcesis a list of entries, each with a requiredresource.
Conformance (section 11) is three rules. Every non-reserved .md file has a parseable YAML frontmatter block. Every block has a non-empty type. index.md and log.md follow their own sections if present. Just as useful is what consumers must not reject a bundle for: missing optional fields, unknown type values, unknown extra keys, broken cross-links, or missing index.md files. Extra keys of your own are fine.
That gives you a concrete thing to check: does every note open with a frontmatter block, and does that block have a non-empty type? Then, softer: which of the four recommended fields are missing?
Way 1: fifteen lines of Python, no dependencies
This reads only top-level key: value lines. That's enough for type, which is a plain string. It skips the reserved filenames, fails any file without a frontmatter block or a non-empty type, and lists the recommended fields that are missing.
import sys, pathlib
RECOMMENDED = ["title", "description", "resource", "tags"]
bad = 0
for p in sorted(pathlib.Path(sys.argv[1]).rglob("*.md")):
if p.name in ("index.md", "log.md"): continue # reserved filenames, §3.1
lines = p.read_text(encoding="utf-8").splitlines()
if not lines or lines[0] != "---" or "---" not in lines[1:]:
print(f"FAIL {p}: no frontmatter block"); bad += 1; continue
fm = lines[1:lines.index("---", 1)]
keys = {l.split(":", 1)[0].strip(): l.split(":", 1)[1].strip() for l in fm if ":" in l and not l.startswith((" ", "#", "-"))}
if not keys.get("type"):
print(f"FAIL {p}: no non-empty 'type'"); bad += 1; continue
missing = [k for k in RECOMMENDED if k not in keys]
print(f"ok {p}" + (f" (recommended, missing: {', '.join(missing)})" if missing else ""))
sys.exit(1 if bad else 0)
Here it is on a sample folder of six notes, one of which has no frontmatter at all:
$ python3 okf_check.py tiers
ok tiers/bronze/leave-policy.md (recommended, missing: title, description, resource, tags)
ok tiers/gold/leave-policy.md (recommended, missing: resource, tags)
ok tiers/mixed/expenses.md (recommended, missing: description, resource, tags)
ok tiers/mixed/leave-policy.md (recommended, missing: resource, tags)
FAIL tiers/mixed/notes.md: no frontmatter block
ok tiers/silver/leave-policy.md (recommended, missing: resource, tags)
$ echo $?
1
It exits with 1 when anything fails, so it can go straight into CI or a pre-commit hook. It is honest about its limits. It does not parse YAML properly, so it won't notice a frontmatter block that is broken YAML, and it would accept type: "". Section 11 asks for "parseable" YAML, so if you need that guarantee, swap the keys line for yaml.safe_load and accept one dependency.
Way 2: grep or yq, in one line
To list the notes with no type line at all:
$ grep -rL --include='*.md' -E '^type: *[^ ]' tiers
tiers/mixed/notes.md
That is fast, but it also matches a type: line in the body of a note. For a real YAML read of the frontmatter only, mikefarah's yq (v4) has a front-matter mode:
$ for f in $(find tiers -name '*.md' ! -name index.md ! -name log.md | sort); do printf '%s\t%s\n' "$f" "$(yq --front-matter=extract '.type // "MISSING"' "$f")"; done
tiers/bronze/leave-policy.md policy
tiers/gold/leave-policy.md policy
tiers/mixed/expenses.md procedure
tiers/mixed/leave-policy.md policy
tiers/mixed/notes.md MISSING
tiers/silver/leave-policy.md policy
Change .type to .description (or any other field) to find the notes that are missing that one.
If you'd rather have your AI tool do the check, I also maintain a free, local MCP server that runs it and lists each file's missing fields: https://github.com/Jidoseal/jidoseal-mcp
來源:DEV Community · dev.to