Skip to content

Repository files navigation

@playcode/okf-lint

Docs your agents can navigate, and diagrams that draw themselves. A linter for an OKF-style docs/ knowledge base - plus OKF Diagrams, which turns a compact text source into an interactive, themed, self-contained architecture canvas with automatically routed arrows.

An okf-d diagram: an order pipeline, its data model, and a failure path

Rendered from examples/orders.diagram.md by okf diagram - no coordinates in the source, no hand-placed arrows, no Mermaid.

A linter for the docs/ knowledge base. It enforces a minimal OKF-style profile (markdown + YAML frontmatter + cross-links), generates per-folder index.md manifests, checks links, and reports staleness - so docs stay findable by both humans and agents.

Dependency-free at runtime (hand-rolled frontmatter/markdown parsing); run from source via tsx, no build step.

Usage

From the repo root:

pnpm okf check [--staged]   # validate frontmatter (+ links). --staged: only git-staged docs (the ratchet)
pnpm okf links              # dangling cross-links across the whole bundle
pnpm okf index [--check]    # regenerate index.md. --check fails on drift, --dry previews
pnpm okf diagram [--check]  # render *.diagram.md / *.diagram.json sources to sibling interactive .html canvases
pnpm okf stale [--days=N]   # docs older than N days by git last-commit date (default 365)
pnpm okf fix                # stamp missing type, add status to archive/ docs, regenerate indexes
pnpm okf precommit          # hook entry: regenerate+stage indexes, check staged docs, reverse rename check

