113 added, 131 removed. Audit A to A.
# Agent Harness Docs
- Local docs mirror for AI coding agents — auto-synced, indexer-agnostic.
+ Local docs mirror for AI coding agents, consumed through per-agent skills that
+ search the docs with [Miyo](https://miyo.md).
- Today this repo collects documentation for four coding-agent harnesses:
+ This repo collects documentation for four coding-agent harnesses:
- **Claude Code** — sourced from `code.claude.com` via sitemap + `.md` twin
- **OpenAI Codex** — sourced from `developers.openai.com/codex/llms.txt` (Vercel-fronted, needs Chrome `sec-ch-ua*` / `sec-fetch-*` headers)
- **OpenCode** — sourced from `github.com/anomalyco/opencode` via `git sparse-checkout` on `packages/web/src/content/docs/*.mdx`, then converted MDX → Markdown by a Node `remark-mdx` pipeline
- - **Pi** — sourced from `github.com/earendil-works/pi` via `git sparse-checkout` on `packages/coding-agent/docs/*.md` (already clean Markdown, no conversion needed)
+ - **Pi** — sourced from `github.com/earendil-works/pi` via `git sparse-checkout` on `packages/coding-agent/docs/*.md` (already clean Markdown)
- More harnesses to come. Docs update upstream every 3 hours (GitHub
- Actions). The installer wires up a launchd job that syncs all four into
- the user's home every hour. End users decide how to index/search the
- files (Miyo, ripgrep, Cursor index, plain Read — anything works).
+ CI re-fetches every 3 hours (GitHub Actions) and commits to `docs/`. Users
+ `git pull`, index the four `docs/<docset>/` folders in Miyo, and install a small
+ skill per agent. There is **no global installer and no background daemon** — the
+ old macOS `install.sh` / launchd model was removed.
+ ## Toolchain — mise is the source of truth
+
+ Every tool, dependency, and task is declared in [`mise.toml`](mise.toml). Never
+ `pip install` / `npm install -g` by hand — add it to `mise.toml` and run
+ `mise install`. Entering the repo (`cd` in under `mise activate`) installs the
+ toolchain + deps and wires the local git hooks.
+
+ - **Tools** (`[tools]`): python, uv, node, ruff, hk, pkl (pinned; exact for
+ runtimes, `latest` for CLIs — [`mise.lock`](mise.lock) records exact versions).
+ - **Deps** (`[deps.*]`): `uv pip install -r scripts/requirements.txt` and
+ `npm ci` in `scripts/`, blake3 freshness-checked.
+ - **Tasks**: aggregates (`fetch`, `check`, `ci`, `setup`) live in `mise.toml`;
+ leaf tasks live as auto-discovered file-tasks in [`.mise/tasks/`](.mise/tasks/)
+ (`render`, `fmt`, `lint/python`, `fetch/{claude,codex,opencode,pi}`).
+
+ ```bash
+ mise run fetch # re-fetch all four docsets
+ mise run render # templates → skills/<docset>/SKILL.md
+ mise run check # ruff lint + ruff format-check + render-check
+ mise run ci # render + check
+ ```
+
+ Git hooks ([`hk.pkl`](hk.pkl)): pre-commit runs safety builtins + `mise run check`.
+ `docs/**` is excluded from the whitespace/newline auto-fixers (it is a verbatim
+ upstream mirror). Hooks install on **local** repo entry only, never in CI, so the
+ docs-bot's hourly auto-commits to `main` stay ungated. Line endings are LF
+ everywhere ([`.gitattributes`](.gitattributes)).
+
## Repo layout
```
agent-harness-docs/
- ├── docs/
- │ ├── claude-code/ ~140 .md + docs_manifest.json
- │ ├── codex/ ~80 .md + docs_manifest.json
- │ ├── opencode/ ~34 .md + docs_manifest.json
- │ └── pi/ ~27 .md + docs_manifest.json
- ├── scripts/
- │ ├── fetch_claude_docs.py → writes docs/claude-code/
- │ ├── fetch_codex_docs.py → writes docs/codex/
- │ ├── fetch_opencode_docs.py → writes docs/opencode/
- │ ├── fetch_pi_docs.py → writes docs/pi/
- │ ├── mdx_to_md.mjs Node MDX → MD converter (used by opencode fetcher)
- │ ├── package.json Node deps for the converter
- │ ├── package-lock.json committed for reproducible CI installs
- │ ├── render_skills.py renders Jinja2 → SKILL.md
- │ └── requirements.txt requests, Jinja2 (maintainer-only)
+ ├── docs/ # mirrored docs (CI-updated) — index these in Miyo
+ │ ├── claude-code/ ~150 .md + docs_manifest.json
+ │ ├── codex/ ~90 .md + docs_manifest.json
+ │ ├── opencode/ ~35 .md + docs_manifest.json
+ │ └── pi/ ~27 .md + docs_manifest.json
├── skills/
- │ ├── templates/ Jinja2 source-of-truth
- │ │ ├── claude-code-docs.SKILL.md.j2
- │ │ ├── codex-docs.SKILL.md.j2
- │ │ ├── opencode-docs.SKILL.md.j2
- │ │ └── pi-docs.SKILL.md.j2
- │ ├── claude-code-docs/ rendered variants
- │ │ ├── plain/SKILL.md ls/grep/Read flow
- │ │ └── miyo/SKILL.md Miyo MCP flow
- │ ├── codex-docs/
- │ │ ├── plain/SKILL.md
- │ │ └── miyo/SKILL.md
- │ ├── opencode-docs/
- │ │ ├── plain/SKILL.md
- │ │ └── miyo/SKILL.md
- │ └── pi-docs/
- │ ├── plain/SKILL.md
- │ └── miyo/SKILL.md
- ├── rules/
- │ └── claude-code-docs.md Codex/OpenCode have no rule-file analog; skill metadata is enough
- ├── install.sh macOS-only; quad rsync + indexer-aware skill pick
- ├── uninstall.sh
- └── .github/workflows/update-docs.yml runs all four fetchers in one job
+ │ ├── templates/ # Jinja2 source of truth
+ │ │ └── <docset>.SKILL.md.j2
+ │ └── <docset>/SKILL.md # rendered, ONE per docset (Miyo-only)
+ ├── scripts/
+ │ ├── fetch_claude_docs.py → docs/claude-code/
+ │ ├── fetch_codex_docs.py → docs/codex/
+ │ ├── fetch_opencode_docs.py → docs/opencode/
+ │ ├── fetch_pi_docs.py → docs/pi/
+ │ ├── mdx_to_md.mjs # Node MDX → MD (used by opencode fetcher)
+ │ ├── render_skills.py # templates → skills/<docset>/SKILL.md
+ │ ├── requirements.txt package.json package-lock.json
+ ├── mise.toml mise.lock # toolchain + deps + tasks
+ ├── .mise/tasks/ # leaf file-tasks
+ ├── hk.pkl # git hooks
+ ├── ruff.toml .editorconfig .flake8 .gitattributes
+ └── .github/workflows/update-docs.yml # CI: fetch + commit every 3h
```
## How users consume this repo
- `install.sh` does NOT clone the repo into the user's home. Instead it:
+ 1. `git pull` to get fresh `docs/`.
+ 2. Index each `docs/<docset>/` folder in Miyo with the label `claude-code`,
+ `codex`, `opencode`, or `pi`.
+ 3. Install the skill for each agent with the [`skills`](https://skills.sh) CLI —
+ **one skill per matching agent** (Claude Code doesn't need Codex docs):
- 1. Maintains a hidden cache clone at `~/Library/Caches/agent-harness-docs-mirror/`
- 2. Rsyncs `docs/claude-code/*` → `~/claude-code-docs/` (flat)
- 3. Rsyncs `docs/codex/*` → `~/codex-docs/` (flat)
- 4. Rsyncs `docs/opencode/*` → `~/opencode-docs/` (flat)
- 5. Rsyncs `docs/pi/*` → `~/pi-docs/` (flat)
- 6. Installs **one** launchd job that re-runs the sync every hour
- 7. **Detects** the user's indexer (currently: `miyo` or `plain` fallback)
- 8. Copies skills into the agents' native locations:
- - `~/.agents/skills/claude-code-docs/SKILL.md` (shared) + symlink at `~/.claude/skills/claude-code-docs`
- - `~/.agents/skills/codex-docs/SKILL.md` (Codex)
- - `~/.agents/skills/opencode-docs/SKILL.md` (shared) + symlink at `~/.claude/skills/opencode-docs`
- - `~/.agents/skills/pi-docs/SKILL.md` (shared) + symlink at `~/.claude/skills/pi-docs`
- 9. Copies `rules/claude-code-docs.md` → `~/.claude/rules/`
- 10. If indexer is `miyo`:
- - Registers Miyo MCP with `claude` and `codex` CLIs (idempotent)
- - Merges a Miyo entry into `~/.config/opencode/opencode.json[c]` (idempotent; bails out cleanly if the user's file has comments, printing a paste-able snippet instead)
+ ```bash
+ npx skills add mrkhachaturov/agent-harness-docs -g -a claude-code --skill claude-code-docs
+ npx skills add mrkhachaturov/agent-harness-docs -g -a codex --skill codex-docs
+ npx skills add mrkhachaturov/agent-harness-docs -g -a opencode --skill opencode-docs
+ npx skills add mrkhachaturov/agent-harness-docs -g -a pi --skill pi-docs
+ ```
- End users with Miyo then add the four folders to Miyo with labels
- `claude-code-docs`, `codex-docs`, `opencode-docs`, `pi-docs`. Users
- without an indexer get the `plain` skill variant that tells the agent to
- use ls/grep/Read directly.
+ `npx skills add . …` installs from a local clone without a push. Skills are
+ `disable-model-invocation: true` (manual-only, zero idle context) and invoked
+ with `/<name>`.
## Four source-fetching patterns
| Source | Discovery | Per-page URL or method |
|---|---|---|
| Claude Code | `https://code.claude.com/docs/sitemap.xml` | `<page-url>.md` |
| Codex | `https://developers.openai.com/codex/llms.txt` (markdown list) | `https://developers.openai.com/codex/<slug>.md` |
| OpenCode | `git ls-remote` for default branch on `anomalyco/opencode` | `git sparse-checkout` of `/packages/web/src/content/docs/*.mdx` (non-cone), then `mdx_to_md.mjs` |
| Pi | `git ls-remote` for default branch on `earendil-works/pi` | `git sparse-checkout` of `/packages/coding-agent/docs/*.md` (non-cone), direct copy |
- Codex pages 403 against bare `curl` — the fetcher sends the full Chrome
- header set (`sec-ch-ua`, `sec-ch-ua-mobile`, `sec-ch-ua-platform`,
- `sec-fetch-*`); without those Vercel returns "deny". TLS fingerprint is
- not checked, just headers.
-
- OpenCode uses Astro Starlight MDX with `import` / `export` statements and
- JSX components (`<Tabs>`, `<TabItem>`, `<Card>`, ...). The MDX→MD pipeline
- strips imports, unwraps JSX (keeping children), and emits clean Markdown
- that any indexer can read. The pipeline lives in `scripts/mdx_to_md.mjs`
- and is invoked as a subprocess by `fetch_opencode_docs.py`. Frontmatter,
- fenced code, lists, tables, and Starlight directive callouts (`:::tip`,
- `:::note`) are preserved.
-
- Pi docs are already clean Markdown — no MDX or HTML conversion is needed.
- The fetcher simply sparse-checks out the `.md` files and copies them into
- `docs/pi/`.
+ Codex pages 403 against bare `curl` — the fetcher sends the full Chrome header
+ set (`sec-ch-ua`, `sec-ch-ua-mobile`, `sec-ch-ua-platform`, `sec-fetch-*`);
+ without those Vercel returns "deny". TLS fingerprint is not checked, just headers.
- ## Skills — three shared, one per-harness
+ OpenCode uses Astro Starlight MDX with `import` / `export` statements and JSX
+ components (`<Tabs>`, `<TabItem>`, `<Card>`, …). The MDX→MD pipeline
+ ([`scripts/mdx_to_md.mjs`](scripts/mdx_to_md.mjs)) strips imports, unwraps JSX
+ (keeping children), and emits clean Markdown. Frontmatter, fenced code, lists,
+ tables, and Starlight directive callouts (`:::tip`, `:::note`) are preserved.
- Each agent scans its own conventional skill location:
+ Pi docs are already clean Markdown — no conversion needed.
- - **Claude Code** → `~/.claude/skills/`
- - **Codex** → `~/.agents/skills/` (open agent skills standard)
- - **OpenCode** → `~/.config/opencode/skills/` (native), `~/.claude/skills/`, AND `~/.agents/skills/` — all three are read
+ ## Skills
- `claude-code-docs`, `opencode-docs`, and `pi-docs` are **shared**: any
- harness can use them when the user asks about Claude Code, OpenCode, or Pi
- while chatting. The canonical SKILL.md lives at `~/.agents/skills/<docset>/`
- (which opencode AND codex pick up natively), and a symlink at
- `~/.claude/skills/<docset>` exposes it to Claude Code too.
+ One skill per docset, rendered to `skills/<docset>/SKILL.md`. Each skill:
- `codex-docs` is the only per-harness skill — it lives solely in the agent's
- own folder (`~/.agents/skills/codex-docs/`), which Codex reads natively.
+ - addresses its docs **only through the Miyo folder label** (`claude-code`,
+ `codex`, `opencode`, `pi`) — no hardcoded filesystem path, so it is portable
+ across macOS / Linux / Windows;
+ - carries `disable-model-invocation: true` — manual-only, zero idle context,
+ invoked with `/<name>`;
+ - follows the same shape: portable-access note, a "how Miyo retrieval works
+ (build better queries)" section, whole-docset + `path`-scoped search,
+ when-to-read-the-full-file, anti-patterns, and `/<name>` sub-commands.
- This is the pattern all cross-harness skills should follow.
+ Install each to its **own** agent (`-a claude-code` / `-a codex` / `-a opencode`
+ / `-a pi`); cross-install only when deliberately useful. The `skills` CLI places
+ each agent's skill in that agent's conventional directory automatically.
## Skill rendering
- Skills are written once in `skills/templates/<docset>.SKILL.md.j2` with
- Jinja2 `{% if indexer == "miyo" %}…{% else %}…{% endif %}` blocks for the
- indexer-specific bits. `scripts/render_skills.py` expands each template
- into one file per indexer at `skills/<docset>/<indexer>/SKILL.md`. Those
- rendered files are committed to the repo so `install.sh` stays bash-only
- — no Python or Node on the user's machine for installation. CI runs
- `render_skills.py --check` to guard against template/output drift.
-
- Adding a new indexer:
-
- 1. Add the name to `INDEXERS` in `scripts/render_skills.py`.
- 2. Extend each Jinja2 template with `{% elif indexer == "<new>" %}` blocks.
- 3. Extend the `detect_indexer()` function in `install.sh`.
- 4. Run `python scripts/render_skills.py` and commit everything together.
+ Skills are written once in
+ [`skills/templates/<docset>.SKILL.md.j2`](skills/templates/) and rendered to
+ `skills/<docset>/SKILL.md` by
+ [`scripts/render_skills.py`](scripts/render_skills.py) (`mise run render`). The
+ rendered files are committed; `mise run check` runs `render --check` to guard
+ against template/output drift. There are no per-indexer variants — the skills are
+ Miyo-only.
## MDX→MD conversion
- The OpenCode fetcher is the only one that needs Node — for MDX parsing.
- The Node side is fully isolated:
+ The OpenCode fetcher is the only one that needs Node — for MDX parsing. The Node
+ side is isolated under `scripts/`:
- - `scripts/package.json` lists deps (`unified`, `remark-parse`,
- `remark-mdx`, `remark-gfm`, `remark-stringify`, `unist-util-visit`,
- `mdast-util-to-string`)
- - `scripts/package-lock.json` is committed so CI reproduces exactly
- - CI runs `npm ci` inside `scripts/` before invoking the fetcher
+ - `scripts/package.json` lists deps (`unified`, `remark-parse`, `remark-mdx`,
+ `remark-gfm`, `remark-stringify`, `unist-util-visit`, `mdast-util-to-string`)
+ - `scripts/package-lock.json` is committed so installs reproduce exactly
+ - `mise run deps` runs `npm ci` inside `scripts/`
- `scripts/mdx_to_md.mjs` reads MDX from stdin, emits Markdown on stdout
- `fetch_opencode_docs.py` calls `node scripts/mdx_to_md.mjs` per file
- - End users (running `install.sh`) never see Node — they just rsync the
- pre-converted `.md` files
## Key files
- @install.sh
- @uninstall.sh
- @scripts/fetch_claude_docs.py
- @scripts/fetch_codex_docs.py
- @scripts/fetch_opencode_docs.py
- @scripts/fetch_pi_docs.py
- @scripts/mdx_to_md.mjs
- @scripts/render_skills.py
- @skills/templates/claude-code-docs.SKILL.md.j2
- @skills/templates/codex-docs.SKILL.md.j2
- @skills/templates/opencode-docs.SKILL.md.j2
- @skills/templates/pi-docs.SKILL.md.j2
- @rules/claude-code-docs.md
- @.github/workflows/update-docs.yml
+ - [`mise.toml`](mise.toml) — toolchain, deps, task aggregates
+ - [`.mise/tasks/`](.mise/tasks/) — leaf file-tasks
+ - [`hk.pkl`](hk.pkl) — git hooks
+ - [`scripts/fetch_claude_docs.py`](scripts/fetch_claude_docs.py), [`fetch_codex_docs.py`](scripts/fetch_codex_docs.py), [`fetch_opencode_docs.py`](scripts/fetch_opencode_docs.py), [`fetch_pi_docs.py`](scripts/fetch_pi_docs.py)
+ - [`scripts/mdx_to_md.mjs`](scripts/mdx_to_md.mjs), [`scripts/render_skills.py`](scripts/render_skills.py)
+ - [`skills/templates/`](skills/templates/) — the four `.SKILL.md.j2` templates
+ - [`.github/workflows/update-docs.yml`](.github/workflows/update-docs.yml)