CtrlK
BlogDocsLog inGet started
Tessl Logo

wagneripjr/okf-maintain

Adopt and maintain an Open Knowledge Format v0.2 documentation bundle: frontmatter, generated indexes, a fail-closed conformance check, and coverage that names every unindexed document

72

Quality

91%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Passed

No findings from the security scan

Overview
Quality
Evals
Security
Files

adoption.mdreferences/

Adoption — making a repository OKF, and keeping it that way

Step 0 — read the manifest, then carry on

cat docs/okf.yaml 2>/dev/null

docs/okf.yaml is not part of OKF v0.2 — the spec has no manifest file. It is a convention some toolchains add to declare a dialect: which frontmatter keys are mandatory, and for which documents. Report what it says, because it changes how a conformance verdict should be read. Then continue.

This used to be a hard stop. The argument was that a profile ships its own index generator whose rows differ from v0.2's, paired with a commit gate that regenerates the index and compares it byte-for-byte with what is staged — so writing v0.2 rows would brick the repo at someone else's next commit. It is a good argument and, where it holds, it still is. The problem was that nothing verified either half. Check them instead of assuming them:

  • Is there a generator? Look for the command, not the claim. A tool that renders a traceability matrix or a status roll-up is not an index generator; it projects a different thing.
  • Is a gate armed? Look in the repo's hooks and CI for something that regenerates an index and diffs it. A gate nobody installed cannot deny a commit.

Where both hold, the path that tool owns belongs in .okfignore — that is the mechanism for "this is not mine", and it says so per path rather than abandoning the whole repository. Where they do not hold, the old refusal produced exactly the wrong outcome: the repositories with a manifest were the ones guaranteed to have no index at all, and nobody was told, because writing nothing looked like a deliberate policy rather than a missing feature.

Do not offer to convert an existing corpus to another dialect either. A profile migration has consequences past documentation and is not a call this skill makes; indexing a repository is not a migration.

This skill does not create a manifest — v0.2 needs none, and writing one would declare a dialect the repo has not chosen.

Step 1 — survey before restructuring

List what markdown already exists and how it is grouped.

Do not reorganise directories that already make sense. OKF is explicitly agnostic about layout (§3): producers organise concepts however suits the knowledge. A reshuffle costs every existing link, every bookmark, and every reference in code comments, and buys nothing the format asks for.

Note what is not a concept document: README files that are project front matter rather than knowledge, generated API dumps, vendored third-party docs, delivery logs another tool writes. They are still listed — a reader looking for the contributing guide should find it where they look for everything else — but they are exempt from the required-key check, so a readme with no frontmatter is not a violation.

Two kinds of "somebody else's" the tool settles without asking, so they need no decision from you — but read the run's report, because both are announced there. A directory holding a .git entry is another repository's working tree and the walk stops at it (separate-repo:). An existing index.md with no generation marker is hand-maintained or another tool's output, and it is left byte-identical (foreign-index:) rather than downgraded to v0.2 rows.

The decision that matters here is narrower: which paths does something else own. Three outcomes, in order of preference:

  1. Make it conformant. A generated file whose generator stamps type and a one-sentence description is an ordinary concept document. Prefer this for anything readers should find — a traceability matrix belongs on the index, not hidden from it. Fixing the generator is the only fix that survives, because hand-stamping frontmatter into a file a tool rewrites is erased on that tool's next run, and nothing reports it.
  2. Move it outside the bundle root. Available only when nothing else writes there.
  3. Declare it in .okfignore. For what you cannot make conformant and cannot move.

Write the .okfignore lines during the survey, before generating. Discovering them later means reading a violation list and deciding, file by file, which complaints were real — and the wrong call there is invisible in both directions.

.okfignore — the grammar, and why it is this dumb

One file at the bundle root. One bundle-root-relative path per line, trailing / for a directory, # for a comment. Matching is exact string equality — no globs, no prefixes.

# delivery logs, not knowledge
docs/plans/

# generated by another tool; delete this line once it stamps frontmatter
docs/TRACEABILITY.md

