Build and maintain a branded, interactive user's manual for this codebase. First run walks a setup flow (brand assets, codebase orientation, optional issue-tracker link to Jira or GitHub Issues, push-time guard); later runs update the manual to match the user-facing surfaces that have changed. Use for "/manual", "build the manual", "update the manual", when the pre-push guard reports the manual is stale, or when a <reorder> or <recopy> payload from the manual needs applying.
Scanned 9/6/2026
Install to Claude Code
npx -y skills add dougstanford/living-manual --skill manual --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Manual?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/dougstanford-manual)More formats (shields.io, HTML) on the badges page.
---
name: manual
description: Build and maintain a branded, interactive user's manual for this codebase. First run walks a setup flow (brand assets, codebase orientation, optional issue-tracker link to Jira or GitHub Issues, push-time guard); later runs update the manual to match the user-facing surfaces that have changed. Use for "/manual", "build the manual", "update the manual", when the pre-push guard reports the manual is stale, or when a <reorder> or <recopy> payload from the manual needs applying.
---
# The living manual
One HTML file, self-contained, brand-styled, interactive: clickable
headings file notes that become tickets, roadmap previews show planned
changes, a glossary explains novel concepts on first use, and readers
rearrange the manual by dragging its contents or correct its prose in
place. The browser keeps a reader's order and their edits; an optional
payload makes either one committed. This skill builds it once, then
keeps it matched to the released code.
All prose you generate follows `reference/writing-style.md` in this
plugin. Read it before writing any manual content. It is binding.
Token discipline: the scripts in `scripts/` exist so you never do their
work by hand. Run them, read their compact output, act on it. Do not
re-read the whole codebase on update runs; `stale.sh` tells you exactly
what changed.
`$LM` below is the plugin root. This file lives at
`$LM/skills/manual/SKILL.md`, so resolve it from the path this skill
loaded from.
Every question you ask during setup must be answerable from what is on
the user's screen. Print the thing you are asking about (the feature
map, the palette and its proposed roles, the tracker's projects,
labels, and milestones) in your
reply first, then ask. Answer choices never reference content the user
has not been shown.
## Every invocation starts the same way
```
sh $LM/scripts/state.sh
```
Branch on the JSON: `configured: false` → setup flow. Otherwise →
update flow (or the specific request the user made).
## Setup flow (first run)
Work through these in order. Each step ends with something written to
disk, so an interrupted setup resumes cleanly (state.sh shows what
exists).
**1. Orient to the codebase.**
Run `sh $LM/scripts/inventory.sh <repo-root>`. From its output plus the
README and any changelog/roadmap docs, build the feature map:
user-facing surfaces and the shipped features on each. Read the main UI
entry point and each surface component; the manual documents what users
experience, so ground every section in the code that renders it.
**Print the full map in your reply** (a plain nested list: surface →
features), then ask the user to confirm or correct it — missing
features, wrong groupings, things that aren't user-facing. A wrong map
here costs the whole document.
**2. Locate brand.**
inventory.sh lists brand-candidate directories. Read what's there:
style guides, palettes, logo files, design docs. Extract: palette (with
semantic roles), typography, shape language, voice. Print the extracted
palette with each hex value and its proposed role before asking the
user to confirm; when roles are ambiguous, the question names the
specific colors in its options. No brand assets:
offer the neutral default palette (scaffold.py's fallback) and note it
in the config so a later brand pass knows. A logo file becomes a data
URI (downscale to ~96px first: `sips -Z 96 in.png --out small.png`,
then base64) so the manual stays self-contained.
**3. Issue tracker link (optional).**
Read `$LM/reference/trackers.md` first; it defines the supported
providers, the config schema, and the inspect-propose-commit
discipline this step follows. Run each provider's detect step (Jira:
Atlassian MCP via ToolSearch; GitHub Issues: `gh` authenticated and
the repo resolves). None usable: record
`"tracker": {"provider": "none"}` with the reason and the enable path.
Otherwise ask which provider receives tickets, offering only the
usable ones plus "none".
Then, for the chosen provider: run its inspect step and print what the
project already has (Jira: issue types, components; GitHub: labels,
milestones) before proposing anything. Propose a mapping of ticket
types (`bug`, `idea`, `feedback`) onto that existing taxonomy, plus
any optional routing (component, milestone, extra labels) drawn from
the same lists. Never assume the defaults in trackers.md fit.
Map a type onto an existing label only when that label means what the
type means, not when it is merely the closest thing available. When
nothing fits, lead with creating a label named for the type, marked
"would be created", and offer reuse as the alternative to it rather
than as an equal option. When you do propose reuse under a different
name, say so in the proposal and say what the existing label actually
means, so the user is choosing the compromise rather than inheriting
it. Create nothing without explicit confirmation.
A type may map to several labels when the project routes that kind of
work with more than one; see the config schema in trackers.md. Verify
write permission the provider's way; never create a test issue to
probe. Record the confirmed mapping in the `tracker` block. Write
access missing: record `"provider": "none"` with the reason, in the
user's terms.
**4. Write the config.**
`.living-manual.json` at repo root:
```json
{
"product": "...",
"tagline": "...",
"tagline_pill": "...",
"manual_path": "docs/USER_MANUAL.html",
"tickets_dir": "docs/tickets",
"ticket_skill": "/living-manual:ticket",
"user_facing_paths": ["src/", "app/src/"],
"brand": { "ink": "...", "surface": "...", "deep": "...", "accent": "...",
"warn": "...", "caution": "...", "font_stack": "...",
"hero_gradient_css": "...", "logo_data_uri": "..." },
"tracker": { "provider": "github",
"github": { "repo": "acme/app",
"labels": { "bug": "bug", "idea": "enhancement",
"feedback": "feedback" } } },
"ci": { "provider": "github-actions",
"workflow": ".github/workflows/manual-guard.yml" }
}
```
`ci` records what step 5 wired up, so a later run knows whether the
server-side check exists. It is written by step 5, not here.
`user_facing_paths` are the globs whose changes make the manual stale.
Choose them from the inventory; confirm with the user.
**5. Wire the project.**
- `sh $LM/scripts/install-hook.sh` installs the pre-push guard: pushes
block when a user-facing surface no longer matches the content hash the
manual records for it.
- Append `templates/claude-md-snippet.md` to the repo's CLAUDE.md
(create it if absent) so every future session, by any dev, updates
the manual as part of a push. This is the durable half; the hook is
the enforcement half.
- Create `tickets_dir` with a README explaining the queue and statuses.
- **Offer the CI check, on GitHub only.** The hook guards a clone. A
squash merge or an "Update branch" click in the web UI rewrites
history on the server, passes through no hook, and can land a stale
manual. A workflow catches that where it happens.
Offer this only when the repo is hosted on GitHub (`git remote get-url
origin` names github.com, or `gh repo view` resolves). Elsewhere,
write nothing CI-related and say nothing about it. If the user
accepts, copy `$LM/templates/manual-guard.yml` to
`.github/workflows/manual-guard.yml`, filling `{{DEFAULT_BRANCH}}`
from the repo and `{{PLUGIN_VERSION}}` from the `version` in
`$LM/.claude-plugin/plugin.json`.
Record the outcome either way, alongside `tracker` in the config:
```json
"ci": { "provider": "github-actions",
"workflow": ".github/workflows/manual-guard.yml" }
```
Declined or unavailable: `"ci": { "provider": "none", "reason": "..." }`
in the user's terms, so a later run can say what to change.
Then tell the user the check is advisory until they make it required:
the plugin does not write branch protection. Point them at the
repository's branch protection settings for the default branch, where
"Manual reflects the code" can be added as a required check. Supplying
the signal is the plugin's job; deciding its force is the repo
owner's.
**6. Build the manual.**
- `python3 $LM/scripts/scaffold.py $LM/templates/manual-shell.html .living-manual.json <manual_path>`
(run from the repo root) produces the shell: full interactive
machinery, brand applied, user-facing surfaces stamped. You never write
modal/glossary/preview code.
- Fill the content slots (`<!-- SLOT: ... -->`): TOC, an intro callout
(with `manual-meta` class) explaining the note and preview
affordances, one section per surface, a "What's new" seeded from
release history. Concepts first, surfaces second, reference tables
last. Sentence-case headings ending in periods unless the brand voice
says otherwise. App-state illustrations use the `.mock` panel classes
already in the shell.
- Build the data payload as JSON and apply it:
`python3 $LM/scripts/sync-index.py <manual_path> payload.json` with
`glossary` (novel concepts: id, label, pattern, flags, summary),
`defined` (each concept's id mapped to a **list** of defining
contexts, each `sec:<id>` or `h3:<id-or-heading-slug>`), `previews` (roadmapped work from the
project's plans, with `.preview-btn` icons placed inline where each
applies), `tickets` (empty at first), and `asof`.
- Verify: `python3 $LM/scripts/verify.py <manual_path>` must print OK.
Then load it in a browser once: click a heading, open a preview,
hover a term. Fix what fails before reporting done.
## Update flow
```
sh $LM/scripts/stale.sh
```
`CURRENT`: say so, stop. Otherwise the output names the user-facing
surfaces (the `user_facing_paths` entries) whose content no longer
matches what the manual records. It exits nonzero in that case, which is
the signal that work is needed, not a script failure; read the output
and carry on. Then:
1. Read what changed in each named surface. On a feature branch the
natural reference is the trunk you will merge into:
`git diff origin/main...HEAD -- <surface>` shows what your branch did
to it. There is no single base commit and you do not need one — the
manual describes the current end state, so when in doubt read the
surface as it now stands and make the prose match. Derive what changed
in the user's experience, not in the code.
2. Revise the affected sections. New concept → glossary + defined
entry. Shipped roadmap item → delete its preview and icon; the
content moves into the section and "What's new".
3. Append one "What's new" release block (newest first, dated,
user-facing language).
4. Rebuild the queue against the tracker:
`python3 $LM/scripts/tickets-index.py <tickets_dir> <manual_path>`.
Never with `--no-tracker` on a release: the queue a reader files
notes against should be true as of the release.
5. Sync data + re-stamp surfaces in one call:
`python3 $LM/scripts/sync-index.py <manual_path> payload.json` with
the changed blocks plus `asof` (today) and `"surfaces": true`. The
surfaces stamp is taken from committed state (HEAD), so commit the
code before this step; it rewrites only the hash lines for surfaces
that actually moved, which is what lets branches merge in any order.
6. `python3 $LM/scripts/verify.py <manual_path>` must print OK.
Browser-check only when slots or machinery changed.
## Exporting a static copy
When the user wants a copy to hand outside the team:
```
python3 $LM/scripts/export.py <manual_path> [dest]
```
Default destination is the manual's path with `_prod.html` in place of
`.html`. The export drops the note-filing path and its affordances, the
queue (`TICKETS` and `QUEUE_SYNC` travel inside the file, so a
distributed copy carrying them would publish the backlog and name the
tracker), and the roadmap previews, which describe work that has not
shipped. It also drops the two surfaces that write back to the repo:
editing the prose and rearranging the contents. An outside reader has
no repo to commit to, and an affordance that cannot finish what it
starts is worse than none.
The glossary and all prose stay, and so does the responsive layout: a
distributed copy still moves between the sidebar and the compact menu
with the window, because that is how it is read rather than how it is
changed.
Never hand-edit an export, and never commit one: it is a build artifact
regenerated from the manual, and the maintained manual is the only
document anyone keeps current. `verify.py` recognises an export by its
`manual-export` comment and judges it by the right rules.
The pre-push hook calls this flow by name
(`claude -p "/living-manual:manual update"`), so keep the update path
non-interactive: no questions unless the diff is genuinely ambiguous,
and then fail with a clear message rather than guessing.
## Revising the copy
The manual's prose is editable in the browser. A reader's edits live in
their own storage until they press Commit copy, which produces a
`<recopy>` block naming the manual, followed by a fenced JSON payload
of `{"edits": [{"key", "original", "new"}]}`. Write that JSON to a file
and apply it:
```
python3 $LM/scripts/recopy.py <manual_path> payload.json
```
It replaces each passage by exact match and writes nothing unless every
entry checks out, so a payload lands whole or not at all. Never retype
the prose by hand; the script exists so the edit that lands is the one
the reader made. Then `verify.py` must print OK. Do not re-stamp the
surfaces and do not add a What's new entry: the reader changed wording,
not behaviour.
An edit that renamed a heading carries a `toc` field naming the anchor,
the label the file holds, and the label it should read. The script
renames that contents entry in the same pass, because an entry is a
pointer to its heading and the two must not drift. Section order is
untouched, so this is a copy edit: do not run `reorder.py` for it, and
do not treat it as a rearrangement.
A refusal is information, not a failure to work around. "No longer in
the file" means the manual moved on after the edit, so the reader
should redo it against current wording. "Appears more than once" means
the passage is not distinctive enough to place. Surface either message
rather than guessing which paragraph was meant.
## Reordering sections
A reader rearranges the manual by dragging entries in its contents
panel; the browser persists their order with no further step. Making
an order the committed one is the optional path: the copy affordance
produces a `<reorder>` payload naming the manual and the ordered
section ids. Applying it is mechanical:
```
python3 $LM/scripts/reorder.py <manual_path> <id ...>
```
The script moves the section blocks and their TOC link groups and
touches nothing else. Then `verify.py` must print OK. Do not re-stamp
the surfaces and do not add a What's new entry: no code moved and no
prose changed, only the order of whole sections. The script refuses an id
set that does not match the manual's sections; surface its message
rather than guessing.
## Rules
- The manual describes shipped behavior. Planned work lives in
previews; never blend the two.
- One glossary alert per concept, only before its definition. The
DEFINED_IN map moves when prose moves.
- Items with roadmap previews take notes against the plan, not current
behavior. The shell enforces this; keep preview icons placed
accurately so it can.
- Never edit inside the `/*@...*/` data markers by hand.
- Ticket queue truth: the TICKETS block mirrors `tickets_dir`. The
ticket skill owns routine sync; fix drift when you see it.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!