Writing Documentation
When to Use This Skill
- Creating a new package README
- Writing website documentation (MDX)
- Creating or updating skills
- Reviewing documentation for quality
- Documenting Phase 7 of create-destination or create-source
Prerequisites
- understanding-flow - Architecture context
- understanding-events - Event naming rules
Documentation Types
Where Content Belongs
| Type | Purpose | Audience |
|---|---|---|
| Package README | Installation, basic usage, API reference | Package users |
| Website docs | Guides, integration examples, detailed config | Integrators |
| Skills | Process knowledge, workflows | AI assistants, contributors |
Divio Documentation Types
Keep these separate - don't mix tutorials with reference:
| Type | Purpose | User State |
|---|---|---|
| Tutorial | Learning | Studying, beginner |
| How-To Guide | Problem-solving | Working, knows what they need |
| Reference | Information lookup | Working, needs facts |
| Explanation | Understanding | Studying, needs context |
Example Validation (CRITICAL)
The Problem
AI-generated examples can be:
- Syntactically correct but use non-existent APIs
- Plausible-looking but don't match actual exports
- Outdated, referencing deprecated patterns
Source of Truth Hierarchy
TIER 1: apps/quickstart/
✓ Tested ✓ Compiled ✓ CI-validated
→ USE FOR: All code examples
TIER 2: packages/core/src/eventGenerator.ts
✓ Canonical events ✓ Real data structures
→ USE FOR: Event examples
TIER 3: packages/*/src/index.ts exports
✓ Actual public API
→ USE FOR: Verifying API names exist
TIER 4: Package READMEs & Website docs
⚠ May contain errors
→ VERIFY against Tier 1-3 before trusting
Pinned onboarding examples
Getting-started and onboarding code examples are sourced from Tier-1
apps/quickstart/src/*.ts (CI-validated) first, then the docs source them. The
current canonical examples:
first-event.ts, the zero-to-first-event console example used by the quickstart and integrated-mode pages and the README.ga4-ecommerce.ts, theproduct addto GA4add_to_cartmapping used by the GA4 reference page and the GA4 ecommerce tutorial.
These are pinned against drift by the doc-lint at
apps/scripts/validate-docs.ts (run npx tsx apps/scripts/validate-docs.ts).
It extracts canonical tokens from the Tier-1 source and asserts the docs
snippets contain them, tolerating intentional doc simplifications (dropping the
seen capture, arrow push instead of a method). first-event.ts is pinned on
the quickstart, integrated-mode, and README pages, plus the GA4 ecommerce page;
react.mdx is pinned shape-only (it has no elb('page view') push).
The doc-lint also runs these structural and coherence checks:
- Bundled-snippet platform guard: every JSON flow with
bundlecarriesconfig.platform(getting-started, README, flow reference). - Docker env guard: quickstart Docker snippets use
BUNDLE, notFLOW. - Forbidden CLI verbs: no
walkeros serveor standalonewalkeros simulatein getting-started, guides, or the gtag page. - Same-page simulate-key resolution (getting-started): every
push --simulate <type>.<key>must resolve to a step key declared in a flow.json on the SAME page; a simulate command on a page with no flow.json fails. This is what catches an integrated page showing a bundled push loop. - Flow.json schema validation (all docs): every full
version: 4flow snippet is run throughschemas.validateFlowConfigfrom@walkeros/core/dev; fragments andversion: 3migration "before" snippets are out of scope. - Boundary register +
:::cloudplacement (getting-started + MCP page): no prices, tier names, or upgrade language;:::cloudis forbidden on golden-path pages and required where an app-only capability is documented.
The rule: when you edit a pinned example, edit the Tier-1 .ts source and
re-run the doc-lint. If the docs snippet must change too, update both so the
lint passes.
Getting-started editorial rules
Three rules govern the getting-started surface (decided 2026-07-06, encoded in the doc-lint):
See your event, per mode. Integrated-mode pages (quickstart, react, nextjs,
modes/integrated, ga4-ecommerce) verify via their own console output; they must
never show walkeros push --simulate. Only bundled-mode pages may show it, and
only targeting a <type>.<key> that a flow.json on the SAME page defines.
Open/paid boundary (Self-Host Sufficiency Rule). Every getting-started task
must reach a working result with MIT packages, the CLI, and reader-controlled
infrastructure only. The hosted app appears at most once per page, only as a
marked optional alternative, only via the :::cloud admonition, and only on the
Deploy page and the MCP page. The :::cloud template: first sentence names the
capability, second names the open-source path to the same result, optional third
links https://app.walkeros.io. Never prices, tier names, or upgrade language
anywhere in open docs. Self-host is always listed first.
Examples recipes. Pages under getting-started/examples/ are lean proof
recipes with a fixed shape: exactly one flow.json, one run loop, one
expected-output block, plus a short intro and next steps. The run loop uses
walkeros push … --simulate <type>.<key> where <key> is a step key declared
in the page's own flow.json; recipes are bundled flows, so the per-mode rule
applies and the same-page simulate gate enforces resolution. The expected-output
block shows what actually arrives (the captured destination call or the logged
event), derived from the package's step examples or the real CLI output; never
invent config, mappings, or output. Every snippet is derived from validated
material (package examples, tested sources, existing validated docs snippets).
No :::cloud on recipe pages. The shape is enforced by checkRecipeProof in
apps/scripts/validate-docs.ts.
Validation Checklist
Before publishing ANY code example:
- API exists? Check
packages/*/src/index.tsexports - Pattern validated? Compare against
apps/quickstart/ - Events canonical? Use patterns from
eventGenerator.ts - Example compiles? TypeScript check passes
- Imports correct? Package names match actual packages
Red Flags
| Red Flag | What It Indicates |
|---|---|
| API name not in package exports | Hallucinated or outdated API |
| Import path doesn't match package.json | Wrong package reference |
| Event name with underscore | Wrong format (should be space) |
| No imports shown | Context missing, harder to validate |
DRY Patterns
Configuration snippet (package pages)
When to use: Every package page for destinations, sources, transformers, and
stores. The <Configuration> snippet owns the ## Configuration,
## Settings, ## Mapping, and ## Examples H2 sections. One callsite per
page, after ## Installation.
import data from '@walkeros/web-destination-gtag/walkerOS.json';
import Configuration from '@site/src/components/snippets/_configuration.mdx';
## Installation
<CodeSnippet
code={`npm install @walkeros/web-destination-gtag`}
language="bash"
/>
<Configuration type="destination" data={data} />
type must be one of destination, source, transformer, store. data is
the full walkerOS.json import. The snippet:
- emits
## Configurationwith a short pointer to the shared group-level configuration reference, - emits
## Settingsusing<PropertyTable schema={data.schemas.settings} />when the schema is present, otherwise a "no package-specific settings" note, - emits
## Mappingusing<PropertyTable schema={data.schemas.mapping} />when present, otherwise a pointer to the standard rule fields, - emits
## Examplesonly ifdata.examples.env.initordata.examples.step.*is present; iteratesexamples.stepand renders each through<StepExample>.
Never hand-roll ## Configuration, ## Settings, ## Mapping, or
## Examples on a package page. Place package-specific content (vendor quirks,
advanced usage, platform-specific setup) BELOW the <Configuration> block with
its own sentence-case H2 heading. Fold ## Features bullet lists into the intro
paragraph as prose — no ## Features heading on package pages.
Shared configuration reference (group index pages)
On group index pages (docs/destinations/index.mdx, sources/index.mdx,
transformers/index.mdx, stores/index.mdx), add a ## Configuration section
that documents the wrapper fields around settings — fields shared across all
packages of that type (consent, mapping, env, id, …).
Drive the table from the core Zod schemas, filtering dev-only fields at the representation layer:
import { schemas } from '@walkeros/core/dev';
import { omitSchemaProperties } from '@site/src/utils/schema';
export const destinationConfig = omitSchemaProperties(
schemas.DestinationSchemas.configJsonSchema,
['settings', 'init', 'mock', 'onError', 'onLog'],
);
## Configuration
<PropertyTable schema={destinationConfig} />
Available schemas:
schemas.DestinationSchemas.configJsonSchemaschemas.SourceSchemas.configJsonSchemaschemas.TransformerSchemas.configJsonSchemaschemas.StoreSchemas.configJsonSchema
Use omitSchemaProperties to hide settings (documented per-package) and any
dev-only fields (init, mock, chainMocks, onError, onLog) from the
user-facing table.
Code display: CodeView vs CodeBox
@walkeros/explorer exports two components for code display:
<CodeBox>renders Monaco, the full editor. Use when the user can edit the code (playgrounds, interactive demos). Ships the IDE.<CodeView>renders Shiki, read-only. Use for static code in docs, marketing pages, and anywhere editing is not a requirement. Same<Box>frame as<CodeBox>(traffic lights, header, copy button, tabs) so visuals match. Zero Monaco runtime cost.
Tokens match because Shiki uses the same TextMate grammars as VS Code / Monaco,
with the matching dark-plus / light-plus themes.
<CodeView> and <CodeSnippet> render their code server-side, so it appears in
the static HTML and the markdown/llms.txt export. Code is shown exactly as
authored (no automatic reformatting), so format multi-line snippets before
putting them in the code prop.
Rule: default to <CodeView>. Only reach for <CodeBox> when the code must
be editable.
PropertyTable component
<PropertyTable schema={...} /> from @walkeros/explorer is the primitive. It
renders a pure table — no heading, no captions. Never wrap it in a custom
heading on package pages; use the <Settings /> snippet instead.
When NOT to use:
- Pages without package configuration
- Reference tables (Logger API, CLI commands)
- Conceptual explanations
Schema Exports (dev.ts)
Every destination/source should export schemas:
// src/dev.ts
export * as schemas from './schemas';
export * as examples from './examples';
Don't Duplicate
- Link to source files instead of copying type definitions
- Reference
apps/quickstart/examples instead of writing from scratch - Use PropertyTable instead of hardcoded markdown tables
Writing Hints (src/hints.ts)
Hints are the "experienced colleague" layer in walkerOS.json — they tell AI
agents when, why, and what to watch out for beyond what schemas and
examples convey. Surfaced via MCP package_get. Not human-facing docs.
Audience: AI agents configuring packages on behalf of users.
Core Principle: Expand Awareness, Don't Narrow It
Hints should open up the space of what's possible, not prescribe a single path. An LLM reading hints should think "I have more options than I realized" — not "I must follow these steps exactly."
Writing Rules
| Rule | Do | Don't |
|---|---|---|
| Describe capabilities | "Supports SA key, ADC, and custom client" | "Use a SA key file when outside GCP" |
| Reference schemas/examples | "See settings.projectId in the schema" | Repeat what the schema description says |
| Explain why behind defaults | "Defaults to EU location; override via location" | "Set location to US" |
| Flag non-obvious interactions | "When snakeCase: true, all data keys transform before send" | Describe obvious behavior |
| Symptoms → causes | "Empty table? Check projectId and dataset existence" | Step-by-step fix instructions |
Key Naming
- kebab-case, group related hints with prefixes:
auth-*,storage-*,query-*,troubleshoot-* - Keep keys descriptive enough to scan:
auth-methodsnota1
When to Add Hints
Most packages don't need hints — schemas and examples cover the common case. Add hints when:
- Multiple auth or config strategies exist and it's non-obvious when to use which
- Non-obvious default behaviors need explaining
- Features interact in ways the schema can't express
- Prerequisites outside walkerOS are required
- Common troubleshooting patterns exist
Accuracy Check
Before publishing hints, verify each claim is factually correct. Don't describe features that aren't implemented. Don't assume behavior — confirm it.
Quality Check
- Each hint expands awareness (describes capabilities, not prescriptions)
- Code snippets reference schema fields, not duplicate them
- No hint restates what a schema
.describe()already says - Troubleshooting hints use symptom → cause format
- Hints are atomic — one concept per hint
- Every claim is factually correct against current implementation
Export Pattern
// src/hints.ts
import type { Hints } from '@walkeros/core';
export const hints: Hints = {
'auth-methods': {
text: 'Supports three auth methods: ...',
code: [{ lang: 'json', code: '{ "settings": { ... } }' }],
},
};
// src/dev.ts
export * as schemas from './schemas';
export * as examples from './examples';
export { hints } from './hints';
Note: hints is a direct export (not * as), because it's already a
Record<string, Hint>.
Quality Checklist
Structure
- Follows appropriate Divio type (Tutorial/How-To/Reference/Explanation)
- Code example within first 100 words
- First example under 20 lines
- Uses
<details>for advanced content - Has "Next Steps" or "Related" section
Content
- All event names use
"entity action"format with space - Flow config shown as primary usage pattern
- Examples are complete and copy-pasteable
- Includes imports in code examples
AI Readability
- Clear semantic headers (H2, H3, H4 hierarchy - no skipped levels)
- Tables for structured data
- Links to source of truth TypeScript files
- Static fallback content alongside dynamic components
Consistency
- Uses standard table formats
- Follows package/skill/website templates
- Terminology matches: walkerOS, collector, destination, source
- Headings use sentence case (e.g., "Next steps" not "Next Steps")
-
walkerOS.jsonconvention followed (walkerOS field in package.json, buildDev in tsup)
Templates
Package README Template (thin pointer)
READMEs are thin pointers to the website docs, which are the single source
of truth. A README carries only: logo, title, one-line description, install, ONE
current version: 4 Quick Start, and a prominent link to the package's docs
page. No ## Features, no ## Configuration Reference, no inline API
reference, no ## Examples block. All reference content lives on the website so
it can never drift in two places.
The docs link MUST be an absolute https://www.walkeros.io/docs/... URL, never
a relative path: packages publish to npm individually and relative links break
on npmjs.com. These absolute docs links are validated by
npm run validate:links (it maps the URL to the website/docs/ source file and
asserts it exists).
<p align="left">
<a href="https://www.walkeros.io">
<img alt="walkerOS" title="walkerOS" src="https://www.walkeros.io/img/walkerOS_logo.svg" width="256px"/>
</a>
</p>
# @walkeros/[package-name]
[one-sentence description, adapted from the docs page intro]
[Documentation](https://www.walkeros.io/docs/[path]) •
[NPM Package](https://www.npmjs.com/package/@walkeros/[package-name]) •
[Source Code](https://github.com/elbwalker/walkerOS/tree/main/packages/[path])
## Installation
```bash
npm install @walkeros/[package-name]
```
## Quick start
```json
{
"version": 4,
"flows": {
"default": {
"config": { "platform": "[web|server]" },
"[sources|destinations]": {
"[name]": {
"package": "@walkeros/[package-name]",
"config": {}
}
}
}
}
}
```
## Documentation
Full configuration, mapping, and examples live in the docs:
**https://www.walkeros.io/docs/[path]**
## Contribute
Feel free to contribute by submitting an
[issue](https://github.com/elbwalker/walkerOS/issues), starting a
[discussion](https://github.com/elbwalker/walkerOS/discussions), or getting in
[contact](https://calendly.com/elb-alexander/30min).
## License
MIT
Verify the Quick Start against apps/quickstart/ and the package's real export
name before publishing. Platform-agnostic packages (core, collector) show their
most representative current usage instead of a config.platform flow.
walkerOS.json
Every package should document its walkerOS.json convention in the README:
{
"walkerOS": { "type": "destination", "platform": "web" }
}
The walkerOS field is an object with type and platform metadata describing
the package's role in the walkerOS ecosystem.
Website Doc Template (MDX)
Website docs fold features into the intro paragraph (no ## Features heading).
Thin-pointer READMEs also have no ## Features heading; key capabilities go
into the one-line description. This template is website-only.
---
title: [Title]
description: [SEO description]
sidebar_position: [N]
---
import data from '@walkeros/[package]/walkerOS.json';
import Configuration from '@site/src/components/snippets/_configuration.mdx';
# [Title]
<PackageLink package="@walkeros/[package]" />
[1-sentence description that folds in key features as prose — no separate `##
Features` heading on website docs.]
## Quick start
```json
// Flow config example (<15 lines)
```
Installation
<CodeSnippet code={npm install @walkeros/[package]} language="bash" />
<Configuration type="destination" data={data} />
Next steps
Related guide 1
---
## Priority Matrix
### Issue Classification
| Priority | Criteria | Action |
|----------|----------|--------|
| **P0 Critical** | Incorrect examples, wrong APIs, security issues | Fix immediately |
| **P1 High** | Missing PropertyTable, outdated domains, missing sections | Fix soon |
| **P2 Medium** | Inconsistent terminology, skipped headings | Plan to fix |
| **P3 Low** | Style issues, minor wording | Backlog |
---
## Non-Negotiables
### Punctuation
Never use em dashes (`—`). Use a comma, period, or rephrase the sentence
instead.
```text
CORRECT: "free and without sampling caps"
WRONG: "free — without sampling caps"
```
### Event Naming
Event naming (`"entity action"`, space not underscore): see
[walkeros-understanding-events](../walkeros-understanding-events/SKILL.md).
Package References
CORRECT: `@walkeros/collector` (with backticks) WRONG: @walkeros/collector (no
backticks)
Domain References
CORRECT: `www.walkeros.io` or relative paths DO NOT USE: legacy domain
references
Process
For New Package Documentation
- Verify examples exist in
apps/quickstart/or create them first - Write README using template above
- Write website doc using MDX template
- Run quality checklist
- Verify all code examples against Tier 1-3 sources
For Documentation Updates
- Identify issue priority using matrix above
- Check current state against source of truth
- Make minimal changes - don't over-engineer
- Verify examples still compile
- Run quality checklist
Schema naming (.meta({id, title}))
Every exported schema in packages/core/src/schemas/*.ts (and in the *Schema
exports of destination / source / transformer / store packages) must carry
.meta({ id, title, description }) so the generated JSON Schema links back to
the canonical TypeScript name. Without meta, the website's PropertyTable falls
back to __schema0, object, or any.
Convention:
- id — PascalCase, namespace-prefixed so it is globally unique across every
schema in
packages/core/src/schemas. Examples:WalkerOSConsent,DestinationConfig,CollectorPushContext,LoggerConfig. - title — dotted form matching the VS Code TS hover:
WalkerOS.Consent,Destination.Config,Collector.PushContext,Logger.Config. - description — one short sentence. May repeat an existing
.describe()where both are present.
Reference implementation (copy this shape for new schemas):
export const ConsentSchema = z.record(z.string(), z.boolean()).meta({
id: 'WalkerOSConsent',
title: 'WalkerOS.Consent',
description: 'Consent state mapping (group name to granted state).',
});
Coverage is enforced by
packages/core/src/schemas/__tests__/meta-coverage.test.ts. When adding a new
schema, run that test — it will fail with the exact schema name if meta is
missing.
Destination / source / transformer / store packages that ship their own
SettingsSchema should also call .meta({ id, title }) on it, e.g.
DestinationGtag.Settings / GtagDestinationSettings.
Related Skills
- walkeros-understanding-flow - Data flow architecture
- walkeros-understanding-events - Event structure and naming