Commit ee25216
authored
🤖 feat: add coder-docs Mux agent skill with offline docs snapshot (#21)
## Summary
Adds a new Mux agent skill (`coder-docs`) that bundles a text-only
offline snapshot of the `coder/coder` documentation, enabling future Mux
instances to answer "how does Coder do X?" without pasting the entire
docs corpus into context.
## Background
When working in this repo, agents frequently need to reference Coder's
documentation (API behavior, deployment options, template authoring,
etc.). Without a local skill, they either hallucinate or require
expensive web fetches. This skill provides progressive-disclosure access
through a generated navigation tree and ~4.5MB of committed Markdown
docs.
## Implementation
### New files
- **`hack/gen-coder-docs-skill/main.go`** — Go CLI tool (stdlib only)
that:
- Syncs a text-only docs snapshot from upstream `coder/coder/docs/`
(excluding images)
- Parses `manifest.json` and renders a hierarchical docs tree
- Injects generated content between markers in `SKILL.md`
- Writes `SNAPSHOT.json` with provenance metadata
- Defensive: fails fast on missing files, bad markers, unreferenced
paths
- **`hack/update-coder-docs-skill.sh`** — Shell script that
clones/updates `coder/coder` into `tmpfork/` and runs the generator
- **`.mux/skills/coder-docs/SKILL.md`** — Skill definition with
generated navigation tree (415 routes)
- **`.mux/skills/coder-docs/NOTICE.md`** — Licensing attribution
- **`.mux/skills/coder-docs/LICENSE`** — AGPL-3.0 (upstream license)
- **`.mux/skills/coder-docs/references/docs/`** — Committed snapshot
(~4.5MB text-only)
### Modified files
- **`Makefile`** — Added `update-coder-docs-skill` target
- **`AGENTS.md`** — Added skill reference and update command
## Validation
- `make build` ✅
- `make test` ✅
- `make verify-vendor` ✅
- `GOFLAGS=-mod=vendor go vet ./...` ✅
- `make update-coder-docs-skill` ✅ (end-to-end generation from upstream)
- Verified: no images copied, all manifest routes resolve, markers
populated
## Risks
Low risk — this is additive infrastructure (new skill + generator). No
existing code paths are modified. The snapshot is committed but can be
refreshed at any time via `make update-coder-docs-skill`.
---
<details>
<summary>📋 Implementation Plan</summary>
# Plan: Convert `coder/coder` docs into a Mux Agent Skill (`coder-docs`)
## Context / Why
We want a **project-local agent skill** that gives Mux agents
**progressive-disclosure access** to the `coder/coder` documentation:
- Offline(ish) access via files committed under
`.mux/skills/.../references/`.
- A deterministic, auto-generated **docs index tree** (like Mux’s
built-in `mux-docs` skill) so agents can quickly locate relevant pages.
- A repeatable **update script** that refreshes the snapshot from
upstream `coder/coder` into this repo.
This enables future Mux instances working in this repo to answer “how
does Coder do X?” without pasting the entire docs corpus into context.
## Evidence (what we verified)
- Mux’s built-in docs skill is generated via markers + nav parsing:
- `tmpfork/mux/scripts/gen_builtin_skills.ts` (injects `DOCS_TREE`
between `<!-- BEGIN ... -->` markers; bundles docs).
- `coder/coder` docs are file-based Markdown with a machine-readable
navigation tree:
- `tmpfork/coder/docs/manifest.json` (recursive `routes[]` with `title`,
`description`, `path`, optional `children`).
- Example doc format: `tmpfork/coder/docs/install/index.md` (Markdown
with some inline HTML; no YAML frontmatter).
- Size constraints:
- `tmpfork/coder/docs` is ~89MB total, but `docs/images` is ~85MB.
- Text-only docs (excluding `images/`) are ~4.5MB, which is feasible to
commit into `.mux/skills/.../references/`.
- Licensing note:
- `tmpfork/coder/LICENSE` is AGPL-3.0. Vendoring docs requires
intentional attribution/notice.
## Implementation details
### 1) Add a new skill directory: `.mux/skills/coder-docs/`
Create a new skill named **`coder-docs`**:
- `.mux/skills/coder-docs/SKILL.md`
- `.mux/skills/coder-docs/references/docs/` (generated snapshot;
committed)
#### `SKILL.md` (template + generated sections)
Keep the file mostly static, but with two generated blocks updated by
the update script:
- `SNAPSHOT` block: what upstream commit this snapshot came from.
- `DOCS_TREE` block: a hierarchical index derived from
`docs/manifest.json`.
Template skeleton:
```md
---
name: coder-docs
description: Index + offline snapshot of coder/coder documentation (progressive disclosure).
---
# Coder Docs
This skill bundles a text-only snapshot of the documentation from `coder/coder`.
## How to use
- Use the generated **Docs tree** below to locate a topic, then read the exact page:
```ts
agent_skill_read_file({
name: "coder-docs",
filePath: "references/docs/install/docker.md",
});
```
- To understand the full navigation source-of-truth, read the manifest:
```ts
agent_skill_read_file({
name: "coder-docs",
filePath: "references/docs/manifest.json",
});
```
- If you need to keyword-search locally (outside the skill tool), use ripgrep in the repo:
```bash
rg -n "<keyword>" .mux/skills/coder-docs/references/docs
```
## Snapshot
<!-- BEGIN SNAPSHOT -->
<!-- END SNAPSHOT -->
## Docs tree (auto-generated)
<!-- BEGIN DOCS_TREE -->
<!-- END DOCS_TREE -->
```
### 2) Add an update script: `hack/update-coder-docs-skill.sh`
Goal: one command that (a) updates `tmpfork/coder` and (b) regenerates
the skill’s `references/docs/` snapshot + `SKILL.md` generated sections.
**Why `hack/`?** This repo already uses `hack/update-*.sh` for generated
artifacts.
#### Script responsibilities
1. **Dependency checks** (fail fast): `git`, `go`.
2. **Clone/update** `coder/coder` into `./tmpfork/coder`:
- If missing: shallow clone.
- If present: `fetch` and `reset --hard` to a known ref (default:
`origin/main`).
3. **Run the Go generator** (`hack/gen-coder-docs-skill/main.go`) which:
- Refreshes the snapshot under `.mux/skills/coder-docs/references/docs/`
(wipes destination first to avoid stale files; copies `manifest.json` +
all `**/*.md`; **excludes** `docs/images/`).
- Regenerates the `DOCS_TREE` block in
`.mux/skills/coder-docs/SKILL.md`.
- Writes snapshot metadata (upstream commit SHA + timestamp) to:
- the `SNAPSHOT` block in `SKILL.md`, and
- `references/docs/SNAPSHOT.json` (machine-readable).
#### Proposed script shape (pseudo-code)
```bash
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
CODER_DIR="$ROOT/tmpfork/coder"
SKILL_DIR="$ROOT/.mux/skills/coder-docs"
DEST_DOCS_DIR="$SKILL_DIR/references/docs"
# 1) deps
command -v git >/dev/null
command -v go >/dev/null
# 2) clone/update
if [[ ! -d "$CODER_DIR/.git" ]]; then
git clone --depth=1 https://github.com/coder/coder "$CODER_DIR"
else
git -C "$CODER_DIR" fetch origin main --depth=1
git -C "$CODER_DIR" reset --hard origin/main
fi
CODER_SHA="$(git -C "$CODER_DIR" rev-parse HEAD)"
# 3) sync docs snapshot + generate + inject (Go tool does it all)
cd "$ROOT"
GOFLAGS=-mod=vendor go run ./hack/gen-coder-docs-skill/main.go \
--source-docs-root "$CODER_DIR/docs" \
--dest-docs-root "$DEST_DOCS_DIR" \
--skill-md "$SKILL_DIR/SKILL.md" \
--coder-sha "$CODER_SHA" \
--snapshot-out "$DEST_DOCS_DIR/SNAPSHOT.json"
```
### 3) Add the generator helper: `hack/gen-coder-docs-skill/main.go`
Implement the JSON parsing + marker injection logic in Go (stdlib only)
so it matches this repo’s primary language and avoids a Python runtime
dependency.
#### Generator requirements (defensive by default)
- **Fail fast** if `SKILL.md` markers are missing or malformed.
- **Sync snapshot** from `--source-docs-root` → `--dest-docs-root`:
- wipe destination first (prevents stale docs),
- copy `manifest.json` + all `**/*.md`,
- skip `images/` and any non-markdown assets.
- **Fail fast** if a `manifest.json` route references a doc path that
does not exist under `--dest-docs-root` (after sync).
- Normalize newlines (`\r\n` → `\n`) for deterministic diffs.
- Use only the Go standard library (no `go.mod` / `vendor/` changes).
#### Core logic sketch
```go
type Manifest struct {
Routes []Route `json:"routes"`
}
type Route struct {
Title string `json:"title"`
Description string `json:"description"`
Path string `json:"path"`
Children []Route `json:"children"`
}
func injectBlock(content, marker, block string) (string, error) {
begin := "<!-- BEGIN " + marker + " -->"
end := "<!-- END " + marker + " -->"
bi := strings.Index(content, begin)
ei := strings.Index(content, end)
if bi == -1 || ei == -1 || ei < bi {
return "", fmt.Errorf("missing markers for %s", marker)
}
before := content[:bi+len(begin)]
after := content[ei:]
return before + "\n" + strings.TrimRight(block, "\n") + "\n" + after, nil
}
func normalizeManifestPath(p string) (string, error) {
if !strings.HasPrefix(p, "./") {
return "", fmt.Errorf("expected manifest path starting with './': %q", p)
}
return strings.TrimPrefix(p, "./"), nil
}
func renderRoutes(routes []Route, indent int, out *[]string, docsRoot string) error {
prefix := strings.Repeat(" ", indent)
for _, r := range routes {
rel, err := normalizeManifestPath(r.Path)
if err != nil {
return err
}
if _, err := os.Stat(filepath.Join(docsRoot, rel)); err != nil {
return fmt.Errorf("missing doc referenced by manifest: %s: %w", rel, err)
}
title := strings.TrimSpace(r.Title)
if title == "" {
return fmt.Errorf("manifest route missing title for path %q", r.Path)
}
if len(r.Children) > 0 {
title = "**" + title + "**"
}
suffix := ""
if desc := strings.TrimSpace(r.Description); desc != "" {
suffix = " — " + desc
}
*out = append(*out, fmt.Sprintf(
"%s- %s (`%s`) → `references/docs/%s`%s",
prefix, title, rel, rel, suffix,
))
if len(r.Children) > 0 {
if err := renderRoutes(r.Children, indent+1, out, docsRoot); err != nil {
return err
}
}
}
return nil
}
```
Output:
- `DOCS_TREE`: `strings.Join(lines, "\n")` after
`renderRoutes(manifest.Routes, 0, &lines, docsRoot)`.
- `SNAPSHOT`: short Markdown block like:
- `Upstream: coder/coder@<sha>`
- `Generated: <RFC3339 timestamp>`
### 4) Make it easy to run: add a Makefile target
Add a convenience target:
```make
.PHONY: update-coder-docs-skill
update-coder-docs-skill:
bash ./hack/update-coder-docs-skill.sh
```
### 5) Make it discoverable (optional but recommended)
- Update `README.md` (or `AGENTS.md`) with a short “Mux skills” note:
- Skill name: `coder-docs`
- Update command: `make update-coder-docs-skill`
### 6) Licensing / attribution
Because `coder/coder` is AGPL-3.0:
- Add `.mux/skills/coder-docs/NOTICE.md` describing:
- what is vendored,
- where it came from (repo URL + commit SHA),
- and the applicable license.
- Include a copy of the upstream AGPL license text within the skill
directory (e.g. `.mux/skills/coder-docs/LICENSE`) so the vendored-docs
subtree is self-describing.
<details>
<summary>Why call this out?</summary>
Vendoring content across repositories can have license implications.
This plan keeps the skill directory explicit about provenance and
license so downstream consumers are not surprised.
</details>
## Validation / Acceptance Criteria
After implementation (Exec mode):
1. Run `make update-coder-docs-skill`.
2. Confirm files exist:
- `.mux/skills/coder-docs/SKILL.md` contains populated `SNAPSHOT` +
`DOCS_TREE` blocks.
- `.mux/skills/coder-docs/references/docs/manifest.json` exists.
- `.mux/skills/coder-docs/references/docs/install/docker.md` exists.
3. Confirm **no images** were copied:
- `.mux/skills/coder-docs/references/docs/images/` should not exist.
4. Confirm integrity checks pass:
- every `manifest.json` `path` exists under `references/docs/`.
5. (Mux smoke test) In a Mux session, verify:
- `agent_skill_read({ name: "coder-docs" })` works.
- `agent_skill_read_file({ name: "coder-docs", filePath:
"references/docs/install/index.md" })` returns content.
Success looks like: a committed `.mux/skills/coder-docs` directory with
a small (~5MB) text snapshot + deterministic index, and a single update
script that can refresh it from upstream.
</details>
---
_Generated with `mux` • Model: `anthropic:claude-opus-4-6` • Thinking:
`xhigh` • Cost: `$1.41`_
<!-- mux-attribution: model=anthropic:claude-opus-4-6 thinking=xhigh
costs=1.41 -->1 parent 4cc1631 commit ee25216
424 files changed
Lines changed: 81067 additions & 1 deletion
File tree
- .mux/skills/coder-docs
- references/docs
- about
- contributing
- admin
- external-auth
- infrastructure
- validated-architectures
- integrations
- devcontainers
- envbuilder
- licensing
- monitoring
- notifications
- networking
- provisioners
- security
- setup
- templates
- extending-templates
- managing-templates
- startup-coordination
- users
- oidc-auth
- ai-coder
- agent-boundaries
- ai-bridge
- ai-bridge-proxy
- clients
- install
- cloud
- kubernetes
- other
- releases
- reference
- agent-api
- api
- cli
- start
- support
- tutorials
- best-practices
- user-guides
- desktop
- devcontainers
- workspace-access
- jetbrains
- hack
- gen-coder-docs-skill
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
Lines changed: 32 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
Lines changed: 77 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
0 commit comments