Exact matching is the point, not a shortcut. Under prefix or glob matching an empty pattern matches everything, so one stray blank line would silently exclude the corpus and every command would still exit green over nothing — the grep -F -f failure mode, reproduced in a tool whose whole job is enumeration. Under equality an empty pattern matches nothing by construction, and a repo with a dozen scattered generated files simply lists a dozen lines.

The remaining silent failures are reported rather than engineered away:

ReportMeans
ignored: docs/plans/ (.okfignore:2)This path was skipped, by that line. Every run, every skip.
unused-ignore: docs/evals/ (.okfignore:3)That line matched nothing — the path moved or was deleted, and the line now protects nothing.
exit 77 from check and indexThe declared paths swallowed the corpus. Never a 0.

A line is a standing claim that a path is still unowned. unused-ignore: is how that claim gets retired instead of accumulating.

Step 2 — the description triage

Run the check before writing anything:

node ${CLAUDE_PLUGIN_ROOT}/skills/okf-maintain/scripts/okf.mjs check .

A corpus that already has frontmatter rarely lacks descriptions — it has descriptions that are paragraphs. Each one comes back as:

note: docs/requirements/FR-001.md: description is 342 chars (max 160); the index omits it rather
than truncating (§4.1 is one sentence)

That list is the worklist. Shorten each to one sentence, from the document's own opening. It is not a conformance failure and the check still exits 0, because §11 does not require a description at all — but every unshortened one is an entry that answers nothing before it is opened.

Generating before the triage is finished is legitimate. An index of titles and links with few descriptions still routes: every title is present, so "does a document for Y exist" is still one read. The alternative is worse in a way that is easy to miss — an index whose entries are paragraphs grows larger than the documents it enumerates, which is the exact opposite of progressive disclosure and sends readers straight back to grepping.

What is never acceptable is a machine-cut description. The script drops rather than truncates for that reason, and the same rule binds you: half a sentence in the field consumers trust most is a summary no author wrote.

Step 3 — the frontmatter pass

For each concept document that has none, add a block with type, title, and description.

Read each file. A description derived from a filename is a fabricated claim sitting in the field consumers trust most, and it is invisible once written — nothing downstream can tell an invented summary from an accurate one. Take the document's own opening sentence and compress it.

Where a document genuinely does not say what it is for, leave description out and report it. An entry with no description is a visible gap someone can fix. A wrong one is not.

Keep type values consistent. Runbook and Playbook in one bundle split what should be a single index section into two, and neither reads as complete.

Existing frontmatter keys the repo already uses stay — §4.1 permits any additional keys and forbids consumers from rejecting them. Preserve what you find; you are adding, not replacing.

Step 4 — the history pass

Two deletions, both lossless, both for the same reason.

git ls-files '*log.md' | grep -E '(^|/)log\.md$'    # find them before deleting
git rm docs/log.md

log.md is a hand-maintained copy of what git already stores. The two disagree the first time someone commits without updating it, and nothing fails when they do — the drift is discovered by a reader who trusted the wrong one. git log --follow <path> is derived from the commits and cannot be wrong. Because the file is tracked, its content is already in history: removing it deletes a duplicate, not a record.

Then strip in-document history — ## History, ## Changelog, ## Revision history, version tables, Last updated: lines. Same argument, one level down.

