Files changed: - .gitea/workflows/ci.yml - CHANGES.md - INSTALL.md - README.md - VERSION - docs/why-gates-are-code.md - instructions/bootstrap.md - instructions/bug-report.md - instructions/preflight.md - instructions/setup-instance.md - instructions/upgrade-instance.md - tools/CONTRACT.md - tools/README.md - tools/bugreport.py - tools/chemenu/cli.py - tools/chemenu/commands/dist_cmd.py - tools/chemenu/commands/doctor.py - tools/chemenu/prerequisites.py - tools/chemenu/tests/conftest.py - tools/chemenu/tests/test_bugreport.py - tools/chemenu/tests/test_doctor.py - tools/chemenu/tests/test_preflight.py - tools/chemenu/tests/test_preflight_pwsh.py - tools/chemenu/toolpaths.py - tools/preflight.ps1 - tools/wikitool - tools/wikitool.ps1
162 KiB
tools/ - Compiler Contract
wikitool is a deterministic CLI for Chemenu, used by agents (and humans) so
mechanical wiki operations - frontmatter, index statistics, cross-references, log
formatting, git publishing - never have to be re-derived by
an LLM. The root AGENTS.md holds the invariants that say when
using these commands is mandatory; this file is the full reference. Changes to
wikitool itself are tracked in the repo root CHANGES.md,
not here.
Look up the command you need; do not read this file end to end. Every
command carries one data record - name, synopsis, properties, exit status, and
so on - attached to its function in code (tools/chemenu/cli_contract.py).
Three views render from that one source:
tools/wikitool raw accept -h # the full record, for one command
tools/wikitool -h | grep '^raw accept' # the index line, for a cross-command question
grep -n '^#### `raw accept`' tools/CONTRACT.md # this file's own copy, generated
The § Commands section below is wikitool docs contract's output: an index
(one line per command, grep-stable) followed by each ### group's commands
as #### <path> records, in the same order wikitool -h prints. Reading the
file end to end is for changing the CLI itself.
Contents
- Setup (one time)
- Usage
- Commands
- Design notes
- Tests
- Maintenance schedule
- Future considerations (not implemented)
Setup (one time)
Full bootstrap for a fresh clone - including publishing the skills, which are not
committed - is instructions/bootstrap.md. The
environment alone is the preflight (instructions/preflight.md),
from the repo root:
tools/preflight.sh
From PowerShell 7 on Windows, the twin: pwsh -NoProfile -ExecutionPolicy Bypass -File tools/preflight.ps1.
Until it has passed, every tools/wikitool call exits 42 and names it.
Usage
Run from the repo root:
tools/wikitool <command> -h
-h and --help are identical and TTY-independent - the same plain, unframed text either
way, for a human or an agent. Bare tools/wikitool -h prints the index below plus a pointer back
to this form.
A call that ends through _util.fail() (exit 1) prints its ERROR line to stdout as before, then
its record's ON FAILURE reaction(s) to stderr, in the same <cause> -> <reaction> form -h prints
- so the reaction is in front of the caller without a second
-hcall. A record with no exit-1 cause of its own falls back to a baresee: wikitool <cmd> -hpointer.
Commands
new write non-idempotent budget:counted exit:0,1,42 Scaffold a new wiki page of any type.
task new write non-idempotent budget:counted exit:0,1 Create one open item in the configured task tracker - never a kb/ page.
task list read idempotent budget:counted exit:0,1 List a project's open items - id, title, and whether each carries the WAITING status.
task close write idempotent budget:counted exit:0,1 Mark one tracker item done - never delete it.
touch write idempotent budget:counted exit:0,1 Bump a page's `modified:` date and optionally rewrite its other frontmatter fields.
rename write idempotent budget:counted exit:0,1 Rename a page, or repoint references that name a page that never existed.
rm write non-idempotent budget:counted exit:0,1 Delete a page and mechanically de-link it from the rest of the wiki.
move write idempotent budget:counted exit:0,1 Move a page (or every misplaced page) to the directory its type-spec computes.
xref add write idempotent budget:counted exit:0,1 Declare that A <rel> B.
xref remove write idempotent budget:counted exit:0,1 Remove a cross-reference: the inverse of `xref add`.
xref link-source write idempotent budget:counted exit:0,1 Batch-link a source page to every entity/concept it mentions.
links show read idempotent budget:exempt exit:0,1 Show the edges out of and into a page.
cite id read idempotent budget:exempt exit:0 Print the deterministic footnote id `cite add` would use for this (title, file) pair.
cite add write idempotent budget:counted exit:0,1 Upsert a `[^cite-id]: [[Source - X]]` definition in a page's footnotes region.
cite sync write idempotent budget:counted exit:0,1 Reconcile each page's footnotes region against its actual `[^id]` references.
index rebuild write idempotent budget:counted exit:0,1 Regenerate the catalog from every page's frontmatter.
log append write non-idempotent budget:counted exit:0,1 Append a formatted entry to `kb/log.md`.
log status read idempotent budget:counted exit:0 Read-only: count `ingest` entries logged since the last `lint` entry.
lint write idempotent budget:counted exit:0,1 Run structural lint checks against kb/.
search read idempotent budget:exempt exit:0,1 Find pages in `kb/` by text and/or frontmatter.
review read idempotent budget:exempt exit:0,1 The GTD weekly review.
sources coverage read idempotent budget:counted exit:0 List raw files with no source page, broken `raw_files:` references, and legacy directory/URL-only source pages.
sources trace read idempotent budget:counted exit:0,1 Trace provenance in either direction: raw file, or page.
sources rebuild-index write idempotent budget:counted exit:0,1 Regenerate the `kb/provenance.md` reverse index.
raw accept write non-idempotent budget:counted exit:0,1 Promote one or more files from `incoming/` into `raw/`.
upload list read idempotent budget:counted exit:0 List every MCP submission currently waiting in the quarantine (`mcp-upload/`).
upload show read idempotent budget:counted exit:0,1 Print one submission's manifest in full.
upload accept write non-idempotent budget:counted exit:0,1,42 **Upload Review Gate:** promote a submission's file from quarantine into `incoming/`.
upload reject write non-idempotent budget:counted exit:0,1 Delete a submission's material, keeping only its ledger trail.
sync write idempotent budget:counted exit:0,1,42 Fetch `<remote>/<branch>` and bring the local branch up to date with it.
publish write non-idempotent budget:counted exit:0,1,42 Reconcile with `<remote>/<branch>`, then stage all changes, commit, and push.
work new write non-idempotent budget:counted exit:0,1 Scaffold `work/<runkey>/` for one workshop run.
work close write non-idempotent budget:counted exit:0,1 Delete a finished workshop.
budget status read idempotent budget:exempt exit:0 Show the current session's `wikitool` call count and recent command history.
budget reset write non-idempotent budget:counted exit:0,1 Clear the current session's (or every session's) iteration budget state.
types list read idempotent budget:counted exit:0 List every type-spec under `types/`.
types describe read idempotent budget:counted exit:0,1 Print one type's full contract.
instructions sync write idempotent budget:counted exit:0,1 Publish every `instructions/<name>/SKILL.md` into the harness skill directories.
instructions verify read idempotent budget:counted exit:0,1 Check the instruction layer.
instructions list read idempotent budget:counted exit:0 List the flat instructions with their descriptions.
docs verify read idempotent budget:counted exit:0,1 Check the docs that mirror the code.
docs toc write idempotent budget:counted exit:0 Create, refresh or remove the generated table-of-contents region.
docs contract write idempotent budget:counted exit:0,1 Regenerate `tools/CONTRACT.md`'s `<!-- wikitool:commands -->` region.
eval sessions read idempotent budget:exempt exit:0 List the sessions that have a trace under `reports/telemetry/`.
eval score read idempotent budget:exempt exit:0,1 Score one traced session.
dist export write idempotent budget:counted exit:0,1 Write a contentless, distributable copy of this repo's machinery.
dist upgrade write non-idempotent budget:counted exit:0,1 Apply a stack update `dist export` produced - the write half of `version check`.
version show read idempotent budget:exempt exit:0,1 Print this instance's stack version and where it came from.
version check read idempotent budget:exempt exit:0,1 Ask the origin's release feed whether a newer stack exists.
version notes read idempotent budget:exempt exit:0,1 Print one version's release notes.
version bump write non-idempotent budget:counted exit:0,1 Raise or continue the one running candidate between two releases.
version regrade write non-idempotent budget:exempt_without_args exit:0,1 List the running candidate's bump titles with their impact grade, or change one or more of them.
version release write non-idempotent budget:counted exit:0,1 Fix the running candidate: strip `VERSION`'s `-beta.N` suffix and close its `CHANGES.md` entry.
migrate list read idempotent budget:exempt exit:0 List every migration document under `instructions/migrations/`.
migrate status read idempotent budget:exempt exit:0,1 Show the migrations this instance still owes, in the order they must run.
migrate verify read idempotent budget:exempt exit:0,1 Compare `kb/` against a git revision on the invariants a content migration must not change.
migrate done write non-idempotent budget:counted exit:0,1 Record one migration as applied, advancing `kb_version` in `.wikitool-kb.json`.
migrate baseline write idempotent budget:counted exit:0,1 Declare `kb_version` once, for an instance predating `.wikitool-kb.json`.
upstream merge write non-idempotent budget:counted exit:0,1 Take a stack update into a private instance's branch, machinery only.
upstream verify read idempotent budget:exempt exit:0,1 Compare two revisions: did anything under a content stage change except through a stack-owned path?
doctor read idempotent budget:exempt exit:0,1 Check that this instance is correctly configured.
Pages
new
Scaffold a new wiki page of any type.
SYNOPSIS
wikitool new <type-name> --name "<Name>" [--type <path>] [--set field=value ...]- Any type; its type-spec decides fields, directory, title prefix and template.wikitool new entity --name "<Name>" --set entity_type=<t> [--set tags=a,b] [--set related=X,Y] [--set sources="Source - Z"] [--set provenance=sourced|general|mixed]- Writeskb/entities/<subdir>/<Name>.md-<subdir>fromentity_typevia the type-spec'slayout:wikitool new concept --name "<Name>" --set concept_type=<t> ...- Writeskb/concepts/<subdir>/<Name>.md-<subdir>fromconcept_typevia the type-spec'slayout:wikitool new source --name "<Name>" --set source_type=<t> --set raw_files=raw/notes/x.md,raw/notes/y.md --set fidelity=<f> --set authority=<a> [--set source_url=<URL>] [--set entities=A,B] [--set concepts=C,D]- Writeskb/sources/<subdir>/Source - <Name>.md(prefix added automatically;<subdir>fromsource_typevia the type-spec'slayout:) with araw_files:list; rejects paths that don't existwikitool new comparison --name "X vs Y" --set entities=X,Y- Writeskb/comparisons/X vs Y.mdwikitool new project --name "<Name>" --set responsibility=<bereich> [--resume]- Writeskb/gtd/<bereich>/<Name>.mdand, with a task tracker configured, a same-named tracker project
PROPERTIES
- effect: write
- idempotent: no
- atomic:
new <type>: Yes - single file write.new project: No for the tracker-configured case - a tracker-project write (or its human-clearance request) happens before the kb/ page write, so a failure between the two leaves a tracker project with no page (a statereview's check 3 already reports), never a page with no tracker project. Still a single file write when no tracker is configured - budget: counted
- network: no
- gates: human-intervention-required (
new projectonly)
EXAMPLES
tools/wikitool new entity --name "Docker" --set entity_type=tool --set tags=containerstools/wikitool new source --name "Docker Cheatsheet" --set raw_files=raw/2026/09/docker-cheatsheet.md --set fidelity=verbatim --set authority=reportingtools/wikitool new project --name "Homelab migration" --set responsibility=infrastruktur --resume # re-run after exit 42, once the user created the tracker project
EXIT STATUS
- 0 success
- 1 A page with this title already exists, the type is unknown, or a
--setvalue is invalid - 1 The title is not a valid file name (forbidden character, control character, reserved name such as
CONorIndex, trailing dot or space, empty), collides with another page by case or Unicode normalization, the target file already exists, or the target path is over the 160-character path budget - 1 A
raw_filespath does not exist - 1 A capture field the type-spec requires is missing, or set to
unknown - 1
--resumewith a type other thanproject - 1 new project: The name is already taken in the tracker (case-insensitively; for
caldavagainst every list in the account) - 1 new project: The configured access path has no write path (Super Productivity's
access: "snapshot") - 1 new project: The page write failed after the tracker project was confirmed to exist
- 42 new project: The provider cannot create the project itself (Super Productivity's
access: "api"); the output says what a human has to create
ON FAILURE
- A page with this title already exists, the type is unknown, or a
--setvalue is invalid -> Not transient - fix the argument and retry once - The title is not a valid file name (forbidden character, control character, reserved name such as
CONorIndex, trailing dot or space, empty), collides with another page by case or Unicode normalization, the target file already exists, or the target path is over the 160-character path budget -> Not transient - choose another (for the budget: a shorter) title and retry once. Nothing was created, and fornew projectno tracker project either - A
raw_filespath does not exist -> Not transient - fix the path and retry once - A capture field the type-spec requires is missing, or set to
unknown-> Pass it explicitly (e.g.--set fidelity=verbatim --set authority=reporting), then retry once --resumewith a type other thanproject-> Drop--resumeand retry once- new project: The name is already taken in the tracker (case-insensitively; for
caldavagainst every list in the account) -> Not transient - choose another name. If an earlier run of this exact command asked a human to create the project and they did, re-run with--resume - new project: The configured access path has no write path (Super Productivity's
access: "snapshot") -> Not transient - point at theaccess: "api"instance the error names. A--resumeretry refuses the same way, since nothing about the config changes by asking again - new project: The page write failed after the tracker project was confirmed to exist -> Fix the write error, then re-run with
--resume- a plain re-run is refused as a tracker collision - new project: The provider cannot create the project itself (Super Productivity's
access: "api"); the output says what a human has to create -> Show the user the command's full output verbatim and stop. Once they have created the project, re-run the same command with--resume; it re-verifies and exits 42 again, unchanged, if the tracker still does not have it
NEVER
- Never hand-craft the page, or its frontmatter, instead.
NOTES
- A title becomes a file name, so it must be valid and unique on Windows and macOS as well as Linux, whichever platform runs the command and whichever root the type writes to. The rule is
kb/CONTRACT.md§ Titles are identifiers; it is checked on the full title, aftertitle_prefix. - The target's path below the instance root may be at most 160 characters, counted in UTF-16 code units the way Windows counts MAX_PATH, so a Windows checkout without long paths keeps working. A longer one is refused, for every root, naming the length and how much shorter it has to get.
newnever overwrites: a file already at the target - or one a case-insensitive file system would treat as the same file - is refused for every root,instructions/included.- The type-spec drives everything: fields, directory (
base_dir/layout), title prefix, and template.types list/types describeshow what a type requires. - A schema
default:is materialized only for a field the schema also lists inrequired:. --setis repeatable, and comma-separated values fill array fields. An element that itself contains a comma is written\,, or passed as its own repeated--setfor that field - repeating an array field appends.- A capture field the type-spec requires (a source's
fidelity/authority) must be passed with--set;newnever guesses it and refusesunknownfor it. - Produces structurally correct frontmatter and a body skeleton only - the prose (Description, Summary, judgment calls about relationships) is written afterwards.
new project: with.wikitool-tasks.jsonconfiguring a task tracker, also makes sure a same-named tracker project exists - one name, one identity. No tracker configured is a legitimate, explicitly announced state: page only.new project: tracker before page. The tracker side is settled first, so a failure past that point leaves a tracker project with no page - a statereview's check 3 reports - never a page with no tracker project.new project: a name already taken, case-insensitively, inkb/or the tracker is refused outright, naming where it was found, and creates nothing. Forcaldavthe tracker check covers every list in the account, not only the ones counted as projects.new project: a provider whose configured access path has no write path (Super Productivity'saccess: "snapshot") refuses entirely with exit 1, naming theaccess: "api"instance to use instead - neither the tracker project nor the page is created, and--resumebehaves the same.new project: a provider that could write but has no project-creation call of its own (Super Productivity'saccess: "api"-GET /projectsexists,POST /projectsdoes not) prints instructions for a human and exits 42, creating nothing. That is not one of the named gates, but the same exit code and the same handling.caldavnever does this:MKCALENDARcreates the list, so a valid, non-colliding name always creates it.--resumeis how a later run tells the command a human has done what that message asked: it re-verifies through the tracker's read path before continuing to page creation, rather than trusting the claim, and exits 42 again if the tracker still does not have the project.--resumeon any other type is refused.
SEE ALSO
wikitool types describe <type>- what a type requires and where it landswikitool touch- changes a page's own frontmatter afterwardswikitool task new- a tracker item without a pagedocs/knowledge-and-commitment.md- why a project is a page and a tracker project
task new
Create one open item in the configured task tracker - never a kb/ page.
SYNOPSIS
wikitool task new --title "<Title>" (--project "<Name>" | --inbox) [--waiting [--follow-up-at YYYY-MM-DD]] [--notes "..."]
PROPERTIES
- effect: write
- idempotent: no
- atomic: Yes - a single API call, made only once every precondition (the project's own id, the WAITING tag's own id) is confirmed to exist, so a missing one never leaves a half-written item behind
- budget: counted
- network: yes
EXAMPLES
tools/wikitool task new --title "Renew the TLS certificate" --project "Homelab migration"tools/wikitool task new --title "Quote from the electrician" --project "Homelab migration" --waiting --follow-up-at 2026-10-05tools/wikitool task new --title "Read the qmd README" --inbox --notes "from [[Source - qmd - GitHub Repository]]"
EXIT STATUS
- 0 success
- 1 No
.wikitool-tasks.json- no tracker configured - 1
WIKITOOL_TASKS_CONFIGnames a file that does not exist or is broken - 1 Neither or both of
--project/--inbox, or a--follow-up-atwithout--waitingor notYYYY-MM-DD - 1 A
--projectname matching no tracker project - 1
--waitingagainst a provider with no way to represent it right now (Super Productivity: thewaitingtag does not exist) - 1 A read-only access path (Super Productivity's
access: "snapshot")
ON FAILURE
- No
.wikitool-tasks.json- no tracker configured -> Not transient - configure a tracker first WIKITOOL_TASKS_CONFIGnames a file that does not exist or is broken -> Not transient - fix the path or unset the variable- Neither or both of
--project/--inbox, or a--follow-up-atwithout--waitingor notYYYY-MM-DD-> Fix the argument and retry once - A
--projectname matching no tracker project -> Create the tracker project first, or fix the name, then retry once --waitingagainst a provider with no way to represent it right now (Super Productivity: thewaitingtag does not exist) -> Create the tag in the tracker first, then retry once- A read-only access path (Super Productivity's
access: "snapshot") -> Point at theaccess: "api"instance the error names, then retry once
NEVER
- Never fall back to
--inbox, or to another project, when--projectdoes not match.
NOTES
- Creates one open item in the configured task tracker; never touches
kb/. - Exactly one of
--projector--inboxis required; an omitted--projectrefuses rather than silently falling into the inbox. --projectnames an existing tracker project, matched case-insensitively - never created, and never searched or guessed.--inboxfiles into the tracker's own inbox. An item filed there never appears inreview, since every one of its checks reaches items through a project name.--waitingsets the WAITING statusreview's waiting-overdue check reads.--follow-up-atis refused without--waiting- it is never a due date on its own.--notescarries a freetext backref (e.g. to thekb/source page the item came from), stored verbatim, never parsed.- The tracker configuration is read from
.wikitool-tasks.json, or from the fileWIKITOOL_TASKS_CONFIGnames when that variable is set (so one checkout can be run against several trackers in turn). A set variable that names no file is an error, never "no tracker configured". - No
.wikitool-tasks.jsonfails immediately with a "no tracker configured" message. - A provider whose configured access path has no write path (Super Productivity's
access: "snapshot") refuses entirely with exit 1, naming theaccess: "api"instance to use instead. - Never exits 42. A
--projectmatching no tracker project, or--waitingagainst a provider that cannot represent it right now (Super Productivity: thewaitingtag does not exist yet, and its API cannot create tags), are ordinary exit-1 refusals that create nothing. - Not idempotent: every successful run creates another item.
SEE ALSO
wikitool task list- a project's open items and their idswikitool task close- marks an item donewikitool new project- a project page and its tracker projectdocs/knowledge-and-commitment.md- why commitments live in the tracker, not inkb/
task list
List a project's open items - id, title, and whether each carries the WAITING status.
SYNOPSIS
wikitool task list --project "<Name>"
PROPERTIES
- effect: read
- idempotent: yes
- atomic: Yes - read-only, nothing to leave half-written
- budget: counted
- network: yes
EXAMPLES
tools/wikitool task list --project "Homelab migration"
EXIT STATUS
- 0 success
- 0 A
--projectmatching no tracker project - prints "No open items", not an error - 1 No
.wikitool-tasks.json- no tracker configured - 1
WIKITOOL_TASKS_CONFIGnames a file that does not exist or is broken
ON FAILURE
- No
.wikitool-tasks.json- no tracker configured -> Not transient - configure a tracker first, then retry once WIKITOOL_TASKS_CONFIGnames a file that does not exist or is broken -> Not transient - fix the path or unset the variable
NOTES
- Lists one tracker project's open items: id, title, and whether each carries the WAITING status.
- The id source
task closeandreview'swaiting_overdue/someday_stalefindings need, without runningreviewfirst. - Works on every access path a provider offers, read-only ones included.
- A
--projectmatching no tracker project prints "No open items": the tracker read does not distinguish an empty project from an unknown one. - Read-only.
SEE ALSO
wikitool task close- closes an item by the id listed herewikitool review- the findings that name items
task close
Mark one tracker item done - never delete it.
SYNOPSIS
wikitool task close --id <item-id>
PROPERTIES
- effect: write
- idempotent: yes
- atomic: Yes - a single API call; an unknown id is rejected by the provider itself (Super Productivity:
404 TASK_NOT_FOUND) before anything is written - budget: counted
- network: yes
EXAMPLES
tools/wikitool task close --id <item-id>
EXIT STATUS
- 0 success
- 1 No
.wikitool-tasks.json- no tracker configured - 1
WIKITOOL_TASKS_CONFIGnames a file that does not exist or is broken - 1 An
--idmatching no tracker item right now - 1 A read-only access path (Super Productivity's
access: "snapshot")
ON FAILURE
- No
.wikitool-tasks.json- no tracker configured -> Not transient - configure a tracker first WIKITOOL_TASKS_CONFIGnames a file that does not exist or is broken -> Not transient - fix the path or unset the variable- An
--idmatching no tracker item right now -> Get a current id fromtask listorreview, then retry once - A read-only access path (Super Productivity's
access: "snapshot") -> Point at theaccess: "api"instance the error names, then retry once
NEVER
- Never pass a title as
--id.
NOTES
- Marks one tracker item done. It never deletes or moves an item - the only closing write this stack makes.
--idis the provider's own item id, fromtask listor areviewfinding - never a title.- The tracker configuration is read from
.wikitool-tasks.json, or from the fileWIKITOOL_TASKS_CONFIGnames when that variable is set (so one checkout can be run against several trackers in turn). A set variable that names no file is an error, never "no tracker configured". - No
.wikitool-tasks.jsonfails with a "no tracker configured" message. - A provider whose configured access path has no write path (Super Productivity's
access: "snapshot") refuses entirely with exit 1, naming theaccess: "api"instance to use instead. - Never exits 42.
SEE ALSO
wikitool task list- where the id comes fromwikitool review- findings that carry item ids
touch
Bump a page's modified: date and optionally rewrite its other frontmatter fields.
SYNOPSIS
wikitool touch --page "<Title>" [--summary "..."] [--provenance <v>] [--date YYYY-MM-DD] [--set field=value ...] [--add field=value ...] [--remove field=value ...] [--no-date] [--dry-run]
PROPERTIES
- effect: write
- idempotent: yes
- atomic: Yes - single file write, and every refusal happens before it
- budget: counted
- network: no
EXAMPLES
tools/wikitool touch --page "Docker" --summary "Container runtime and image format"tools/wikitool touch --page "Docker" --add tags=containers --no-datetools/wikitool touch --page "Source - Docker Cheatsheet" --date 2026-09-01 --dry-run
EXIT STATUS
- 0 success
- 1 Page not found
- 1 An invalid value for a field it writes, or a
raw_files:path that doesn't exist - 1 A field owned by another command (
type:, a page-ref array) or absent from the type's schema - 1
--add/--removeon a non-array field
ON FAILURE
- Page not found -> Fix the title and retry once
- An invalid value for a field it writes, or a
raw_files:path that doesn't exist -> Fix the argument and retry once - A field owned by another command (
type:, a page-ref array) or absent from the type's schema -> Use the command the error names, or a field from the list it prints --add/--removeon a non-array field -> Use--setfor a scalar field, then retry once
NEVER
- Never edit
type:or a page-ref array (related:/sources:/entities:/concepts:) by hand -type:changes go through the page-lifecycle procedure, page-ref arrays throughxref.
NOTES
- Bumps
modified:to today and optionally rewrites any other field the page's type declares. --summary/--provenanceare shorthands;--setreaches every other field and replaces its value, while--add/--removechange single elements of an array field (removing an absent element succeeds and says so).- Repeating
--setfor one array field appends within the call, and\,is a literal comma. - Refused, naming the command that owns them instead:
type:(the page-lifecycle procedure) and the page-ref arraysrelated:/sources:/entities:/concepts:(xref). Everything else the schema declares is settable, and an unknown field is refused with the list of fields the page actually has. - Schema-validates the fields it writes;
raw_files:entries must exist on disk. - A source page declares
date:instead ofmodified:: the publication date of the raw material. It is never bumped to today and changes only when--datenames a value explicitly. --no-datechanges only the given fields and leaves the date alone;--dry-runpreviews the new frontmatter without writing.- Safe to re-run as-is:
--setand--addare idempotent, and--removeof an already-absent element succeeds while reporting it.
SEE ALSO
wikitool xref add/wikitool xref remove- the page-ref arraysinstructions/page-lifecycle.md- changing a page'stype:
rename
Rename a page, or repoint references that name a page that never existed.
SYNOPSIS
wikitool rename --from "<Old>" --to "<New>" [--dry-run]
PROPERTIES
- effect: write
- idempotent: yes
- atomic: No - one write per referencing page, then the file move
- budget: counted
- network: no
EXAMPLES
tools/wikitool rename --from "act_runner" --to "Act Runner" --dry-runtools/wikitool rename --from "Docker Engine" --to "Docker"
EXIT STATUS
- 0 success
- 1
--fromequals--to - 1 Neither
--fromnor--tois a page - 1 The
--totitle is already taken - also by a page that differs only in case or Unicode normalization, or by a file in the page's directory - or is not a valid file name, or would put the page's path over the path budget (seekb/CONTRACT.md§ Titles are identifiers) - 1 A page write failed partway; nothing was renamed on disk
ON FAILURE
--fromequals--to-> Fix the arguments and retry once- Neither
--fromnor--tois a page -> Create the page first withwikitool new, or drop the reference withwikitool xref remove - The
--totitle is already taken - also by a page that differs only in case or Unicode normalization, or by a file in the page's directory - or is not a valid file name, or would put the page's path over the path budget (seekb/CONTRACT.md§ Titles are identifiers) -> Choose another, or a shorter, title and retry once. Checked under--dry-runtoo - A page write failed partway; nothing was renamed on disk -> Check
git status, resolve the write failure (permissions/disk), then re-run the full command - safe, since each page's rewrite is idempotent
NEVER
- Never fix up references by hand instead.
NOTES
- Renames a page and repoints every reference to it: body
[[wikilinks]](aliases and anchors preserved), a[^cite-id]whose id was derived from the old title (refreshed to match the new one, both in its Footnotes definition and every reference to it), the page's own H1, and every page-ref frontmatter array declared by the type'spage_ref_fields:. - If
--fromis not a page but is referenced, it instead repoints those references onto the existing--topage and moves nothing - the fix for a reference spelledact_runnerwhen the page isAct Runner. - Each page's rewrite is idempotent, so a re-run as-is is safe. If a write fails midway, nothing is renamed on disk and the error lists what was updated.
--dry-runlists every page it would change; run it first to see the blast radius.- Only
--tois checked against the title rule and the path budget (160 UTF-16 code units for the whole path below the instance root). A page whose current title breaks either (lint's Unportable Titles and Long Paths) can always be renamed away from it, and a title that differs from the page's own only by case (FootoFOO) is allowed.
SEE ALSO
instructions/page-lifecycle.md- renaming, moving and deleting a pagewikitool move- changes a page's directory, not its titlewikitool rm- deletes a page
rm
Delete a page and mechanically de-link it from the rest of the wiki.
SYNOPSIS
wikitool rm --page "<Title>" [--yes] [--dry-run]
PROPERTIES
- effect: write
- idempotent: no
- atomic: No - one write per referencing page, then the delete
- budget: counted
- network: no
EXAMPLES
tools/wikitool rm --page "Old Draft" --dry-runtools/wikitool rm --page "Old Draft" --yes # after the user approved the inbound list
EXIT STATUS
- 0 success
- 1 Page not found
- 1 Other pages still reference it and
--yeswas not passed - 1 A de-link write failed partway; the page was not deleted
ON FAILURE
- Page not found -> Fix the title and retry once
- Other pages still reference it and
--yeswas not passed -> Show the user the inbound list, get approval, then re-run with--yes - A de-link write failed partway; the page was not deleted -> Check
git status, resolve the write failure, then re-run
NEVER
- Never pass
--yesbefore the user has seen the inbound list and approved it.
NOTES
- Deletes a page and de-links it from the rest of the wiki: strips page-ref array entries and bare
- [[Title]]/- **label:** [[Title]]bullets pointing at it. - Leaves prose references and inline citations in place and reports them afterwards; those are an editorial fix, not a reason to retry.
- Refuses without
--yeswhile other pages still reference it, listing them. - If a de-link write fails midway, the page is not deleted, so nothing is orphaned and a re-run is safe.
--dry-runlists what would change, without writing.- Not idempotent: once the page is gone, a second run finds nothing to delete.
SEE ALSO
instructions/page-lifecycle.md- when a page is deleted rather than renamedwikitool rename- repoints references instead of removing them
move
Move a page (or every misplaced page) to the directory its type-spec computes.
SYNOPSIS
wikitool move --page "<Title>"wikitool move --reconcile [--dry-run]
PROPERTIES
- effect: write
- idempotent: yes
- atomic:
--page: yes, a single file move.--reconcile: no - one file move per page, each idempotent - budget: counted
- network: no
EXAMPLES
tools/wikitool move --page "Docker"tools/wikitool move --reconcile --dry-runtools/wikitool move --reconcile
EXIT STATUS
- 0 success
- 1 Neither or both of
--page/--reconcilegiven - 1 The named page is not found, or has no
type:to compute a placement from - 1 The destination already holds an entry with the same name, or one that differs only in case or Unicode normalization (a pre-existing duplicate-stem collision), or its path would be over the path budget - refused rather than silently skipped
- 1
--reconcilefailed partway
ON FAILURE
- Neither or both of
--page/--reconcilegiven -> Fix the arguments and retry once - The named page is not found, or has no
type:to compute a placement from -> Fix the title, or give the page itstype:, then retry once - The destination already holds an entry with the same name, or one that differs only in case or Unicode normalization (a pre-existing duplicate-stem collision), or its path would be over the path budget - refused rather than silently skipped -> Resolve the collision, or
wikitool renamethe page to a shorter title, then retry --reconcilefailed partway -> Safe to retry as-is ---reconcileonly re-moves what is still misplaced
NEVER
- Never choose a directory by hand instead.
NOTES
- Moves a page to the directory its type-spec computes for its current frontmatter (
base_dir+layout, the same rulenewplaces a page by) - never to a hand-chosen destination; there is no--to <dir>. - A destination whose path would be over the path budget (160 UTF-16 code units below the instance root) is refused, and
--reconcileskips such a page and names it, as it does for an occupied destination -wikitool renamethe page to a shorter title. --reconcileapplies it corpus-wide: every misplaced page moves in one call, and a second run reports nothing left to do. It fixeslint'sMisplaced Pages(advisory) andNested Pages(hard) findings.- Neither mode touches a body or a frontmatter field, and the page's title - its only identity in the wiki - never changes; only the file moves.
- A directory a move empties is removed with it, so a page nested below its area leaves no leftover directory behind.
- A page already at its computed location is reported and left alone, and
--reconcileonly re-moves what is still misplaced, so a re-run is safe. --dry-run(with--reconcile) lists the moves; run it first to see the blast radius.- Run
wikitool index rebuildafterwards.
SEE ALSO
wikitool lint- reports Misplaced and Nested Pageswikitool index rebuild- run after movinginstructions/page-lifecycle.md- moving, renaming and deleting a page
Links and citations
xref add
Declare that A B.
SYNOPSIS
wikitool xref add --a "<A>" --b "<B>" --rel <label> [--dry-run]
PROPERTIES
- effect: write
- idempotent: yes
- atomic: Yes - a single write to A; B is never touched, and every refusal happens before it
- budget: counted
- network: no
EXAMPLES
tools/wikitool xref add --a "Gitea Actions" --b "Act Runner" --rel depends-ontools/wikitool xref add --a "Gitea Actions" --b "Act Runner" --rel depends-on --dry-run
EXIT STATUS
- 0 success
- 1 Page A or B not found
- 1 A's type declares no
related:field - 1
<label>is not authorised for this collection pair, or the source collection authorises no labels into the target's collection at all
ON FAILURE
- Page A or B not found -> Fix the title and retry once - re-running never duplicates a link
- A's type declares no
related:field -> For a source page usexref link-source; otherwise there is nothing to link from this page <label>is not authorised for this collection pair, or the source collection authorises no labels into the target's collection at all -> Pick a label from the authorised set the refusal lists, then retry once
NEVER
- Never create the missing page just to force the link through.
- Never hand-write a reference field the type does not declare.
- Never edit a collection's
outbound:block just to get past a label refusal - authorising a further label is a deliberate edit of its own.
NOTES
- Declares one edge,
A <label> B: written into A'srelated:as- <label>: Band rendered into A's generated links region. - B is not touched and does not point back - its inbound view is rendered from the graph.
- Idempotent; re-running with a different label relabels rather than appending, since one page asserts one thing about another.
- Refuses before writing when A's type does not declare
related:- a source page declaresentities:/concepts:instead, and the refusal names them and points atxref link-source. - Refuses before writing when
<label>is not authorised by the source collection'soutbound:block for the target's collection; the refusal lists the authorised set and points atinstructions/link-taxonomy.md. --dry-runreports the edge without writing.
SEE ALSO
wikitool xref remove- removes a referencewikitool links show- the edges out of and into a pageinstructions/link-taxonomy.md- the labels and what each asserts
xref remove
Remove a cross-reference: the inverse of xref add.
SYNOPSIS
wikitool xref remove --a "<A>" --b "<B>" [--dry-run]
PROPERTIES
- effect: write
- idempotent: yes
- atomic: No - writes A then B, both idempotent
- budget: counted
- network: no
EXAMPLES
tools/wikitool xref remove --a "Gitea Actions" --b "act_runner"tools/wikitool xref remove --a "Gitea Actions" --b "Act Runner" --dry-run
EXIT STATUS
- 0 success
- 1 Page A not found (B is allowed not to exist)
ON FAILURE
- Page A not found (B is allowed not to exist) -> Fix the title; safe to retry freely
NEVER
- Never hand-edit a page-ref array to remove a reference.
NOTES
- Clears the reference in both directions: the cleanup command for a deleted or hand-renamed page rather than the strict inverse of a one-directional
xref add. - Clears
<B>from every page-ref frontmatter field<A>'s type declares (related:,sources:,entities:,concepts:) plus the matching bullets. - Also sweeps a page-ref field the type does not declare but some other type does, and drops that key once empty.
--bneed not still exist as a page, so this clears a reference left by a hand-deleted or hand-renamed page without hand-editing frontmatter.- Idempotent: removing an absent link is a no-op.
--dry-runreports without writing.
SEE ALSO
wikitool xref add- declares an edgewikitool rm- deletes a page and de-links it
xref link-source
Batch-link a source page to every entity/concept it mentions.
SYNOPSIS
wikitool xref link-source --source "Source - X" --entities A,B,C [--dry-run]
PROPERTIES
- effect: write
- idempotent: yes
- atomic: No - one write per entity plus one for the source page, idempotent per page
- budget: counted
- network: no
EXAMPLES
tools/wikitool xref link-source --source "Source - Docker Cheatsheet" --entities Docker,Podman --dry-runtools/wikitool xref link-source --source "Source - Docker Cheatsheet" --entities Docker,Podman
EXIT STATUS
- 0 success
- 1 Source page not found
- 1 A page in
--entitiesdoes not exist, or a target could not be written; the others were linked - 1 The source page itself could not be written after its targets were
ON FAILURE
- Source page not found -> Fix the title and retry once
- A page in
--entitiesdoes not exist, or a target could not be written; the others were linked -> Fix the named pages and re-run - safe, since every write is idempotent.sources trace --page "<Title>"shows who is already linked - The source page itself could not be written after its targets were -> Fix the write failure and re-run (idempotent)
NOTES
- Each target gets the source page in its
sources:, and the source page records each target in its ownentities:orconcepts:. No body bullet is written on either side -sources:is the record. - Which field a target lands in follows its collection (
kb/entities/->entities:), so a new collection needs no code change. - A target whose collection matches no reference field the source type declares is linked one way and named in the output.
- A target that does not exist is skipped and named; the others are still linked, and the run then exits 1.
- Idempotent in both directions.
--dry-runreports what would be linked.
SEE ALSO
wikitool sources trace- who a source is already linked towikitool xref add- one labelled edge between two pageswiki-ingestskill - where a source is linked after it is written
links show
Show the edges out of and into a page.
SYNOPSIS
wikitool links show --page "<Title>" [--json]
PROPERTIES
- effect: read
- idempotent: yes
- atomic: Read-only
- budget: exempt
- network: no
EXAMPLES
tools/wikitool links show --page "Act Runner"tools/wikitool links show --page "Act Runner" --json
EXIT STATUS
- 0 success
- 1 Page not found
ON FAILURE
- Page not found -> Check the exact title with
search; a wikilink target is not always the page's stem
NOTES
- Shows the declared graph around one page in both directions: the edges it asserts (from its own
related:, with labels) and the edges other pages assert about it. - The inbound half is computed across the corpus on every call, never stored, so it is complete.
--jsonprints both halves as JSON.- Read-only; exempt from the Iteration Budget Gate.
SEE ALSO
wikitool xref add/wikitool xref remove- change the outbound edgeswikitool search- finds the exact title
cite id
Print the deterministic footnote id cite add would use for this (title, file) pair.
SYNOPSIS
wikitool cite id --title "Source - X" [--file <qualifier>]
PROPERTIES
- effect: read
- idempotent: yes
- atomic: Read-only
- budget: exempt
- network: no
EXAMPLES
tools/wikitool cite id --title "Source - Docker Cheatsheet"
EXIT STATUS
- 0 success
NEVER
- Never paste an id from here into a page by hand -
cite addwrites the definition and prints the marker to paste.
NOTES
- Prints the footnote id
cite addwould use for this (title, file) pair. - A preview only: it does not check that the id is free on any given page.
- Never fails; safe to retry freely. Read-only and exempt from the Iteration Budget Gate.
SEE ALSO
wikitool cite add- writes the definition
cite add
Upsert a [^cite-id]: [[Source - X]] definition in a page's footnotes region.
SYNOPSIS
wikitool cite add --page "<Title>" --source "Source - X" [--file <qualifier>] [--dry-run]
PROPERTIES
- effect: write
- idempotent: yes
- atomic: Yes - single file write
- budget: counted
- network: no
EXAMPLES
tools/wikitool cite add --page "Docker" --source "Source - Docker Cheatsheet"tools/wikitool cite add --page "Docker" --source "Source - Docker Cheatsheet" --file part-2
EXIT STATUS
- 0 success
- 1 The page is not found
- 1 The source page is not found - citing it would be a dangling reference
ON FAILURE
- The page is not found -> Fix the title and retry once
- The source page is not found - citing it would be a dangling reference -> Fix the source title, or create the source page first, then retry once
NEVER
- Never compute or type a
[^cite-id]or its definition by hand - paste the marker this prints.
NOTES
- Upserts a
[^cite-id]: [[Source - X]]definition in the page's generated footnotes region, creating the region between<!-- wikitool:footnotes -->markers if absent. - Reuses the id when the page already cites this exact source/file pair, so a repeat changes nothing.
- Adds
Source - Xto the page's frontmattersources:. - Prints the
[^cite-id]marker; pasting it into the prose is a manual, editorial step. --dry-runreports without writing.
SEE ALSO
wikitool cite sync- prunes and re-orders the regionwikitool cite id- previews an id
cite sync
Reconcile each page's footnotes region against its actual [^id] references.
SYNOPSIS
wikitool cite sync [--page "<Title>" | --all] [--dry-run]
PROPERTIES
- effect: write
- idempotent: yes
- atomic: No - one write per page, each idempotent
- budget: counted
- network: no
EXAMPLES
tools/wikitool cite sync --page "Docker"tools/wikitool cite sync --all --dry-run
EXIT STATUS
- 0 success
- 1 Neither or both of
--page/--allgiven, or the page is not found - 1 A page write failed partway
ON FAILURE
- Neither or both of
--page/--allgiven, or the page is not found -> Fix the arguments and retry once - A page write failed partway -> Resolve the write failure and re-run - each page's re-render is idempotent
NOTES
- Prunes definitions nothing references any more, re-renders the region in first-reference order, and reports every
[^id]reference left with no definition. - An undefined-reference report is not a failure: fix the reference, or run
cite add, and re-run. - A page still carrying the pre-4.0.0 undelimited footnote block is converted to a marked region in the same pass.
- Each page's re-render is idempotent; safe to retry freely.
--dry-runreports without writing.
SEE ALSO
wikitool cite add- writes a definition
Catalog and log
index rebuild
Regenerate the catalog from every page's frontmatter.
SYNOPSIS
wikitool index rebuild [--dry-run]
PROPERTIES
- effect: write
- idempotent: yes
- atomic: No - each catalog file (the map plus one shard per collection/area) is rewritten independently, then stale shards are removed; an interruption can leave some regenerated and others not
- budget: counted
- network: no
EXAMPLES
tools/wikitool index rebuildtools/wikitool index rebuild --dry-run
EXIT STATUS
- 0 success
- 1 An I/O error while writing or removing a catalog file (rare)
ON FAILURE
- An I/O error while writing or removing a catalog file (rare) -> Safe to retry freely - the plan is always recomputed from the pages currently on disk, so a re-run converges
NEVER
- Never hand-edit
kb/index.mdor anINDEX.md- re-run this command instead.
NOTES
- Rewrites
kb/index.mdas a map: statistics, one row per collection and per area, and links to the shards - no page rows. - Writes the page tables to a generated
INDEX.mdin each collection. An area with more than 50 rows gets its ownINDEX.mdin its directory. - Deletes stale shards - an
INDEX.mdof a collection or area that no longer exists - in the same pass. - Warns about every page nested more than one directory below its collection; it is still catalogued, folded into its area.
--dry-runprints every file it would write and every stale shard it would remove, and writes nothing.
SEE ALSO
wikitool search- finds a page without reading the catalogwikitool lint- its Nested Pages finding is what the warning previewsinstructions/publish-cycle.md- where a write session runs this
log append
Append a formatted entry to kb/log.md.
SYNOPSIS
wikitool log append --op ingest|query|lint|create|update|delete|rename|move --title "..." [--body "..."|--body-file path]
PROPERTIES
- effect: write
- idempotent: no
- atomic: Yes - single append
- budget: counted
- network: no
EXAMPLES
tools/wikitool log append --op ingest --title "raw/articles/docker-cheatsheet.md" --body "Created [[Docker]]; updated [[Container]]."tools/wikitool log append --op lint --title "2026-09-26" --body-file lint-summary.md
EXIT STATUS
- 0 success
- 1
--opis not one of ingest, query, lint, create, update, delete, rename, move - 1
--body-fileis missing, not a readable file, or not valid UTF-8
ON FAILURE
--opis not one of ingest, query, lint, create, update, delete, rename, move -> Nothing was written - fix the argument and retry once--body-fileis missing, not a readable file, or not valid UTF-8 -> Nothing was written - fix the path and retry once
NEVER
- Never re-run after an uncertain outcome without first checking the tail of
kb/log.md- a second run appends a second entry.
NOTES
- Appends one entry to
kb/log.md: a## [YYYY-MM-DD] <op> | <title>heading, the body if one is given, and a---separator. - Not idempotent: every successful run appends a new entry, including a repeated one.
SEE ALSO
wikitool log status- counts the ingests logged since the last lintinstructions/publish-cycle.md- where a write session runs this
log status
Read-only: count ingest entries logged since the last lint entry.
SYNOPSIS
wikitool log status
PROPERTIES
- effect: read
- idempotent: yes
- atomic: Read-only
- budget: counted
- network: no
EXAMPLES
tools/wikitool log status
EXIT STATUS
- 0 success
- 0
kb/log.mdis missing or empty - reported as nothing logged, not a failure
NOTES
- Counts the
ingestentries inkb/log.mdafter the most recentlintentry, or since the start of the log if it was never linted, and the total number of entries. - At 10 or more it prints that the
wiki-lintskill is due next - the every-10-sources full lint of the Maintenance Schedule. - Read-only; safe to retry freely.
SEE ALSO
wikitool log append- writes the entries this countswiki-lintskill - what the threshold asks fortools/CONTRACT.md§ Maintenance schedule - the cadence this reports on
Finding and checking
lint
Run structural lint checks against kb/.
SYNOPSIS
wikitool lint [--json] [--markdown out.md] [--full] [--fail-on-error]
PROPERTIES
- effect: write
- idempotent: yes
- atomic: Writes one report file (single atomic write) unless
--json - budget: counted
- network: no
EXAMPLES
tools/wikitool linttools/wikitool lint --jsontools/wikitool lint --fail-on-error
EXIT STATUS
- 0 success
- 1 Only with
--fail-on-error: hard findings exist
ON FAILURE
- Only with
--fail-on-error: hard findings exist -> Act on the findings - exit 1 here means "act on the findings", not "the tool is broken". Re-running is safe, but only to re-measure after a fix
NEVER
- Never re-run just to re-read the findings - the printed path holds the full report.
NOTES
- Structural and provenance checks over
kb/: broken wikilinks, dangling frontmatter references, orphan pages, index drift, schema gaps, duplicate titles, title mismatches, uncovered raw files, brokenraw_files:refs, raw files claimed by more than one source page, unmarked provenance, citation/frontmatter drift, and unbalanced generated-region markers. - Unportable Titles is a hard finding, and hard at every
kb_version: a page whose title is not a valid file name on Windows and macOS (forbidden character, reserved name, trailing dot or space), or that collides with another page by case or Unicode normalization.wikitool renameis the fix. - Pages nested more than one directory below their collection are a hard finding - the generated catalog folds these into their area silently rather than merely reading it.
- Edges whose label is missing or not authorised by the source collection's
outbound:are both hard oncekb_versionhas reached the release that introduced labelled edges, and advisory below it. - Advisory only:
see-alsoedges whose reverse direction already carries a specific label - never migration-gated. - Advisory only: a collection past the catalog's per-area shard threshold that has no areas to shard, reported with the split its subtype field would produce, and only when that split puts every resulting area at or under the threshold.
- Advisory only: source pages sitting in the
unclassifiedcatalog slot. - Advisory only: Long Paths - a file under
kb/orraw/whose path below the instance root is over 160 UTF-16 code units, the budget that keeps a Windows checkout without long paths working. Reported as{path, length}; a corpus over the budget breaks no lint run.wikitool renameis the fix for a page. - Advisory only: quote-limit overages (>2 blockquotes/page).
- Prints only the sections that found something and always writes the full report to
reports/Lint Report <date>.md(or--markdown), naming the path.--fullprints everything;--jsonprints the findings and writes nothing. - Exits 0 whatever it finds unless
--fail-on-erroris passed.
SEE ALSO
wiki-lintskill - the procedure that runs thiswikitool move --reconcile- fixes Misplaced and Nested Pageswikitool rename- fixes Unportable Titles and, for a page, Long Pathswikitool log status- whether a full lint is due
search
Find pages in kb/ by text and/or frontmatter.
SYNOPSIS
wikitool search ["<text>"] [--field <predicate> ...] [--kind/--subtype/--collection/--tag <v>] [--regex] [--limit N] [--sort [-]<field>] [--backend <name>] [--matches] [--json]
PROPERTIES
- effect: read
- idempotent: yes
- atomic: Read-only
- budget: exempt
- network: no
EXAMPLES
tools/wikitool search "act runner"tools/wikitool search --field entity_type=system --field '!sources'tools/wikitool search "docker" --collection entities --limit 10 --json
EXIT STATUS
- 0 success
- 1
rgis not installed - 1
rgdid not finish within 30 s - 1 A malformed
--fieldpredicate, or an unknown--backend - 1 An unknown field name; the error lists the fields that exist
ON FAILURE
rgis not installed -> Not transient - installrg, then retryrgdid not finish within 30 s -> A timeout is a pathological pattern or an unresponsive corpus directory, not a slow answer - narrow the query or drop--regexrather than retrying it unchanged- A malformed
--fieldpredicate, or an unknown--backend-> Fix the argument and retry - An unknown field name; the error lists the fields that exist -> Pick a field from that list and retry
NEVER
- Never retry a timed-out query unchanged.
- Never grep
kb/yourself instead - it can add no page this misses, only the generated files it excludes.
NOTES
- Finds pages in
kb/without reading the catalog. Text search runs through a pluggable backend (rgtoday). --fieldpredicates are evaluated on frontmatter -f=v,f~substring,'f>=v','f:*'(present),'!f'(absent) - repeatable and ANDed. With no text this is a pure structured query.- One hit per line,
|-separated asscore | kind/subtype | title | path | summary. Title and path are never truncated - the title is the identifiertouch/xref/citetake. The summary, the one lossy field and the only one that may contain the separator, goes last, so splitting on" | "withmaxsplit=4is unambiguous. - Scope is pages: the backend walks
kb/but drops the kb-root meta files, everyCOLLECTION.mdand every generatedINDEX.md- a hand-run grep overkb/can add none of them but those. --limitdefaults to 50 (0for no limit), and a truncated result says so:50 of 182 result(s)in the table,total/truncated/limitbesidecountin--json, wherecountstays the number of results in the payload.api.searchand the MCPsearchtool carry the same default and the same fields.- A page whose frontmatter does not parse can match no positive predicate, so it is named rather than dropped:
--jsonalways carries anunreadablelist of{path, reason}(usually empty), and the table form writes the same lines to stderr. - An unknown field name is reported with the list of fields that do exist - never answered with an empty result, which would read as "no such pages".
--regexis applied byrgalone, whose engine is linear; the ranking boosts for title and summary are literal-containment only, so a non-literal pattern is ranked by match count.rgis killed after 30 s and reported as a failure.- Read-only, and exempt from the Iteration Budget Gate.
SEE ALSO
wikitool links show- the edges around a page once foundwiki-queryskill - answering a question from the wiki
review
The GTD weekly review.
SYNOPSIS
wikitool review [--json]
PROPERTIES
- effect: read
- idempotent: yes
- atomic: Read-only
- budget: exempt
- network: yes
EXAMPLES
tools/wikitool reviewtools/wikitool review --json
EXIT STATUS
- 0 success
- 1 No
.wikitool-tasks.json, or a malformed one - a clear "no tracker configured" message - 1
WIKITOOL_TASKS_CONFIGnames a file that does not exist or is broken - 1 The provider was reachable at config-parse time but a read call failed mid-run; the full report (findings plus which checks ran) was printed first
ON FAILURE
- No
.wikitool-tasks.json, or a malformed one - a clear "no tracker configured" message -> Not fixed by retrying unchanged - configure or repair.wikitool-tasks.jsonfirst WIKITOOL_TASKS_CONFIGnames a file that does not exist or is broken -> Not transient - fix the path or unset the variable- The provider was reachable at config-parse time but a read call failed mid-run; the full report (findings plus which checks ran) was printed first -> Start the unreachable provider (e.g. the tracker app), then retry plainly
NEVER
- Never present a report that exited 1 as complete.
NOTES
- Joins the configured task-tracker provider against
kb/gtd/project pages over the case-normalized project name, at read time, storing nothing - not even areports/file. - stalled: a tracker project with zero open items whose
kb/page isstate: active;dormant/completed/abandonednever fire. - waiting-overdue: a
WAITINGitem whosefollow_up_atis older thanthresholds.stalled_waiting_days. - unpaged-project: a tracker project with no matching
kb/page, older thanthresholds.unpaged_project_weeks. - no-open-loop: a
kb/pagestate: activewith no matching tracker project, or one with zero open items - the reverse direction of unpaged-project, so a rename on either side surfaces on both. - someday-stale: a someday/maybe item untouched for longer than
thresholds.someday_stale_months. - A value a provider genuinely cannot supply - a
WAITINGitem with nofollow_up_at, a tracker project with no determinable creation date - is its own finding (waiting_no_follow_up/project_age_unknown) rather than a silent skip. - Thresholds come from the tracker configuration, never from the schema.
- The tracker configuration is read from
.wikitool-tasks.json, or from the fileWIKITOOL_TASKS_CONFIGnames when that variable is set (so one checkout can be run against several trackers in turn). A set variable that names no file is an error, never "no tracker configured". - Text output is one
[check] project: messageline per finding, preceded by aSource:line naming which access path answered and, forsuperproductivity'saccess: "snapshot", the snapshot's age.--jsoncarries the same findings pluschecks_run/checks_skipped/kb_project_count/complete/source({"kind": ..., "detail": ...}ornull). - A provider that cannot be reached mid-run degrades only the checks that needed the failing call; the report is printed in full, then exit 1 follows - never rendered as if it were complete.
- Re-reads everything fresh on every call, so nothing is ever stale to re-fetch.
- Read-only, and exempt from the Iteration Budget Gate.
SEE ALSO
gtd-weekly-reviewskill - turns the findings into decisionswikitool task list/wikitool task close- act on an itemwikitool new project- a project page and its tracker project
Provenance
sources coverage
List raw files with no source page, broken raw_files: references, and legacy directory/URL-only source pages.
SYNOPSIS
wikitool sources coverage [--json]
PROPERTIES
- effect: read
- idempotent: yes
- atomic: Read-only
- budget: counted
- network: no
EXAMPLES
tools/wikitool sources coveragetools/wikitool sources coverage --json
EXIT STATUS
- 0 success
NOTES
- Lists raw files with no source page, broken
raw_files:references, and legacy directory/URL-only source pages. --jsonprints the same lists as JSON.- Never fails; read-only and safe to retry freely.
SEE ALSO
wikitool sources trace- follows one file or pagewiki-ingestskill - turns an uncovered raw file into a source page
sources trace
Trace provenance in either direction: raw file, or page.
SYNOPSIS
wikitool sources trace --raw <path> | --page "<Title>"
PROPERTIES
- effect: read
- idempotent: yes
- atomic: Read-only
- budget: counted
- network: no
EXAMPLES
tools/wikitool sources trace --page "Docker"tools/wikitool sources trace --raw "raw/articles/llm-wiki.md"
EXIT STATUS
- 0 success
- 1 Neither or both of
--raw/--pagegiven, or--pagenames an unknown page - 1
--rawnames a file no source page covers - reported as a plain finding plus exit 1, not the usualERROR-prefixed rejection
ON FAILURE
- Neither or both of
--raw/--pagegiven, or--pagenames an unknown page -> Fix the argument and retry --rawnames a file no source page covers - reported as a plain finding plus exit 1, not the usualERROR-prefixed rejection -> Nothing to retry: the file is uncovered. Ingest it, or check the path
NOTES
--raw <path>: raw file -> the source page(s) covering it -> the pages citing those.--page "<Title>": page -> its sources -> their raw files.- Read-only.
SEE ALSO
wikitool sources coverage- every uncovered raw file at oncewikitool xref link-source- links a source page to what it mentions
sources rebuild-index
Regenerate the kb/provenance.md reverse index.
SYNOPSIS
wikitool sources rebuild-index [--dry-run]
PROPERTIES
- effect: write
- idempotent: yes
- atomic: Yes - the single provenance file is regenerated from scratch
- budget: counted
- network: no
EXAMPLES
tools/wikitool sources rebuild-indextools/wikitool sources rebuild-index --dry-run
EXIT STATUS
- 0 success
- 1 An I/O error while writing
kb/provenance.md(rare)
ON FAILURE
- An I/O error while writing
kb/provenance.md(rare) -> Safe to retry freely
NEVER
- Never hand-edit
kb/provenance.md- re-run this command instead.
NOTES
- Regenerates the
kb/provenance.mdreverse index (raw file -> source page -> citing pages) from scratch. --dry-runprints the result instead of writingkb/provenance.md.
SEE ALSO
wikitool index rebuild- the page catalog, rebuilt alongsideinstructions/publish-cycle.md- where a write session runs this
Raw material and uploads
raw accept
Promote one or more files from incoming/ into raw/.
SYNOPSIS
wikitool raw accept <file> [<file> ...] --fidelity <v> --authority <v> [--page "<Title>"] [--dry-run]- Promote one or more files fromincoming/into today'sraw/<YYYY>/<MM>/shardwikitool raw accept <file> --replaces <raw-path> [--fidelity <v>] [--authority <v>] [--dry-run]- Overwrite one existing raw file in place with a new edition
PROPERTIES
- effect: write
- idempotent: no
- atomic:
raw accept: No - one filesystem move per file, then (with--page) one page write.raw accept --replaces: No - oneunlink()+ onerename(), plus (if--fidelity/--authoritywas given) one page write - budget: counted
- network: no
EXAMPLES
tools/wikitool raw accept incoming/docker-cheatsheet.md --fidelity verbatim --authority reportingtools/wikitool raw accept incoming/part-2.md --fidelity verbatim --authority reporting --page "Source - Docker Cheatsheet"tools/wikitool raw accept incoming/cluster.md --replaces raw/documents/cluster.md
EXIT STATUS
- 0 success
- 1 raw accept: A file does not exist, is not under
incoming/, or is nested more than one level below it; two files in one call share a filename; a target path already exists; or a target path would be over the path budget (160 UTF-16 code units below the instance root) - 1 raw accept:
--fidelity/--authorityis missing, or namesunknownor a value outside the schema's enum - 1 raw accept: The target name is already occupied anywhere under
raw/by something the call does not own - 1 raw accept:
--pagenames an unknown page or one with noraw_files:yet, an existingraw_files:entry is missing on disk, a file to be moved has more than one owning page, or--pagewould overwrite an already-setfidelity/authoritywith a different value - 1 raw accept --replaces: More than one incoming file, or
--pagealso given - 1 raw accept --replaces: The incoming file does not exist or is not under
incoming/(or is nested more than one level below it), its filename differs from the target's, or the target does not lie underraw/or does not exist - 1 raw accept --replaces:
--fidelity/--authoritynamesunknownor a value outside the schema's enum, or the target has more than one owning source page
ON FAILURE
- raw accept: A file does not exist, is not under
incoming/, or is nested more than one level below it; two files in one call share a filename; a target path already exists; or a target path would be over the path budget (160 UTF-16 code units below the instance root) -> Fix the named argument and retry once. For a path over the budget, rename the file inincoming/to something shorter - the refusal comes before anything moves, soincoming/andraw/are unchanged - raw accept:
--fidelity/--authorityis missing, or namesunknownor a value outside the schema's enum -> Pass both with a valid value, then retry once - raw accept: The target name is already occupied anywhere under
raw/by something the call does not own -> Not fixed by retrying: the refusal names--replaces(same source, new edition) and renaming inincoming/(a separate source) as the two routes, and neither is the tool's to pick. Show the message to the user and wait - raw accept:
--pagenames an unknown page or one with noraw_files:yet, an existingraw_files:entry is missing on disk, a file to be moved has more than one owning page, or--pagewould overwrite an already-setfidelity/authoritywith a different value -> Fix the named argument and retry once; a different capture value on an existing page is a new edition ---replaces - raw accept --replaces: More than one incoming file, or
--pagealso given -> A replacement is one file for one file - fix the call and retry once - raw accept --replaces: The incoming file does not exist or is not under
incoming/(or is nested more than one level below it), its filename differs from the target's, or the target does not lie underraw/or does not exist -> Fix the named argument and retry once - every check runs before the filesystem is touched, so both files are exactly as they were - raw accept --replaces:
--fidelity/--authoritynamesunknownor a value outside the schema's enum, or the target has more than one owning source page -> Fix the named argument and retry once; nothing was touched
NEVER
- Never choose the destination under
raw/by hand, and never move a file intoraw/yourself. - Never pick between
--replacesand renaming on your own initiative after a name-occupied refusal - the user tells the two intents apart.
NOTES
- Promotes files from
incoming/intoraw/<YYYY>/<MM>/, computed from the accept date rather than chosen by hand. A subdirectory underincoming/is tolerated and ignored, not inspected -raw/does not address by type. - One file promoted alone lands with no directory of its own; several files in one call nest under
raw/<YYYY>/<MM>/<stem>/, named after the first file's stem. --fidelity/--authorityare required on a plain accept (types describe sourcelists the values);unknownis refused - it is backfill-only.--page "<Title>"additionally extends that existing source page'sraw_files:in the same call and writes both capture fields onto it - refused if it already carries a different value, since a capture field is fixed once.- If
--pageraises the page past one file, its already-promoted file is folded into a bundle at its own parent directory, not today's shard, so a bundle never mixes an old and a new capture date - after checking that file has no other owner. - Every name occupied anywhere under
raw/- file stems and bundle directory names alike, old type directories and date shards together - stays unique: a promote whose target name already belongs to something this call does not itself own is refused, naming--replacesand renaming inincoming/as the two routes, without recommending either. --replaces <raw-path>overwrites that file in place with the single incoming file (same filename required), leaves every page'sraw_files:untouched and writes nokb/page; the previous edition survives only ingit log --follow <raw-path>.- With
--replaces,--fidelity/--authorityare optional, and passing one overwrites the owning page's already-set value - the one path the fixed-once rule does not block. --replacesrefuses a target with more than one owning source page; with none, it replaces anyway and says so. It cannot be combined with--pageor with more than one incoming file.--replacesprints the source page (if any) and its citing pages, so their update lands in the same commit as the replacement.- A file already at its computed destination is what "already exists" reports, not a partial prior run to resume - safe to retry as-is once a cause is fixed.
--dry-runreports the moves without making them.
SEE ALSO
raw/CONTRACT.md"Getting a file in: incoming/" - the rules and whywikitool types describe source- the capture field valueswikitool new source- the source page for a promoted file
upload list
List every MCP submission currently waiting in the quarantine (mcp-upload/).
SYNOPSIS
wikitool upload list [--json]
PROPERTIES
- effect: read
- idempotent: yes
- atomic: Read-only
- budget: counted
- network: no
EXAMPLES
tools/wikitool upload listtools/wikitool upload list --json
EXIT STATUS
- 0 success
NOTES
- Lists every submission waiting in
mcp-upload/, oldest id first: id, filename, size, submitter. - Only ever non-empty when
.wikitool-upload.jsonopts the checkout into the MCP server'ssubmittool. - A submission directory with a corrupt manifest is silently skipped.
- Never fails; read-only and safe to retry freely.
SEE ALSO
wikitool upload show- one submission's manifestINSTALL-MCP.md§ "Schritt 7: Optional - densubmit-Pfad freischalten"
upload show
Print one submission's manifest in full.
SYNOPSIS
wikitool upload show <id> [--json]
PROPERTIES
- effect: read
- idempotent: yes
- atomic: Read-only
- budget: counted
- network: no
EXAMPLES
tools/wikitool upload show <id>
EXIT STATUS
- 0 success
- 1 Unknown or malformed submission id
ON FAILURE
- Unknown or malformed submission id -> Fix the id (see
upload list) and retry
NOTES
- Prints one submission's manifest in full: filename, size, sha256, submitter, submitter source (the header name, not a claim the header was honest), submission time.
- What a reviewer reads before
upload accept. - Read-only.
SEE ALSO
wikitool upload accept- promotes it after reviewwikitool upload reject- declines it
upload accept
Upload Review Gate: promote a submission's file from quarantine into incoming/.
SYNOPSIS
wikitool upload accept <id> [--confirm TOKEN]
PROPERTIES
- effect: write
- idempotent: no
- atomic: No - one filesystem move, one directory delete, one ledger append; the gate check runs first, before any of them
- budget: counted
- network: no
- gates: upload-review
EXAMPLES
tools/wikitool upload accept <id>tools/wikitool upload accept <id> --confirm <token> # re-run after exit 42, once the user approved the manifest
EXIT STATUS
- 0 success
- 1 Unknown or malformed submission id, or the submission's file is missing from
mcp-upload/<id>/ - 1
incoming/<filename>already exists - 42 Upload Review Gate:
--confirmis absent or does not match the manifest's current token
ON FAILURE
- Unknown or malformed submission id, or the submission's file is missing from
mcp-upload/<id>/-> Fix the named argument and retry once incoming/<filename>already exists -> Not fixed by retrying unchanged - rename or clear it first- Upload Review Gate:
--confirmis absent or does not match the manifest's current token -> Show the user the command's full output verbatim and stop. Once they have approved it, run the re-run line the output prints, which carries--confirm <token>. Without that token, or with a wrong, invented or superseded one, it exits 42 again with the current state
NEVER
- Never pass a
--confirmtoken the user has not seen and approved.
NOTES
- Promotes a submission's file from
mcp-upload/<id>/intoincoming/, deletes the quarantine directory, and appends anacceptedevent tomcp-upload/ledger.jsonl. - Upload Review Gate: without a matching
--confirm, exits 42 and prints the manifest in full plus the exact re-run line - one submission at a time. The gate check runs before anything is moved. - The token digests id, filename, size, sha256 and submitter, so an edited or superseded manifest invalidates it.
- An occupied
incoming/<filename>is an ordinary validation error, not the gate.
SEE ALSO
wikitool upload show- the manifest to reviewwikitool raw accept- the next step for the file inincoming/instructions/gates.md- the gate procedure
upload reject
Delete a submission's material, keeping only its ledger trail.
SYNOPSIS
wikitool upload reject <id> --reason "<why>"
PROPERTIES
- effect: write
- idempotent: no
- atomic: No - one ledger append, then one recursive delete; the ledger write happens first, so an interruption still leaves the reason on record
- budget: counted
- network: no
EXAMPLES
tools/wikitool upload reject <id> --reason "duplicate of raw/articles/llm-wiki.md"
EXIT STATUS
- 0 success
- 1 Unknown or malformed submission id, or an empty
--reason
ON FAILURE
- Unknown or malformed submission id, or an empty
--reason-> Fix the argument and retry once. After a first successful call, "unknown id" is confirmation, not a failure
NOTES
- Appends an append-only
rejectedevent naming the reason and the sha256 of what was declined, then deletes the submission's material. - The ledger write happens first, so an interruption still leaves the reason on record.
- No gate - rejecting needs no clearance, only accepting does.
- Not idempotent: a second call with the same id reports "unknown id", which confirms the first call worked.
SEE ALSO
wikitool upload show- the manifest to review
Git
sync
Fetch <remote>/<branch> and bring the local branch up to date with it.
SYNOPSIS
wikitool sync [--remote origin] [--branch main] [--confirm-rebase TOKEN]
PROPERTIES
- effect: write
- idempotent: yes
- atomic: No - fetch, then at most one merge/rebase attempt, aborted cleanly on failure
- budget: counted
- network: yes
- gates: rebase-review
EXAMPLES
tools/wikitool synctools/wikitool sync --confirm-rebase <token> # re-run after exit 42, once the user approved
EXIT STATUS
- 0 success
- 0 No remote configured - reported and skipped, not a failure
- 0 The remote cannot be reached - reported and skipped, not a failure
- 1 The automatic rebase hit a real conflict (git failed); it is aborted cleanly
- 42 Rebase-review gate:
<remote>/<branch>moved and both sides changed the same file; the output lists the upstream commits, the overlapping files and their diff
ON FAILURE
- The automatic rebase hit a real conflict (git failed); it is aborted cleanly -> Do not retry and do not force - resolve the conflict manually, then re-run
- Rebase-review gate:
<remote>/<branch>moved and both sides changed the same file; the output lists the upstream commits, the overlapping files and their diff -> Show the user the command's full output verbatim and stop. Once they have approved it, run the re-run line the output prints, which carries--confirm-rebase <token>. Without that token, or with a wrong, invented or superseded one, it exits 42 again with the current state
NEVER
- Never retry a conflict unchanged, and never force past it.
- Never pass a
--confirm-rebasetoken the user has not seen and approved.
NOTES
- Fetches
<remote>/<branch>, then: fast-forwards when only the remote moved; rebases the local commits on top when both sides moved but touched disjoint files; exits 42 (rebase-review gate) when both sides touched the same file. - A refused call performs no rebase attempt and leaves the branch where it was.
- The
--confirm-rebasetoken covers the exact upstream state and the set of files touched on both sides; either one moving makes it stale. - Makes no commit, no push, and no forced operation of any kind.
- Three messages for a fetch that fails: no remote of that name, a remote that answers but has no such branch yet (a new, empty repository), and a remote that cannot be reached. All three exit 0 here;
publishstops on the first and the last. - Run it once at the start of a writing session.
SEE ALSO
wikitool publish- runs the same reconcile before it commits and pushesinstructions/session-setup.md- where a session runssyncinstructions/gates.md- the gate procedure
publish
Reconcile with <remote>/<branch>, then stage all changes, commit, and push.
SYNOPSIS
wikitool publish --message "<op>: <desc>" [--no-push] [--confirm TOKEN] [--confirm-rebase TOKEN] [--threshold N] [--remote origin] [--branch main] [--path P ...]
PROPERTIES
- effect: write
- idempotent: no
- atomic: No - sequential git operations. Every gate runs before staging, except on the one retry of a rejected push, where the rebase-review gate can exit 42 after the commit: nothing is pushed, and the re-run with
--confirm-rebasepushes that commit - budget: counted
- network: yes
- gates: mass-update, publish-remote, rebase-review
EXAMPLES
tools/wikitool publish --message "ingest: docker-cheatsheet"tools/wikitool publish --confirm <token> --message "ingest: docker-cheatsheet" # re-run after a Mass-Update exit 42, once the user approvedtools/wikitool publish --confirm-rebase <token> --message "ingest: docker-cheatsheet" # re-run after a rebase-review exit 42, once the user approved
EXIT STATUS
- 0 success
- 1 git failed -
git add,git commit,git push, or the reconcile's automatic rebase - 1 The push target (
--branch) is not the checked-out branch, or HEAD is detached; the unborn branch of a freshgit initis not this case - 1 No
--no-push, and the remote is not configured or cannot be reached; nothing was committed - 1
--yes/-ywas passed - the flag does not exist and fails with an explicit error - 1
.wikitool-remotes.jsonis unreadable or has no usableallowed_push_urlslist - 42 Mass-Update Gate:
--threshold(default 10) or more counted files would be committed, or the--confirmtoken does not match this changeset - 42 Rebase-review gate:
<remote>/<branch>moved and both sides changed the same file; the output lists the upstream commits, the overlapping files and their diff - 42 Publish-Remote Gate:
.wikitool-remotes.jsonexists and the push URL of--remoteis not listed in it, or--remoteresolves to no push URL
ON FAILURE
- git failed -
git add,git commit,git push, or the reconcile's automatic rebase -> Do not retry and do not force - report and ask the user.publishhas already made its one retry of a rejected push itself, where a reconcile resolved the rejection - The push target (
--branch) is not the checked-out branch, or HEAD is detached; the unborn branch of a freshgit initis not this case -> Check out the branch you mean to publish, or pass--branch <checked-out branch>, then retry once - No
--no-push, and the remote is not configured or cannot be reached; nothing was committed -> Show the message to the user and ask whether to commit locally with--no-push. Never push by hand - the nextpublishthat reaches the remote sends that commit --yes/-ywas passed - the flag does not exist and fails with an explicit error -> Drop it. The Mass-Update Gate is cleared only with--confirm <token>from the gate's own refusal output.wikitool-remotes.jsonis unreadable or has no usableallowed_push_urlslist -> Show the error to the user and stop - a malformed file is not permission, and fixing or deleting it is theirs to do- Mass-Update Gate:
--threshold(default 10) or more counted files would be committed, or the--confirmtoken does not match this changeset -> Show the user the command's full output verbatim and stop. Once they have approved it, run the re-run line the output prints, which carries--confirm <token>. Without that token, or with a wrong, invented or superseded one, it exits 42 again with the current state - Rebase-review gate:
<remote>/<branch>moved and both sides changed the same file; the output lists the upstream commits, the overlapping files and their diff -> Show the user the command's full output verbatim and stop. Once they have approved it, run the re-run line the output prints, which carries--confirm-rebase <token>. Without that token, or with a wrong, invented or superseded one, it exits 42 again with the current state - Publish-Remote Gate:
.wikitool-remotes.jsonexists and the push URL of--remoteis not listed in it, or--remoteresolves to no push URL -> Show the user the push URL it names and the allowed ones, and stop. This gate has no token and no flag: only the user resolves it, by adding the URL to that file
NEVER
- Never retry a failed git step unchanged, and never force (
--force,--force-with-lease). - Never pass a
--confirmor--confirm-rebasetoken the user has not seen and approved. - Never edit
.wikitool-remotes.jsonto get past a Publish-Remote refusal - that is opening a gate on your own initiative.
NOTES
- Order: branch check and Publish-Remote Gate, then the reconcile with
<remote>/<branch>, then the Mass-Update Gate, thengit add -A, commit and push.--no-pushskips all but the Mass-Update Gate and the commit. - Without
--no-push, a remote that is not configured or cannot be reached ends the call with exit 1 at the reconcile - before the gate,git addand the commit, and also on a clean tree. Nothing is committed, the index and the working tree are unchanged, and the message names--no-pushas the way to a local commit. The nextpublishthat reaches the remote pushes that commit along with whatever is new. A remote that answers but has no<branch>yet (a new, empty repository) is not this case: the first publish of an instance commits and pushes as before. - Reconcile: fetches
<remote>/<branch>, fast-forwards when only the remote moved, rebases the local commits on top when both sides moved but touched disjoint files, and exits 42 (rebase-review gate) when both sides touched the same file. A refused reconcile performs no rebase attempt. The--confirm-rebasetoken covers the exact upstream state and the set of files touched on both sides. - The push target must be the checked-out branch; this is checked before anything is staged. The unborn branch of a fresh
git init -b maincounts as checked out, so the first publish of a new instance works; a real detached HEAD is refused. - With nothing new to stage, a local commit the remote lacks is still pushed: one left behind by an earlier publish whose push failed, or every commit when the remote answers but does not have the branch yet (a new, empty remote repository).
- A rejected push that finds the remote unreachable on its one retry reports the original push error.
- A rejected push gets exactly one more reconcile-and-push; never more than one.
- Mass-Update Gate: counts the files that would be committed, refuses with exit 42 at
--threshold(default 10) or more, and prints a review report - a scale line (file count, total lines added/removed, status breakdown), attention notes where they apply (deletions by name, control-plane and harness-config touches, published pages, the largest single change, binaries), and every counted path grouped by area with its status and churn. The list is what the commit will hold: it is computed from a scratch copy of the index aftergit add -A, so a path that is staged as deleted and back in the working tree is not counted twice, and a rename counts as its old path deleted plus its new path added. The real index and the working tree are not touched, so a refused publish leaves both byte-identical. - Never counted and never shown for approval, but committed like everything else: anything under
work/, and the fileswikitoolgenerates itself (kb/index.md,kb/log.md,kb/provenance.md, everyINDEX.md). The refusal line accounts for both, by reason. - The
--confirmtoken covers each counted path, the blob id of its contents and the publish target: a different file list or edited contents need a new clearance. - Publish-Remote Gate: when the checkout carries
.wikitool-remotes.jsonand the push URL of--remoteis not listed in it, exits 42 before the reconcile fetches anything. The URL is read withgit remote get-url --push, so a repointed remote does not pass on its name. An absent file means unrestricted; a malformed one is an error, not permission. --path(repeatable) scopes the whole operation - gate count, staging and commit - to that subtree.- After a successful commit or push whose changed files include
tools/,types/,instructions/,AGENTS.mdor a path ending inCONTRACT.md, prints one reminder line: the phase past this point (an issue-body rewrite,docs/staleness, a changelog entry's accuracy) is not covered bydocs verify,instructions verifyorpytest. It is not a gate: no exit code change, nothing to clear, and silent for an ordinary content publish.
SEE ALSO
wikitool sync- the same reconcile on its own, without committing or pushinginstructions/gates.md- the gate procedureinstructions/setup-instance.md- the first publish of a new instance
Workshop runs and session budget
work new
Scaffold work/<runkey>/ for one workshop run.
SYNOPSIS
wikitool work new (--input <raw path> | --key <run key>) [--again] [--dry-run]
PROPERTIES
- effect: write
- idempotent: no
- atomic: Yes - one directory with two files
- budget: counted
- network: no
EXAMPLES
tools/wikitool work new --input raw/2026/09/handbuchtools/wikitool work new --key migrate-7.0.0tools/wikitool work new --input raw/2026/09/handbuch --again
EXIT STATUS
- 0 success
- 1 Neither or both of
--input/--keygiven, or a--keythat is empty or starts withingest- - 1
--inputis outsideraw/, does not exist, or israw/itself (an empty run key) - 1 The workshop already exists
ON FAILURE
- Neither or both of
--input/--keygiven, or a--keythat is empty or starts withingest--> Fix the argument and retry once --inputis outsideraw/, does not exist, or israw/itself (an empty run key) -> Point--inputat material underraw/, or use--keyfor a run with no raw input- The workshop already exists -> A collision is not transient: resume the existing run instead, or pass
--againif the tree itself changed
NEVER
- Never create a numbered variant of a run key by hand.
NOTES
- Creates
work/<runkey>/with the requiredREADME.mdandplan.md. --inputderives the run key from the path belowraw/(an ingest);--keynames it outright for a run with no raw input - a migration or a sweep acrosskb/- and may not start withingest-, which stays reserved for derived keys. Exactly one of the two.- Refuses a collision instead of suffixing it.
--againopens a dated second pass (<runkey>-<date>) over a tree that has itself changed.--dry-runreports the run key and files without writing.
SEE ALSO
work/CONTRACT.md- run keys, required files, how a run closeswikitool work close- deletes the run when it is doneinstructions/ingest-large-tree.md- the ingest that opens a run
work close
Delete a finished workshop.
SYNOPSIS
wikitool work close --run-key <name> [--yes] [--dry-run]
PROPERTIES
- effect: write
- idempotent: no
- atomic: No - a recursive delete
- budget: counted
- network: no
EXAMPLES
tools/wikitool work close --run-key ingest-2026-09-handbuch --dry-runtools/wikitool work close --run-key ingest-2026-09-handbuch --yes
EXIT STATUS
- 0 success
- 1 Unknown run key
- 1
--yeswas not passed - the output lists what would be lost
ON FAILURE
- Unknown run key -> Check
ls work/for the open runs, then retry once --yeswas not passed - the output lists what would be lost -> Check the listed files are no longer needed, confirm the conclusions are inkb/, then re-run with--yes
NEVER
- Never pass
--yesbefore the run's conclusions are inkb/.
NOTES
- Deletes
work/<run-key>/recursively. - Without
--yesit lists what would be lost and refuses: nothing in a workshop is recoverable from the rest of the repo, so the durable conclusions must already be inkb/. --dry-runlists the files without deleting.- Not idempotent: once deleted, the run key is unknown.
SEE ALSO
work/CONTRACT.md- how a run closeswikitool work new- opens a run
budget status
Show the current session's wikitool call count and recent command history.
SYNOPSIS
wikitool budget status
PROPERTIES
- effect: read
- idempotent: yes
- atomic: Read-only
- budget: exempt
- network: no
EXAMPLES
tools/wikitool budget status
EXIT STATUS
- 0 success
NOTES
- Shows the current session's
wikitoolcall count and its recent command history. - Never counted against the budget; never fails; safe to retry freely.
SEE ALSO
instructions/session-setup.md- scoping the budget to a taskinstructions/gates.md- what to do when the Iteration Budget Gate refuses
budget reset
Clear the current session's (or every session's) iteration budget state.
SYNOPSIS
wikitool budget reset --yes [--all]
PROPERTIES
- effect: write
- idempotent: no
- atomic: Read/rewrite of one JSON file (or its deletion, with
--all) - budget: counted
- network: no
EXAMPLES
tools/wikitool budget reset --yes # only after the user approved it
EXIT STATUS
- 0 success
- 1
--yesnot passed
ON FAILURE
--yesnot passed -> Get the user's approval, then re-run with--yes
NEVER
- Never run it on your own initiative to get past a budget or loop-breaker refusal.
NOTES
- Clears the current session's iteration budget state;
--alldeletes every session's. - Requires
--yes: clearing the counter is itself a way around the Iteration Budget Gate, so it needs the same explicit human approval. - Counted against the budget like any other call.
SEE ALSO
wikitool budget status- the current countinstructions/gates.md- whybudget resetis not the escape hatch
Types, instructions and docs
types list
List every type-spec under types/.
SYNOPSIS
wikitool types list [--json]
PROPERTIES
- effect: read
- idempotent: yes
- atomic: Read-only
- budget: counted
- network: no
EXAMPLES
tools/wikitool types listtools/wikitool types list --json
EXIT STATUS
- 0 success
NEVER
- Never pick a page's directory by hand -
types describeandnewcompute it.
NOTES
- Lists every type-spec under
types/: name, schema path, subtype field, and description - which page types exist, without readingtypes/*.mddirectly. - Never fails; read-only and safe to retry freely.
SEE ALSO
wikitool types describe <name>- one type's full contract
types describe
Print one type's full contract.
SYNOPSIS
wikitool types describe <name> [--json]
PROPERTIES
- effect: read
- idempotent: yes
- atomic: Read-only
- budget: counted
- network: no
EXAMPLES
tools/wikitool types describe sourcetools/wikitool types describe project --json
EXIT STATUS
- 0 success
- 1 Unknown type name
ON FAILURE
- Unknown type name -> Fix the name (see
types list) and retry
NOTES
- Prints one type's full contract: required and optional frontmatter fields with enums, its subtype field (if any), and its authoring body.
- Where the type-spec declares
guidance:, the stack-ownedtypes/<name>.guidance.mdis composed in, so aroot: kbtype's contract reads as one answer even though it may live in two files.--jsonreports it separately asguidance/guidance_path, absent for a type with none. - The generated table-of-contents region a long type-spec (or guidance file) carries is stripped from this output.
- Read-only.
SEE ALSO
wikitool types list- every typewikitool new <type>- scaffolds a page of the type
instructions sync
Publish every instructions/<name>/SKILL.md into the harness skill directories.
SYNOPSIS
wikitool instructions sync [--force]
PROPERTIES
- effect: write
- idempotent: yes
- atomic: No - one directory copy per skill per target (
.agents/skills/,.claude/skills/); each copy is idempotent, so a re-run converges even after a partial failure - budget: counted
- network: no
EXAMPLES
tools/wikitool instructions sync
EXIT STATUS
- 0 success
- 1 No skills found under
instructions/ - 1 A target directory is not a published skill (no
SKILL.md) and--forcewas not passed
ON FAILURE
- No skills found under
instructions/-> Fix the named cause and retry - A target directory is not a published skill (no
SKILL.md) and--forcewas not passed -> Check whether the flagged target holds anything worth keeping, then re-run with--forceif not
NEVER
- Never hand-edit a published copy under
.agents/skills/or.claude/skills/- edit the source and re-run this.
NOTES
- Publishes every
instructions/<name>/SKILL.mdinto.agents/skills/and.claude/skills/as copies, and deletes published skills whose source is gone. - Both targets are gitignored, so a fresh clone runs this once.
- Re-running repairs a drifted copy: the source always wins.
--forceis required only to replace a target directory that is not a published skill at all (noSKILL.mdin it).- Each copy is idempotent, so a re-run converges even after a partial failure.
SEE ALSO
instructions/bootstrap.md- the fresh-clone procedure that runs thiswikitool instructions verify- checks the copies match
instructions verify
Check the instruction layer.
SYNOPSIS
wikitool instructions verify
PROPERTIES
- effect: read
- idempotent: yes
- atomic: Read-only
- budget: counted
- network: no
EXAMPLES
tools/wikitool instructions verify
EXIT STATUS
- 0 success
- 1 Nothing found under
instructions/at all, or a malformed instruction orSKILL.md - 1 A
SKILL.mdcarries a relative markdown link - 1 A published copy drifted from its source
- 1 An instruction nothing references, or a
manual: trueone that IS linked from AGENTS.md, CLAUDE.md or a skill and so risks running implicitly - 1 Something under
instructions/dev/is referenced from outside it and outside adist:stripblock
ON FAILURE
- Nothing found under
instructions/at all, or a malformed instruction orSKILL.md-> Fix the flagged file, then re-run - A
SKILL.mdcarries a relative markdown link -> Rewrite it as a repo-root-relative plain path, then re-run - A published copy drifted from its source -> Re-run
instructions sync- the source underinstructions/always wins - An instruction nothing references, or a
manual: trueone that IS linked from AGENTS.md, CLAUDE.md or a skill and so risks running implicitly -> Link it from where it is used, or drop the link to a manual one, then re-run - Something under
instructions/dev/is referenced from outside it and outside adist:stripblock -> Remove the reference or wrap it in adist:stripblock, then re-run
NEVER
- Never fix drift by hand-editing the published copy.
NOTES
- Flat instructions validate against
types/instruction.schema.yaml, and eachSKILL.mdcarries the frontmatter its harness reads. - No
SKILL.mdcarries a relative markdown link:synccopies it to a different depth than the source, so aSKILL.mdreferences a target as a repo-root-relative plain path instead (instructions/CONTRACT.md§ "A skill's outbound reference is a plain path, not a link"). - Every published copy is byte-identical to its source. Missing every copy is reported as "run sync", not as drift - that is a clean checkout.
- No instruction is left that nothing references; one marked
manual: truemust instead not be linked from AGENTS.md, CLAUDE.md or a skill. - Nothing under
instructions/dev/is referenced from outside it; a<!-- dist:strip-start/end -->block is exempt (instructions/CONTRACT.md). - Read-only.
SEE ALSO
wikitool instructions sync- publishes the copiesinstructions/CONTRACT.md- the rules this checks
instructions list
List the flat instructions with their descriptions.
SYNOPSIS
wikitool instructions list [--json]
PROPERTIES
- effect: read
- idempotent: yes
- atomic: Read-only
- budget: counted
- network: no
EXAMPLES
tools/wikitool instructions listtools/wikitool instructions list --json
EXIT STATUS
- 0 success
NOTES
- Lists the flat instructions with their descriptions - how the instruction layer is discovered;
searchcoverskb/only. - Never fails: an empty
instructions/prints "No instructions found." Read-only and safe to retry freely.
SEE ALSO
wikitool search- the same question forkb/
docs verify
Check the docs that mirror the code.
SYNOPSIS
wikitool docs verify
PROPERTIES
- effect: read
- idempotent: yes
- atomic: Read-only
- budget: counted
- network: no
EXAMPLES
tools/wikitool docs verify
EXIT STATUS
- 0 success
- 1 A command, contract, or type-form mismatch
- 1 The
<!-- wikitool:commands -->region oftools/CONTRACT.mdis stale - 1 A type-spec's own frontmatter fails its schema
- 1 A shipped
.md/.templatecites an issue number - 1 A reference file's table-of-contents region is missing or stale
- 1 A reference file's relative markdown link does not resolve to an existing file
ON FAILURE
- A command, contract, or type-form mismatch -> Fix the documentation it names, then re-run
- The
<!-- wikitool:commands -->region oftools/CONTRACT.mdis stale -> Rundocs contract --apply, then re-run - A type-spec's own frontmatter fails its schema -> Fix the field, or add a matching line to
types/type-spec.schema.yamlif the field is legitimately new - A shipped
.md/.templatecites an issue number -> Say what was decided instead of pointing at where, or move the pointer behind a<!-- dist:strip-start/end -->block - A reference file's table-of-contents region is missing or stale -> Run
docs toc --apply, then re-run - A reference file's relative markdown link does not resolve to an existing file -> Fix the
../count or the target's name
NEVER
- Never hand-write a table-of-contents region or the commands region - regenerate it.
NOTES
- Checks the docs that mirror the code. The name is about documentation parity, not the
docs/directory - it neither reads nor requires one. - Commands: every command has a
cli_contractrecord and is listed incli_contract.GROUPS, in both directions; every command's non-hidden flags appear in its record's SYNOPSIS and vice versa;tools/CONTRACT.md's generated<!-- wikitool:commands -->region matches whatdocs contractwould write; no command's rendered--help/-htext cites an issue number. - Collections: every directory under
kb/has aCOLLECTION.mdand no directory outside it does; every collection declaresprofile:and arequired_by_stack:that agrees with the stack's own list. - Types: every type the stack lists (currently
sourceandproject) has a type-spec of that name whose schema requires the field the stack list names (raw_files:/state:); every file undertypes/declaringtype: types/type-spec.mdvalidates againsttypes/type-spec.schema.yaml; no pre-migrationtype: entityblocks are left in the contracts. kb/CONVENTIONS.md, if it exists at all, names all three tool-owned section headings; every stage contract is present.- The
.gitignorecanaries clear in both directions: nothing ignored underraw//kb/,incoming/ignored, everything ignored underreports/and the published skill directories. - No
.md/.templatefiledist exportwould ship cites an issue number. A<!-- dist:strip-start/end -->region is exempt: the check reads the export plan's text, from which it is already gone. - Every reference file
docs toccovers carries the current table-of-contents region for its own headings - missing and stale are one check. - Every relative markdown link in one of those reference files resolves to an existing file. A target's
#anchorsuffix is stripped first, and code fences and inline code spans are masked before scanning, so link syntax shown as an example is not mistaken for a real reference. - Read-only.
SEE ALSO
wikitool docs toc- regenerates tables of contentswikitool docs contract- regenerates the commands regionwikitool instructions verify- the same kind of check forinstructions/
docs toc
Create, refresh or remove the generated table-of-contents region.
SYNOPSIS
wikitool docs toc [--apply]
PROPERTIES
- effect: write
- idempotent: yes
- atomic:
--applyrewrites each named file in place, one at a time and idempotently, so a re-run after an interruption converges rather than doubling a region; the dry-run form is read-only - budget: counted
- network: no
EXAMPLES
tools/wikitool docs toctools/wikitool docs toc --apply
EXIT STATUS
- 0 success
- 0 Never fails on content: a file with no
##heading, or one at or under the threshold, is simply left without a region
NEVER
- Never hand-write or hand-edit a table-of-contents region.
NOTES
- Creates, refreshes or removes the generated table-of-contents region on every reference file over 100 lines:
AGENTS.md, every stage contract,kb/CONVENTIONS.md, everykb/*/COLLECTION.md, every flatinstructions/**.mdfile, everytypes/*.mdtype-spec, and everydocs/page - each together with the<name>.templateit ships as, where one exists. - The scope is computed from those categories rather than listed, so a file added later is in scope without a code change.
- Out of scope: every
SKILL.md, and the human docs (README.md,CHANGES.md,EVALS.md,INSTALL.md,tools/README.md). - Dry-run by default (prints which files would change);
--applywrites. Idempotent: a re-run after an interruption converges rather than doubling a region. - If
docs verifystill reports a stale region after--apply, the file's##headings changed in between; run it again.
SEE ALSO
wikitool docs verify- checks every region is current
docs contract
Regenerate tools/CONTRACT.md's <!-- wikitool:commands --> region.
SYNOPSIS
wikitool docs contract [--apply]
PROPERTIES
- effect: write
- idempotent: yes
- atomic: Yes - the whole region is rewritten in one file write
- budget: counted
- network: no
EXAMPLES
tools/wikitool docs contracttools/wikitool docs contract --apply
EXIT STATUS
- 0 success
- 1
tools/CONTRACT.mdis missing
ON FAILURE
tools/CONTRACT.mdis missing -> Not transient - restore the file, which carries hand-written prose around the region this command does not generate, then retry
NEVER
- Never hand-edit the region - change the record in code and re-run this.
NOTES
- Rebuilds the region from every
cli_contractrecord: the index (one line per command,GROUPSorder) followed by each###group's commands as#### <path>man-page-shaped sections. - Dry-run by default (says whether the file would change);
--applywrites. docs verifychecks the result stays current.
SEE ALSO
tools/README.md§ Adding a command - how a command gets its recordwikitool docs verify- checks the region is current
Telemetry
eval sessions
List the sessions that have a trace under reports/telemetry/.
SYNOPSIS
wikitool eval sessions [--json]
PROPERTIES
- effect: read
- idempotent: yes
- atomic: Read-only
- budget: exempt
- network: no
EXAMPLES
tools/wikitool eval sessionstools/wikitool eval sessions --json
EXIT STATUS
- 0 success
NOTES
- Lists the sessions that have a trace under
reports/telemetry/, most recent first. - Never fails; an empty list is a valid answer.
- Read-only and exempt from the Iteration Budget Gate.
SEE ALSO
wikitool eval score- scores one of themEVALS.md- how telemetry and evaluation work
eval score
Score one traced session.
SYNOPSIS
wikitool eval score [--session <id>] [--json] [--markdown out.md] [--save] [--fail-on-error]
PROPERTIES
- effect: read
- idempotent: yes
- atomic: Read-only, apart from the files
--save/--markdownwrite - budget: exempt
- network: no
EXAMPLES
tools/wikitool eval scoretools/wikitool eval score --session wiki-1727330000 --save
EXIT STATUS
- 0 success
- 1 No trace exists for the named session
- 1 Only with
--fail-on-error: the tree has hard errors or an invariant was violated
ON FAILURE
- No trace exists for the named session -> Run
eval sessionsto see which ids exist; check withdoctorwhether telemetry is on - Only with
--fail-on-error: the tree has hard errors or an invariant was violated -> Act on the scorecard; re-run only to re-measure
NOTES
- Scores one traced session: structural state from
lint's own checks (L1) plus trajectory rules over the trace (L2) - was a refused call repeated unchanged, was a gate flag passed without that gate having refused anything, did a publish ofkb/pages go unlogged. - Defaults to the current session;
--session <id>picks another. --savewritesreports/evals/<date>/<session>.{json,md};--markdownwrites the report to the named file.- A session records nothing when telemetry is off -
WIKI_TRACE=0, or a distributed instance with no opt-in (wikitool doctorsays which) - so an absent trace is not necessarily a fault. - Read-only over
kb/, safe to retry, and exempt from the Iteration Budget Gate.
SEE ALSO
wikitool eval sessions- which session ids existEVALS.md- the scoring levels
Distribution and versioning
dist export
Write a contentless, distributable copy of this repo's machinery.
SYNOPSIS
wikitool dist export <target> [--dry-run] [--source-repo U] [--source-commit SHA] [--release-url U] [--update-url U]
PROPERTIES
- effect: write
- idempotent: yes
- atomic: Yes - nothing is written until every file is planned
- budget: counted
- network: no
EXAMPLES
tools/wikitool dist export ../my-wiki --dry-runtools/wikitool dist export ../my-wiki
EXIT STATUS
- 0 success
- 1
<target>exists and is not empty, or is not a directory - 1 The tree has no readable
VERSION - 1 A licence file is missing from the source tree
- 1 The plan would carry this instance's own data (a filled personalization, conventions or page type-spec file)
ON FAILURE
<target>exists and is not empty, or is not a directory -> Point<target>at an empty (or new) directory and retry- The tree has no readable
VERSION-> FixVERSION, then retry - A licence file is missing from the source tree -> Restore it, then retry
- The plan would carry this instance's own data (a filled personalization, conventions or page type-spec file) -> Report it: it is an allowlist bug in
dist export, not something to work around by deleting files from the target
NEVER
- Never merge an export into a non-empty directory by hand.
NOTES
- Writes a contentless, distributable copy of this repo's machinery into an empty or new
<target>directory. - Ships
AGENTS.md/README.md/EVALS.mdwith anydist:strip-start...dist:strip-endmarker region removed,instructions/(minusinstructions/dev/),types/(theroot: kbpage type-specs and their schemas re-keyed as.template, the stack's own verbatim),docs/verbatim,tools/(no venv/caches), the.github/hooks/+.vibe/session-tracing config plus.claude/settings.json,kb/CONTRACT.md(no pages, no areas) andVERSION. - Ships
raw/andincoming/as flat roots, each with a.gitkeepand no subdirectories.incoming/.gitkeepis trackable and survives becoming a git repository, so a plain clone gets the directory without any bootstrap step. - Ships templates, never the filled files:
USER.md.template/SOUL.md.template,kb/CONVENTIONS.md.template, and each collection's contract re-keyed askb/<name>/COLLECTION.md.template. The filledUSER.md/SOUL.md/kb/CONVENTIONS.md/kb/<name>/COLLECTION.md/types/<page-type>.mdbind their instance;find_leaksrefuses a plan carrying one. - Writes a generated
.wikitool-release.jsonstamp: version, export date, origin, and a sha256 per exported file - the base a later upgrade compares against. - The four origin options only fill stamp fields:
exportnever calls git and cannot discover them. - One-way: no command reconstructs a distributed instance into a dev instance - work on the stack in the origin repo, or in a new dev instance exported from it.
--dry-runlists every file it would write, and writes nothing.
SEE ALSO
instructions/setup-instance.md- what comes after the exportwikitool dist upgrade- applies a later export to an existing instancewikitool version show- reads the stamp this writes
dist upgrade
Apply a stack update dist export produced - the write half of version check.
SYNOPSIS
wikitool dist upgrade (<source> | --latest [--expect <version>]) [--dry-run] [--keep-local] [--take-release <path>]... [--prune] [--pre]
PROPERTIES
- effect: write
- idempotent: no
- atomic: Yes for every refusal - nothing is written. Once writing starts it is a plain sequential file copy with no partial-state cleanup: an interruption mid-copy (killed process, disk full) can leave the tree part-old, part-new
- budget: counted
- network: yes
EXAMPLES
tools/wikitool dist upgrade ../chemenu-7.1.0.tar.gz --dry-runtools/wikitool dist upgrade ../chemenu-7.1.0.tar.gztools/wikitool dist upgrade ../chemenu-7.1.0.tar.gz --take-release tools/README.mdtools/wikitool dist upgrade --latest --expect 8.0.0 --dry-run
EXIT STATUS
- 0 success
- 0 The source's version equals the installed one - a no-op success
- 1 Both
<source>and--latest, or neither; or--expectwithout--latest - 1 Local
VERSIONmissing, or no local.wikitool-release.jsonwith afilesblock - 1
.wikitool-kb.jsonis missing - 1 A migration is already outstanding against the installed machinery
- 1 The working tree is dirty
- 1
<source>does not exist, fails its.sha256, or does not unpack to exactly one top-level directory - 1
--latest: the release feed cannot be reached, or answers with something that is not a release - 1
--latest --expect: the feed's latest release is another version than the one expected; nothing was downloaded - 1
--latest: the release publishes no archive or no.sha256under the expected name; nothing was downloaded - 1
--latest: an asset download fails, or the archive fails its.sha256 - 1
--latest: the downloaded archive'sVERSIONis not the version the feed announced - 1 The source carries no
VERSION,.wikitool-release.jsonorfilesblock - 1 The source's version is older than the installed one, or a pre-release without
--pre - 1 A
--take-releasepath this run does not classify as locally changed - the one refusal a--dry-runalso raises - 1 Locally changed files that neither
--keep-localnor a--take-releaseanswers for; nothing was written
ON FAILURE
- Both
<source>and--latest, or neither; or--expectwithout--latest-> Not transient - name exactly one source, and pass--expectonly with--latest - Local
VERSIONmissing, or no local.wikitool-release.jsonwith afilesblock -> Not transient - fix the named precondition and retry. A checkout with shared git history takes stack updates withwikitool upstream mergeinstead .wikitool-kb.jsonis missing -> Runwikitool migrate baseline <version>, then retry- A migration is already outstanding against the installed machinery -> Finish it first -
wikitool migrate statusnames it - then retry - The working tree is dirty -> Commit or stash first, then retry
<source>does not exist, fails its.sha256, or does not unpack to exactly one top-level directory -> Fix the path or re-download the release archive, then retry--latest: the release feed cannot be reached, or answers with something that is not a release -> Transient - retry once; then report the URL from the message, or take the release page's archive by hand and pass it as<source>--latest --expect: the feed's latest release is another version than the one expected; nothing was downloaded -> Not transient - readwikitool version notesfor the version the message names, then either expect that one or stop--latest: the release publishes no archive or no.sha256under the expected name; nothing was downloaded -> Not transient - the message lists the assets present and the release page; report it there rather than upgrading without the checksum--latest: an asset download fails, or the archive fails its.sha256-> Retry once; if it fails again, report the exact message - do not fall back to an unchecked archive--latest: the downloaded archive'sVERSIONis not the version the feed announced -> Not transient - an inconsistent release; report it against the release page- The source carries no
VERSION,.wikitool-release.jsonorfilesblock -> Point<source>at a distribution export, then retry - The source's version is older than the installed one, or a pre-release without
--pre-> Not transient - choose another source, or pass--prefor a pre-release - A
--take-releasepath this run does not classify as locally changed - the one refusal a--dry-runalso raises -> Correct it against the locally-changed list the refusal prints, then retry - Locally changed files that neither
--keep-localnor a--take-releaseanswers for; nothing was written -> Choose one of the three answers the refusal names, re-run lines filled in:--take-release <path>to write the release's version over it (ends the divergence),--keep-localto leave them untouched (reported again on every later run until they stop diverging), or reconcile by hand and retry
NEVER
- Never treat any of the three answers to locally changed files as the default.
NOTES
- Exactly one of
<source>and--latestnames the release.<source>is an already-fetched export directory or.tar.gzrelease archive and touches no network: the archive is verified against a sibling.sha256if one is present (a missing one is a WARN, not a block) and must unpack to exactly one top-level directory - the shape.gitea/workflows/release.ymlpacks. --latestasks the release feed (update_urlof the local stamp, overridable with$WIKITOOL_UPDATE_URL;$WIKITOOL_UPDATE_TOKENis sent along) which release is latest, checks that version ---expect, a downgrade, a pre-release without--pre, already installed - before any download, then downloads the archive and its.sha256into a scratch directory removed on every exit. The checksum is mandatory here (a release without one, or an archive that fails it, is an error, not a WARN) and the archive's ownVERSIONmust equal the feed's version. The asset URLs are the feed's ownbrowser_download_urlvalues, and the token reaches an asset download only if it is on the feed's origin. The checksum protects against transfer errors, not against a feed that is itself compromised - authenticity is the trust in the feed's host.--expect <version>(only with--latest) pins the release the feed may announce: a different latest version is refused before anything is downloaded. Pass the versionwikitool version noteswas read for, in the dry run and in the real run alike.- The write set is exactly the new
.wikitool-release.json'sfilesblock, minus what an export re-seeds from a blank template every time (kb/log.md,raw/.gitkeep) or seeds once and the instance owns from then on (.wikitool-kb.json,CHANGES.md), plus the stamp itself, always rewritten. - Every candidate path is classified against the local
.wikitool-release.json's recorded digest: unchanged is overwritten silently, absent from the old stamp is created, and locally modified or locally deleted is never silently overwritten. - Locally changed files abort the run with the full list; the abort text names the three answers with the command line filled in, and none of them is the default.
--keep-localproceeds and leaves every locally changed file untouched. The new stamp is still written whole, recording the release's digest for files that were not written, so a skipped file keeps diverging and is reported again on every later run.--take-release <path>(repeatable) writes the release's version over the named path, discarding the local change, and re-creates it if it was locally deleted. The path then matches the stamp and stops being reported.- The two are decided per path and compose on one call: without
--keep-local, a locally changed path that no--take-releasenames still aborts the run. - A path in the old stamp but not the new one is reported as no longer part of the release and left alone, unless
--pruneis passed, which removes it only if it is still unchanged since installation. - Reports the migration chain the new machinery would owe, but never runs any of it - there is no
migrate run. - Reports, but does not block on, a crossed compatibility boundary.
- The local preconditions -
VERSION, the release stamp,.wikitool-kb.json, no outstanding migration, a clean working tree - are checked before the source is read. Not being a git repository at all is a WARN, not a refusal. - Never touches git - no commit, no push.
--dry-runclassifies and reports without writing; a pre-release (-beta.N) source needs--pre. With--latestit still downloads and verifies the archive - that is the only way to classify - and removes it again.- An interrupted write is not resumed automatically: compare the tree against the printed classification and finish or revert by hand.
- The closing report names
instructions/upgrade-instance.md, which carries the order for everything after the swap and resumes atinstructions sync.
SEE ALSO
wikitool version check- finds out whether an update existswikitool version notes- the notes of the release--expectshould nameinstructions/upgrade-instance.md- the order after the swapINSTALL.md§ "Version und Updates" - which release, whether to take it, where the tarball comes fromwikitool upstream merge- the update path for a checkout with shared git historywikitool migrate status- the migrations the report names
version show
Print this instance's stack version and where it came from.
SYNOPSIS
wikitool version show [--json]
PROPERTIES
- effect: read
- idempotent: yes
- atomic: Read-only
- budget: exempt
- network: no
EXAMPLES
tools/wikitool version showtools/wikitool version show --json
EXIT STATUS
- 0 success
- 1
VERSIONis missing or unparseable
ON FAILURE
VERSIONis missing or unparseable -> FixVERSIONand retry
NOTES
- Prints
VERSIONand where this tree came from: "development tree" without a release stamp, otherwise the export date, source commit and repository from.wikitool-release.json, plus the release page when the stamp records one. --jsonprints the version, its compatibility key, the stamp and the update URL.- Bare
wikitool versionis an alias for this. - Read-only and offline; exempt from the Iteration Budget Gate.
SEE ALSO
wikitool version check- asks whether a newer stack existswikitool version notes- prints a version's release notes
version check
Ask the origin's release feed whether a newer stack exists.
SYNOPSIS
wikitool version check [--url U] [--timeout S] [--json]
PROPERTIES
- effect: read
- idempotent: yes
- atomic: Read-only, no local writes
- budget: exempt
- network: yes
EXAMPLES
tools/wikitool version checktools/wikitool version check --json
EXIT STATUS
- 0 success
- 1
VERSIONis missing or unparseable - 1 The feed could not be reached, answered non-JSON, or carried no
tag_name
ON FAILURE
VERSIONis missing or unparseable -> FixVERSIONand retry- The feed could not be reached, answered non-JSON, or carried no
tag_name-> A network failure is transient - retry once, then report it. HTTP 401/403 names$WIKITOOL_UPDATE_TOKEN; 404 means no release exists yet or the URL points at the wrong repo
NEVER
- Never report "up to date" for a check that failed - an unreachable feed is not that answer.
NOTES
- Asks the release feed for its latest release and compares it with
VERSION:stateiscurrent,update,migration(the step crosses a compatibility boundary) orahead. - The only command whose whole job is the network call -
version notesreaches the same feed too, but only as a fallback on a distributed instance, anddist upgrade --latestasks it which release to download. - Never reached implicitly from another command (
dist upgradeasks only when passed--latest), needs no key, and times out after--timeoutseconds (default 10). - The feed is
--url, else$WIKITOOL_UPDATE_URL, else the release stamp's, else the built-in origin.$WIKITOOL_UPDATE_TOKENis only needed if that feed is not readable anonymously. - For
updateormigrationit prints that applying the release is a separate, manual step (INSTALL.md§ "Eine Instanz aktualisieren"). - Read-only; exempt from the Iteration Budget Gate.
SEE ALSO
wikitool dist upgrade- applies the update this reportswikitool version notes- prints the release notesINSTALL.md§ "Version und Updates" - what the operator decides before an update
version notes
Print one version's release notes.
SYNOPSIS
wikitool version notes [--version X.Y.Z] [--offline] [--url U] [--timeout S]
PROPERTIES
- effect: read
- idempotent: yes
- atomic: Read-only
- budget: exempt
- network: yes
EXAMPLES
tools/wikitool version notestools/wikitool version notes --version 7.0.0tools/wikitool version notes --offline
EXIT STATUS
- 0 success
- 1
--versionis unparseable,VERSIONis unreadable when--versionis omitted, orCHANGES.mdis missing - 1 No entry for the requested version in a tree with no release stamp (a dev checkout)
- 1 No entry for the requested version, and
--offlinewas passed - 1 No entry for the requested version, and the feed could not be reached or returned a release with an empty
body
ON FAILURE
--versionis unparseable,VERSIONis unreadable when--versionis omitted, orCHANGES.mdis missing -> Fix the named argument or file, then retry- No entry for the requested version in a tree with no release stamp (a dev checkout) -> Write the entry, or run
version bump - No entry for the requested version, and
--offlinewas passed -> Read the release page the error names - No entry for the requested version, and the feed could not be reached or returned a release with an empty
body-> A feed failure is transient - retry once, then read the release page the error names
NOTES
- Prints the
CHANGES.mdentry for--version(default: this tree'sVERSION). - With no such entry, in a tree with a release stamp (a
dist exporttree) and without--offline, it prints the release feed's latest release notes instead. A tree without a stamp (a dev checkout) never asks the feed. - Only the feed's latest release can be asked for. When that is a different version than requested, stderr names it and the notes are printed anyway - the expected case before an upgrade, where
VERSIONstill names the release being left. - stdout carries nothing but the notes; the line naming the feed being asked and the one naming the release that answered go to stderr.
release.ymlredirects stdout into the file it posts as the release body. --offlinenever asks the feed and fails with the stamp'srelease_urlinstead.- Every failure names the stamp's
release_urlwhere it has one, so a run that cannot read the notes is still told where they are. - Read-only, safe to retry, and exempt from the Iteration Budget Gate.
SEE ALSO
wikitool version check- asks the same feed whether a newer stack existswikitool version bump- writes the entry this prints
version bump
Raise or continue the one running candidate between two releases.
SYNOPSIS
wikitool version bump --major|--minor|--patch --title "<...>" [--impact high|medium|low] [--breaking "<what breaks>"] [--no-migration "<reason>"] [--migration-required] [--dry-run]
PROPERTIES
- effect: write
- idempotent: no
- atomic: No -
VERSIONthenCHANGES.md - budget: counted
- network: no
EXAMPLES
tools/wikitool version bump --patch --title "Fix the lint report path" --impact lowtools/wikitool version bump --minor --title "New command: wikitool review" --dry-runtools/wikitool version bump --major --title "Rename --gate-file to --remotes-file" --breaking "--gate-file is gone; scripts must pass --remotes-file" --no-migration "no content changes"
EXIT STATUS
- 0 success
- 1 Not exactly one of
--major/--minor/--patch, an empty--title, or an unknown--impact - 1
VERSIONorCHANGES.mdis missing, orVERSIONand the changelog's newest entry name different versions - 1 An escalation to a boundary crossing without
--breaking, or with neither a migration document targeting the new base nor--no-migration - 1
--breakingor--no-migrationon a bump that crosses nothing - 1
--migration-requiredcombined with--no-migration, on a bump with no running candidate, with no--no-migrationline to retract, or without a migration document already targeting the new base
ON FAILURE
- Not exactly one of
--major/--minor/--patch, an empty--title, or an unknown--impact-> Nothing was written - fix the argument and retry once VERSIONorCHANGES.mdis missing, orVERSIONand the changelog's newest entry name different versions -> Nothing was written - fix whichever is wrong, then retry- An escalation to a boundary crossing without
--breaking, or with neither a migration document targeting the new base nor--no-migration-> Nothing was written - add what the error asks for, or, if nothing actually breaks, choose a smaller part --breakingor--no-migrationon a bump that crosses nothing -> Nothing was written - drop the flag and retry--migration-requiredcombined with--no-migration, on a bump with no running candidate, with no--no-migrationline to retract, or without a migration document already targeting the new base -> Nothing was written - write the migration document or fix the combination, then retry
NEVER
- Never re-run after an uncertain outcome without first reading
VERSIONand the top ofCHANGES.md- a second run escalates or continues the candidate again. - Never hand-edit
VERSIONor the machine-written parts of the entry (heading, bump list, breaking and migration lines);--migration-requiredis the only way to take the migration line back.
NOTES
VERSIONgets a-beta.Nsuffix: one running candidate between two releases, never a fresh number per bump.--major/--minor/--patchis max-wins escalation against the last release (patch < minor < major): a--patchon a MINOR candidate only advancesN, and escalation never steps back down.- The first bump of a candidate opens its
CHANGES.mdentry - heading, date, author, and a machine-managed<!-- wikitool:bumps -->list of every--titlecollected so far, graded by--impact(defaultmedium). Every later bump of the same candidate updates that entry in place: one entry per candidate, not one per bump. - The bump list renders grouped under
**High/Medium/Low impact**headings, empty groups omitted - except while every bump ismedium, where it stays one flat list.version regradecorrects a grade after the fact. - Writes the heading, the bump list and the breaking/migration lines; the entry's prose is left to the author.
- Compatibility follows the leftmost non-zero component of the candidate's base, which for this stack (at
1.0.0and up) means MAJOR: PATCH is a fix, MINOR a compatible capability, MAJOR a version that is not a drop-in replacement - any hand-work on update, or a downgrade that no longer works. Whether content must be migrated is a second, independent question. - The bump that first escalates a candidate past the boundary requires
--breaking "<what stops working>"and, on top of it, a migration document targeting the candidate's base or--no-migration "<reason>". Both are anchored just above the bump list, persist over later bumps of the same candidate without being repeated, and are refused on a bump that crosses nothing at all. - On a later crossing of the same candidate, a further
--breakingjoins the reasons already recorded (flat on the marker line while there is one, bullets under a bare marker from the second on; repeating a reason verbatim is a no-op), while a further--no-migrationreplaces the single migration line. --migration-requiredretracts the running candidate's--no-migrationline; it needs a migration document already targeting the new base. Nothing retracts a recorded--breakingreason.- Enforces that a crossing documents itself, never that the part was chosen correctly.
- Not idempotent: every successful run escalates or continues the candidate again.
--dry-runreports the step without writing.
SEE ALSO
wikitool version regrade- corrects an--impactgradewikitool version release- fixes the candidate into a releaseinstructions/migrate-corpus.md- how a migration document is written
version regrade
List the running candidate's bump titles with their impact grade, or change one or more of them.
SYNOPSIS
wikitool version regrade [INDICES...] [--impact high|medium|low]
PROPERTIES
- effect: write
- idempotent: no
- atomic: No -
CHANGES.mdonly, and only when indices are given - budget: exempt_without_args
- network: no
EXAMPLES
tools/wikitool version regradetools/wikitool version regrade 3 7 --impact high
EXIT STATUS
- 0 success
- 1
VERSIONorCHANGES.mdis missing,VERSIONand the changelog's newest entry name different versions, or the topmost entry has no bump list - 1 An index outside the rendered list's range, indices given without
--impact, or an unknown--impact
ON FAILURE
VERSIONorCHANGES.mdis missing,VERSIONand the changelog's newest entry name different versions, or the topmost entry has no bump list -> Nothing was written - fix whichever is wrong, then retry- An index outside the rendered list's range, indices given without
--impact, or an unknown--impact-> Nothing was written - list again with the bare command, then retry once with corrected arguments
NEVER
- Never re-run the same indices after a write without listing again first - the positions may have moved.
NOTES
- Without arguments: lists the running candidate's bump titles with their grade, numbered by 1-based position in the rendered list (High before Medium before Low, chronological within a grade). Read-only and exempt from the Iteration Budget Gate.
- With indices and
--impact: sets the grade of every named position in one call, all resolved against a single read of the current list -version regrade 3 7 --impact highgrades what is at 3 and 7 now, not 7 after 3 has moved. WritesCHANGES.mdand is counted by the Iteration Budget Gate. - The correction path for an
--impactgrade judged at bump time. - Touches only the topmost entry's bump list - never
VERSION, never any other part ofCHANGES.md. - A write is not idempotent against a changed list: after a first success, the same indices may name different bumps.
SEE ALSO
wikitool version bump- sets the grade in the first placewikitool version release- refuses without a summary above this list
version release
Fix the running candidate: strip VERSION's -beta.N suffix and close its CHANGES.md entry.
SYNOPSIS
wikitool version release [--title "<...>"] [--dry-run]
PROPERTIES
- effect: write
- idempotent: no
- atomic: No -
VERSIONthenCHANGES.md - budget: counted
- network: no
EXAMPLES
tools/wikitool version release --dry-runtools/wikitool version release --title "Command records rewritten for agents"
EXIT STATUS
- 0 success
- 1
VERSIONis already a release - there is no running candidate to fix - 1
VERSIONorCHANGES.mdis missing, orVERSIONand the changelog's newest entry name different versions - 1 Two or more bumps and no summary paragraph above the changesets
ON FAILURE
VERSIONis already a release - there is no running candidate to fix -> After an uncertain run this means it already ran; otherwise there is nothing to releaseVERSIONorCHANGES.mdis missing, orVERSIONand the changelog's newest entry name different versions -> Fix whichever is wrong, then retry- Two or more bumps and no summary paragraph above the changesets -> Write a short summary paragraph right below the bump list, then retry
NEVER
- Never retry after an uncertain outcome without reading
VERSIONfirst - a release-shapedVERSIONmeans it already ran.
NOTES
- Strips
VERSION's-beta.Nsuffix - the candidate's base becomes the release - and closes the candidate'sCHANGES.mdentry. - Without
--titlethe heading keeps whichever bump last set it;--titlereplaces it - the normal case for a candidate that collected several bumps, whose entry wants a summarising heading rather than the most recent one. - Leaves the entry's machine-managed bump list untouched, as the record of what happened.
- From two bumps on, requires a summary paragraph (at least 200 non-whitespace characters) between the bump list and the first
### <bump title>changeset heading. A candidate with exactly one bump is exempt, since there its own changeset already is the summary.--dry-runruns this check too. - Commits nothing and pushes nothing. The following
publishmovesVERSIONontomain, whichrelease.ymlreacts to. - Not idempotent: a second run fails once the suffix is gone.
SEE ALSO
wikitool version bump- opens and continues the candidatewikitool version regrade- shows the bump list the summary sits underwikitool publish- moves the releasedVERSIONontomain
Content migrations
migrate list
List every migration document under instructions/migrations/.
SYNOPSIS
wikitool migrate list [--json]
PROPERTIES
- effect: read
- idempotent: yes
- atomic: Read-only
- budget: exempt
- network: no
EXAMPLES
tools/wikitool migrate list
EXIT STATUS
- 0 success
NOTES
- Lists every migration document under
instructions/migrations/, oldest target first, with its kind and obligation. - Never fails. Read-only and exempt from the Iteration Budget Gate.
SEE ALSO
wikitool migrate status- which of them this instance still owesinstructions/migrate-corpus.md- how a migration is run
migrate status
Show the migrations this instance still owes, in the order they must run.
SYNOPSIS
wikitool migrate status [--json]
PROPERTIES
- effect: read
- idempotent: yes
- atomic: Read-only
- budget: exempt
- network: no
EXAMPLES
tools/wikitool migrate statustools/wikitool migrate status --json
EXIT STATUS
- 0 success
- 1
.wikitool-kb.jsonis missing - the content version is undeclared - 1
VERSIONis unreadable
ON FAILURE
.wikitool-kb.jsonis missing - the content version is undeclared -> Runmigrate baseline <version>once, then retryVERSIONis unreadable -> FixVERSION, then retry
NOTES
- Shows every required migration whose
migrates_tolies in(kb_version, VERSION], in the order it must run. offeredmigrations are listed separately above the chain: they never block, never count as owed, and are bounded by the applied ledger rather than bykb_version- taking one does not move the version.- With a release stamp present, also reports which shipped files this instance has since edited (from the per-file sha256 in
.wikitool-release.json) - which says whether an offer may be copied over or has to be reconciled by hand. Without a stamp that question is reported as unanswerable rather than answered. - Exits 1 only when the content version is undeclared (
.wikitool-kb.jsonmissing) orVERSIONis unreadable; it never guesses the content's shape. - Read-only, safe to retry freely, and exempt from the Iteration Budget Gate.
SEE ALSO
wikitool migrate done- records one as appliedinstructions/migrate-corpus.md- how a migration is runinstructions/upgrade-instance.md- where an upgrade checks this
migrate verify
Compare kb/ against a git revision on the invariants a content migration must not change.
SYNOPSIS
wikitool migrate verify --from <rev> [--path P ...] [--expect-body-change] [--json] [--fail-on-error]
PROPERTIES
- effect: read
- idempotent: yes
- atomic: Read-only
- budget: exempt
- network: no
EXAMPLES
tools/wikitool migrate verify --from HEADtools/wikitool migrate verify --from HEAD --path kb/concepts --expect-body-change
EXIT STATUS
- 0 success
- 1 Only with
--fail-on-error: an invariant changed - 1
--fromis not a revision in this repository
ON FAILURE
- Only with
--fail-on-error: an invariant changed -> Act on the findings - exit 1 here means "act on the findings", not "the tool is broken". A finding names a page and what changed on it; it is never fixed by re-running --fromis not a revision in this repository -> Fix the revision and retry
NEVER
- Never re-run to make a finding go away - fix the page it names.
NOTES
- Compares
kb/against the revision--fromon the invariants a content migration must not change: wikilink and citation counts (not sets), footnote definitions, H1, structural frontmatter, and the count of generated-region marker pairs. - Pages are matched by title, not path, so a page
move(ormove --reconcile) relocated compares as itself - reported separately asmoved- rather than as a removed-and-added pair. - Reports added and removed pages without failing on them.
--expect-body-changeadditionally flags a page whose body did not change at all.- Not migration-specific: worth running after any bulk rewrite.
- Exits 0 whatever it finds unless
--fail-on-erroris passed. - Read-only and exempt from the Iteration Budget Gate.
SEE ALSO
instructions/migrate-corpus.md- where a migration runs thiswikitool lint- the single-revision checks
migrate done
Record one migration as applied, advancing kb_version in .wikitool-kb.json.
SYNOPSIS
wikitool migrate done <version> [--pages N] [--dry-run]
PROPERTIES
- effect: write
- idempotent: no
- atomic: Yes - single file write
- budget: counted
- network: no
EXAMPLES
tools/wikitool migrate done 7.0.0 --pages 42 --dry-runtools/wikitool migrate done 7.0.0 --pages 42
EXIT STATUS
- 0 success
- 1 Unknown version, no
.wikitool-kb.json, or nothing outstanding - 1 A required version that is not the next link in the chain
ON FAILURE
- Unknown version, no
.wikitool-kb.json, or nothing outstanding -> Checkmigrate status, fix the argument, then retry once - A required version that is not the next link in the chain -> Run
migrate statusand apply the migrations in the order it prints
NEVER
- Never force the order of required migrations.
- Never hand-edit
.wikitool-kb.jsonto advance the version.
NOTES
- Records one migration as applied, advancing
kb_versionin.wikitool-kb.jsonto its target. - Refuses any required version that is not the next link in the chain.
- An
offeredmigration is recorded in the applied ledger without movingkb_versionand with no ordering rule applied. Re-recording one already in the ledger is a no-op, not an error - idempotent and safe to repeat. - Not idempotent for a required migration: it advances the chain.
--dry-runreports without writing.
SEE ALSO
wikitool migrate status- the order to apply them ininstructions/migrate-corpus.md- the migration procedure
migrate baseline
Declare kb_version once, for an instance predating .wikitool-kb.json.
SYNOPSIS
wikitool migrate baseline <version> [--force]
PROPERTIES
- effect: write
- idempotent: yes
- atomic: Yes - single file write
- budget: counted
- network: no
EXAMPLES
tools/wikitool migrate baseline 6.2.0
EXIT STATUS
- 0 success
- 1 Unparseable version
- 1 A declaration already exists and
--forcewas not passed
ON FAILURE
- Unparseable version -> Fix the version and retry
- A declaration already exists and
--forcewas not passed -> It is almost alwaysmigrate donethat was wanted
NEVER
- Never use
--forceto advance the version past a migration - that ismigrate done.
NOTES
- Declares
kb_versiononce, for an instance predating.wikitool-kb.json. - Refuses to overwrite an existing declaration without
--force. Advancing the version after a migration ismigrate done, which checks the chain; this command does not. - Safe to re-run with the same version.
SEE ALSO
wikitool migrate done- advances the version after a migrationwikitool migrate status- what is owed from the declared version
Private instances
upstream merge
Take a stack update into a private instance's branch, machinery only.
SYNOPSIS
wikitool upstream merge [--remote upstream] [--branch main] [--no-fetch]
PROPERTIES
- effect: write
- idempotent: no
- atomic: No - can leave an open, uncommitted merge behind on refusal after fetching
- budget: counted
- network: yes
EXAMPLES
tools/wikitool upstream mergetools/wikitool upstream merge --remote upstream --branch main --no-fetch
EXIT STATUS
- 0 success
- 1 Dirty working tree, or a merge already in progress
- 1 The remote does not resolve, the fetch failed, or
HEADdoes not resolve - 1 git refused to open the merge at all (unrelated histories); nothing was touched
- 1 A real conflict remains in
tools//types//instructions/after the content stages and stack-owned paths were restored; the merge is left open - 1 A git step failed inside the open merge (
git checkout MERGE_HEAD -- <path>orgit commit --no-edit) - 1 The postcheck after the commit found a leak; the merge commit already exists
ON FAILURE
- Dirty working tree, or a merge already in progress -> Fix the named precondition and retry once
- The remote does not resolve, the fetch failed, or
HEADdoes not resolve -> Fix--remote/--branchor the repository state, then retry once - git refused to open the merge at all (unrelated histories); nothing was touched -> Do not retry unchanged - report it to the user
- A real conflict remains in
tools//types//instructions/after the content stages and stack-owned paths were restored; the merge is left open -> Do not retry, do not force - resolve the named paths by hand (take the upstream side, or re-file the local change as an issue against the public repo perinstructions/private-instance.md) and eithergit commit --no-edityourself orgit merge --abort - A git step failed inside the open merge (
git checkout MERGE_HEAD -- <path>orgit commit --no-edit) -> Do not retry unchanged - inspect the open merge by hand - The postcheck after the commit found a leak; the merge commit already exists -> It is not rolled back automatically - inspect it by hand; this is a bug report, not a retry
NEVER
- Never retry a failed merge unchanged, and never force.
NOTES
- Refuses on a dirty working tree, a merge already in progress, or a remote that does not resolve. WARNs (does not block) when
.wikitool-remotes.jsonis absent, pointing at the setup step that arms it. - Fetches
<remote>/<branch>(unless--no-fetch) and reports "already up to date" if nothing new exists. - Otherwise opens
git merge --no-commit --no-ff <remote>/<branch>, and stops, untouched, if git refused to open a merge at all (unrelated histories). - Forces every content stage (
kb/,raw/,work/,reports/) back to the local side by removing only the paths tracked in either tree and checkingHEAD's back out - never the stage directory wholesale, so untracked and ignored local data under a stage (telemetry traces, saved eval and lint reports) is never deleted. - Then restores from the upstream side exactly the machinery paths -
<stage>/CONTRACT.mdand anything ending.templateunder a content stage - including a deletion, if the upstream removed one. - A real conflict left in
tools/,types/orinstructions/after that leaves the merge open, uncommitted, and exits 1 rather than guessing. - Commits with
git commit --no-edit, then re-checks the resulting range with theupstream verifycheck; a finding there is a loud error, and the merge commit is not rolled back. - Never pushes.
- Not idempotent, and not safe to retry unchanged.
SEE ALSO
instructions/private-instance.md§ "Taking a stack update" - the procedure this implementswikitool upstream verify- the same check on any revision rangewikitool publish- pushes the merge afterwards
upstream verify
Compare two revisions: did anything under a content stage change except through a stack-owned path?
SYNOPSIS
wikitool upstream verify --since <rev> [--until HEAD]
PROPERTIES
- effect: read
- idempotent: yes
- atomic: Read-only
- budget: exempt
- network: no
EXAMPLES
tools/wikitool upstream verify --since HEAD~1tools/wikitool upstream verify --since v7.0.0 --until HEAD
EXIT STATUS
- 0 success
- 1 A leak: content changed under a content stage through a path that is not stack-owned
- 1
--since/--untilis not a revision in this repository
ON FAILURE
- A leak: content changed under a content stage through a path that is not stack-owned -> A finding is not fixed by re-running - it names the paths that leaked
--since/--untilis not a revision in this repository -> Fix the revision argument and retry
NEVER
- Never re-run to make a leak finding go away.
NOTES
- Compares
--sincewith--until(defaultHEAD): did anything under a content stage change except through a stack-owned path? - The same check
upstream mergeruns after its commit, so a hand-resolved merge conflict, or adist upgrade, can be verified the same way. - Exits 1 with the offending paths if anything leaked; otherwise reports which stack-owned paths legitimately moved.
- Read-only and exempt from the Iteration Budget Gate.
SEE ALSO
wikitool upstream merge- runs this check after its commitinstructions/private-instance.md- the private-instance workflow
Instance health
doctor
Check that this instance is correctly configured.
SYNOPSIS
wikitool doctor [--json]
PROPERTIES
- effect: read
- idempotent: yes
- atomic: Read-only
- budget: exempt
- network: yes
EXAMPLES
tools/wikitool doctortools/wikitool doctor --json
EXIT STATUS
- 0 success
- 1 At least one check reported
FAIL(aWARN, e.g. no remote or noWIKITOOL_SESSION_ID, does not exit 1)
ON FAILURE
- At least one check reported
FAIL(aWARN, e.g. no remote or noWIKITOOL_SESSION_ID, does not exit 1) -> Each finding names its own fix command; re-run after applying it
NOTES
- Checks dependencies (Python, ripgrep), author resolution, stack version, git identity/branch/remote, published skills, the kb/raw/reports/work/instructions structure, and generated files.
- Tool paths (
tool-paths):.wikitool-tools.jsonwritten by a preflight that finished, every tooltools/prerequisites.txtnames for this platform recorded, and every recorded path still there - aFAILotherwise, fixed by runningtools/preflight.shagain (PowerShell 7:tools/preflight.ps1). - Install folder (
install-dir): on Windows with long paths off, aFAILwhen the folder holdingtools/is longer thantools/prerequisites.txtallows (95 characters) - the limit the preflight enforces before it sets anything up. - PowerShell (
execution-policy,script-marks; Windows only,OKelsewhere): aFAILwhen the effective execution policy isRestrictedorAllSigned- the line then says whether a group policy sets it, which only whoever administers the computer can change - and aFAILwhen a script undertools/carries a Mark of the Web from the internet zone, which a browser download unpacked in Explorer leaves behind andInvoke-WebRequestplustardo not.tools/preflight.ps1checks the same two things before it stops. - Personalization:
USER.md/SOUL.mdpresent and filled - a file still carrying the template's sentinel is aFAIL. - KB conventions:
kb/CONVENTIONS.mdpresent, unsentinelled, and naming all three tool-owned section headings - aFAILon any of the three. - Environment note:
ENVIRONMENT.mdis optional, so absent isOK; a still-templated one is aWARN. - MCP
submittool: whether.wikitool-upload.jsonis present, absent or malformed, its limits, and how many submissions wait inmcp-upload/. Absent isOKand means the write path does not exist at all; malformed is aFAIL. - Task tracker:
.wikitool-tasks.jsonpresent, absent or malformed - absent isOK(no tracker configured), malformed is aFAIL. WhenWIKITOOL_TASKS_CONFIGis set, that file is read instead and the finding names it; a set variable that names no file is aFAIL, neverOK. - For a configured
superproductivityprovider, the configuredaccesspath's own state:access: "api"reports whether its local REST API answersGET /healthwith a ready renderer right now,access: "snapshot"whether a backup file is ready. The other access path is never attempted, and neither state is ever aFAIL. - For a configured
caldavprovider, whether the server is reachable and Basic auth succeeds - never aFAIL; only a broken config block is. - Session id source:
OKforWIKITOOL_SESSION_IDor a registered harness variable,WARNonly for the bare parent-pid fallback. - Telemetry: on or off and why - installation-form default,
.wikitool-telemetry.json, orWIKI_TRACE- and the current session's count and byte total against both caps; never aFAIL. - Exits 1 only on a
FAIL; a missing remote, session id orVERSIONis aWARN, not a fault. - Read-only and exempt from the Iteration Budget Gate.
SEE ALSO
instructions/setup-instance.md- the setup steps most findings point back toinstructions/preflight.md- whattool-pathsandinstall-dirpoint back toINSTALL.md§ "Konfiguration" - the per-checkout configuration filesEVALS.md- telemetry state and capsinstructions/session-setup.md- settingWIKITOOL_SESSION_ID
Design notes
- All commands operate on the real repo, so they can be run from any working
directory. The root is resolved by precedence - an explicit argument, then
$CHEMENU_ROOT, then a walk up from the package's own location - and the walk-up is the default, sotools/wikitoolwith no variable set behaves exactly as it always has. Nothing under the root is bound at import time:KB_DIR,RAW_DIRand the rest follow whateverROOTcurrently is, which is what makes the half-repointed state (a movedROOTwith a staleKB_DIR) unconstructible rather than merely discouraged. - The library boundary.
chemenu.api.Corpusis the in-process entry point: it takes a corpus root, returns the same structures the--jsonforms print, and raisesChemenuErrorwhere the CLI printsERRORand exits 1. It is read-only structurally - nothing underchemenu.commandsis imported from it, sonew,publishand the rest are not reachable, rather than filtered. The cores it calls (search/service.py,lint_core.py,types_core.py) import notyperand norich; the modules undercommands/are the terminal adapters over them. A second consumer is therefore a second adapter, not a second implementation. - The MCP server (
chemenu/mcp/) is that second adapter:search,types,describe_type,lintandstatusoverchemenu.api, onstdioorstreamable-http. Its dependency is optional and lives inrequirements-mcp.txt, so a CLI-only instance does not install it. Five of its tools are structurally read-only - nothing undercommands/is importable from the server, sonew/touch/xref/cite/publish/migrateare unreachable rather than filtered - and every response carries the commit it was computed from. The one exception issubmit(opt-in via.wikitool-upload.json- absent means the tool is not registered at all): the server may write, but by a positive list rather than an absence -chemenu.upload._write_atomic_withinresolves every target and refuses anything outsidemcp-upload/, the one directory the process may touch. The reviewer commands that move a submission out of that quarantine (upload accept/upload reject) keep the original absence property: they live undercommands/, not reachable from the server, same as every other write command. Running the server, and keeping its checkout current, is instructions/mcp-read-server.md; reviewing a submission is instructions/ingest-queue.md. Authentication and rate limiting are middleware in front of the process, not code here; the Iteration Budget Gate is deliberately not applied to any of the six tools, because it bounds an agent session rather than a user. new/xref/log appendonly produce structurally-correct frontmatter and body skeletons/edits - the prose (Description, Summary, judgment calls about relationships) is still written by the LLM afterwards.lintonly reports what's mechanically verifiable. Contradictions, staleness judgment, and "what's worth writing next" remain the LLM's job;lintproduces a markdown skeleton with a "Semantic Review" section for that.- Iteration Budget Gate / Loop-Breaker (see the root
AGENTS.md"Gates" section): every invocation is recorded and checked inmain()(cli.py) before Typer dispatches to any subcommand, so it applies uniformly without each command needing its own opt-in. State lives in the gitignoredtools/.wikitool_session/budget.json, keyed bychemenu.session's fallback chain (WIKITOOL_SESSION_ID, else a registered harness session variable, else the caller's parent process id), so a new terminal/session starts with a clean budget - and a bucket whose recorded origin no longer matches the current one starts a fresh count rather than inheriting a stranger's. Default ceiling: 60 calls/session, or 3 identical calls in a row (whichever trips first). A call that left through_util.fail()- a rejected argument, or a read-only check reporting findings - is refunded: it declined instead of acting, and the contract's own answer to a rejected argument is "retry once", which would otherwise cost two slots for one operation. The call stays in the loop-breaker's history.budget statusis exempt so the situation stays reportable after the gate trips;budget resetis not, and additionally requires--yes. Bypass only with--override-budget, and only after explicit human approval - never on the agent's own initiative.
Tests
cd tools
.venv/bin/pip install pytest pytest-cov # one time; pytest-cov is optional
.venv/bin/python -m pytest -q # add --cov for a coverage report
Neither is in requirements.txt, and pytest-cov is CI-only by intent - see
README.md and EVALS.md.
Maintenance schedule
Run by the LLM through the skills, on this cadence:
| Task | Frequency | Command |
|---|---|---|
| Log append | Every operation | log append --op <type> --title "..." |
| Index rebuild | Every page change | index rebuild |
| Provenance index rebuild | After any source/citation change | sources rebuild-index |
| Publish | After any change worth persisting | publish --message "<op>: <desc>" |
| Full lint | Every 10 sources (checked via log status), or on request |
lint, then carry the semantic findings into kb/log.md via log append --op lint - the report itself is gitignored |
| Raw coverage check | Every 10 sources | sources coverage |
| Docs/instruction verification | After changing the CLI, a contract, or an instruction | docs verify, instructions verify |
| Budget check | Any time a session feels long | budget status |
| Retention review | Every 90 days | Manual |
Future considerations (not implemented)
- A pre-commit hook running
wikitool lint --fail-on-errorbefore everywikitool publish. CI already runs it on every push (.gitea/workflows/ci.yml), which catches it after the fact rather than before.