> Part of the walkerOS documentation. Project overview and full index: <https://www.walkeros.io/llms.txt>

# Writing Documentation

## When to Use This Skill[​](#when-to-use-this-skill "Direct link to 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[​](#prerequisites "Direct link to Prerequisites")

* [understanding-flow](https://www.walkeros.io/skills/walkeros-understanding-flow.md) - Architecture context
* [understanding-events](https://www.walkeros.io/skills/walkeros-understanding-events.md) - Event naming rules

***

## Documentation Types[​](#documentation-types "Direct link to Documentation Types")

### Where Content Belongs[​](#where-content-belongs "Direct link to 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[​](#divio-documentation-types "Direct link to 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)[​](#example-validation-critical "Direct link to Example Validation (CRITICAL)")

### The Problem[​](#the-problem "Direct link to 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[​](#source-of-truth-hierarchy "Direct link to 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[​](#pinned-onboarding-examples "Direct link to 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`, the `product add` to GA4 `add_to_cart` mapping 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 `bundle` carries `config.platform` (getting-started, README, flow reference).
* **Docker env guard**: quickstart Docker snippets use `BUNDLE`, not `FLOW`.
* **Forbidden CLI verbs**: no `walkeros serve` or standalone `walkeros simulate` in 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: 4` flow snippet is run through `schemas.validateFlowConfig` from `@walkeros/core/dev`; fragments and `version: 3` migration "before" snippets are out of scope.
* **Boundary register + `:::cloud` placement** (getting-started + MCP page): no prices, tier names, or upgrade language; `:::cloud` is 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[​](#getting-started-editorial-rules "Direct link to 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[​](#validation-checklist "Direct link to Validation Checklist")

Before publishing ANY code example:

* [ ] **API exists?** Check `packages/*/src/index.ts` exports
* [ ] **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-flags "Direct link to 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[​](#dry-patterns "Direct link to DRY Patterns")

### Configuration snippet (package pages)[​](#configuration-snippet-package-pages "Direct link to 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 `## Configuration` with a short pointer to the shared group-level configuration reference,
* emits `## Settings` using `<PropertyTable schema={data.schemas.settings} />` when the schema is present, otherwise a "no package-specific settings" note,
* emits `## Mapping` using `<PropertyTable schema={data.schemas.mapping} />` when present, otherwise a pointer to the standard rule fields,
* emits `## Examples` only if `data.examples.env.init` or `data.examples.step.*` is present; iterates `examples.step` and 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)[​](#shared-configuration-reference-group-index-pages "Direct link to 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.configJsonSchema`
* `schemas.SourceSchemas.configJsonSchema`
* `schemas.TransformerSchemas.configJsonSchema`
* `schemas.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[​](#code-display-codeview-vs-codebox "Direct link to 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-component "Direct link to 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)[​](#schema-exports-devts "Direct link to 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[​](#dont-duplicate "Direct link to 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`)[​](#writing-hints-srchintsts "Direct link to writing-hints-srchintsts")

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[​](#core-principle-expand-awareness-dont-narrow-it "Direct link to 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[​](#writing-rules "Direct link to 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[​](#key-naming "Direct link to Key Naming")

* **kebab-case**, group related hints with prefixes: `auth-*`, `storage-*`, `query-*`, `troubleshoot-*`
* Keep keys descriptive enough to scan: `auth-methods` not `a1`

### When to Add Hints[​](#when-to-add-hints "Direct link to 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[​](#accuracy-check "Direct link to 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[​](#quality-check "Direct link to 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[​](#export-pattern "Direct link to 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[​](#quality-checklist "Direct link to Quality Checklist")

### Structure[​](#structure "Direct link to 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[​](#content "Direct link to 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[​](#ai-readability "Direct link to 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[​](#consistency "Direct link to 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.json` convention followed (walkerOS field in package.json, buildDev in tsup)

***

## Templates[​](#templates "Direct link to Templates")

### Package README Template (thin pointer)[​](#package-readme-template-thin-pointer "Direct link to 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]) &bull;

[NPM Package](https://www.npmjs.com/package/@walkeros/[package-name]) &bull;

[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[​](#walkerosjson "Direct link to 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-doc-template-mdx "Direct link to 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[​](#installation "Direct link to Installation")

\<CodeSnippet code={`npm install @walkeros/[package]`} language="bash" />

\<Configuration type="destination" data={data} />

## Next steps[​](#next-steps "Direct link to 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[​](#package-references "Direct link to Package References")

```


CORRECT: `@walkeros/collector` (with backticks) WRONG: @walkeros/collector (no

backticks)
```

### Domain References[​](#domain-references "Direct link to Domain References")

```


CORRECT: `www.walkeros.io` or relative paths DO NOT USE: legacy domain

references
```

***

## Process[​](#process "Direct link to Process")

### For New Package Documentation[​](#for-new-package-documentation "Direct link to For New Package Documentation")

1. **Verify examples exist** in `apps/quickstart/` or create them first
2. **Write README** using template above
3. **Write website doc** using MDX template
4. **Run quality checklist**
5. **Verify all code examples** against Tier 1-3 sources

### For Documentation Updates[​](#for-documentation-updates "Direct link to For Documentation Updates")

1. **Identify issue priority** using matrix above
2. **Check current state** against source of truth
3. **Make minimal changes** - don't over-engineer
4. **Verify examples still compile**
5. **Run quality checklist**

***

## Schema naming (`.meta({id, title})`)[​](#schema-naming-metaid-title "Direct link to schema-naming-metaid-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[​](#related-skills "Direct link to Related Skills")

* [walkeros-understanding-flow](https://www.walkeros.io/skills/walkeros-understanding-flow.md) - Data flow architecture
* [walkeros-understanding-events](https://www.walkeros.io/skills/walkeros-understanding-events.md) - Event structure and naming