Check for translated variants before declaring the pass done; a corpus written in another language carries the same sections under other names (## Histórico, ## Histórico de revisões, Última atualização). A pattern list written in one language reports clean over a corpus written in another.

What replaces them: generated: { by, at } and verified: [{ by, at }] (§5.2). Note the shape — each is a point-in-time fact about the current content, not an accumulating list. One timestamp in a field cannot contradict itself; six entries in a prose changelog eventually do.

Step 5 — generate

node ${CLAUDE_PLUGIN_ROOT}/skills/okf-maintain/scripts/okf.mjs index .

Run it from the repo root so ./index.md, docs/index.md and every folder index are written as one chain. Supply a --describe for each directory the script reports. See index-format.md for the grammar and the round-trip that means you write each description once.

The bundle root must be the repo root, and not only for the chain. wire writes its entry files into the bundle root, so index docs followed by wire docs produces a docs/CLAUDE.md that nothing loads and a root left with no address at all. The two commands take the same argument because they describe the same bundle; passing a subdirectory is silently wrong rather than an error, which is the only reason it is worth stating.

If ./index.md is a file the repo has never had, that is expected: it is the address the entry block points at, and the reason the chain has a single entrance.

Step 6 — wire the agent-entry files

An index nobody is told to read saves nothing. This step is what converts the format into fewer tokens and fewer wrong answers.

node ${CLAUDE_PLUGIN_ROOT}/skills/okf-maintain/scripts/okf.mjs wire .

This writes the following into CLAUDE.md and AGENTS.md at the repo root, creating either file if it does not exist and leaving whatever is already there untouched:

<!-- okf:entry -->
## Documentation

Start at [index.md](index.md). Every documentation folder carries a generated `index.md` listing
each document's title and one-line description — answer "which doc covers X" and "does a doc for Y
exist" from that index in one read, and open a document only after the index names it. Do not grep
`docs/` for a document's identity; grep stays correct only for a literal phrase inside a body that
the index cannot carry.
<!-- /okf:entry -->

The <!-- okf:entry --> / <!-- /okf:entry --> markers exist so the block can be replaced rather than appended. Two copies that disagree are worse than none, which is why the replacement is scripted rather than left to a hand edit: the markers are a byte-level contract, and hand edits are what byte-level contracts lose to.

GEMINI.md uses import lines rather than prose, so it receives @index.md on its own line alongside whatever it already imports.

wire refuses with exit 1 until index.md exists. An entry block pointing at a missing file sends every agent that trusts it to a dead end.

Placement inside the file barely matters; the stated rule does. Two halves, and the second is what makes the first credible:

  • An id, status, existence, or description lookup is one index read.
  • A literal phrase inside a body is still a grep, because the index structurally cannot carry it.

Omitting the exception does not get you more index reads. It gets the rule classified as overreach and ignored wholesale.

Step 7 — verify

node ${CLAUDE_PLUGIN_ROOT}/skills/okf-maintain/scripts/okf.mjs check .

Exit 0 conformant · 1 violations named · 77 nothing evaluated · 64 usage.

77 means the scan found no concept document — wrong path, empty tree, or a bundle root that is not where you thought. Never read it as a pass. A clean verdict issued over zero files is the most convincing wrong answer a checker can give, which is why it is a distinct exit state rather than a 0 with a footnote.

Then the half check cannot do:

node ${CLAUDE_PLUGIN_ROOT}/skills/okf-maintain/scripts/okf.mjs coverage .

check reads the corpus through the same walk that wrote the index, so a document the walk never reached is missing from both and the two agree. coverage asks git ls-files --cached --others --exclude-standard instead and names every markdown file no index links to. --others matters: without it the enumeration is the git index, which does not contain the document you are adding, so the check would go green on exactly the commit that introduces the problem. Same exit contract, with 77 meaning there was no git work tree to enumerate — again a refusal to verify, not a pass.

A finding is closed one of two ways: index the document, or put it in .okfignore because something else owns it. Silence is not one of them.

Step 8 — report

State plainly:

  • what was created (indexes, entry blocks)
  • what was deleted (log.md files, history sections) — deletions are the part a reviewer most needs to see
  • every .okfignore line and the reason for it, marking which are meant to be temporary
  • every document whose description you left blank or left over-long, and why

The last two are the items most worth surfacing. They are the honest residue of the pass, and they are exactly the lists someone with domain knowledge can close in a few minutes. An .okfignore line in particular is invisible once written — it silently keeps working, which is the point and also the risk, so the reason belongs in the report and in a comment beside the line.

Migrating a docs tree that is already structured

A repo with a hand-maintained docs/README.md table of contents needs one extra decision: the generated index.md replaces it. Move any prose that is genuinely knowledge (an orientation, a glossary) into its own concept document with a type, then delete the table of contents rather than leaving two listings that will disagree.

Redirect inbound links to index.md in the same commit. A stale link to a deleted table of contents is the first thing a returning reader hits.

SKILL.md

tile.json