Add --json for machine output (use pnpm -s okf ... --json so pnpm's banner stays off stdout). Exit codes: 0 ok, 1 errors found, 2 usage.

The profile

Every concept doc carries frontmatter:

---
type: guide            # REQUIRED - routable kind (adr|daily|meeting|person|feature-plan|exec-plan|draft|bug|hypothesis|report|guide)
description: One line.  # REQUIRED - what the doc is (exempt under archive/)
status: active         # OPTIONAL - active|draft|superseded|archived
tags: [sky, storage]   # OPTIONAL
---

index.md (one per folder, generated) and README.md are reserved. Chronological logs (daily/, meetings/) and date-shard dirs are not indexed.

Rules

Severity Rules
error (blocks) frontmatter-missing, type-missing, description-missing, value-yaml-unsafe, duplicate-frontmatter-key, description-multiline, index-stale (in index --check)
warning (informs) link-dangling, type-unknown, status-invalid, filename-type-mismatch, archive-status-missing, adr-superseded-unmarked, multiple-h1, description-too-long, index-not-generated

link-dangling is a warning in v1 (promote to error after a clean okf links pass). Severities live in src/config.ts (SEVERITY).

Finding docs with the structure

grep -rl '^type: adr' docs                 # every ADR
grep -rl '^tags:.*\bsky\b' docs            # by tag
grep '^\* ' docs/sky/index.md              # one-line summary of a folder's docs

OKF Diagrams

You write this:

row:
  shop[purple]:  Storefront    | web + mobile checkout
  api[teal]:     Orders API    | :8080 - validates + prices
  queue[amber]:  order.created | at-least-once - 7d retention
  worker[green]: Fulfilment    | idempotent by order id

edges:
  shop -purple-> api : POST /orders
  api -teal-> queue : publish
  queue -amber-> worker

and get a routed, themed, pannable canvas - in both colour schemes, from the same source:

Dark Light
dark theme light theme

...or a whole document

Set render: doc and the same source becomes a page instead of a board: ordinary markdown - headings, tables, ordered and nested lists, code fences, blockquotes, links - with routed diagrams dropped in wherever a picture explains it faster than a paragraph. Souped-up markdown, one file in, one self-contained page out.

Doc mode: an on-call runbook with a table, a checklist, a shell snippet and an inline routed diagram

Rendered from examples/runbook.diagram.md - the diagram in the middle is an okf-d block sitting between a code fence and a blockquote.

How the arrows work

The arrows are not in the source. The renderer routes them: orthogonal elbows that never cross a box, ports spread along shared sides, parallel runs kept in separate lanes, and labels placed where they cover nothing - and okf diagram --verify proves all of that per diagram in headless Chromium.

okf diagram turns compact diagram sources under docs/ into sibling self-contained .html canvases - the same generated-artifact-next-to-source pattern as index.md, with --check as the drift gate and automatic regeneration in okf precommit (a broken source blocks the commit). Sources carry SEMANTIC roles and authored STRUCTURE only (no colors, no coordinates, no CSS); the packaged runtime owns everything visual:

  • Routed arrows: orthogonal elbows with obstacle avoidance (straight -> L -> Z -> A*), ports spread along shared sides, lane separation, label chips that find a free spot. Arrows never cross a box.
  • Authored layout, reflowed by the browser: rows / columns / grids / clusters / ranked trees; boxes size to content, arrows re-route on any change. No auto-layout graph engine - structure stays yours.
  • Node kinds: boxes, saturated instance boxes, ER table cards (edges may attach to a FIELD row), clusters, panels, notes, legends.
  • Two themes (dark + light) with a toggle, defaulting to prefers-color-scheme.
  • Interactions: drag anywhere to pan, click a box to edit its text in place (exploration only - the source is the truth), hover to spotlight an element's arrows, per-section jump buttons, zoom/fit.
  • PNG previews, on by default: a plain okf diagram also captures <base>.<theme>.png (dark; --images=light|both for others) and writes a marked preview block under each source's H1 - the image plus a link to the interactive canvas - so an editor's markdown preview shows the real diagram above its source. --no-images keeps it HTML-only, --no-embed writes the PNGs without touching sources. Needs the optional playwright peer; without it the implicit run just says so and exits clean. The parser skips the block, so it never reaches the model.
  • Doc mode: render: doc in the frontmatter turns the source into a flowing PAGE instead of an infinite board - markdown prose interleaves with the diagram sections (prose attaches to the section that follows it; trailing prose becomes the outro). Supported markdown: paragraphs, ###/#### headings, - and 1. lists with nesting, > blockquotes, --- rules, pipe tables, fenced code blocks, and inline back-tick code / bold / italic / [text](url) links. Native scroll and text selection; same router, themes, and hover spotlight.

Two source formats, one model. The preferred format is the okf-d DSL - a *.diagram.md doc (type: diagram frontmatter) whose H1 is the title, whose leading prose is the caption, and whose fenced ```okf-d blocks each hold one section:

---
type: diagram
description: My service - request flow.
---

# My service

```okf-d
section: 1 - Flow
nav: 1-Flow

row:
  api[teal]: API | :8080 - validates
  db[amber]: DB | rows of record

edges:
  api -> db : SQL
```

The block grammar (2-space indentation, # comments): containers row rowT rowS rowW col colS stack grid2 ranks rankrow panel and cluster "Heading" (subtitle) layout:; leaves id[role flags]: Name | line | line (flags: wide span), table id[role]: Name with fieldId: text / - text rows, inst id[role]: Name | line, note: / warn: / sub: one-liners, spacer; legend: role=text, flow=text, fk=text; edges from -> to, from -role-> to, from -.-> to (FK), optional @<exit><enter> side hints (l r t b, . = auto) and : label. A *.diagram.json file with the raw model works too (same renderer); see test/fixtures/example.diagram.md and its JSON twin - they parse to the identical model, and a test keeps it that way.

Roles: purple teal amber green red blue yellow gray. Also: a <-> b / a <-role-> b draws arrowheads on both ends; a -> a draws a self-loop (side via @ hint); [nudge=DX,DY] on any box/inst/table shifts it visually as a last resort (survives reflow, arrows re-aim); rowT(110): overrides a container's gap. Two escapes keep text intact: \| is a literal pipe, and a segment starting with \ keeps its leading/trailing spaces.

A *.diagram.json file holding the raw model works too (same renderer) - useful when a script generates the diagram. okf diagram --to-dsl converts JSON sources to the compact DSL and is round-trip guarded: it re-parses what it emits and refuses to write unless the model is reproduced exactly (typically ~2.8x fewer characters, and far easier to read or patch).

One source, several artifacts. A diagram is one *.diagram.md; the .html (and the local .png) are generated siblings. To keep the file tree clean, exclude them in the editor rather than reorganising the repo - the Playcode workspace does this in .zed/settings.json via file_scan_exclusions, which also keeps generated HTML out of search results. Doc-mode sources get no PNG at all: a markdown preview already renders them, and a full-height capture of a long page is enormous.

Editor support. editors/zed/ is a Zed extension giving okf-d syntax highlighting - inside ```okf-d fences in markdown and in .okf-d files. Run editors/zed/install.sh, then zed: install dev extension. Zed has no webview API, so in-editor HTML preview is not possible for any extension; the PNG preview block above is the workaround.

Generated file shape. The rendered .html leads with its <title> and the diagram's MODEL, then the page skeleton, then a LIBRARY CODE BELOW - stop here banner ahead of the minified CSS + runtime. Everything specific to a diagram sits in the first ~40 lines, so a reader (or an agent) never has to page through the renderer. Also: a <-> b / a <-role-> b draws arrowheads on both ends; a -> a draws a self-loop (side via @ hint); [nudge=DX,DY] on any box/inst/table shifts it visually as a last resort (survives reflow, arrows re-aim); rowT(110): overrides a container's gap.

okf diagram --verify proves the router invariants per diagram in headless Chromium (every edge drawn, no arrow through a box, no label covering a card) - the same gates the runtime promises, asserted instead of trusted. See BEST-PRACTICES.md for the design rules the renderer encodes and the knobs to reach for when a diagram reads badly.

Enforcement

.husky/pre-commit runs okf precommit on docs-touching commits (the hook path is installed by the repo-root prepare script, so a fresh pnpm install wires it up - no husky package needed). It regenerates and stages indexes, enforces type+description on touched docs (errors block), and warns on inbound links a rename/delete would break.

Known limit: the hook only sees staged files, so broken inbound links from untouched files on a rename are caught only by running pnpm okf links (there is no CI). Run it periodically.

Layout

src/cli.ts          commands + flags + exit codes
src/config.ts       paths, taxonomy, severities, the fs walker
src/frontmatter.ts  CRLF/BOM-safe parse/serialize, duplicate-key detection
src/markdown.ts     code stripping + link extraction
src/links.ts        link resolution + dangling rule
src/index-gen.ts    manifest generation + write-guard
src/rules/          frontmatter + convention rules
src/{git,doc,stale,fix,precommit,report}.ts
test/               vitest fixtures per area
pnpm test         # vitest
pnpm typecheck    # tsgo
pnpm lint         # oxlint + prettier

About

OKF-style docs knowledge-base linter: frontmatter, cross-links, generated indexes, staleness. Zero deps, CLI + API.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages