54d9540c08
Files changed: - .wikitool-kb.json - AGENTS.md - CHANGES.md - INSTALL-MCP.md - INSTALL.md - README.md - VERSION - instructions/capture-session.md - instructions/dev/issue-tracking.md - instructions/german-terminology.md - instructions/kb-profiles.md - instructions/migrate-corpus.md - instructions/migrations/5.0.0-confidence-removal.md - instructions/private-instance.md - instructions/setup-instance.md - instructions/wiki-lint/SKILL.md - instructions/wiki-manage/SKILL.md - instructions/wiki-query/SKILL.md - kb/CONTRACT.md - kb/CONVENTIONS.md - kb/CONVENTIONS.md.template - kb/concepts/architectures/Consolidation Tiers.md - kb/concepts/architectures/Context Isolation.md - kb/concepts/architectures/Cross-platform Agent Skills.md - kb/concepts/architectures/Episodic Memory.md - kb/concepts/architectures/Hybrid Search.md - kb/concepts/architectures/Implementation Spectrum.md - kb/concepts/architectures/Knowledge Graph.md - kb/concepts/architectures/LLM Wiki Pattern.md - kb/concepts/architectures/MCP-Leseserver.md - kb/concepts/architectures/Memory Lifecycle.md - kb/concepts/architectures/OKF Compatibility.md - kb/concepts/architectures/Optional Instance Context File.md - kb/concepts/architectures/Personalization Plane.md - kb/concepts/architectures/Procedural Memory.md - kb/concepts/architectures/RAG.md - kb/concepts/architectures/Scale Ceiling.md - kb/concepts/architectures/Semantic Memory.md - kb/concepts/architectures/Three-Layer Architecture.md - kb/concepts/architectures/Token Economics.md - kb/concepts/architectures/Working Memory.md - kb/concepts/decisions/Delete Rather Than Anonymize.md - kb/concepts/decisions/Denylist over Allowlist.md - kb/concepts/decisions/Diff-Reviewable Agent Edits.md - kb/concepts/decisions/Dual Licensing by File Plan.md - kb/concepts/decisions/Issue Label Scheme.md - kb/concepts/decisions/KB Stack Versioning.md - kb/concepts/decisions/Structural Enforcement over Documented Rule.md - kb/concepts/patterns/Audit Trail.md - kb/concepts/patterns/BM25.md - kb/concepts/patterns/Command Round-Trip Integrity.md - kb/concepts/patterns/Confidence Scoring.md - kb/concepts/patterns/Contradiction Resolution.md - kb/concepts/patterns/Entity Extraction.md - kb/concepts/patterns/Filter on Ingest.md - kb/concepts/patterns/Forgetting.md - kb/concepts/patterns/Graph Traversal.md - kb/concepts/patterns/Mesh Sync.md - kb/concepts/patterns/Quality Scoring.md - kb/concepts/patterns/Reciprocal Rank Fusion.md - kb/concepts/patterns/Self-Healing.md - kb/concepts/patterns/Shared vs Private.md - kb/concepts/patterns/Typed Relationships.md - kb/concepts/patterns/Vector Search.md - kb/concepts/patterns/Work Coordination.md - kb/concepts/problems/Ambient Environment Dependency.md - kb/concepts/problems/Detect-Repair Asymmetry.md - kb/concepts/problems/Green Suite Blind Spot.md - kb/concepts/problems/Naming Convention Conflict.md - kb/concepts/problems/Write-Once Frontmatter Fields.md - kb/concepts/protocols/CPPC.md - kb/concepts/protocols/Modbus.md - kb/concepts/protocols/SSD TRIM.md - kb/concepts/workflows/Anti-Cramming Heuristic.md - kb/concepts/workflows/Bulk Operations.md - kb/concepts/workflows/CI Integration.md - kb/concepts/workflows/Checkpoint Audit.md - kb/concepts/workflows/Claude Code Auto Mode.md - kb/concepts/workflows/Content Quality Control.md - kb/concepts/workflows/Crystallization.md - kb/concepts/workflows/Event-Driven Automation.md - kb/concepts/workflows/Hooks.md - kb/concepts/workflows/Index Scaling.md - kb/concepts/workflows/Iteration and Cost Limits.md - kb/concepts/workflows/KB Migration.md - kb/concepts/workflows/Knowledge Compounding.md - kb/concepts/workflows/Lint Workflow.md - kb/concepts/workflows/Mass-Update Gate.md - kb/concepts/workflows/Multi-Agent Collaboration.md - kb/concepts/workflows/Privacy and Governance.md - kb/concepts/workflows/Publish-Remote Gate.md - kb/concepts/workflows/Quality and Self-Correction.md - kb/concepts/workflows/Semantic Lint Automation.md - kb/concepts/workflows/Session Orientation.md - kb/concepts/workflows/Split Merge Reclassify.md - kb/concepts/workflows/Split Threshold.md - kb/concepts/workflows/Stub Threshold.md - kb/concepts/workflows/Supersession.md - kb/concepts/workflows/User Management.md - kb/concepts/workflows/Workflow Extraction.md - kb/concepts/workflows/Workflow Orchestration.md - kb/entities/people/Andrej Karpathy.md - kb/entities/people/E3DC GmbH.md - kb/entities/people/Rohit Gupta.md - kb/entities/people/Vannevar Bush.md - kb/entities/projects/BCDModule.md - kb/entities/projects/Chemenu.md - kb/entities/projects/andybalholm-edl.md - kb/entities/projects/goresponsiveness.md - kb/entities/projects/ha-core.md - kb/entities/projects/hacs-e3dc.md - kb/entities/projects/hacs-integration-blueprint.md - kb/entities/projects/llm-wiki-skills.md - kb/entities/projects/plugnburn-edl.md - kb/entities/projects/wiki-skills-vanillaflava.md - kb/entities/projects/wiki-skills.md - kb/entities/systems/AGENTS.md.md - kb/entities/systems/CLAUDE.md.md - kb/entities/systems/E3DC.md - kb/entities/systems/ENVIRONMENT.md.md - kb/entities/systems/Memex.md - kb/entities/systems/Tolkien Gateway.md - kb/entities/technologies/Arch Linux.md - kb/entities/technologies/Disk Encryption.md - kb/entities/technologies/Docker.md - kb/entities/technologies/GRUB.md - kb/entities/technologies/Gitea Actions.md - kb/entities/technologies/Gitea.md - kb/entities/technologies/Go.md - kb/entities/technologies/Home Assistant.md - kb/entities/technologies/Kernel PM Governors.md - kb/entities/technologies/LVM.md - kb/entities/technologies/Linux Kernel.md - kb/entities/technologies/MQTT.md - kb/entities/technologies/OPC UA.md - kb/entities/technologies/Python.md - kb/entities/technologies/Rust.md - kb/entities/technologies/Wine GE.md - kb/entities/technologies/Wine-Staging.md - kb/entities/technologies/acpi-cpufreq.md - kb/entities/technologies/amd-pstate.md - kb/entities/technologies/iii Engine.md - kb/entities/tools/AUR.md - kb/entities/tools/Act Runner.md - kb/entities/tools/Agent Memory.md - kb/entities/tools/Aura.md - kb/entities/tools/Bottles.md - kb/entities/tools/ChatGPT.md - kb/entities/tools/Claude Code.md - kb/entities/tools/Codex CLI.md - kb/entities/tools/Dataview.md - kb/entities/tools/GPG.md - kb/entities/tools/GitHub Copilot.md - kb/entities/tools/Gitea MCP Server.md - kb/entities/tools/Lutris.md - kb/entities/tools/Marp.md - kb/entities/tools/Mistral Vibe.md - kb/entities/tools/NotebookLM.md - kb/entities/tools/Obsidian Web Clipper.md - kb/entities/tools/Obsidian.md - kb/entities/tools/OpenAI Codex.md - kb/entities/tools/OpenCode.md - kb/entities/tools/Pi.md - kb/entities/tools/Proton.md - kb/entities/tools/Steam.md - kb/entities/tools/Wine.md - kb/entities/tools/awesome-llm-wiki.md - kb/entities/tools/farzaa gist.md - kb/entities/tools/gdeploy.md - kb/entities/tools/makepkg.md - kb/entities/tools/pascalandy schema.md - kb/entities/tools/qmd.md - kb/entities/tools/wikitool.md - kb/index.md - kb/log.md - raw/CONTRACT.md - tools/CONTRACT.md - tools/README.md - tools/chemenu/api.py - tools/chemenu/cli.py - tools/chemenu/commands/confidence_decay.py - tools/chemenu/commands/docs_verify.py - tools/chemenu/commands/doctor.py - tools/chemenu/commands/index_build.py - tools/chemenu/commands/new_page.py - tools/chemenu/commands/search.py - tools/chemenu/commands/touch.py - tools/chemenu/commands/version_cmd.py - tools/chemenu/conventions.py - tools/chemenu/corpus_diff.py - tools/chemenu/frontmatter_io.py - tools/chemenu/lint_core.py - tools/chemenu/mcp/server.py - tools/chemenu/page.py - tools/chemenu/search/base.py - tools/chemenu/search/filters.py - tools/chemenu/search/ripgrep.py - tools/chemenu/search/service.py - tools/chemenu/search/types.py - tools/chemenu/tests/conftest.py - tools/chemenu/tests/test_api.py - tools/chemenu/tests/test_confidence_decay.py - tools/chemenu/tests/test_corpus_diff.py - tools/chemenu/tests/test_docs_verify.py - tools/chemenu/tests/test_frontmatter_io.py - tools/chemenu/tests/test_index_build.py - tools/chemenu/tests/test_kb_scan.py - tools/chemenu/tests/test_lint.py - tools/chemenu/tests/test_mcp_server.py - tools/chemenu/tests/test_new_page.py - tools/chemenu/tests/test_page_ops.py - tools/chemenu/tests/test_provenance.py - tools/chemenu/tests/test_raw_cmd.py - tools/chemenu/tests/test_search.py - tools/chemenu/tests/test_touch.py - tools/chemenu/tests/test_type_resolver.py - tools/chemenu/tests/test_version_cmd.py - tools/chemenu/tests/test_xref.py - tools/chemenu/version.py - types/concept.md - types/concept.schema.yaml - types/entity.md - types/entity.schema.yaml - types/instruction.md - types/type-spec.md
188 lines
11 KiB
Markdown
188 lines
11 KiB
Markdown
# tools/
|
|
|
|
Developer documentation for `wikitool` - how the CLI is built, how to change it,
|
|
and how to run its tests.
|
|
|
|
**This is not the command reference.** That is [CONTRACT.md](CONTRACT.md), which
|
|
`wikitool docs verify` checks against the registered commands. Copying the
|
|
command table here would create a second copy that drifts, so this file
|
|
deliberately has none - and `docs verify` now enforces that.
|
|
|
|
| Document | Audience |
|
|
|---|---|
|
|
| `README.md` (this file) | Humans working *on* wikitool |
|
|
| [`CONTRACT.md`](CONTRACT.md) | Agents working *with* wikitool - commands, error contracts, maintenance schedule |
|
|
| [`../AGENTS.md`](../AGENTS.md) | The invariants that say when using a command is mandatory |
|
|
| [`../CHANGES.md`](../CHANGES.md) | What changed in the stack, and when |
|
|
|
|
## Setup
|
|
|
|
```bash
|
|
cd tools
|
|
python3 -m venv .venv
|
|
.venv/bin/pip install -r requirements.txt
|
|
```
|
|
|
|
`jsonschema` and `PyYAML` are hard dependencies, not optional extras: schema
|
|
validation is the tool's whole safety net, so `cli.py` fails loudly with the
|
|
install command rather than degrading silently.
|
|
|
|
## Layout
|
|
|
|
```
|
|
tools/
|
|
wikitool entry point
|
|
chemenu/
|
|
cli.py Typer app: registers every command, runs the budget gate
|
|
config.py repo layout: root resolution and every path under it
|
|
api.py the in-process entry point - point Chemenu at a corpus and read it
|
|
errors.py ChemenuError / ValidationError / BackendError
|
|
corpus_cache.py one parsed corpus per commit, never cached while the tree is dirty
|
|
kb_scan.py page iteration/loading over kb/
|
|
blocks.py generated regions in a page body, found by marker rather than by heading
|
|
links.py labelled edges in `related:` - the graph's semantics as data, not prose
|
|
kb_collections.py collection discovery (a directory with COLLECTION.md), and what one declares about itself
|
|
conventions.py kb/CONVENTIONS.md: what this instance decided about authoring, as opposed to what the stack enforces
|
|
ownership.py the stack-vs-instance boundary under a content stage - one predicate, read by `dist_cmd.py` and `commands/upstream_cmd.py` so the two cannot answer it differently
|
|
type_resolver.py type-spec loading and schema resolution
|
|
catalog.py how the corpus groups into collections and areas, and the shard threshold - with no CLI attached
|
|
lint_core.py the lint checks and the report, with no CLI attached
|
|
types_core.py type-spec listing/description, with no CLI attached
|
|
markdown_code.py masks code spans/fences so a page may show wiki notation, not only use it
|
|
version.py the stack version: VERSION, the release stamp, the compatibility rule
|
|
kb_state.py the KB version (.wikitool-kb.json) and the migration chain
|
|
corpus_diff.py invariant comparison of kb/ between two revisions
|
|
search/ pluggable search backends, plus service.py - the search core
|
|
commands/ one module per command or command group: the terminal adapters
|
|
tests/ pytest suite
|
|
```
|
|
|
|
**Two consumers, one core.** The CLI is not the only caller any more. The cores
|
|
(`search/service.py`, `lint_core.py`, `types_core.py`, `catalog.py`) hold what
|
|
decides an answer and import no `typer` and no `rich`; the modules under `commands/` turn
|
|
those values into terminal output and those exceptions into exit codes.
|
|
`api.Corpus` is the in-process entry point over the same functions - it takes a
|
|
corpus root, returns exactly the structures the `--json` forms print, and
|
|
raises instead of exiting. A second consumer is therefore a second adapter
|
|
rather than a second implementation, and the write commands are unreachable
|
|
from `api` because nothing under `commands/` is imported there.
|
|
|
|
**Which corpus.** The root resolves by precedence: an explicit argument, then
|
|
`$CHEMENU_ROOT`, then a walk up from the package's own location. The walk-up is
|
|
the default, so the CLI is unaffected by any of this. Nothing under the root is
|
|
bound at import time - `KB_DIR` and friends follow whatever `ROOT` currently is.
|
|
|
|
## Adding a command
|
|
|
|
1. Write the module under `chemenu/commands/`. A group is a `typer.Typer()`
|
|
app; a single command is a plain function.
|
|
2. Register it in `cli.py` (`app.add_typer(...)` or `app.command(...)`).
|
|
3. Add a row to [CONTRACT.md](CONTRACT.md)'s command table **and** to its error
|
|
contract table. `docs verify` fails in both directions - an undocumented
|
|
command and a documented non-command are equally reported. Write the rows
|
|
without citing an issue number: `docs verify` also refuses any `.md` or
|
|
`.template` `dist export` ships that carries one, because the tracker exists
|
|
only in this repo (`instructions/dev/issue-tracking.md` § Citing an issue in
|
|
the repo).
|
|
4. Add tests. Pure logic belongs in a function separate from the Typer callback
|
|
(see `mass_update_gate_message`, `derive_run_key`, `run_export`), so a test
|
|
does not need a CLI runner - and a Typer callback called directly from a test
|
|
receives `OptionInfo` objects, not values, for any argument the test omits.
|
|
5. Raise the version: `wikitool version bump --minor --title "..."` for a new
|
|
command (`--patch` for a fix, `--major` when the new version is **not a
|
|
drop-in replacement** - a renamed flag or artefact, a stricter check that
|
|
newly fails on content an instance already had, anything needing hand-work
|
|
after the copy). Content migration is one way to land there, not the
|
|
definition of it: a `--major` may well ship `--no-migration`, and one that
|
|
does migrate also needs a document under `instructions/migrations/`. The
|
|
full test is `instructions/dev/version-parts.md` - read it before choosing
|
|
`--major`.
|
|
A new command reaches every future instance, and CI's version gate refuses a
|
|
stack change that moved no version.
|
|
|
|
Every command is counted against the iteration budget unless it is listed in
|
|
`run_budget.SKIP_COMMANDS` / `SKIP_COMMAND_PATHS`. Only read-only retrieval
|
|
belongs there.
|
|
|
|
## Design notes
|
|
|
|
**Deterministic by default.** Anything an LLM would otherwise re-derive -
|
|
frontmatter, index statistics, cross-reference bookkeeping, log formatting,
|
|
version arithmetic - is computed here so it comes out the same every time.
|
|
|
|
**Gates are code, not prompts.** The Mass-Update Gate (`git_publish.py`) and the
|
|
Iteration Budget Gate (`run_budget.py`) refuse in-process, because a
|
|
prompt-level limit is one an agent can talk itself past. Exemption lists are
|
|
constants, never flags.
|
|
|
|
**Clearance is an exit code, not an instruction.** `EXIT_NEEDS_CLEARANCE`
|
|
(42, in `commands/_util.py`) is a third outcome beside success and validation
|
|
error, meaning "a human has to see this output first". The command's own
|
|
message carries the reason, the evidence and the exact `--confirm <token>`
|
|
re-run line; the instruction layer deliberately holds none of it, because a
|
|
procedure written down in advance is one an agent can complete alone. Whether
|
|
a human *actually* saw it is not enforced here - that question is answered in
|
|
the eval layer (`evals/trajectory.py`, `clearance-ended-the-turn`).
|
|
|
|
**Nothing locates a region by its prose.** `xref` owns the links region and `cite` the footnotes
|
|
region, and each is delimited by a `<!-- wikitool:<name> -->` marker pair (`blocks.py`). The
|
|
heading inside is rendered from `kb/CONVENTIONS.md` and is replaced along with the rest of the
|
|
region on every write - so no heading text exists in Python, and changing the declaration cannot
|
|
split a page.
|
|
|
|
Both halves of that mattered. Matching on the heading made the KB language a compiler constant;
|
|
*guessing* where the region ended - at the next heading, and before that at the end of the file -
|
|
silently deleted content sitting after it on eight pages. `migrate verify` compares marker-pair
|
|
counts for the same reason it compares wikilink counts: a dropped marker is invisible otherwise.
|
|
|
|
**A relationship label is data, not prose.** `related:` carries `- <label>: <target>`
|
|
(`links.py`), the label drawn from `instructions/link-taxonomy.md` and authorised per
|
|
destination by the *source* collection's `outbound:` block. The body bullet is a rendering of
|
|
that, which is what removed the need to parse a German phrase back into a relationship - and why
|
|
the vocabulary can be checked at all, after drifting to 152 distinct labels while it could not
|
|
be. Both readers accept a bare title as an unlabelled edge: that is the shape a page is in
|
|
between the machinery landing and the migration reaching it, and `lint` is what reports it.
|
|
|
|
**An edge is authored in one direction.** `xref add` writes one, on the asserting page. The
|
|
inbound view is rendered from the graph rather than stored, so navigation does not depend on
|
|
anyone writing a mirror - and per-collection authorisation stays coherent, which it cannot be if
|
|
the tool writes edges into a collection whose rules the author never read.
|
|
|
|
**Generated output is never committed.** `reports/`, `.agents/skills/` and
|
|
`.claude/skills/` are build output; `docs verify` carries canaries in both
|
|
directions so an ignore rule can neither swallow tracked content nor stop
|
|
ignoring generated copies.
|
|
|
|
## Tests
|
|
|
|
```bash
|
|
cd tools && .venv/bin/python -m pytest -q
|
|
```
|
|
|
|
`pytest.ini` sets the import path. Tests use `tmp_path` fixtures and monkeypatch
|
|
`config` paths rather than touching the real `kb/`.
|
|
|
|
Coverage is optional locally and measured on every CI run:
|
|
|
|
```bash
|
|
.venv/bin/pip install pytest-cov # one time, CI-only dependency
|
|
.venv/bin/python -m pytest -q --cov
|
|
```
|
|
|
|
`pytest-cov` is deliberately not in `requirements.txt`: that file is what an *instance*
|
|
needs at runtime and ships with `dist export`, and an instance does not measure this
|
|
suite. Configuration is `.coveragerc` (coverage.py does not read `pytest.ini`), which
|
|
measures `chemenu/` without `chemenu/tests/` and sets no threshold - see EVALS.md
|
|
for the measured number and how to read it.
|
|
|
|
**Two exceptions, and they bite.** `test_types_cmd.py` and `test_index_build.py` resolve the real
|
|
`types/` through the shared `resolver`, because what they assert *is* that behaviour comes from the
|
|
type-specs rather than from a hardcoded map. Editing a type-spec can therefore fail tests that look
|
|
unrelated to it - translating the wiki broke six that way. Anything those tests pin must be
|
|
structural: `dir:` values and layout order are asserted exactly, display titles are read from the
|
|
spec at assert time, never written out. The same rule applies to the template fence - anchor on a
|
|
section name from `sections`, not on a literal.
|
|
|
|
Assert on prose in a type-spec or a contract only when the prose is the subject of the test.
|
|
Otherwise it is a tripwire that fires on an edit nobody connected to the test.
|