Skip to content

🤖 feat: add coder-docs Mux agent skill with offline docs snapshot#21

Merged
ThomasK33 merged 7 commits into
mainfrom
question-gen
Feb 10, 2026
Merged

🤖 feat: add coder-docs Mux agent skill with offline docs snapshot#21
ThomasK33 merged 7 commits into
mainfrom
question-gen

Conversation

@ThomasK33

Copy link
Copy Markdown
Member

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.


📋 Implementation Plan

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:

---
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:
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:
rg -n "<keyword>" .mux/skills/coder-docs/references/docs

Snapshot

Docs tree (auto-generated)


### 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

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:

.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.
Why call this out?

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.

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.


Generated with mux • Model: anthropic:claude-opus-4-6 • Thinking: xhigh • Cost: $1.41

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

Initial PR for the coder-docs Mux agent skill. Adds a Go generator, shell update script, skill skeleton with licensing, and an initial docs snapshot from coder/coder.

The coder/coder manifest.json uses arrays for the state field
(e.g. ["beta"], ["premium"]), not plain strings. Changed the
route.State type from string to []string and added a
routeStateContains helper for the hidden check.
Snapshot from coder/coder@342d2e4bedf6 (2026-02-10).
Text-only docs (~4.5MB), 415 routes in the docs tree.
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

Rebased onto main to resolve Makefile conflicts (new docs-* targets on main). All local checks pass.

- Add package comment (revive)
- Use 0o750 for directory permissions (gosec G301)
- Use 0o600 for file write permissions (gosec G306)
- Add nolint:gosec annotations for G304 (file paths from trusted CLI flags)
- Regenerate snapshot with updated permissions
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

Fixed all lint issues: package comment, directory/file permissions, gosec nolint annotations. Regenerated snapshot.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4733c799d9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread hack/gen-coder-docs-skill/main.go
Block symlink traversal in syncDocsSnapshot to prevent copying files
outside the docs root. Checks d.Type()&fs.ModeSymlink before copy.
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

Added symlink check in syncDocsSnapshot per review feedback — skips any entry with fs.ModeSymlink before copying.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3316cc34ec

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread hack/gen-coder-docs-skill/main.go Outdated
Comment thread hack/update-coder-docs-skill.sh
- Replace symlink-only check with d.Type().IsRegular() to also skip
  FIFOs, sockets, and devices that could hang os.ReadFile.
- Add git clean -fdx after reset --hard in the update script to remove
  untracked files from the local clone before snapshot generation.
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

Addressed both review comments:

  1. Replaced symlink-only guard with !d.Type().IsRegular() to reject all non-regular files (FIFOs, sockets, devices).
  2. Added git clean -fdx after reset --hard in the update script to remove untracked files before snapshot generation.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Hooray!

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ThomasK33 ThomasK33 added this pull request to the merge queue Feb 10, 2026
@ThomasK33

Copy link
Copy Markdown
Member Author

Merged via the queue into main with commit ee25216 Feb 10, 2026
7 checks passed
@ThomasK33 ThomasK33 deleted the question-gen branch February 10, 2026 08:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant