Imported from JHSeo-git/openwiki-skill (
skills/openwiki/SKILL.md). Install upstream withnpx skills add JHSeo-git/openwiki-skill --skill openwiki. Copyright stays with the author.
OpenWiki — repository wiki agent (code mode)
Port of langchain-ai/openwiki v0.5.2, repository ("code") mode. Since upstream 0.4.0 (#713) repository generation is a resumable page-job lifecycle rather than one long agent turn: a bounded planner fixes the complete page set, then one fresh worker writes each page and submits its material Claims, and deterministic code finalizes the wiki. Upstream 0.5.0 (#720) made that resumability durable per page and portable across hosts — a committed openwiki/.page-manifest.json ledger records which pages are already correct for which commit, so a run interrupted anywhere can publish the pages it finished and the next run picks up from there. This skill reproduces the lifecycle — Step 3 routes to references/prompt-planner.md (upstream src/agent/repository-prompts.ts createRepositoryPlannerPrompt + src/generation/page-jobs.ts), Step 4 to references/prompt-page.md (createRepositoryPagePrompt + src/claims/guidance.ts + src/generation/repository-run.ts's submission gates) — wrapped in the runtime steps the upstream CLI performs around them (Step 0 from src/ingestion/code-mode.ts; Steps 1, 2, 6 from src/agent/utils.ts + src/platform/language.ts + src/agent/openwiki-ignore.ts + src/agent/wiki-replacement.ts + src/generation/page-manifest.ts; Steps 2 and 5 from src/agent/wiki-finalizer.ts and what it orchestrates — src/okf/index-sync.ts, src/okf/index-labels.ts, src/mermaid/wiki.ts, src/agent/wiki-link-validator.ts, src/okf/claim-sources.ts, src/okf/generated-provenance.ts). You are the agent; the current repository is the target. No CLI, no API key — you do the work with your own tools.
Harness adaptations are marked [adapted]; upstream content with no equivalent here is marked [omitted]. Everything else is upstream text — keep it that way so upstream syncs stay line-mappable (see UPSTREAM.md in this skill's source repo). Upstream's personal knowledge wiki ("local-wiki" mode at ~/.openwiki/wiki) is ported as the separate openwiki-personal skill; wiki Q&A as openwiki-ask.
Mode resolution
- The user explicitly asks to initialize / build from scratch → init.
- The user explicitly asks to update / refresh → update.
- Otherwise auto-detect:
openwiki/quickstart.mdexists → update; it does not → init. - Init is destructive since upstream 0.4.0 (#699): it regenerates the wiki from scratch, keeping only the user-authored
openwiki/INSTRUCTIONS.md. Step 2 performs that replacement. If auto-detection lands on init becauseopenwiki/exists without aquickstart.md, say so and confirm before wiping — the user may have meant update. - The user asks to migrate the wiki to OKF / fix wiki front matter → run update: Step 2's normalization pass migrates every non-compliant page deterministically. The request counts as an additional user instruction, so Step 1's early no-op exit does not apply.
- The user asks for the wiki in a specific language ("write the wiki in Korean", "switch the wiki to zh-CN") → that is the run's requested output language, resolved in Step 1 (upstream: the
--languageflag). Since 0.4.0 a language switch on an update is no longer a separate translation pass: it forces the run (#548) and Step 3 injects a rewrite job for every existing page, so the page workers rewrite them in the new language. The request counts as an additional user instruction, so Step 1's early no-op exit does not apply. - Step 1 finds stale or unresolved page evidence → run update even on a clean tree: upstream runs Claims validation before no-op detection precisely so a clean
git statuscannot hide stale grounding. - The user asks to pull LangSmith runtime traces / production run evidence into the repo wiki → runtime evidence update run: read
references/runtime-evidence.mdin this skill's directory and follow it (every step here still applies; the file's guidance block becomes Step 3's planning context). - Any other instruction in the user's request (e.g. "focus on the API routes") is an additional user instruction. Upstream passes it as the run's
planningContextand it forces the run (force: Boolean(userMessage)), skipping the no-op check — carry it into Step 3's planner prompt as the "User and connector planning context" block, and let the planner copy the relevant parts into the affected pages'instructions.
Model tier
Upstream defaults to frontier coding models. Documentation quality depends on it — run this skill on a frontier tier, not a small/fast model.
Step 0 — Code setup (ported from upstream code-mode.ts)
Upstream performs this repository setup on every code-mode invocation, outside the agent. Do it at the start of every init/update run, before Step 1:
- Ensure
/AGENTS.mdand/CLAUDE.mdeach carry its snippet below (upstreamCODE_MODE_AGENT_FILES— each file is created when missing and refreshed in place when already present; since 0.3.3, #640, the two files get different snippets: AGENTS.md the full block, CLAUDE.md a minimal pointer to it, so one file stays the canonical source of agent instructions while Claude Code still has a file it reads at startup):- Markers
<!-- OPENWIKI:START -->/<!-- OPENWIKI:END -->present → the file must contain exactly oneSTARTmarker followed by exactly oneENDmarker (since 0.3.0, #547). Then replace everything between and including the markers with the file's snippet — skip the write when the existing block is already identical ([adapted] upstream rewrites unconditionally; the byte outcome is identical). Malformed or duplicated markers → fail the setup with upstream's error (Cannot update <file> because its OpenWiki managed markers are malformed or duplicated. Expected either no markers or exactly one <!-- OPENWIKI:START --> marker followed by one <!-- OPENWIKI:END --> marker. Repair or remove the markers and retry; the file was left unchanged.) and write NEITHER file — upstream validates both files before writing either, so a malformed sibling leaves both untouched. - [adapted] A legacy
## OpenWikisection without markers (written by pre-0.1.0 versions of this skill) present → replace that section with the file's snippet instead of appending a duplicate (upstream never sees this state; this port migrates it). - Neither present → append the file's snippet to the end of the file, separated by one blank line; if the file does not exist, create it containing only the snippet.
- Markers
- Exception for
/CLAUDE.md(upstream 0.5.1, #841 — a port-original adaptation until upstream adopted it): if the file's entire content, trimmed, is exactly@AGENTS.md, leave it completely alone — no markers, no pointer block, not even a rewritten trailing newline. Upstream returns before the marker branches (nextContent: undefined), so such a file is never appended to and never validated for markers. The reason is the one this port already gave: Claude Code loads AGENTS.md through the import, which does the pointer's job. [adapted] upstream's rule is narrower than the one this skill carried before 0.5.1, which treated any@AGENTS.mdline anywhere in the file as covering it — follow upstream, so a CLAUDE.md holding the import plus other content now gets the managed pointer block appended like any other file. That block is marker-managed and idempotent, so the redundancy costs nothing, and matching upstream keeps a repo set up by this skill byte-identical to one set up by the native CLI. Since 0.5.2 (#777) the skip is self-evidently safe: the managed block's entire content is now@AGENTS.md, so a file that already consists of exactly that line carries what the block would have written, minus the markers. - [omitted] Upstream also creates
.github/workflows/openwiki-update.yml(a scheduledopenwiki code --update --printworkflow that needs a provider API key) — since 0.2.3 onlyopenwiki code --initcreates it, and an existing file is never overwritten, so operator customizations survive. This keyless port does not create CI files unasked — for scheduled updates readreferences/automation.md.
The AGENTS.md snippet — keep byte-identical to upstream createCodeModeAgentsSnippet():
<!-- OPENWIKI:START -->
## OpenWiki
This repository has a generated `openwiki/` evidence index. It is optional just-in-time context, not required startup reading.
- Treat source code and tests as authoritative. A brief's unknowns and review items are verification gaps, not automatic requirements.
- Prefer the narrowest quiet validation that proves the changed behavior. Preserve complete failure output.
The scheduled OpenWiki GitHub Actions workflow refreshes the repository wiki. Do not hand-edit generated OpenWiki pages unless explicitly asked; prefer updating source code/docs and letting OpenWiki regenerate.
<!-- OPENWIKI:END -->
The CLAUDE.md snippet — keep byte-identical to upstream createCodeModeClaudeSnippet() (since 0.3.3; the Markdown pointer became a real import in 0.5.2, #777):
<!-- OPENWIKI:START -->
## OpenWiki
@AGENTS.md
<!-- OPENWIKI:END -->
The import syntax is the whole point of that fix, and it is a fix about this host: Claude Code expands only its own @path syntax, and it reads AGENTS.md on its own only when no CLAUDE.md sits beside it — which, once this snippet is written, is never. So the See [AGENTS.md](AGENTS.md) link upstream shipped from 0.3.3 through 0.5.1 was inert: the OpenWiki instructions reached Codex through AGENTS.md and never reached Claude Code, the host CLAUDE.md exists for. Write the import, not a link.
If /CLAUDE.md and /AGENTS.md are the same file on disk (a symlink or hard link, which some repositories use to serve both hosts from one file), give that file the AGENTS.md snippet instead — the full block, not the pointer (upstream 0.5.2, #777: resolvesToSameFile compares ino+dev, treating inode 0 as unknown because some Windows filesystems report it for every file). An import would otherwise point the file at itself. Check this before writing, since the two files are then one write, not two.
Only Step 0 may touch /AGENTS.md and /CLAUDE.md. The documentation run itself never does.
[omitted] Upstream 0.4.0 also ships openwiki install <host> (#685, #711), which installs a bundled openwiki skill plus an MCP server into Codex, Claude Code, or OpenCode so those hosts drive the same lifecycle through openwiki_begin / openwiki_submit_plan / openwiki_next_page / openwiki_submit_page / openwiki_finish tools. This port is that integration without the runtime: the behavioral contract from upstream's bundled integrations/openwiki/SKILL.md is folded into Steps 3–5 and references/prompt-page.md, and the lifecycle's code-owned bookkeeping is this skill's deterministic steps.
Step 1 — Run context, evidence preflight, and early no-op check (before any write; all git read-only; use git --no-pager)
Read openwiki/.last-update.json if it exists to recover gitHead, updatedAt, status, language, and model (upstream readLastUpdate: an unreadable or structurally invalid file counts as no metadata; a status other than "interrupted", including the field being absent from pre-0.2.4 metadata, counts as "complete"). Read openwiki/INSTRUCTIONS.md if it exists — the user-authored OpenWiki brief for this repository, rendered into Step 3's planner prompt as "Repository OpenWiki instructions" (upstream readRepositoryWikiInstructions; absent or empty → the block is omitted entirely).
Resolve the wiki output language (ported from upstream resolveLanguage + createRunContext): the effective language is the requested language when the user asked for one, else the metadata's language, else en — an update without a language request inherits the wiki's persisted language so it stays consistent instead of mixing languages, and English is always materialized as an explicit en rather than encoded by an absent field. Canonicalize a requested language to a BCP-47 tag (ko, zh-CN, pt-BR).
An unrecognizable language request stops the run (upstream 0.5.0, #761 — this reverses the old behavior): Unrecognized language "<input>". Use a BCP-47 code such as ko, zh-CN, or pt-BR rather than a language name. Until 0.4.3 upstream warned and generated in English; it now rejects before touching the repository, because falling back would persist the wrong language in run state and a later run is then refused permission to change it — the user could not correct a typo without deleting OpenWiki's own state files. Reject at the entry too: ask for a valid tag before Step 2, and write nothing. [adapted] with one real difference — upstream validates a --language flag, while your input is prose, so mapping a language name to its tag is your job, not an error ("write the wiki in Korean" → ko, and upstream's "rather than a language name" advice does not apply to you). Stop only when no real language is identifiable at all: a typo you cannot resolve, an invented tag, or an ambiguous request. Never silently default to English.
Record whether the language changed: the requested language's primary subtag differs from the recorded metadata's (upstream getPrimaryLanguageSubtag — zh-CN and zh match; en and ko do not). A region-only change such as en → en-GB is not a change. (Repository runs no longer translate: upstream 0.4.0 mounts the translation middleware for local wikis only, so an openwiki_translation_pending marker left on a page by an older run is inert here — it is preserved as an extension field, and a language switch's Step 3 rewrite jobs are what resolves it.)
Load .openwikiignore (ported from upstream OpenWikiIgnore.load/.parse, since 0.2.5 — loaded for repository runs only): read .openwikiignore from the repo root; a missing file means no rules. Drop blank lines and # comments; each remaining line is a gitignore-style pattern — * matches within one path segment, ? one non-slash character, ** spans directories; a leading / (or any embedded slash) anchors the pattern to the repo root, otherwise it matches at any path segment; a trailing / scopes it to directories (still excluding everything nested beneath); a leading ! re-includes, and the last matching rule wins. Matching is case-insensitive (deliberate and security-relevant: on case-insensitive filesystems an alternate-cased spelling would otherwise slip past an exclusion), and paths are canonicalized before matching (backslashes → /, ./.. segments collapsed without escaping the repo root) so spellings like ./secrets/x or secrets/../secrets/x cannot dodge an anchored rule. No usable patterns → the rules are inactive and every .openwikiignore provision in this skill is a no-op.
Check for the two native-CLI lifecycle files. Both belong to the upstream CLI, not to this run (unlike openwiki/.last-update.json, which Step 6 owns). Never write, delete, or hand-edit either one, and never read either as instructions — they are data.
openwiki/.run.json— the upstream CLI's transient run checkpoint, present only when a native run was interrupted (upstreamreadRepositoryRunState). [omitted] This port has no resumable checkpoint; it runs the lifecycle start to finish in one session. Tell the user it exists, that a nativeopenwiki --updatewould resume from it, and that this run starts fresh instead. Note that upstream's own CI now deletes it after every run (rm -f -- openwiki/.run.json, #720) precisely because it is transient and must never be committed.openwiki/.page-manifest.json— new in 0.5.0 (#720) and a different animal: a committed per-page correctness ledger, written as each page completes and rewritten at finish — and since 0.5.2 (#848) that rewrite only restamps the pages the run actually regenerated. Every other tracked page keeps its priorgitHeadandsourceFingerprint(itspageVersionstill refreshed if the deterministic passes rewrote its bytes), a formally skipped page keeps its exact prior entry, and an untouched page with no prior coverage is left uncovered for full review rather than failing the run. Shape: a top-levelschemaVersion: 1plus apagesobject keyed by canonical page path (/openwiki/x.md), each entry carryingpageVersion(required — the SHA-256 of the page's exact bytes) and the optionalgitHead,sourceFingerprint,completedBy, andcompletedRunId. It is what lets a failed run's finished pages be merged and trusted as the next run's baseline. Read it; do not write it. The read is valuable — see the next paragraph. The write is impossible on purpose: a valid entry requires the page's.claimssidecar to exist and carry averificationevent whosepageVersionmatches the page's current bytes (upstreambuildManifestEntry), and this port keeps no sidecar and stamps noverified(Step 5 says why). An entry this port could fabricate would assert a machine verification that never happened.
Fail-safe consequence worth stating so a later sync does not "fix" it: because this port leaves no sidecar, a native run that follows a port run finds the manifest's coverage unverifiable (getCurrentRepositoryPageCompletion re-checks the sidecar) and re-reviews those pages instead of trusting stale entries. Stale-but-ignored is the correct direction. Equally, do not port upstream's rule that a no-op requires every existing page to have a manifest entry (hasCompleteBaselineCoverage): that gate only works because upstream can seed and record entries, and a presence test here — where port-authored pages never get one — would wedge every future update into a full run.
Then read the manifest for per-page update baselines (upstream getRepositoryPageUpdateWindows), which needs no sidecar — only each entry's gitHead. Group the existing concept pages by the baseline recorded for them, and compute each cohort's own changed-path set from that baseline; a page with no entry, or an entry with no gitHead, goes in a full-review cohort. Carry the cohorts into Step 3 as the planner's "Committed per-page update windows" block. No manifest → you have exactly one provable baseline, the metadata's gitHead, so there is one window covering every page (or full review when no head was recorded), which is what this port did before 0.5.0. Upstream additionally advances a page's baseline when nothing visible changed since it (fastForwardUnchangedRepositoryPageCoverage, for commits that touched only generated wiki files); you cannot advance a ledger you do not write, but the same conclusion falls out of the window itself — a cohort whose changed-path set is empty needs no source-driven work.
How much you can trust a recorded gitHead changed in 0.5.2 (#848), and it matters here more than the fix looks. Until then the finish rewrite stamped every surviving page with the finishing run's checkpoint, including pages that run never regenerated — so a page nobody re-reviewed claimed to be verified as of the newest commit, and a window computed from it would skip real changes in the unsafe direction. Entries written by 0.5.2 or later are honest per-page truths. Read an older native run's manifest with that in mind: if you have reason to doubt an entry's head, put that page in the full-review cohort, which is also what upstream now does for an untouched page it cannot cover.
Run the evidence preflight ([adapted] from upstream runClaimsPreflight + RepositoryEvidenceResolver). Upstream resolves every persisted Claim's evidence against current source and classifies it unresolved (the resource no longer exists) or stale (its content version moved), using opaque per-Claim versions stored in openwiki/.claims/. This port keeps no sidecar (see Step 5), so read the durable projection instead — each concept page's own OKF sources front matter:
- For every
.mdfile underopenwiki/exceptindex.md,log.md,INSTRUCTIONS.md, and dot-files/dot-directories, parsesourcesand collect each entry'sresource. - A
repo://<path>resource whose file no longer exists, or exists but is not a regular file → an unresolved issue for that page. - A
repo://<path>resource whose file changed → a stale issue for that page. Take the changed set fromgit --no-pager diff --name-only HEADandgit --no-pager ls-files --others --exclude-standard, plusgit --no-pager diff --name-only <gitHead>..HEADwhen metadata recorded agitHead(the three sources upstream'sgetRepositoryChangedPathsunions). Without git, skip the stale check — absence of history is not evidence of staleness; without a recordedgitHead, the working-tree sources still apply. - A cited resource that resolves through a symlink out of the repository is an unresolved issue — and since 0.5.2 (#881) that is upstream's behavior too, no longer this port's deviation: a containment refusal (
EvidenceSecurityError) is permanent for the cited resource rather than operational, so upstream now reports it for reconciliation instead of aborting the update. A cited resource that has since become excluded by.openwikiignoreis still a hard error upstream, because that path throwsEvidenceResourceErrorand #881 downgraded only the security class: the resolver throws and the whole run fails rather than let unverifiable grounding pass as merely missing. [adapted] Do not fail the run for either — the user asked for a wiki update, not a diagnosis. Treat both as unresolved, and say explicitly which page cites which now-unreadable resource, so the user can fix the rule or the page instead of silently losing that page's grounding. - Sort issues by page, then kind, and carry them into Step 3 as the planner's "Claims requiring attention" block. Fidelity gap worth knowing: this is per-page and per-file, where upstream is per-Claim and content-versioned against opaque resolver tokens, so it cannot distinguish a line range that merely moved from one that was rewritten, it never reports the "range can no longer be located" flavour of unresolved, and a page whose cited file changed anywhere is flagged.
- Init runs skip the preflight entirely (upstream
freshInit): Step 2 replaces the wiki, so there is nothing to reconcile.
Then run the early no-op check (update mode only — ported from upstream getUpdateNoopStatus as called from beginRepositoryRun). Upstream deliberately orders it after the evidence preflight so a clean tree cannot hide stale grounding.
- The user gave an additional instruction → not a no-op (upstream forces the run).
- No recorded metadata or no recorded
gitHead, or not a git repository → not a no-op; proceed. (Without git, infer changes during Step 3 from filesystem timestamps, source inspection, and existing docs.) - Recorded
status: "interrupted"→ do not skip; the previous run may have left a partial wiki, so it must be retried (#365). - The output language changed (above) → not a no-op (#548): the pages have to be rewritten in the new language.
- The preflight found any issue → not a no-op.
- Otherwise run:
git --no-pager status --short --untracked-files=all
git --no-pager rev-parse HEAD
- The worktree must be clean, ignoring the status lines for the two files the lifecycle itself rewrites —
openwiki/.last-update.jsonand, since 0.5.0 (#720),openwiki/.page-manifest.json— and, when ignore rules are active, status lines whose paths are excluded by.openwikiignore(a rename line counts when either its old or new path matches, for any of these). Any other line → not a no-op. - HEAD equals the recorded
gitHead→ no-op. HEAD moved → rungit --no-pager diff --name-only <gitHead>..HEAD; every changed path lies underopenwiki/or is excluded by.openwikiignore(at least one such path — if git reports no changed paths, do NOT treat it as a no-op) → no-op. Anything else → proceed. - On a no-op, still refresh the metadata (#647, new in 0.4.0): rewrite
openwiki/.last-update.jsonso freshness checks reflect the actual last run, carrying the previous run'smodelandlanguageforward unchanged, with a freshupdatedAt, the currentgitHead,command: "update", andstatus: "complete"— the last of which also clears a previousinterruptedstatus. Then report that the wiki is already current and stop: Steps 2 through 6 do not run, and this refresh is the run's only write. - (Step 0 still runs before this exit — upstream refreshes the repo setup even on no-op runs.)
Step 2 — Prepare the wiki (before the work)
Init runs only: replace the existing wiki (ported from upstream beginRepositoryWikiReplacement in src/agent/wiki-replacement.ts, new in 0.4.0 / #699). openwiki/INSTRUCTIONS.md is user-owned control metadata; everything else under openwiki/ is generated state and is removed before the run sees the repository:
- No
openwiki/directory → nothing to do. openwikiexists but is not a real directory, or is a symlink → refuse, with upstream's error:Refusing to replace openwiki: expected a real directory below the repository root.Same refusal for anopenwiki/INSTRUCTIONS.mdthat is not a regular file:Refusing to preserve openwiki/INSTRUCTIONS.md: expected a regular file.- Otherwise: copy
openwiki/to a private temp directory as a recovery backup, delete the whole tree, recreateopenwiki/empty, and copyINSTRUCTIONS.mdback if the backup has one. Tell the user where the backup is. If the run fails or is cancelled before it finishes, restore the backup overopenwiki/— upstream rolls back on SIGINT/SIGTERM for exactly this reason. Discard the backup once the run completes. - [adapted] Upstream drops the backup as soon as its
.run.jsoncheckpoint is durable, because partial pages then become the recovery mechanism. This port has no checkpoint, so keep the backup for the whole run — it is the only recovery path.
Then snapshot the wiki content (ported from upstream createOpenWikiContentSnapshot):
find openwiki -type f ! -name '.last-update.json' ! -name '.run.json' -print0 2>/dev/null | LC_ALL=C sort -z | xargs -0 shasum -a 256 2>/dev/null | shasum -a 256
Record the hash. (shasum -a 256 covers macOS and most Linux; on minimal Linux images substitute sha256sum in both places. Upstream's snapshot ignores only transient run state — .last-update.json and, since 0.4.0, the .run.json checkpoint; _plan.md is gone from the exclusion list because the plan file itself is gone. Note the deliberate asymmetry 0.5.0 introduced: openwiki/.page-manifest.json is not excluded here, because it is committed wiki output and a change to it is a real content change — yet its git status line is ignored by the no-op check above, since a no-op run rewrites it. Keep both behaviors as upstream has them.) You will recompute it in Step 6. [adapted] The hash is compared only within this run — upstream never persists it. Upstream's snapshot additionally hashes directory entries and scopes the metadata exclusions to the wiki root; this one-liner's changed/unchanged verdict differs only on states documentation runs don't produce (empty directories, nested metadata files).
[adapted] Upstream also fingerprints every model-visible repository source file here (createRepositorySourceFingerprint). Until 0.4.2 a mid-run change invalidated the whole plan and forced a replan; since 0.4.3 (#740) it does not — the run finalizes once and records that a later update is due instead, on the same reasoning as a skipped page: a partially refreshed wiki plus a forced follow-up beats discarding the work. Do the cheap equivalent here: record the current git rev-parse HEAD and worktree state now, re-check it twice — once before Step 5's passes and once at Step 6, since a change that starts mid-finalize counts too — and if either check differs, treat the run as drifted. Step 6 says what that costs.
Then repair the wiki's front matter (ported from upstream repairOkfFrontmatter, which migrateWikiToOkf delegates to, run by prepareWikiForAuthoring so the run operates over an already-conformant wiki). Since 0.4.1 (#728) this is a field-by-field repair, not a rebuild: invalid optional metadata used to abort a run, and now it is repaired or removed deterministically instead. Every path below ends in a page that passes OKF validation. For every .md file under openwiki/ except index.md, log.md, INSTRUCTIONS.md, and dot-files/dot-directories:
- The page already passes OKF validation → leave it byte-for-byte unchanged. (This is a stronger test than 0.4.0's "parses and has a non-empty
type": junk optional fields are no longer tolerated, they are repaired.) - Otherwise, if the YAML block still parses to a mapping → repair in place, keeping every other line and every producer extension field as written:
typemissing or not a non-empty string → set the fallbacktypeand setopenwiki_generated: true.title→ set the derived title when either thetypewas just derived and notitlekey is present, or atitlekey is present but is not a non-empty string. A valid page missing onlytitleis left alone —titleis optional.description,resource,timestamppresent but not a non-empty string → remove the field.tagspresent → keep only the non-empty string entries; nothing left → remove the field.generatedpresent but not a valid{by, at?}actor event → remove it. A trust assertion that cannot be proven conformant is removed rather than rewritten into a false one.verifiedpresent → keep only the conformant events (re-rendered as a list).sourcespresent → keep only the entries with a non-empty stringresource.statusnot one ofdraft/stable/deprecated, orstale_afternot an ISO 8601 datetime with an explicit offset → remove the field.
- If the YAML block cannot be parsed, or the repair still does not validate → replace the front matter with exactly this minimal derived block (one blank line after the closing
---, then the body as-is — since 0.4.1 its leading whitespace is no longer trimmed, so a page that had an unusable block may keep a blank line the old rule removed):
---
type: "Reference"
title: "<first ATX H1 in the body; fallback: filename without .md, -/_ runs → spaces, first character upper-cased>"
openwiki_generated: true
---
- Values are JSON-double-quoted.
openwiki_generated: trueflags code-derived metadata; the page's Step 4 worker should replace it with accurate metadata grounded in the page body and then remove the field. - Non-English wiki language → the derived
typeis that language's localized label fromreferences/index-labels.mdinstead of"Reference"(upstreamresolveConceptTypeLabel: full tag → primary subtag → English fallback). - Upstream's 0.4.0 carry-across lists (
PRESERVED_EXTENSION_FIELDS,PRESERVED_STRUCTURED_FIELDS) are gone in 0.4.1 and this port drops them too: path 2 preserves the original block outright, so nothing needs carrying. The consequence is worth knowing — on path 3, where the YAML is unusable,openwiki_translation_pendingand the code-ownedgenerated/verified/sourcesfamilies are lost, because they cannot be read back from a block that does not parse.
Then capture the generated-provenance baseline (ported from upstream snapshotGeneratedProvenance, new in 0.4.0 / #581, #684). For every concept page (same exclusions), record two things — Step 5 needs both:
- the SHA-256 of its body, i.e. the content after the leading front-matter block, whitespace included:
# Set `page` per file and re-run. Deliberately a variable, not a shell
# function taking a positional parameter: a dollar sign followed by a digit
# is substituted with this skill's invocation arguments before the agent
# ever reads the file, which would silently corrupt the command.
page=openwiki/quickstart.md
if [ "$(head -n1 "$page")" = "---" ]; then sed '1,/^---$/d' "$page"; else cat "$page"; fi | shasum -a 256
- its existing
generatedevent, when it has a valid one (a mapping with a non-empty stringby, and anatthat is a non-empty string when present).
Step 3 — Plan the run (planning phase)
Read references/prompt-planner.md in this skill's directory and act on it exactly. It carries the planner system prompt, the plan payload schema, and the validation rules the plan must satisfy — including the required jobs an update run has to inject for Step 1's preflight issues and for a language change. Render Step 1's per-page update windows into its "Committed per-page update windows" block: since 0.5.0 (#720) that block replaces the flat changed-path list, and the reason is a planning rule, not a formatting change — a page whose baseline already covers a change must not be regenerated for it.
Shared adaptation conventions (this and Step 4's file assume them): (a) upstream's /-rooted virtual paths stay the wiki's canonical page identifiers in the plan and the queue, and resolve to real repo-relative files when you touch the filesystem — /openwiki/quickstart.md is openwiki/quickstart.md; (b) upstream enforces every boundary in code (src/agent/docs-only-backend.ts: writes confined to openwiki/, and since 0.4.0 confined further to the one page a worker owns; .claims state hidden from every generic tool and from shell; reads/edits of .openwikiignore paths hard-denied and ls/glob/grep results filtered; unbounded root globs and .git targets rejected) — here they are hard rules you follow; (c) upstream's lifecycle tools (submit_plan, submit_page) do not exist for you, so their payloads become records you validate yourself against the same gates; (d) template placeholders are rendered in place. [omitted] Upstream's chat-mode prompt (wiki-first question answering + the OpenWiki CLI reference) is the openwiki-ask skill's domain; connector-fed personal wikis are the openwiki-personal skill's.
An update whose plan has no pages and no deletions is legitimate: skip Step 4 and go straight to Step 5.
Step 4 — Work the page queue (generating phase)
Read references/prompt-page.md in this skill's directory and act on it once per planned page, in the queue order Step 3 fixed (all other pages first, /openwiki/quickstart.md last). It carries the page-worker system prompt, the Claims substance and reconciliation standards, the submission gates each page must pass before you move on, and upstream's non-negotiable boundaries. Its Claims contract was rewritten in 0.5.0 (#769) to be sparse — a worker submits only the decisions its edits require, and silence now retains an existing Claim instead of retracting it — so read that file's opening note before the first page: most of the sparse machinery is inert in this port, and it says exactly which part is not.
Two boundaries deserve repeating here because they replace behavior earlier versions of this skill had:
- No subagents. Upstream 0.4.0 deleted
skeleton_critic,wiki_question_finder, andwiki_answer_verifier, strips the delegation tool from every worker, and its bundled host skill says plainly not to spawn planning, page, reviewer, critic, or QA subagents. Work the queue yourself, sequentially. - No working files.
openwiki/_plan.mdandopenwiki/_skeleton.mdno longer exist; any_-prefixed page path is rejected. The plan lives in your working notes, not in the wiki.
A page you cannot finish is skipped, not fatal (since 0.4.1, #732)
Before 0.4.1 a page worker that could not complete aborted the whole update, throwing away every page already written. Now the failure is contained to its own page. Follow the same protocol:
- Snapshot the page before you touch it (upstream
captureRepositoryPageSnapshot): record its exact current Markdown, or that it does not exist yet. An absent page is a valid snapshot, never a failure — it is the normal case on init and for every newly planned page, and upstream had to fix exactly this in 0.4.2 (#737). [adapted] Upstream also snapshots the page's.claimssidecar; this port has none (Step 5), so the Markdown is the whole snapshot. - If you cannot complete the page — you cannot ground it, its gates keep failing, or the work is not converging — restore the snapshot exactly: write the recorded Markdown back, or delete the page if it did not exist before. A delete that reports the file was already absent is success, not a failure — upstream had to widen its check in 0.5.0 (#767) because its backends spell that outcome two different ways, and a rollback that treats "already gone" as an error aborts a run for no reason. Then mark the job skipped, tell the user which page and why, and move to the next job. Do not leave a half-written page behind.
- Keep going. Skipped jobs do not block the rest of the queue, and they do not block Step 5.
- One exception stays fatal: a submission failure that is not a correctable input problem — a page you cannot persist at all — aborts the run. A page that merely fails validation is correctable, so fix it and re-check; a wiki you cannot write to is not.
Carry the list of skipped pages and their snapshots into Steps 5 and 6; both need it. [adapted] Upstream also resets a skipped job to pending in its durable checkpoint so the next run retries it. This port re-plans from scratch every run, so the retry is automatic — but Step 6's metadata is what makes the next run look, so do not skip it.
Step 5 — Finalize (after the work; ported from upstream finishRepositoryRun + src/agent/wiki-finalizer.ts)
Upstream runs these deterministic passes in exactly this order on every init/update run. Do the same, after the page work and before Step 6, so their writes land in the Step 6 content hash. Skip the whole step only when Step 1 exited at the early no-op.
First, restore every skipped page. For each page Step 4 skipped, write its snapshot back one more time before anything else runs (upstream finishRepositoryRun re-restores them at this point, because a later pass may have touched them). A skipped page must reach Step 6 byte-identical to how this run found it. Then treat those pages as excluded for the rest of this step: they take no sources projection and no Claims reconciliation, since this run produced no Claims for them and projecting an empty set would strip the grounding the previous run recorded.
Then apply deletions. If a mid-run replan abandoned pages this run created that are in neither the final plan nor the pre-run page inventory, delete those (upstream applyAbandonedGeneratedPageDeletions — it never touches a page that existed before the run). Then delete each page in the plan's deletePages; a page that is already gone is not an error (upstream reaffirmed this in 0.5.0, #767 — for both deletion passes). Also re-run Step 2's front-matter repair over every concept page that no longer passes OKF validation, so index generation never fails on a non-compliant page.
Then validate Mermaid diagrams (ported from validateWikiMermaid): for every .md file under openwiki/ except index.md, log.md, INSTRUCTIONS.md, and dot-files/dot-directories (upstream EXCLUDED_FILES), check that every fenced mermaid block parses (a mermaid example nested inside a longer outer fence does not count):
- [adapted] Upstream parses each fence with the real Mermaid parser when its optional
mermaid+jsdompeers are installed, and otherwise falls back to a conservative heuristic that only flags near-certain breakages (aflowchart/graphnode id namedend; a semicolon inside a[]/()/{}label; an unescaped angle bracket inside a label). Here, run the check yourself: apply that heuristic plus themermaid-diagramsskill's syntax-safety rules — or a locally installed Mermaid parser when one is available. - [adapted] A broken fence you can confidently repair (you usually wrote it this run) → fix it in place. Otherwise degrade it exactly as upstream does: replace the
mermaid fence with atext fence holding the same body, preceded — at the fence's indentation — by a one-line HTML comment:<!-- openwiki: mermaid parse failed and this diagram was converted to a text fence so it does not break rendering. Fix the diagram source and restore the mermaid fence. Parser error: <one-line reason> -->. A later update run repairs it. - Files whose fences all parse are left byte-for-byte unchanged, so this pass creates no diff noise.
Then synchronize directory indexes. For every directory under openwiki/ (recursively, skipping dot-directories — the wiki root itself included), regenerate its index.md:
- Collect the directory's direct children:
- Files: every
.mdfile directly in it exceptindex.md,log.md,INSTRUCTIONS.md, and dot-files. For each, read its front matter — link label =titlewhen it is a non-empty string (fallback: the filename without.md), and keepdescriptionwhen it is a non-empty string (unusable optional fields are ignored, not errors). - Directories: every subdirectory whose name does not start with
..
- Files: every
- Render exactly this shape — no front matter (
index.mdis a reserved OKF document), except the wiki root's index, which starts with exactly the three-lineokf_versionblock shown; one blank line between sections; a section with no entries omitted entirely; when both sections are empty the sections part is just# Files(the root still keeps its okf_version block above it); trailing newline:
---
okf_version: "0.2"
---
# Files
- [<label>](<URL-encoded filename>) - <description, only when the page has one>
# Directories
- [<name>](<URL-encoded name>/)
- Non-root directories: the same content without the
okf_versionblock. - Sort each list alphabetically by link target (upstream:
localeCompare). Escape\,[, and]in labels. - The
Files/Directoriesheadings (including the empty-directory# Files) and the derivedtypethat Step 2's normalization (re-applied above) stamps on repaired pages are the wiki language's labels from upstream's curated table (resolveIndexLabels/resolveConceptTypeLabel: full tag → primary subtag → English fallback). English (en, the default) usesFiles/Directories/Reference— any other effective language → readreferences/index-labels.mdin this skill's directory and use its row verbatim (curated structural chrome, never your own translation).
- Compare with the existing
index.mdand write only when the content differs — byte-identical output is skipped, so no-op runs stay no-ops.
Then validate internal links (ported from upstream validateWikiInternalLinks; it stamps broken links in place and never fails the run). For every .md file under openwiki/ except index.md, log.md, INSTRUCTIONS.md, and dot-files/dot-directories:
- Strip any previously inserted stamp lines — full-line HTML comments matching
<!-- openwiki: broken internal link ... -->— so revalidation starts clean and a fixed link leaves no residual comment. - Collect every inline Markdown link
[text](dest)with its line number, skipping image links (![...]). Ignore external destinations (any URI scheme, or protocol-relative//…) and empty ones. Drop a trailing Markdown link title (path "Title"), then split an optional#anchoroff the path (URL-decode the anchor before comparing). - Validate each remaining link (since 0.3.1, #585, targets resolve repo-wide, not just within the wiki subtree — a wiki page may legitimately link out to any repository file, which renders correctly on GitHub; a link is broken only when its target genuinely does not exist):
- Anchor-only (
#foo) → the source file's own headings must expose the anchor. Anchors are GitHub-style slugs of ATX heading titles, matchinggithub-sluggerexactly: trim, lowercase, strip everything except Unicode letters/numbers/combining marks/spaces/_/-(combining marks kept so decomposed accents slug correctly), then replace each whitespace character with a hyphen — per character, not collapsed, so stripping the&from "A & B" leaves two spaces and the valid anchor isa--b; duplicate slugs get-1,-2, … suffixes. Missing → messageheading anchor "<anchor>" does not exist in <source path>. - Path → resolve relative to the source file's directory (or the repository root when it starts with
/), normalized — normalization clamps..at the repository root, so nothing escapes it; a path that cannot be resolved to an absolute one →link "<path>" cannot be resolved. A trailing/makes it a directory link — the directory must exist (directory "<path>" does not exist); otherwise the target file must exist (file "<path>" does not exist). - Path + anchor → the anchor is validated only when the target is a
.mdfile (anchors on directories and on non-Markdown targets — e.g. GitHub#L10line anchors on source files — are never flagged): the target's headings must expose the anchor (same slug rules) → elseheading anchor "<anchor>" does not exist in "<path>".
- Anchor-only (
- Insert one stamp line directly above each broken link's line (insert bottom-up so line numbers stay valid; multiple broken links on one line get one stamp each), in upstream's exact format:
<!-- openwiki: broken internal link [<href>] <message>. Fix the href or restore the target, then delete this comment. --> - Write a file back only when its content changed. A later update run repairs stamped links.
Then project this run's Claims evidence into OKF sources (ported from upstream synchronizeClaimSources in src/okf/claim-sources.ts, new in 0.4.0 / #692; skipped pages excluded, per above). Upstream re-projects every page its session holds Claim state for — which on an update is every page with a sidecar, not only the pages this run revisited. [adapted] Without a sidecar this port only knows the Claims its own Step 4 workers submitted, and that is sufficient: an unrevisited page's sources on disk already is its persisted projection, so re-deriving it would be a no-op write. So: for every concept page whose Step 4 worker submitted Claims (skipping any page the run deleted):
- Collect that page's complete evidence-resource set and reduce each resource to its whole-file form — drop any
#Lx-Lyfragment, sorepo://src/agent/index.ts#L40-L82becomesrepo://src/agent/index.ts. Precise ranges stay in the Claim; OKF provenance exposes source files. - Read the page's current
sources. Keep every entry that is not OpenWiki-owned — that is, every entry whoseiddoes not start withopenwiki-source-. A malformedsourcesvalue (not a list, or entries without a non-empty stringresource) counts as empty and gets repaired by this projection. - Deduplicate the projected resources, sort them (
localeCompare), drop any already carried by a retained entry, and append one mapping per remaining resource with a deterministic id derived from the resource itself:
printf '%s' 'repo://src/agent/index.ts' | shasum -a 256 | cut -c1-24 # → the id suffix
giving id: openwiki-source-<that 24-hex-character digest> — the example resource above yields openwiki-source-a953060a04ccefcf777de48e. Stable ids let a later run replace or remove only its own projection.
4. Since 0.4.1 (#728) the pass is self-healing and byte-stable: run Step 2's front-matter repair over the page before reading sources, apply the projection to the repaired content, run the repair once more over the result, and then compare the final bytes against the page as it was read. Write only when they differ — so a repair alone is enough to justify a write, and a projection that changes nothing is not. Replace just the sources field and leave every other front-matter line byte-for-byte. Rendered shape:
sources:
- id: openwiki-source-a953060a04ccefcf777de48e
resource: repo://src/agent/index.ts
An empty list removes the field. A page this run produced no Claims for is left untouched — this pass never strips another producer's or an earlier run's projection just because this run did not revisit the page.
[omitted] Upstream also persists each page's reconciled Claim set to openwiki/.claims/<page>.json and stamps an OKF verified: [{by, at}] event once a page's whole Claim set reconciles durably (ClaimsStore, synchronizeClaimsVerification). Both are skipped here: the sidecar's evidence versions are opaque resolver tokens (repo-file-v1:sha256:…, and for line ranges repo-lines-v1:sha256:<content hash>:<base64url relocation anchors>) that only the resolver can produce and interpret, and a verified stamp with no durable Claim state behind it would assert a machine verification that never happened. The sources projection above is this port's durable grounding record, and Step 1's preflight reads it back. Never hand-write .claims or verified.
Finally, reconcile generated provenance (ported from upstream finalizeGeneratedProvenance, new in 0.4.0 / #581, #684 — it runs last, after every other pass, so front-matter-only edits by those passes do not count as body changes). Pick one ISO 8601 timestamp for the whole run — upstream uses the moment the run began:
date -u +%Y-%m-%dT%H:%M:%S.000Z
For every concept page (same exclusions), recompute the body hash with Step 2's body-extraction command and compare it to the Step 2 baseline:
- New page, or body hash changed → set
generated: { by: "<actor>", at: "<run timestamp>" }and remove anytimestampfield, which OKF v0.2 supersedes. Whitespace counts: any body change advances the stamp. Then, since 0.4.1 (#730), canonicalize the page's trailing line endings to exactly one LF — and only those. Prose wrapping, indentation, tables, and every other authored choice stay untouched, and a page whose body did not change never passes through this normalization at all, so its bytes are preserved exactly. - Body unchanged → restore the baseline: re-set the
generatedevent the page had before the run (a Step 4 rewrite may have dropped or altered it), or removegeneratedentirely if it had none. A front-matter-only change never advances the stamp. Since 0.4.1, compare by meaning first: if the event already on the page has the samebyandatas the baseline, leave the page completely alone rather than re-rendering the field. That is what keeps an older{by: …}spelling from being rewritten into the new{ by: … }one on a page nobody touched. - The actor is the producing host, matching upstream's host registry (
src/integrations/install/registry.ts):claude-code,codex,opencode,cursor(0.5.0, #748), and — both new in 0.5.2 —bob(#780) andkiro(#870). [adapted] upstream's own runs stampopenwiki/<version>, which would misattribute this port's output. - A wiki may legitimately carry a different
generated.byon different pages, and you must never normalize the others to yours. Upstream 0.5.0 (#720) made this explicit: a durable run can now be resumed by a different host, so it records each page's completing producer and passes a per-page actor map into the provenance pass (producerActorsByPage) rather than stamping one actor across the wiki. The rule above already protects this — a page whose body you did not change keeps its baseline event — so the instruction is simply: leave it protected. A page authored bycodexorcursorthat this run did not touch keeps that actor. - Render it as a single-line flow mapping with JSON-quoted members and a space inside each brace (the spacing changed in 0.4.1, #728), replacing an existing
generated:line in place and leaving every other front-matter line untouched:generated: { by: "claude-code", at: "2026-08-26T00:00:00.000Z" }. - Run Step 2's front-matter repair over the reconciled page before writing it, then write only when the content changed.
- This pass never fails the run (since 0.4.1, #728): provenance is optional trust metadata, so a page that cannot be read is skipped and a write that fails is skipped, leaving the already-persisted page as the deterministic fallback rather than discarding the rest of a finalized wiki. Report which pages were skipped.
Never author, edit, or remove generated, verified, sources, or timestamp during Step 4 — they are code-owned, which here means owned by this step.
Step 6 — Persist metadata (ported from upstream persistRunMetadataIfChanged)
Recompute the Step 2 content hash with the same command, then write openwiki/.last-update.json with exactly these fields. Since 0.4.0 (#647) the metadata is written on every completed init/update run, whether or not content changed — a no-op update still means OpenWiki ran, and freshness checks should reflect that:
{
"updatedAt": "<UTC ISO-8601, from: date -u +%Y-%m-%dT%H:%M:%S.000Z>",
"command": "init | update",
"gitHead": "<from: git rev-parse HEAD; omit the key if not a git repo>",
"model": "<your model id if known, else claude-code or codex>",
"status": "complete",
"language": "<the effective language tag from Step 1, e.g. en>"
}
Run the date and git commands — never guess the timestamp or the head. Report the recomputed hash's verdict to the user (changed → what changed; unchanged → the wiki was already accurate) even though it no longer gates the write.
If Step 4 skipped any page (since 0.4.1, #732) or the repository source changed during the run (since 0.4.3, #740), write the metadata differently: status: "interrupted", and gitHead set to the previous run's recorded head rather than the current one — or the key omitted entirely when there was no previous head (upstream 0.4.3 made that explicit: the override accepts null for "no successful baseline exists", where before it silently fell back to the current head). Both matter and for the same reason — the next update must not treat this wiki as complete and current. An interrupted status defeats the early no-op exit, and rewinding gitHead keeps the changed-path and staleness evidence in view. Recording the current head here would make the skipped page, or the source change, invisible forever.
On drift, also tell the user plainly, in upstream's terms: the wiki was finalized without advancing its source checkpoint, and a follow-up update run is needed to reconcile the change. The metadata is what forces that run; the message is what explains it.
Partial progress is a publishable result, not a failure to hide (upstream 0.5.0, #720). Upstream now commits the pages a run finished even when the run as a whole failed, and its CI opens the pull request anyway with the outcome stated in the body: "When the result is failure, this PR intentionally preserves only the pages completed before the failure. Merge it to make that progress the baseline for the next scheduled run." Upstream's baseline is the .page-manifest.json entry each completed page earned; this port's is the metadata you write here — the finished pages plus status: "interrupted" and a rewound gitHead, which is exactly a record saying "this much is done, the rest is still owed." So do not offer to discard a partial run's output to keep things tidy: report which pages landed, which were skipped, and that the wiki is committable as-is. references/automation.md carries the matching CI shape.
Run this step even when the run fails after generating content (upstream persists metadata on the error path too): write the metadata before reporting the failure — with status: "interrupted" instead of "complete", so the already-generated content stays diffable and the next update knows the wiki may be partial and does not skip (#365).
Two exceptions on the failure path:
- A failed init that had a Step 2 backup: restore the backup first and write no metadata. The backup contains the previous
openwiki/.last-update.json, so restoring it puts the recorded state back in agreement with the restored wiki — writing aninterruptedrecord on top would describe a partial wiki that no longer exists. Report the rollback. ([adapted] Upstream only rolls back when the failure precedes its checkpoint becoming durable; after that, partial pages are its recovery mechanism. This port always rolls back, because it has no checkpoint to resume from.) - A failed first init — no prior
openwiki/, so no backup: keep the partial content and writestatus: "interrupted", exactly as upstream's no-op replacement path does.
Final response
Summarize the completed documentation changes and important caveats — the planned page set, what each page covers, deletions, any pages left with stamped Mermaid or link comments, and (on init) that the previous wiki was replaced. State plainly whether the source moved mid-run.
Automation
Asked to set up scheduled/recurring updates or CI? Read references/automation.md in this skill's directory.
Runtime evidence (LangSmith)
Asked to fold LangSmith runtime traces into the repository wiki? Read references/runtime-evidence.md in this skill's directory.