Imported from orvexai/orvex-wiki (
.claude/skills/doc-amend/SKILL.md). Install upstream withnpx skills add orvexai/orvex-wiki --skill doc-amend. Copyright stays with the author.
doc-amend
The heart of update-by-default (PLAN §B.2). Given a unit of content destined for the project's living manual, this workflow finds the one node it belongs to, updates only the affected sections in place, and never spawns a sibling page for a concept that already has one. It asks the human exactly one plain-English question when — and only when — a candidate match is genuinely fuzzy. It creates a new page only when the content is genuinely new, and always at status: draft; promotion to canonical is a separate human act (doc-ratify), never something this skill performs.
A caller may hand this skill a finished content_unit, or it may ask this skill to author the unit from evidence first (the strawman front-stage, Step 0). The front-stage is the productized form of the old tech-writer-manual "bit-loop": gather evidence from the code and the existing wiki, write a plain-language strawman, and flag every inference the evidence does not directly support as one plain-English chat question (P3) — never an unflagged guess, never a body section of open questions, never a Docmost comment used as a chat surface. Once the unit exists (supplied or authored), the rest of the workflow is unchanged: find-before-create → ASK → amend-in-place → create-at-draft. Promotion remains doc-ratify's job.
This skill replaces the old page upsert --status canonical path that the six planning overrides used to call. It enforces principles P1 (one living page per concept, no dated sprawl), P3 (update by default, ask-when-ambiguous, one question), P4 (current-state-only bodies), and P7 (the tldr lead and protected story zones are never flattened).
It is an orchestrator: the durable safeguards (the synchronous dup guard, CAS, the role-anchored lead targeting, the draft→canonical guard) live in the docmost-cli CLI and the Docmost server. This skill walks the router in decision-order.md (branches READ → FIND → ASK → AMEND → CREATE) and branches on exit codes — never on stderr text.
Conventions
- Bare paths (e.g.
references/guide.md) resolve from the skill root. {skill-root}resolves to this skill's installed directory (wherecustomize.tomllives).{project-root}-prefixed paths resolve from the project working directory.{skill-name}resolves to the skill directory's basename.
Inputs (the contract — CONTRACTS.md §3.1)
| Input | Meaning |
|---|---|
content_unit |
The body to route into the manual — markdown text or a file path. The unit of knowledge, not a whole document dump. Optional when author_from_evidence is set — then Step 0 produces it from evidence. |
target_doc_type |
The doc-type from the catalog in taxonomy.md §4 (lowercase-kebab). The caller supplies it; this skill never invents a doc-type. |
space |
The manual's space slug (config key docmost_space). |
parent_hint |
Optional. A hint at the IA parent node; resolved authoritatively against the project's manual-outline.yaml in Step 1. |
author_from_evidence |
Optional flag. When true (or when content_unit is absent and a topic/scope is given), run the strawman front-stage (Step 0): gather code + wiki evidence, draft the unit in plain language, and flag each unsupported inference as one chat question (P3) before routing it. When false (the default for override callers handing a finished unit), Step 0 is skipped. |
topic / scope |
Optional, used only with author_from_evidence. The subject to author and a one-line scope statement bounding it — what this unit must cover that its parent does not. |
Outputs (the contract — CONTRACTS.md §3.1)
{ "page_slug": "<slug>", "page_url": "<url>", "action": "created|amended|forced_new", "asked": true, "authored": false, "deferred_followups": [] }
action: amended— an existing node was updated in place (the default and desired outcome).action: created— no candidate existed; a fresh page was created atstatus: draft.action: forced_new— a candidate existed but a human explicitly chose a new page, carrying a human-attributed token.asked— whether the one-question ASK gate fired this run.authored— whether the strawman front-stage (Step 0) authored thecontent_unitfrom evidence this run (false when the caller supplied a finished unit).deferred_followups— subjects the front-stage flagged as out-of-scope candidate future units (empty unless Step 0 ran and the human deferred something).
Never (the hard guardrails)
- Never rewrites the protected story /
tldr/ "how this manual works" zones — those are transcluded canon and are non-AI-writable (P7). Thetldrlead is amended only through its role anchor (Step 4), never by index or title-stem. - Never creates or promotes a page at
--status canonical. AI authors atdraftonly; promotion isdoc-ratify's job, server-guarded. - Never writes obsolescence/history narration into a body ("previously", "used to", "as of", an in-body changelog). Bodies are current-state-only (P4) — EXCEPT dated/append version pages (
release-notes/retrospective/adr), which are point-in-time records exempt from the P4 obsolescence-narration lint (taxonomy.md §5). - Never writes a sibling page for a concept that already has a live or draft page (P1). A draft on the topic is a real candidate.
- Never bypasses the ASK gate by silently picking a candidate, and never stacks more than one question.
- (Front-stage, Step 0) Never writes an unflagged guess into an authored unit. A claim is either evidenced (a code path or a ratified page it can cite) or it is a flag — one plain-English chat question, never an
## Open questionsbody section and never a Docmost comment used as a chat surface (comments are for genuine page review, not skill flags). Never invents details "to fill a gap"; a gap is a flag. Never copies old wiki prose into the unit as fact — old pages are evidence only, cited in the reasoning, never laundered into canon unreviewed.
On Activation
Step 1: Resolve the Workflow Block
Run: python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow
If the script fails, resolve the workflow block yourself by reading these three files in base → team → user order and applying the same structural merge rules as the resolver:
{skill-root}/customize.toml— defaults{project-root}/_bmad/custom/{skill-name}.toml— team overrides{project-root}/_bmad/custom/{skill-name}.user.toml— personal overrides
Any missing file is skipped. Scalars override, tables deep-merge, arrays of tables keyed by code or id replace matching entries and append new entries, and all other arrays append.
Step 2: Execute Prepend Steps
Execute each entry in {workflow.activation_steps_prepend} in order before proceeding.
Step 3: Load Persistent Facts
Treat every entry in {workflow.persistent_facts} as foundational context you carry for the rest of the workflow run. Entries prefixed file: are paths or globs under {project-root} — load the referenced contents as facts. All other entries are facts verbatim. At minimum this loads skill:doc-session-policy (the constitution) and file:_bmad/doc/data/decision-order.md (the router).
Step 4: Load Config
Load {project-root}/_bmad/doc/config.yaml and resolve:
docmost_space— the manual's space slug.docmost_manual_root_slug— the manual root, entry point for IA-node resolution.docmost_manual_outline— path to this project'smanual-outline.yaml(the project-derived IA).
If docmost_space is missing, HALT and tell the user the manual is not configured. If docmost_manual_root_slug / docmost_manual_outline is missing, the manual is not yet scaffolded — surface that and stop (run manual-scaffold first).
Step 5: Execute Append Steps
Execute each entry in {workflow.activation_steps_append} in order. Activation is complete; do not begin the main workflow until every activation step has run in order.
Pre-flight (always, before the workflow)
docmost-cli auth status --output json # exit non-zero → HALT; tell user to run `docmost-cli auth login`
docmost-cli cache sync --space {docmost_space} # so dup-guard + CAS reads are current
Branch on exit code + the errorCode field from the JSON envelope. Never parse stderr text. (Exit/errorCode table: docmost-cli-reference.md §"Exit codes & error envelope" and CONTRACTS.md §0.4.)
Execution
Establish scope (one crisp question, only if scope was not supplied). Ask: "In one or two sentences, what does this {target_doc_type} on «{topic}» need to cover that its parent section does not?" The answer bounds the unit; anything out of scope becomes a deferred follow-up (a future unit), never wedged in. This is the same P3 one-question discipline the rest of the skill uses.
Gather evidence — code first, wiki second. Build a small evidence list before writing a line. Recent code is strong; stale wiki prose is suspect and is evidence only, never copied as fact.
# Wiki — body FTS + near-topic pages (excluding the manual node itself; we don't cite the manual to itself):
docmost-cli search "<topic>" --cached --content --space {docmost_space} --output json
docmost-cli page list --filter 'title contains "<topic>"' --space {docmost_space} --output json
# Code — relevant paths/identifiers via Grep/Glob/Read. Capture for each evidence item:
# source kind (code|wiki), path-or-slug, one-line summary, last-updated date
# (wiki: updated_at from page get; code: git log -1 --format=%cI <path>).
This evidence probe and the Step-2 find-before-create probe overlap deliberately; both are cheap and cache-backed. Step 0's probe is for authoring material; Step 2's is the dup arbiter. Running Step 0 does not exempt the unit from Step 2 — every authored unit still goes through find-before-create.
Draft the strawman (plain language, current-state-only). Write the unit a smart new teammate could follow: a 2-3 sentence "in short" lead, then the substance of this concept only, drilling down by linking to sibling/child concepts rather than inflating page length. Use the body shape of {project-root}/_bmad/doc/data/doc-type-templates/<target_doc_type>.md as the starting structure. Quote code identifiers / config keys / spec terms verbatim — never paraphrase them. Follow the house style in data/authoring-conventions.md (header-card callout, ·-metadata bar, Canon row + ↑Part-of breadcrumb). No ratification stamp, no ## Open questions section, no ## Evidence cited section, no "previously / used to / as of" narration (P4) — the body reads as finished current-state prose. The evidence list stays in your working reasoning, not in the body.
Satisfy P7 — author the right embeds for the content (not just prose). A flat wall of prose fails P7. After the prose is drafted, add the embeds the content calls for, picking each per the "Embeds by doc-type" table in data/rich-page-authoring.md and the DIAGRAM POLICY: a diagram for any real flow/architecture (coloured MERMAID for multi-node / must-render-unattended; coloured EXCALIDRAW for a simple ≤3–4-node diagram — and ship the adjacent [bake-pending] warning callout the policy requires); a table/chart for structured data/comparisons; a callout for a genuine gotcha or the lead-style header card; a task_list for an ordered procedure; a transclusion for a fact already canonical elsewhere (cite, don't re-state); subpages for an index/landing; and a live linear_* embed where the content is status that should stay current. Author every embed with the page block verb — never hand-write embed markdown into the unit. See data/rich-page-authoring.md (the composition recipe, §0 EMBED-READ LANDMINE, the diagram policy) and data/authoring-conventions.md. Preview with --dry-run, then confirm with verify render once the page exists (Step 4/Step 5). Embeds carrying inferred content are still subject to the P3 flag discipline below.
Flag inferences as ONE chat question at a time (P3 — never an unflagged guess). Walk the draft. For each place where you inferred something the evidence does not directly support, two sources disagree, code contradicts old docs, or you picked one reasonable option among several, queue a plain-English question. Then ask exactly ONE — the highest-impact (structural/scope/naming before minor author-choice) — wait for the answer, fold it into the draft, and only then ask the next. Be selective: if a point can be sensibly defaulted, default it; reserve questions for what the human genuinely must decide. Never stack questions, never post them as a body section, never post them as Docmost comments. One question, in plain English, naming the topic and the single thing you need decided — e.g. "On «{topic}»: the code does X ({path}) but the old wiki says Y — which is current?" Wait for the reply; apply it directly (chat answers are authoritative — do not playback-confirm); then ask the next queued question, or stop when none remain. Record it as a deferred follow-up (a candidate future unit) and drop it from this unit — do not wedge it in. Surface it in the final report, not in the body. Do NOT guess past a flag. Draft only the evidenced portions, and report the open flags as a deferred decision so a human resolves them before the unit is routed. Never launder an unanswered inference into the body.
Set content_unit to the flagged-and-resolved strawman (in memory or a temp file) and continue to Step 1. The unit now carries only evidenced or human-ratified claims, current-state-only. From here the standard spine routes it: find-before-create → ASK → amend-in-place → create-at-draft. Promotion to canonical is still doc-ratify.
Run the find-before-create pre-probe across drafts AND canonical. Treat any hit as a candidate:
docmost-cli search "<topic>" --cached --content --space {docmost_space} --output json
# FTS body match (misses unindexed drafts — the create-path guard in Step 5 is the backstop)
docmost-cli page list --filter 'title contains "<topic>"' --space {docmost_space} --output json
# title match, ALL statuses incl. draft — do NOT add `--status canonical`; drafts are candidates
There is no standalone page duplicate-check verb. The synchronous, draft-inclusive, semantic dup guard is built INTO page create (--force-new + exit 8 DUPLICATE_CANDIDATE — CONTRACTS.md §1.2), which fires in Step 5. This pre-probe (FTS + title match) lets us route an obvious existing concept through the ASK gate up front; anything it misses (an unindexed draft, a semantic-only match) is caught by the create-path guard, which returns the candidate and bounces back to the ASK gate. PENDING: a draft-inclusive semantic POST /api/orvex/pages/duplicate-check endpoint (CONTRACTS.md §2.4) would let this PRE-probe also see semantic/unindexed-draft matches before the create attempt; until it ships, the FTS/title pre-probe plus the create-path guard cover the same ground.
Read the results.
Set {{candidate_slug}} to the top candidate's slug and {{candidate_title}} to its title. GOTO step 3 (ASK GATE). Do NOT silently pick — even a high-similarity match goes through the one question, because the human decides amend-vs-new.
No pre-probe hit. GOTO step 5 (CREATE) — the create-path dup guard is the authoritative backstop; if it finds a candidate the pre-probe missed, it bounces back to the ASK gate (exit 8).
Log a WARN with the errorCode. On a transient failure (SERVER_UNREACHABLE, CACHE_STALE) re-sync and retry once. Even if the pre-probe cannot run, do NOT create blindly — the create-path guard still arbitrates, but a probe outage on top of an unindexed draft is exactly the P1 failure this skill prevents, so prefer surfacing the situation to the human over a silent create.
Read the candidate so the diff is accurate:
docmost-cli page get {{candidate_slug}} --output json
Compose the one-line diff: the single most material thing content_unit would change or add to the candidate's affected section. Keep it to one line.
Make the question durable on the candidate so the decision is auditable and survives the suspend:
docmost-cli comment add {{candidate_slug}} \
--body "Found existing «{{candidate_title}}». Amend it to add: <one-line diff>? (y = amend / n = new page)"
SUSPEND the workflow and surface the SAME one question to the human in chat:
Found an existing page «{{candidate_title}}». Amend it to add: ? Reply amend to update it, or new to create a separate page.
Auto-resume from the chat reply. Set {{asked}} = true.
Read the current body and capture updated_at for CAS. page get is fine for a prose-only page, but if the candidate has (or might have) embeds, read it via page mirror pull — page get (INCLUDING --output json) silently DROPS embeds (they collapse to empty ## headers) and strips link URLs; mirror pull is the only faithful read (the EMBED-READ LANDMINE, data/rich-page-authoring.md §0):
docmost-cli page get {{candidate_slug}} --output json # prose-only candidate; also yields updated_at for CAS
docmost-cli page mirror pull <dir> --space {docmost_space} # embed-bearing candidate — the FAITHFUL read
Capture {{cas_version}} = the returned updated_at (from the page get envelope, or the mirror's recorded version). CAS is the DEFAULT for skill writes (CONTRACTS.md §1.3 / cross-cutting invariant 3) — every write below carries --if-version.
Decide the edit shape:
ALWAYS (current-state living pages): the amended body says what is true NOW. No "previously / used to / as of / no longer", no in-body changelog. History lives in git + Docmost page history + the server-rendered changelog projection (P4). EXCEPT dated/append version pages (release-notes / retrospective / adr), which are point-in-time records exempt from the P4 obsolescence-narration lint (taxonomy.md §5) — but those are CREATEd as dated children in Step 5, not amended here.
Set {{action}} = "amended", capture {{page_slug}} and {{page_url}} from the receipt, and GOTO the output step.
Capture {{page_slug}} and {{page_url}} from the receipt.
Author the P7 embeds the content calls for (not just prose). --content @<content_unit> lands the prose; now add the embeds via the page block verb — never hand-write embed markdown. Pick each per the "Embeds by doc-type" table and the DIAGRAM POLICY in data/rich-page-authoring.md (diagram for flow/architecture, table/chart for data, callout for gotchas / the header card, task_list for ordered procedures, transclusion for facts already canonical elsewhere, subpages for an index, live linear_* for status), following data/authoring-conventions.md. Carry --if-version; preview with --dry-run; then confirm with verify render. For a simple coloured Excalidraw diagram, ship the adjacent [bake-pending] warning callout the policy requires (doc-ratify surfaces the human Save-&-Exit bake as a REQUIRED HUMAN TASK); prefer coloured MERMAID for anything multi-node / must-render-unattended.
Link the new page in (anti-orphan — data/citations-and-crosslinks.md). A fresh page with no inbound link is an orphan. After the create: (1) link the new page into its section landing ({{ia_parent_slug}}) — for a dated/append type this is the section's living index landing; (2) add the ↑Part-of breadcrumb on the new page pointing at its parent (data/authoring-conventions.md); (3) verify the link took:
docmost-cli page backlinks {{page_slug}} --output json # MUST be non-empty after the link-in
The page is at status: draft and is quarantined from ai ask / grounding reads until ratified — STOP here. Promotion to canonical is doc-ratify's job (decision-order §6), never this skill's.
Error handling
Branch on exit code + the errorCode field — never on stderr text.
| Condition | Action |
|---|---|
docmost-cli not on PATH |
HALT; tell the user to install/authenticate the CLI |
auth status non-zero (AUTH_MISSING) |
HALT; tell the user to run docmost-cli auth login |
read exits 3 (CACHE_STALE) |
re-run cache sync once and retry; if still stale, WARN and stop |
find-before-create pre-probe (search / page list) unavailable / non-zero |
the create-path dup guard (exit 8) is the authoritative backstop; on total probe failure, surface to human rather than create blindly |
page create exits 8 (DUPLICATE_CANDIDATE) |
read matches[], return to ASK GATE (step 3); never --force-new to silence it |
page create exits 2 (BANNED_SLUG_SUFFIX / DATE_SLUG_NOT_ALLOWED) |
P1 sprawl signal — strip the suffix and AMEND the existing concept instead |
page create --force-new "<token>" exits 2 (FORCE_TOKEN_REQUIRED) |
no valid human token — do not mint one; return to ASK GATE or amend |
any write exits 7 (CONFLICT, --if-version mismatch) |
re-read live, rebase the edit, retry once; if it conflicts again, stop and surface |
FORBIDDEN (exit 5) |
the caller lacks edit permission on the node — surface to the user; do not retry |
| non-interactive context on a candidate | leave the durable comment, do not create a sibling, report a deferred decision |
Dependencies — what is AS-BUILT vs what remains PENDING
The synchronous create-time dup guard, --force-new <token>, and CAS-by-default are built and are the primary path above; the fallbacks below are genuine error-handling for when the CLI is absent/old. Only the role-anchored lead targeting and the semantic pre-probe endpoint remain PENDING.
- AS-BUILT — synchronous create-time dup guard +
--force-new <FORCE_NEW_TOKEN>(CONTRACTS.md §1.2) —page createruns the draft-inclusive guard (exit 8DUPLICATE_CANDIDATE) and accepts the server-minted human token as the single arg to--force-new(exit 2FORCE_TOKEN_REQUIREDwithout it). This is the primary CREATE path. Fallback (CLI absent/old): find-before-create pre-probe +page create --status draft, with a forced-new waiting on an explicit human decision. - AS-BUILT —
--if-versionCAS by default (CONTRACTS.md §1.3) — every write above carries--if-version; the server enforces CAS for skill writes and returns aCONFLICTenvelope on a version mismatch. - AS-BUILT —
page block callout(and the rest of thepage blockembed surface) — the callout verb is fully built (--op append|prepend|replace-at|insert-at,--type info|success|warning|danger,--content,--if-version), as is the broader embed authoring surface (page blockfor mermaid/excalidraw/table/chart/task_list/transclusion/linear_* etc.). Seedata/rich-page-authoring.mdanddata/docmost-cli-reference.md. Use these to satisfy P7 on every CREATE/AMEND (Step 0, Step 4, Step 5). - PENDING — role-anchored
tldrtargeting: a--role tldranchor + server "find block by role" (CONTRACTS.md §0.5 / §2.10) — ONLY the find-by-role targeting of the lead is missing, not the callout verb. There is no--roleflag on the CLI today, so a callout--opcannot land precisely on thedata-orvex-role="tldr"lead. Until it ships: leave thetldrlead untouched and amend the affected body section (never address the lead positionally). - PENDING —
POST /api/orvex/pages/duplicate-checksemantic pre-probe (CONTRACTS.md §2.4) — a draft-inclusive semantic probe that would let the Step-2 PRE-probe see semantic/unindexed-draft matches before the create attempt. The create-path guard (exit 8) already covers this ground as the backstop; there is no standalonepage duplicate-checkverb. Now: FTS (search --content) + title (page list --filter) pre-probe plus the built-in create-path guard.
This skill never builds tokens, never promotes to canonical, and never creates a sibling for an existing concept.