document
Using this skill: announce "Using document" and the mode (
public,internal, orreconcile), make a todo per numbered step of the chosen mode in## Steps, and do not skip the gates. This skill's worth is its process, not a hand-reproduced outcome. If you were told to "run document", run it, do not improvise its result. (Suite standard: https://github.com/horizon-foundry/foundry/blob/main/reference/skill-authoring.md)
Overview
Documenting a project is a lifecycle stage, not a favor you do when asked. The build sequence is scaffold, build, document, audit, ship. The document step is the one that silently gets skipped, because nobody is blocked without it. The cost lands later: a cold-start agent has no map, a stakeholder cannot see what was built, and worst, a wrong doc gets trusted and built on.
This skill owns the project's words on two layers. Truth: the docs must agree with the running code (a wrong doc is worse than no doc). Presentation: the docs become a live surface (the overview deck, the "Behind the Build" hub) so they cannot rot unseen. User-invoked and idempotent: a run that finds everything current changes nothing. Assumes the doc set defined by the suite's doc-set spec, reference/doc-set-spec.md in the repo; an install ships only this skill's directory, so from anywhere else read it at https://raw.githubusercontent.com/horizon-foundry/foundry/main/reference/doc-set-spec.md.
When NOT to use
- The doc set does not exist yet: that is
scaffold; this skill reconciles what exists and never fabricates docs to render. - The question is whether the product is safe to ship:
production-auditissues verdicts; this skill maintains truth and surfaces. - The work is defining or enforcing voice and tone:
brand-voiceowns the words' sound; this skill owns where docs render and whether they are true. - A doc disagrees with runtime behavior and the fix would change the code: never from here; flag the suspected regression and let a human decide.
Steps
The sections after this one are the reference each step points at.
public
- Confirm an external audience is declared or confirmed by the human. When in doubt the mode is
internal, neverpublic. Check: the audience confirmation, named in the report. - Run the truth checklist. Artifact: the per-item report, each item pass, repaired, or regression-flagged.
- Establish or reconcile the public hub and the overview deck (see those sections). Check: one hub, one deck, established or reconciled, never a second.
- Run the sanitization sweep (the pre-publish checklist under "What sanitized means"). Check: zero marker hits, and every publicly rendered section listed with its public-or-private call.
- Close with the convergence statement: what was already current, what changed, and that on a re-run of an unchanged project the touched-surface diff is empty and the report says so.
internal
- Run the truth checklist. Artifact: the per-item report.
- Establish or reconcile the internal portal (see that section). Check: the full raw doc set renders behind a gate or on a private host.
- Confirm no public surface was created or exposed by this run. Check: that confirmation stated in the report.
- Close with the convergence statement (as in
publicstep 4).
reconcile
- Run the truth checklist. Artifact: the per-item report.
- Pull every existing presentation surface current (see "Reconcile: what a re-run checks"). Check: a per-surface line in the report, current or updated.
- Run the cold-start drill (see that section). Artifact: the drill result, pass or a doc-set finding per failed check.
- End with the convergence check: on an unchanged project the touched-surface diff is empty, and the report says so.
Keep the docs true to the code
The failure this prevents: an agent reads a stale doc, believes it, and acts on a false premise. So on every run, and before any unit of work closes, reconcile the docs against what the code actually does.
The authority model. Code is the truth for what the system does: when a doc describes behavior the code no longer has, repair the doc to match observed behavior. But code is not automatically the truth for what the system should do. Five sources carry authority over different things: code (current behavior), tests (expected behavior), product requirements / the forever spec (intended behavior), decision records (accepted constraints), docs (the communicated contract). When they disagree, classify before repairing:
- Deterministic mismatch (a renamed flag, a wrong path, a stale count, a moved file): repair the doc directly. This is the common case and it is safe.
- Suspected regression (the doc-vs-code disagreement coincides with a failing test, a contradicted decision record in
NOTES.md, or a doc that states the behavior as designed intent): do not silently rewrite the doc to document the new behavior. That would turn a bug into the product's contract. Flag it as a suspected regression and let a human decide which side is wrong. - Recalled memory versus the record (the harness surfaces a fact from an earlier session that the docs contradict): memory is not one of the five authorities. It is per-user, machine-local, and unreviewable, so it never outranks a declared record and never justifies a repair on its own. Treat the disagreement as a signal that one of the two is stale, and surface it rather than silently reconciling either side.
Never change working behavior to satisfy a wrong doc; runtime changes require explicit instruction.
Prohibited drift
Never let documentation:
- Invent components, routes, or services not present in the code.
- Mark a required config field optional, or optional required, against what the code does.
- Describe an unshipped feature as present (the forever-spec invariant:
PRODUCT.mdis present-tense truth only; roadmaps, proposals, and plan files are exempt because they are forward-looking by declaration). - Introduce CLI flags or arguments the parser does not have.
- Leave a runtime artifact (log, cache, temp file the code creates) undocumented and un-gitignored.
- Contradict a sibling doc: the same fact in
CLAUDE.md,README.md, andTODOS.mdmust agree, in order and in count.
The truth checklist
Run against the documentation and the externally-meaningful contracts a change touched (the CLI/API surface, config, schema, runtime artifacts, the display strings docs actually quote), not every source file read during a large session; scope to what the change altered and to what the docs claim. The per-item report (step 1 of every mode) states pass, the repair made, or the regression flagged:
- CLI/API surface, documented flags, args, and endpoints exist in the parser/router; examples use realistic values of the right type.
- Runtime artifacts, anything the code writes at runtime is documented and gitignored.
- Config semantics,
.env.examplecomments match runtime optionality and the actual loading mechanism (auto-load vs manual source). - Display values, hardcoded strings quoted in docs match the source, including the visual result of any CSS transform.
- Cross-document, counts, orderings, and lists agree across every doc that repeats them. When a fact is duplicated, prefer removing it and leaving a pointer over normalizing the same fact in many places.
- Forever spec,
PRODUCT.mddescribes only what exists; unshipped work lives inTODOS.mdand plan files. - Registry (only where one is claimed), if the docs maintain a file or component registry, every listed entry exists and every file is listed. Do not impose a filesystem-mirror registry on a project whose docs never claimed one.
Self-maintenance: NOTES.md, PROMPTS.md, and FRICTION.md
This skill owns the mechanics of the working docs; the doc-set spec states the invariant (maintained as work happens, never in an end-of-session cleanup) and a project's CLAUDE.md needs only a one-line pointer here ("self-maintenance cadence: owned by the document skill"), never an inlined copy of this protocol.
NOTES.md, decisions with reasoning. Update in the same turn a judgment call is made that a future session would otherwise re-derive. Each entry carries the decision, the reasoning (including the constraints that forced it), and the rejected alternative(s): that last line is what stops the next session from proposing the rejected option as an improvement. A reversal gets its own entry and the original stays, because the history is part of the record. Routine work (file created, function written) belongs in git history, never here.PROMPTS.md, what happened, in order. Three sections stay in sync: a Summary paragraph (the framing; update when the framing changes), Phases (chronological narrative; when a unit of work finishes, append a phase entry with what was built, decided, and deferred, and do not edit prior phases except to fix errors), and Origin and direction (the decisions that shaped the build, in the project's own voice). A raw verbatim prompt log is optional: a repo whose docs may render publicly can deliberately keep none (the Phases narrative carries the history), and that is a declared choice, not drift; once declared, verbatim prompts are never reintroduced.FRICTION.md, where the process hurt. Process problems, never product bugs. Attribution is strict: entries the agent observes (three or more iterations on one prompt, consecutive drafts rejected for overlapping reasons, tool failures costing real session time) are added without waiting to be asked and flagged to the user so they can revise or remove; entries the user logs themselves stay theirs. Every entry has three fields: What happened, Likely cause, Candidate improvement. An entry without a candidate improvement is a complaint, not a log. Tone is neutral: facts, not blame.
The cadence: a decision worth preserving updates NOTES.md before moving on; a finished unit of work appends a PROMPTS.md phase entry; observed friction lands in FRICTION.md when observed, not at session end; and finalizing any unit of work verifies all three are current (the truth-checklist run is the natural moment).
Three modes: public, internal, reconcile
A documentation surface serves one of two audiences, and those audiences want opposite things. The mode names which you are building.
document publicbuilds and maintains the public hub: a curated, external-facing surface for someone who does not know the project. It renders only the finished, sanitized docs (the overview deck, the design system, the forever spec, the brand); it never renders raw session logs, decision records, or friction notes. Written for a reader, not for continuity.document internalbuilds and maintains the internal portal: the raw source docs rendered directly (includingPROMPTS.md,NOTES.md,FRICTION.md), optimized for continuity and inspection. Private or authenticated, never a public URL. Because it is gated, rendering raw internal docs here is fine.document reconcileis the idempotent update pass over both the truth layer (docs versus code, above) and whichever presentation surfaces exist: pull each back up to date with what shipped, change nothing already current.
The showcase is conditional on the project type. A public product, an OSS tool, a portfolio piece, or consulting output wants the deck and the public hub. An internal service, a private repo, or an experiment gets the truth layer and (at most) the internal portal. Building a marketing surface for a project with no external audience is drift of a different kind.
Establish, then reconcile. The first time you run public or internal, no hub exists, so you establish it. Every run after is a reconcile: one hub per mode, never a second. If the doc set itself does not exist yet, that is scaffold's job, not this skill's. Do not fabricate docs to render.
The rule shared with brand-voice: raw internal logs never reach a public URL, and brand-voice defers to this skill for that rule; the modes enforce it by construction (the public hub is curated, the internal portal is gated).
What "sanitized" means: project at the section level
File-level whitelisting is not enough. A file that is safe to render publicly (PRODUCT.md, BRAND.md) can still open with a section that is not, so dumping the whole file leaks whatever it leads with (PRODUCT.md opens with the frame: intent, assumptions, open findings, security declarations). So "sanitized" is a section-level projection:
- Public-by-default (safe to render externally): present-tense product truth (what it is, who it serves, core flows, non-goals), positioning and outward brand, design tokens, and a stated security posture summary.
- Private-by-default (curated OUT of a public render even when the containing file is public): the security frame's specifics (trust-boundary enumeration, authn/authz internals, secret-handling detail) beyond a posture summary; any entry still marked
assumed, unconfirmed; open findings and unresolved blanks ((finding: not yet declared)); idea-risk and internal decision rationale; anything a hostile reader could turn into an attack map.
The test: if a line reads as intent, a hedge, an open question, or an attack hint, it belongs to the internal portal, not the public hub. public mode therefore renders a file's public sections only, never a raw dump.
The pre-publish checklist enforces it. Before anything publishes, the sweep (public mode, step 3) requires:
- Grep every publicly rendered doc for the markers
(finding:and(assumed,and for the internal file names (PROMPTS,NOTES,FRICTION). Any hit fails the sweep. - The run report lists every section rendered publicly with its public-or-private call, one line each. An unlisted section fails the sweep.
The public hub
A /behind (or "insider") route with a tab bar. The kicker is "Behind the Build", the heading "How this was made". Curated, external-facing tabs only:
- Overview, the slide deck (below).
- Design system,
DESIGN.md, ideally rendered from the tokens themselves so it cannot disagree with them. - Product,
PRODUCT.md, the forever spec (present-tense truth, written for a reader). - Brand,
BRAND.md, if it reads as outward brand rather than internal strategy.
No placeholders, no empty sections, no internal voice. If a doc still has template scaffolding or empty headings, either fill it for a reader or leave it out. Read docs through a whitelist (fixed filenames, no user input reaching the filesystem, secrets never in the set), and ensure the deploy artifact includes repo-root files if the framework's standalone output omits them; they are read at runtime.
If the project has no rendering surface (a CLI, a library, a backend-only service), the public hub is out of scope: the internal portal is the repo itself on a private host, and the truth layer above is the whole job. Say so and stop. Do not invent a web surface to render into.
The internal portal
The same rendering machinery, pointed at the full raw doc set (PRODUCT, BRAND, DESIGN, PROMPTS, NOTES, FRICTION) for continuity and inspection. It is gated: an authenticated route, or simply the repo itself on a private host. Placeholders and empty sections are fine here; they are honest state, not a marketing surface. Never expose the internal portal at a public URL.
The overview deck
The deck is the project's story on the public hub: authored, not just rendered. A typed Slide[] array drives a keyboard-navigable deck: a title slide (name, tagline, a few stat chips) plus content slides, each with category (a kicker), heading, body, optional evidence items, a hairline glyph (a small monochrome geometric mark, never an illustration), and a curated quote from the project's domain. The template is fixed (top-aligned header, bottom-anchored quote) so nothing jumps between slides. Author the arc first, one slide per core idea, in order: the problem, what it is, who it is for, the method, the payoff or the gate, getting started. Cut any slide you cannot name a lesson for.
Authored, never invented. Every claim traces to something real: the frame, the forever spec, the instrumented funnel, shipped code. Never invent impact numbers, market positioning, customer quotes, or adoption evidence. A deck that says "built to find out" where evidence does not exist yet is honest.
Reconcile: what a re-run checks
A run with nothing to change produces no diff; if a second run of an unchanged project churns, you were rewriting, not reconciling.
The presentation surfaces, from least to most drift-prone:
- The doc-set tabs render from source, so they are inherently current. Confirm the whitelist still covers the intended docs (and, for the public hub, that it excludes the raw logs).
- The overview deck is hand-authored, so it is where drift lives. Check each slide against shipped capability: a shipped feature with no beat, or a slide describing something removed or renamed, is drift. Add the missing beat, fix or cut the stale one, leave accurate slides untouched.
- The showcase copy, any hardcoded enumeration of the product: landing demos, command lists, skill counts ("N skills"), feature lists. A new or renamed skill rots exactly here. Reconcile every count and list against reality.
- Cross-surface agreement: the same fact (a count, an ordering, a name) reads the same on every surface that repeats it.
The cold-start drill
A fresh-context subagent is given only the repo and a terse continuation prompt ("continue", "start the next phase") and must: (1) locate the Master Plan index, (2) open the plan file it names, (3) reconcile that plan against git state, (4) name the correct next action. A failure at any step is a doc-set finding, exactly like drift. Where subagents are unavailable, do not skip silently: state the manual drill (the operator runs the same four checks by hand in a fresh session) and open the run's report with a declaration naming the degradation and its reason, per the suite standard's declared-degradation rule.
Idempotence rules
- Detect before you create. A re-run reconciles the existing hub, portal, and deck; it never spawns a second.
- Update in place, add the missing, remove the stale. Never duplicate a slide or a tab to "add" content that belongs in an existing one.
- Preserve accurate prose. Re-running only touches what drifted; never rewrite a slide, doc, or copy that still tells the truth.
- Report the reconciliation. Name what was current, what changed, and any regression flagged; "all surfaces current" is a valid and common outcome.
Red flags
Symptoms that you skipped something above, not new rules: a doc-vs-code disagreement rewritten silently when it might be a regression; PROMPTS.md, NOTES.md, or FRICTION.md rendered on a public page; a placeholder or empty section on the public hub; a skill added or renamed without a reconcile pass; an arbitrary filename read from a param; a filesystem-mirror registry imposed on a repo that never claimed one; invented impact, positioning, or customer evidence; a public hub built for an internal service or private repo.
Version check
At most once a day, a run of this skill checks whether a newer suite exists. The command enforces the whole contract: the FOUNDRY_NO_VERSION_CHECK opt-out, the daily stamp-file rate limit, a 10s timeout, and silence on any failure. Never block or fail a run over this check.
[ -n "${FOUNDRY_NO_VERSION_CHECK:-}" ] || find "$HOME/.claude/.foundry-version-checked" -mmin -1440 2>/dev/null | grep -q . || { mkdir -p "$HOME/.claude" 2>/dev/null; curl -m 10 -fsS "https://foundry.thehorizonfoundry.com/api/version?skill=document&v=0.2.0"; touch "$HOME/.claude/.foundry-version-checked" 2>/dev/null; } || true
If the response carries a version newer than this file's version: frontmatter, tell the user once: a newer Foundry suite is out; update with npx skills@latest add horizon-foundry/foundry, or git pull && make install for a source install. If it prints nothing, fails, or the versions match, say nothing and proceed. What it sends: this skill's name and installed version, nothing else; it writes ~/.claude/.foundry-version-checked as the rate-limit stamp (full disclosure: the README section "Version check").