A coding agent can produce plausible React code that imports a component your library no longer exports or passes a prop that was removed versions ago. Giving it a versioned reference for your design system helps it check those assumptions before writing code.
One approach is to describe the design system in a CLAUDE.md or AGENTS.md file. A short reference can work well. As the file grows, loading it at session start adds context even when a task needs only one component.
Google’s Open Knowledge Format (OKF) gives the same knowledge a different shape. An OKF bundle stores concepts in Markdown files with YAML frontmatter. Optional index.md files help an agent find relevant documents. Google’s announcement places the format alongside existing Markdown knowledge-sharing patterns, including AGENTS.md and CLAUDE.md.
In this tutorial, you’ll build an OKF bundle for Primer React 38.40.1, GitHub’s component library, generating one concept per component directly from the package. You’ll then point a coding agent at the bundle, check component usage in CI, and compare the results with a flat file built from the same source. The complete project, benchmark included, is in the companion repository.
Start from the companion repository so the generator, fixtures, and benchmark scripts are available. The commands below use the commit reviewed for this article:
git clone https://github.com/Ikeh-Akinyemi/okf-design-contract.git
cd okf-design-contract
git checkout 134e83b72e44a6404bc8fb8dc2af5f2225773659
pnpm install --frozen-lockfile
Use Node.js 24.12 or later and pnpm. The repository records Node.js 26 for the original benchmark. You’ll also need an authenticated Claude Code installation to run the probe or benchmark; those commands make model calls and can incur charges.
The lockfile pins Primer React to v38.40.1 and TypeScript to v6.0.3. The scripts use the TypeScript 6 compiler API through import ts from 'typescript'. Keep that pin when following the tutorial; migrating to TypeScript 7’s compiler tooling requires a separate API compatibility review.
Node.js can run these .ts scripts through built-in type stripping. That feature supports erasable TypeScript syntax and does not perform type checking or apply tsconfig.json transformations.
The component metadata and declarations used to generate the contract ship inside the package. Primer publishes @primer/react/generated/components.json, which records every component’s status, import path, and props. Eleven of its 80 components are deprecated, and Dialog, Tooltip, UnderlineNav, and SelectPanel are each exported from more than one entry point with a different implementation behind each. Those are the cases your contract has to get right.
Your generator turns each components.json entry into a concept file, but it can’t take the metadata at face value, because the metadata already disagrees with the package it ships in. It lists KeybindingHint under @primer/react, although only @primer/react/experimental exports it. It also lists the current Tooltip under @primer/react/next alone, even though the main entry point exports the same component.
Resolve import paths against the type declarations. The loader reads each entry point’s .d.ts file with the TypeScript compiler API and records the directory where each export is declared. For this pinned package, matching that directory against the metadata’s source field resolves the component entry points:
const srcDir = c.source.split('/packages/react/src/')[1]
const matches = (exports.get(c.name) ?? []).filter((e) => e.dir === srcDir || e.dir.startsWith(srcDir + '/'))
const paths = order.filter((entry) => matches.some((m) => m.entry === entry))
const importFrom = paths[0]
Matching on the directory also tells same-named components apart. The current Dialog resolves to dist/Dialog, while the v1 Dialog resolves to dist/deprecated/DialogV1. The same pass reads each export’s @deprecated JSDoc tag, which is how Flash picks up its replacement. Prop descriptions and deprecation flags still come from components.json. This loader depends on Primer’s source and declaration layout, so another library will need its own mapping.
Run the generator to write one file per component, grouped by entry point:
$ node scripts/generate-bundle.ts
wrote 80 concepts to design-system/
Open design-system/core/flash.md, and you’ll see the contract at the top of the file:
---
type: React Component
title: Flash
description: Deprecated. Use Banner from @primer/react instead.
status: deprecated
import:
name: Flash
from: "@primer/react"
also_from:
- "@primer/react/deprecated"
replaced_by: /core/banner.md
# props, generated, and sources omitted
---
The OKF v0.2 specification makes type the only always-required frontmatter key. It permits custom fields such as this project’s import and replaced_by. Version 0.2 defines status: deprecated as kept for history but no longer current. In the body, a plain Markdown link to /core/banner.md puts the replacement one hop away, and the two Dialog concepts link to each other the same way.
Your agent starts at each directory’s index.md, which gives one line per concept:
* [Banner](banner.md) - Alpha component, imported from @primer/react.
* [Flash](flash.md) - Deprecated. Use Banner from @primer/react instead.
The index gives the agent a way to select documents before opening them. Actual token use includes the index, retrieved files, tool calls, and any repeated reads. The format itself does not enforce selective reading.
Some decisions require team judgment. Primer deprecates Tooltip v1 and TabNav without naming a replacement, so deciding what to use instead falls to you and your team. Write those decisions as concepts in design-system/guides/, alongside the generated directories. The repo’s deprecations guide covers each deprecated component whose replacement can’t be derived, and its frontmatter records who made each decision and when the guide is due for another look:
type: Guide
# title, description, tags, and sources omitted
generated: { by: human:ikeh, at: 2026-10-10T12:00:00Z }
verified: { by: human:ikeh, at: 2026-10-10T12:00:00Z }
stale_after: 2027-01-10T00:00:00Z
Under OKF’s advisory trust rules, a verified entry naming a human: actor marks the guide as human-reviewed. The field records a review claim; your review process must establish who made it. Generated concepts carry generated: { by: process:generate-bundle } instead, along with sources entries that point to the exact package version they were built from. This project gives manually maintained guides a stale_after date. Generated files are refreshed when the generator runs. Both can become stale if an upgrade skips that step, and this date alone does not trigger a CI failure.
Deprecated concepts without a derivable replacement link straight to the guide, so your agent reaches your team’s decision in one hop.
Add a pointer to the project’s CLAUDE.md so Claude Code can discover the bundle at session start. For an agent configured to read AGENTS.md, put the pointer there:
# Design system
This project uses Primer React 38.40.1. The design-system contract is an OKF bundle in `design-system/`. Start at `design-system/index.md`, open only the concepts a task needs, and follow their links to replacements and guides. Use only the components and props the bundle documents.
For comparison, the repo’s flat generator writes the contract and guides into one 149,065-byte CLAUDE.md. Claude Code’s memory documentation recommends keeping each file under 200 lines and supports path-scoped rules for more selective loading. The benchmark’s flat file is a deliberately large baseline.
Codex has a separate instruction-file size limit: project instructions have a combined default budget of 32KiB, configurable through project_doc_max_bytes. That limit concerns instruction discovery. It does not prevent an agent from opening a larger reference file with a file-reading tool.
The repo’s probe script asks Claude Sonnet 5.5 to reply with one word without using tools. It compares three workspaces and sums regular input, cache-creation, and cache-read tokens. The author reported these results:
$ node bench/probe.ts
| Workspace | Input tokens | Added by the design-system file |
|---|---|---|
| Empty | 5,810 | 0 |
| OKF pointer | 6,027 | 217 |
Flat CLAUDE.md |
64,299 | 58,489 |
In that probe, the flat file added 58,489 input tokens to the empty-workspace baseline. The OKF pointer added 217 before any bundle files were opened. These are context measurements, not equivalent billed-token counts: caching has separate pricing, and a real task also incurs retrieval and output costs.
CI can check whether generated code follows the contract. TypeScript rejects imports that don’t exist and props that don’t type-check, yet it still compiles import { Flash } from '@primer/react' without complaint, since @deprecated is only a hint for editors.
The repo’s contract checker combines TypeScript diagnostics with a custom deprecation check. The custom pass handles named component imports and explicitly written JSX attributes, including recognized subcomponents. Run it on a fixture written from remembered APIs, and it reports seven problems. Four of them show the split between the layers:
$ node scripts/check-contract.ts fixtures/Bad.tsx
fixtures/Bad.tsx:1:28 TS2305 Module '"@primer/react"' has no exported member 'KeybindingHint'.
fixtures/Bad.tsx:1:44 TS2305 Module '"@primer/react"' has no exported member 'Box'.
fixtures/Bad.tsx:1:9 deprecated-component Flash from '@primer/react': Deprecated. Use Banner from @primer/react instead. See design-system/core/flash.md.
fixtures/Bad.tsx:10:58 deprecated-prop IconButton prop `keyshortcuts` is deprecated. Use `keybindingHint` instead. See design-system/core/icon-button.md.
The compiler produces the TS lines; the custom pass produces the deprecation findings and links to the relevant concept files. This checker does not resolve every usage pattern. Namespace imports, wrapper components, and props passed through spreads need additional handling. It also does not assess rendered behavior, accessibility, or visual quality.
The bundle also needs a freshness check. The generator preserves each generated.at timestamp when the content is unchanged, allowing repeated generation to produce no diff.
Run these checks in a clean CI checkout:
pnpm generate
pnpm check:okf
if [ -n "$(git status --porcelain --untracked-files=all -- design-system flat)" ]; then
git status --short -- design-system flat
echo "Regenerate and commit the design-system contract."
exit 1
fi
Using git status catches newly generated files as well as modifications and deletions. A bare git diff --exit-code misses untracked files. Including flat also keeps the benchmark baseline synchronized.
The repository provides fixtures/Good.tsx and fixtures/Bad.tsx to demonstrate the checker. For an application with a src directory, add pnpm check src to CI, alongside its normal type-checking and tests. Configure the resulting CI job as a required check if it should block merges. The repository’s OKF checker validates selected format rules; add a date check separately if overdue guides should fail CI.
You’ll want evidence before you change how your team feeds context to agents. The repo’s benchmark runs five UI tasks through Claude Code in headless mode. Each task targets a known API pitfall, from a destructive dialog to a set of page tabs. The agent gets file tools only, and every task runs three times in three workspaces, one with the OKF pointer, one with the flat file, and one whose CLAUDE.md only names the Primer version. After each attempt, the checker’s output goes back to the agent for up to three correction rounds. The committed results contain 15 runs per condition for each model, or 90 runs in total. Sonnet used medium effort; Haiku used its default. Costs are Claude Code’s client-side estimates, including correction rounds, and time is summed from the CLI’s reported call durations:
| Model | Context | Clean first attempt | Median estimated cost | Median reported duration |
|---|---|---|---|---|
| Sonnet 5.5 | Version only | 13/15 | $0.028 | 9.0s |
| Sonnet 5.5 | Flat file | 15/15 | $0.264 | 7.1s |
| Sonnet 5.5 | OKF bundle | 15/15 | $0.055 | 11.0s |
| Haiku 4.5 | Version only | 4/15 | $0.035 | 24.5s |
| Haiku 4.5 | Flat file | 13/15 | $0.111 | 15.9s |
| Haiku 4.5 | OKF bundle | 15/15 | $0.032 | 18.4s |
A clean first attempt means the agent wrote a file that passed the type and contract checks. All conditions reached 15/15 passing runs after the allowed corrections. The Sonnet results and Haiku results include individual records and summaries.
Without a contract, Haiku produced 16 type errors across 15 runs, and the mistakes will look familiar if you’ve reviewed agent-written UI code. It wrote the v1 Dialog API, with isOpen and onDismiss, against the current Dialog in all three dialog runs. It labelled the tooltip with aria-label instead of the required text. It imported Box, which v38 no longer exports, and Alert, which Primer doesn’t export, and it imported TabNav from the main entry point. Sonnet’s version-only condition had two first-attempt failures, including one deprecated Flash import.
The OKF runs had no first-attempt checker findings. Haiku’s two flat-file failures were classified as no-output: the agent returned code in its reply without writing the requested file. Those replies were outside the checker’s file-based evaluation, so the result does not establish whether they were correct.
Compared with the flat file, the bundle reduced median estimated cost by about 79 percent for Sonnet and 71 percent for Haiku. Its median reported duration was about 3.9s longer for Sonnet and 2.6s longer for Haiku. The recorded runs also used more turns.
This is a small benchmark of five targeted tasks against one public component library. It supports selective retrieval as an alternative to this large flat baseline. It does not isolate the effect of OKF’s metadata from the effect of splitting documents into linked files. A concise instruction file with ordinary reference documents or path-scoped rules would be a useful additional comparison.
OKF is still evolving. Version 0.2 changed fields from v0.1, so keep your generator and validator aligned with the version declared by the bundle. Because the content is Markdown and YAML in Git, the documents remain readable if you later change formats.
A short instruction file can be sufficient for a smaller API or a few recurring rules. The useful threshold is the amount of relevant documentation, not a fixed component count. A few dozen components with full prop references can exceed Claude Code’s 200-line guidance.
Model choice matters too. Sonnet passed 13 of 15 first attempts with only the Primer version supplied. That result describes these tasks; it cannot tell us what the model encountered during training. Internal APIs and team-specific conventions are useful candidates for a contract because the agent needs an explicit source for them.
Measure the options with your own tasks. Include the time spent navigating files, cached-token pricing, and the effort needed to keep the contract current. The bundle’s savings will depend on how much of it the agent reads and how often it returns to those files.
A generated contract gives a coding agent a versioned reference for component APIs, while CI can catch the type errors and deprecations your checks cover. In this benchmark, reading an OKF bundle on demand cost less than loading the full generated reference at startup.
Start with the components that cause recurring review problems. Generate their metadata, add your team’s decisions, and compare the results with your existing instructions before expanding the bundle.

Is react-markdown slowing down your app? We benchmarked three top streaming Markdown renderers to find the fastest, most efficient alternative for React.

Learn how coding agents streamline TDD with automated tests, quality guardrails, and mutation testing to catch bugs and build reliable software.

We tested five AI code review tools for speed, setup, security, and code quality to compare their strengths and help you choose the right tool.

Compare five React toast libraries for features, bundle size, accessibility, performance, and React 19 compatibility.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up now