Mirror docs at v10.0.0

Wiki Mirror committed 2026-10-13 06:21:25 +00:00
1 parent 624366529f
commit 7b705fa5c4
58 files changed
+2302 -458

No files matched your search

+16 -15
@@ -1,4 +1,4 @@
*Generated from [`docs/dev/development.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/docs/dev/development.md) at v9.0.0 – edit the repository, not this wiki.* *Generated from [`docs/dev/development.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/dev/development.md) at v10.0.0 – edit the repository, not this wiki.*
# Developing this stack # Developing this stack
@@ -40,9 +40,9 @@ cd chemenu
Then have the agent run `instructions/bootstrap.md`: the preflight, then Then have the agent run `instructions/bootstrap.md`: the preflight, then
`tools/wikitool instructions sync`. The agent's side of it - what differs here from an instance, `tools/wikitool instructions sync`. The agent's side of it - what differs here from an instance,
and how `dist export` runs as a test tool - is and how `dist export` runs as a test tool - is
[instructions/dev/dev-setup.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/instructions/dev/dev-setup.md). [instructions/dev/dev-setup.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/instructions/dev/dev-setup.md).
Real documents for `raw derive` live in [testfiles/](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/testfiles/README.md) and are Real documents for `raw derive` live in [testfiles/](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/testfiles/README.md) and are
committed, so CI derives them too. The repository is public: that README says what may go in. committed, so CI derives them too. The repository is public: that README says what may go in.
What a clone has and an instance does not: What a clone has and an instance does not:
@@ -50,7 +50,7 @@ What a clone has and an instance does not:
- **The demo corpus.** Some 170 pages documenting the stack itself: gates, lint, versioning, - **The demo corpus.** Some 170 pages documenting the stack itself: gates, lint, versioning,
search, the wiki pattern. It is a testbed and a walkable example, not a productive knowledge search, the wiki pattern. It is a testbed and a walkable example, not a productive knowledge
base. What may be changed in it is governed by base. What may be changed in it is governed by
[instructions/dev/corpus-policy.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/instructions/dev/corpus-policy.md). [instructions/dev/corpus-policy.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/instructions/dev/corpus-policy.md).
- **A demo persona** in `USER.md`/`SOUL.md`. `doctor` reports both as filled in. They describe - **A demo persona** in `USER.md`/`SOUL.md`. `doctor` reports both as filled in. They describe
the demo setup, not you. the demo setup, not you.
- **Telemetry on.** Without `.wikitool-release.json` the clone is the measuring station the stack - **Telemetry on.** Without `.wikitool-release.json` the clone is the measuring station the stack
@@ -91,7 +91,7 @@ only to downloads on the same host as the feed.
Between two releases the stack carries **one** running version candidate instead of a new Between two releases the stack carries **one** running version candidate instead of a new
number per bump. The full model - where the state lives, how escalation works, why a number is number per bump. The full model - where the state lives, how escalation works, why a number is
only consumed by a release - is in only consumed by a release - is in
[instructions/dev/version-parts.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/instructions/dev/version-parts.md) and [instructions/dev/version-parts.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/instructions/dev/version-parts.md) and
[docs/explanation/version-model.md](Explanation-Version-model). Here only the sequence, in [docs/explanation/version-model.md](Explanation-Version-model). Here only the sequence, in
the order a session actually goes through it: the order a session actually goes through it:
@@ -112,7 +112,7 @@ the order a session actually goes through it:
lines where they apply). A smaller change needs nothing beyond its bump title; a larger one lines where they apply). A smaller change needs nothing beyond its bump title; a larger one
gets a paragraph for its topic, and a short summary is added before the release. The form, and gets a paragraph for its topic, and a short summary is added before the release. The form, and
the size budget `docs verify` and `version release` check, are in the size budget `docs verify` and `version release` check, are in
[instructions/dev/version-parts.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/instructions/dev/version-parts.md) § The candidate [instructions/dev/version-parts.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/instructions/dev/version-parts.md) § The candidate
model. model.
3. **Run the checks before anything is published:** 3. **Run the checks before anything is published:**
@@ -134,7 +134,7 @@ the order a session actually goes through it:
bump titles gets a summarising heading. Refused when the candidate collected two or more bumps bump titles gets a summarising heading. Refused when the candidate collected two or more bumps
and the summary from step 2 is still missing - a candidate with exactly one bump is exempt - and the summary from step 2 is still missing - a candidate with exactly one bump is exempt -
and likewise when the entry is over its size budget. Commits and pushes nothing (invariant 5 and likewise when the entry is over its size budget. Commits and pushes nothing (invariant 5
in [AGENTS.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/AGENTS.md)). in [AGENTS.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/AGENTS.md)).
5. **Publish moves `VERSION` on `main`.** 5. **Publish moves `VERSION` on `main`.**
@@ -143,7 +143,7 @@ the order a session actually goes through it:
``` ```
The Mass-Update Gate and the Publish-Remote Gate apply as on every other publish - see The Mass-Update Gate and the Publish-Remote Gate apply as on every other publish - see
[instructions/gates.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/instructions/gates.md). [instructions/gates.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/instructions/gates.md).
6. **CI does the rest.** `.gitea/workflows/release.yml` reacts to every push that moves 6. **CI does the rest.** `.gitea/workflows/release.yml` reacts to every push that moves
`VERSION`: a `VERSION` with a suffix (a candidate) makes the job skip cleanly before it even `VERSION`: a `VERSION` with a suffix (a candidate) makes the job skip cleanly before it even
@@ -166,12 +166,12 @@ the order a session actually goes through it:
own, so a failing mirror leaves the release and its tag standing and marks only itself red. own, so a failing mirror leaves the release and its tag standing and marks only itself red.
The three check commands are in step 3 above; what each of them checks is in The three check commands are in step 3 above; what each of them checks is in
[tools/CONTRACT.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/tools/CONTRACT.md), where `docs verify` holds it against the actual [tools/CONTRACT.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/tools/CONTRACT.md), where `docs verify` holds it against the actual
CLI. It is deliberately **not** repeated here: a second description of the same commands is CLI. It is deliberately **not** repeated here: a second description of the same commands is
exactly the copy that drifts (AGENTS.md invariant 8), and this document lies outside the files exactly the copy that drifts (AGENTS.md invariant 8), and this document lies outside the files
the command-record check of `docs verify` covers - so a drift here would go unnoticed. What the command-record check of `docs verify` covers - so a drift here would go unnoticed. What
`pytest` expects of the developer at this point is in `pytest` expects of the developer at this point is in
[instructions/dev/testing-conventions.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/instructions/dev/testing-conventions.md). [instructions/dev/testing-conventions.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/instructions/dev/testing-conventions.md).
## The CI half ## The CI half
@@ -191,7 +191,7 @@ Super Productivity version. The image follows the desktop clients' update channe
version. Once, after the very first push, the package has to be linked to the `torben/chemenu` version. Once, after the very first push, the package has to be linked to the `torben/chemenu`
repository by hand. What the suite writes, how to run it against a tracker of your own, and what repository by hand. What the suite writes, how to run it against a tracker of your own, and what
a red run means is in a red run means is in
[instructions/dev/tracker-testing.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/instructions/dev/tracker-testing.md). [instructions/dev/tracker-testing.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/instructions/dev/tracker-testing.md).
### The Gitea wiki ### The Gitea wiki
@@ -281,12 +281,13 @@ low, and three kinds have to be told apart before any of it turns into work:
Three skills (`instructions/dev/`, present only in this origin repository) carry a session that Three skills (`instructions/dev/`, present only in this origin repository) carry a session that
works on the stack itself rather than on wiki content through three phases: `stack-dev` works works on the stack itself rather than on wiki content through three phases: `stack-dev` works
the issue out until its body is "ready", `stack-build` builds, publishes and waits for a green CI the issue out until its body is "ready", `stack-build` builds, publishes and waits for a green CI
run, `stack-close` checks the body's final state and stale `docs/` and contract prose, and closes run, `stack-close` checks the body's final state and stale `docs/` and contract prose, closes
the issue. The handover goes through the state in the tracker, not through a session's context: the issue and, where the body carries a `## Kette` section, names the next link of that chain
with the slash command that starts it. The handover goes through the state in the tracker, not through a session's context:
every phase change works in the same session or after `/clear`. `stack-dev` triggers every phase change works in the same session or after `/clear`. `stack-dev` triggers
automatically; `stack-build` and `stack-close` are started only by the operator, by slash command automatically; `stack-build` and `stack-close` are started only by the operator, by slash command
(`/stack-build #N`, `/stack-close`) - exactly where the choice falls whether to continue in the (`/stack-build #N`, `/stack-close`) - exactly where the choice falls whether to continue in the
same session or a new one, and on which model. None of the three offers a model switch in the same session or a new one, and on which model. None of the three offers a model switch in the
middle of a session. Rules and the phase table are in middle of a session. Rules and the phase table are in
[instructions/dev/stack-mode.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/instructions/dev/stack-mode.md), the issue tracker itself [instructions/dev/stack-mode.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/instructions/dev/stack-mode.md), the issue tracker itself
in [instructions/dev/issue-tracking.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/instructions/dev/issue-tracking.md). in [instructions/dev/issue-tracking.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/instructions/dev/issue-tracking.md).
+16 -7
@@ -1,4 +1,4 @@
*Generated from [`docs/explanation/architecture.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/docs/explanation/architecture.md) at v9.0.0 – edit the repository, not this wiki.* *Generated from [`docs/explanation/architecture.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/explanation/architecture.md) at v10.0.0 – edit the repository, not this wiki.*
# Architecture # Architecture
@@ -43,7 +43,7 @@ citation ids, schema enum values, tags, commands, paths and code. So `GitOps Own
German section heading sit in the same page without contradiction. German section heading sit in the same page without contradiction.
Which lines count as identifiers is the stack's rule, Which lines count as identifiers is the stack's rule,
[kb/CONTRACT.md § Language and identifiers](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/kb/CONTRACT.md#language-and-identifiers). [kb/CONTRACT.md § Language and identifiers](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/kb/CONTRACT.md#language-and-identifiers).
*Which language* the prose is in, and what the tool-owned headings are called, is the instance's own *Which language* the prose is in, and what the tool-owned headings are called, is the instance's own
`kb/CONVENTIONS.md`. That is a per-instance decision, not a property of the pattern, so it lives in a `kb/CONVENTIONS.md`. That is a per-instance decision, not a property of the pattern, so it lives in a
file the instance owns rather than in one the stack ships. A new instance picks its language when it file the instance owns rather than in one the stack ships. A new instance picks its language when it
@@ -83,9 +83,10 @@ chemenu/
│ ├── ingest-large-tree.md │ ├── ingest-large-tree.md
│ ├── ingest-queue.md # Reviewing a submission before wikitool upload accept promotes it │ ├── ingest-queue.md # Reviewing a submission before wikitool upload accept promotes it
│ └── wiki-*/SKILL.md # Skills - copied into .agents/skills/ and .claude/skills/ │ └── wiki-*/SKILL.md # Skills - copied into .agents/skills/ and .claude/skills/
├── mcp-upload/ # QUARANTINE (optional): the MCP `submit` tool's write path, gitignored - ├── quarantine/ # QUARANTINE (optional): where the intake channels (MCP `submit`, a synced
│ # read by no command in the ordinary pipeline; a human reviews it with │ # folder, a command) put what nobody has reviewed, gitignored - read by
│ # `wikitool upload list/show/accept/reject` │ # no command in the ordinary pipeline; a human reviews it with
│ # `wikitool upload list/show/accept/reject`, `upload collect` fills it
├── incoming/ # INBOX: content gitignored - drop a file or one folder per source here, `raw accept` promotes it ├── incoming/ # INBOX: content gitignored - drop a file or one folder per source here, `raw accept` promotes it
├── raw/ # INPUT: immutable, untrusted source material ├── raw/ # INPUT: immutable, untrusted source material
│ ├── CONTRACT.md # Date shard, capture fields, immutability, untrusted content │ ├── CONTRACT.md # Date shard, capture fields, immutability, untrusted content
@@ -142,6 +143,8 @@ chemenu/
│ └── CONTRACT.md # Run keys, required files, how a run closes │ └── CONTRACT.md # Run keys, required files, how a run closes
├── reports/ # DERIVED: lint reports, traces, eval scores. Gitignored ├── reports/ # DERIVED: lint reports, traces, eval scores. Gitignored
│ └── CONTRACT.md │ └── CONTRACT.md
├── sites/ # The instance's own site handlers for `raw fetch`, one JSON file per website
│ └── CONTRACT.md # File format, which handler applies, what a broken file does
└── tools/ # COMPILER: the wikitool CLI └── tools/ # COMPILER: the wikitool CLI
├── CONTRACT.md # Command records (generated) and index, maintenance schedule ├── CONTRACT.md # Command records (generated) and index, maintenance schedule
└── README.md # How wikitool is built and how to change it └── README.md # How wikitool is built and how to change it
@@ -226,12 +229,12 @@ same record is rendered four ways:
command; command;
- a command that fails with exit 1 prints the record's reactions on stderr, right under its `ERROR` - a command that fails with exit 1 prints the record's reactions on stderr, right under its `ERROR`
line; line;
- [tools/CONTRACT.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/tools/CONTRACT.md) holds a generated copy for agents, together with the - [tools/CONTRACT.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/tools/CONTRACT.md) holds a generated copy for agents, together with the
maintenance schedule; maintenance schedule;
- [the command reference](Reference-Commands) is a generated copy for humans. - [the command reference](Reference-Commands) is a generated copy for humans.
`tools/wikitool docs verify` checks the copies, and every command's flags, against the CLI in both `tools/wikitool docs verify` checks the copies, and every command's flags, against the CLI in both
directions. [tools/README.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/tools/README.md) is the other half: how the CLI is built and how directions. [tools/README.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/tools/README.md) is the other half: how the CLI is built and how
to add a command. `AGENTS.md` holds the invariants that say when each command is mandatory. to add a command. `AGENTS.md` holds the invariants that say when each command is mandatory.
The wiki is also a git repository, and `tools/wikitool publish` is the one way its changes are The wiki is also a git repository, and `tools/wikitool publish` is the one way its changes are
@@ -262,6 +265,12 @@ for it.
Setting it up: [set-up-the-mcp-server.md](How-to-Set-up-the-mcp-server). Setting it up: [set-up-the-mcp-server.md](How-to-Set-up-the-mcp-server).
A reader that cannot ask at all - a Microsoft 365 tenant indexing a OneDrive folder - gets a copy
instead: `tools/wikitool export run` writes the pages a target selects into a folder, as a derived,
read-only version regenerated from the committed pages on every run. Nothing flows back from it;
the export marker and the target's manifest keep its files out of `raw/`
([export-to-a-synced-folder.md](How-to-Export-to-a-synced-folder)).
## Built for IT work ## Built for IT work
The shipped defaults are tailored to IT work: The shipped defaults are tailored to IT work:
+115
@@ -0,0 +1,115 @@
*Generated from [`docs/explanation/configuration-tiers.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/explanation/configuration-tiers.md) at v10.0.0 – edit the repository, not this wiki.*
# Configuration tiers
Settings are split into three tiers: what belongs to the instance (`wikitool.json`, committed),
what belongs to one checkout (`.wikitool-local.json`, gitignored), and secrets, which sit
outside the working tree and are reached only by reference. The files, their fields and the
command that writes them are in [the configuration reference](Reference-Configuration#tiered-settings);
the rules an agent follows are in [AGENTS.md § Settings](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/AGENTS.md#settings). This page is
about why it is built this way, and what it does not do.
<!-- wikitool:toc -->
## Contents
- [Three kinds of data that used to share files](#three-kinds-of-data-that-used-to-share-files)
- [One place per value, not an override](#one-place-per-value-not-an-override)
- [What runs, and where it pushes, never travels with a pull](#what-runs-and-where-it-pushes-never-travels-with-a-pull)
- [References instead of values](#references-instead-of-values)
- [What this does not protect against](#what-this-does-not-protect-against)
<!-- /wikitool:toc -->
## Three kinds of data that used to share files
Before the tiers, every feature kept its settings in a gitignored file of its own. Three kinds of
data ended up mixed in them:
- **What belongs to the instance.** The weekly review's thresholds are a decision about the
corpus, and every clone of it should make the same one. Gitignoring them meant each clone had
to be told again.
- **What belongs to one checkout.** A tool path, the push URLs a checkout may publish to, the
consent to record telemetry on this disk. Committing any of these would hand every other clone
an answer that is wrong for it rather than missing.
- **Secrets.** A tracker token sat in plain text in the working tree, protected only by
`.gitignore` - one wrong pattern away from a commit, and in plain view of every agent that read
the file in the course of ordinary work.
The tiers give each kind one place, declared per field, so the question "where does this go" is
answered once, by whoever declares the field, rather than each time by whoever writes a value.
## One place per value, not an override
A familiar design lets the local file override the committed one. It is deliberately not this
one: a value in the wrong file is an error. With two possible sources for one value, every
reader first has to work out which one wins, and the answer differs between clones - a reviewer
looking at `wikitool.json` would no longer know what the instance actually runs with. One place
per value keeps the committed file a true statement about every clone.
## What runs, and where it pushes, never travels with a pull
A field that names a program to run, or that arms or loosens a gate, is never in the committed
file - not even as a reference, since a resolved reference is still a value someone else chose.
Otherwise a `git pull` from another clone could change what `wikitool` executes on this machine,
or where `publish` sends this checkout's content. The rule is checked when a field is declared,
in code, so no reader has to remember it; the reasoning is the same as for the gates
([why-gates-are-code.md](Explanation-Why-gates-are-code)). The same fields are also not written by
`config set`: like the Publish-Remote Gate's allowlist, they change by the user's own edit.
## References instead of values
A secret is only ever a reference, and a private value - a host name, an internal URL - may be
one. A reference names where the value lives: a name, which resolves through a
`WIKITOOL_SECRET_<NAME>` variable or a secrets file in the user's own configuration directory, or
an `op://` URI, which resolves through the 1Password CLI.
That is what makes the committed file portable. An instance that is public commits
`wikitool.json` publicly, so a private value can never sit there as text; as an `op://`
reference it can, and every machine signed in to the same 1Password account resolves it to the
same value. In CI, `op` runs with a service-account token and resolves the same `op://`
references. Alternatively, 1Password's own action resolves values into variables before
`wikitool` starts, which serves references by name. This repository's CI does the second.
A plain private value in `.wikitool-local.json` stays on this machine only as long as that file
does. A backup into a synced folder (the `backup` section) copies the file as it is, because it is
the one record of how this checkout is set up, and a restore without it starts from nothing. That
trade is the operator's, made by writing the section by hand. A reference keeps the value out of
that copy as well, and the secrets file is never in it: it lies outside the instance, and a backup
reads nothing outside it.
A reference is resolved only when a command asks for that one value, never when the files are
read, and the value stays in that process's memory. `doctor` reports whether each reference
resolves, never what it resolves to.
One secret has no reference at all: the session `raw login` stores for a website behind a login.
The user never types it, so nobody could put it into a secrets source by hand. `raw login` writes
it into the secrets file itself, under a name derived from the host, and `raw fetch` finds it by
that name. It never goes to 1Password, because it changes with every sign-in and the stack only
ever reads from there. So it sits in the one source the 1Password safeguards below do not cover.
## What this does not protect against
Keeping secrets out of the tree protects against three things: a secret being committed, a
secret sitting in a file an agent reads while doing ordinary work, and a secret appearing in
`wikitool`'s output.
It does not protect against an agent with shell access that sets out to read one. If 1Password
is unlocked in the terminal, the agent can run `op read` itself. It can read the secrets file. In
general, whatever the `wikitool` process can read in a user account, an agent running in that same
account can read too. The stack does not claim otherwise.
What limits the exposure without preventing it:
- a 1Password vault of its own for this instance's entries, so an unlocked `op` reaches nothing
else;
- the approval prompt of the 1Password desktop app, which asks a human before the CLI may read
anything - per terminal session, not per value, so it stops a session nobody approved and not
one that already was;
- in CI, a service account that sees only that vault.
None of these covers a website session from `raw login`, which lives in the secrets file only.
Whoever can read that file can use the website as the user, until the session expires or the user
signs out there.
The rule in [AGENTS.md § Settings](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/AGENTS.md#settings) - a secret never passes through the
agent - is an instruction, not a barrier. The tiers make following it the easy path. They do not
make breaking it impossible.
+4 -4
@@ -1,4 +1,4 @@
*Generated from [`docs/explanation/knowledge-and-commitment.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/docs/explanation/knowledge-and-commitment.md) at v9.0.0 – edit the repository, not this wiki.* *Generated from [`docs/explanation/knowledge-and-commitment.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/explanation/knowledge-and-commitment.md) at v10.0.0 – edit the repository, not this wiki.*
# Why knowledge and commitments are two layers # Why knowledge and commitments are two layers
@@ -6,8 +6,8 @@ Chemenu compiles knowledge into `kb/`, and it also tracks what its operator has
Those look like one subject - both are "things about my projects" - and the stack deliberately Those look like one subject - both are "things about my projects" - and the stack deliberately
keeps them apart: `kb/gtd/` holds one page per initiative, an external task tracker holds the keeps them apart: `kb/gtd/` holds one page per initiative, an external task tracker holds the
open items, and the only thing that crosses between them is a name. This page is about why that open items, and the only thing that crosses between them is a name. This page is about why that
line was drawn there. The rules that follow from it live in [kb/CONTRACT.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/kb/CONTRACT.md) line was drawn there. The rules that follow from it live in [kb/CONTRACT.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/kb/CONTRACT.md)
and the `review`, `new` and `task new` records in [tools/CONTRACT.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/tools/CONTRACT.md). and the `review`, `new` and `task new` records in [tools/CONTRACT.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/tools/CONTRACT.md).
<!-- wikitool:toc --> <!-- wikitool:toc -->
## Contents ## Contents
@@ -142,7 +142,7 @@ is. An instruction that said "open Super Productivity" would bake one instance's
the shared stack, and the next instance - a different context, a different employer, a different the shared stack, and the next instance - a different context, a different employer, a different
set of constraints - would have to edit prose to change a setting. set of constraints - would have to edit prose to change a setting.
So the provider lives in configuration (`.wikitool-tasks.json`), the adapters live behind one So the provider lives in configuration (the `tasks` section of the settings), the adapters live behind one
protocol, and a capability the provider lacks surfaces as an ordinary tool error rather than as a protocol, and a capability the provider lacks surfaces as an ordinary tool error rather than as a
paragraph of instruction explaining what this particular tracker cannot do. A provider that paragraph of instruction explaining what this particular tracker cannot do. A provider that
cannot create a project, for instance, stops and asks the operator to do it - the same posture the cannot create a project, for instance, stops and asks the operator to do it - the same posture the
+33 -5
@@ -1,15 +1,17 @@
*Generated from [`docs/explanation/language-boundaries.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/docs/explanation/language-boundaries.md) at v9.0.0 – edit the repository, not this wiki.* *Generated from [`docs/explanation/language-boundaries.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/explanation/language-boundaries.md) at v10.0.0 – edit the repository, not this wiki.*
# Language Boundaries # Language Boundaries
Two languages run through this repo at once. `kb/` is written in whatever language the instance Two languages run through this repo at once. `kb/` is written in whatever language the instance
chose - German here, and the value lives in `kb/CONVENTIONS.md`'s `language:`. Everything that chose - German here, and the value lives in `kb/CONVENTIONS.md`'s `language:`. Everything that
tells an agent what to do - [AGENTS.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/AGENTS.md), every `CONTRACT.md`, everything under tells an agent what to do - [AGENTS.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/AGENTS.md), every `CONTRACT.md`, everything under
`instructions/` - is written in English, in every instance, whatever the first value says. So `instructions/` - is written in English, in every instance, whatever the first value says. So
is the stack's own documentation for humans, for a different reason (§ "The stack's human is the stack's own documentation for humans, for a different reason (§ "The stack's human
documentation"). documentation").
The rule itself is in [AGENTS.md § File naming](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/AGENTS.md#file-naming). This page holds the The rules themselves are in [AGENTS.md § File naming](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/AGENTS.md#file-naming) (the language
of a file) and [AGENTS.md § Conversation language](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/AGENTS.md#conversation-language) (the
language of what an agent says). This page holds the
part that is not a rule: why the line runs where it does, why the English half is not a setting, part that is not a rule: why the line runs where it does, why the English half is not a setting,
and which argument for it turned out to be wrong. and which argument for it turned out to be wrong.
@@ -20,6 +22,7 @@ and which argument for it turned out to be wrong.
- [Why the control plane's language is English](#why-the-control-planes-language-is-english) - [Why the control plane's language is English](#why-the-control-planes-language-is-english)
- [Why it is not a parameter](#why-it-is-not-a-parameter) - [Why it is not a parameter](#why-it-is-not-a-parameter)
- [What the KB language still decides](#what-the-kb-language-still-decides) - [What the KB language still decides](#what-the-kb-language-still-decides)
- [Why a rule alone did not hold](#why-a-rule-alone-did-not-hold)
- [Where the line runs around a page type](#where-the-line-runs-around-a-page-type) - [Where the line runs around a page type](#where-the-line-runs-around-a-page-type)
- [The stack's human documentation](#the-stacks-human-documentation) - [The stack's human documentation](#the-stacks-human-documentation)
- [What would put this back on the table](#what-would-put-this-back-on-the-table) - [What would put this back on the table](#what-would-put-this-back-on-the-table)
@@ -40,7 +43,7 @@ guidance is still instruction addressed to an agent, and reads exactly like the
four types the stack ships. Ownership says "yours"; the audience has not moved at all. four types the stack ships. Ownership says "yours"; the audience has not moved at all.
So the question a line answers is not *whose file is this* but *who reads this line*, which is So the question a line answers is not *whose file is this* but *who reads this line*, which is
the same cut [kb/CONTRACT.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/kb/CONTRACT.md#language-and-identifiers) already makes inside a the same cut [kb/CONTRACT.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/kb/CONTRACT.md#language-and-identifiers) already makes inside a
single page between prose and identifiers - applied one level up, to the halves of a document. single page between prose and identifiers - applied one level up, to the halves of a document.
Ownership decides who may change a sentence. The reader decides what language it is in. The two Ownership decides who may change a sentence. The reader decides what language it is in. The two
questions were answered together for as long as they happened to agree. questions were answered together for as long as they happened to agree.
@@ -107,6 +110,31 @@ So an operator who reads no English gets German pages and German answers from an
English instructions. The English is what the machinery is written in, not what it says back - English instructions. The English is what the machinery is written in, not what it says back -
with one exception, the stack's documentation for humans (§ "The stack's human documentation"). with one exception, the stack's documentation for humans (§ "The stack's human documentation").
### Why a rule alone did not hold
For a while the conversation rule was a sentence about files' languages in `AGENTS.md`, and an
instance on German pages still saw an agent that wrote its pages in German and everything else in
English: progress notes between tool calls, the closing report. Pages were right because the
step that writes a page reads `kb/CONVENTIONS.md` first; the chat was wrong because nothing had
put the value in front of the agent by then. Three things stacked up:
- **The rule sat in the wrong section.** A paragraph about the language of files does not read as
a rule about speaking, and it was phrased as a statement of fact rather than an instruction.
It now has a section of its own.
- **The value was one hop away.** `AGENTS.md` ships verbatim to every instance and so cannot
carry the instance's language; it names the file that does. A short ingest has written several
English messages before it opens that file.
- **The context is English.** Instructions, tool output and error text all are, and one short
message from the operator is outweighed by them - the more so for a weaker model.
So the value is also compiled into the published copy of each skill, as a one-line block under
its frontmatter that `wikitool instructions sync` writes: the language tag and a pointer to the
rule, nothing else. The rule stays in `AGENTS.md` and the value in `kb/CONVENTIONS.md`; the block
holds neither as text of its own, only puts the one in front of the task that needs it. It is a
tag, not a language name, because a table of names would be the compiler's opinion about
languages, which `conventions.py` deliberately has none of. A session with no skill is covered by
the `AGENTS.md` section alone.
## Where the line runs around a page type ## Where the line runs around a page type
A page type's contract is where the two languages meet most closely, and it is worth knowing A page type's contract is where the two languages meet most closely, and it is worth knowing
@@ -121,7 +149,7 @@ beside the template, but that split was made for ownership reasons - so an upgra
the guidance without overwriting what the instance chose - and it leaves this page's argument the guidance without overwriting what the instance chose - and it leaves this page's argument
untouched: each part is still written in the language its own reader needs, and a type-spec that untouched: each part is still written in the language its own reader needs, and a type-spec that
declares no `guidance:` keeps both halves in one file with exactly the same rule applying inside declares no `guidance:` keeps both halves in one file with exactly the same rule applying inside
it. [types/type-spec.md § Who owns a type-spec](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/types/type-spec.md#who-owns-a-type-spec) has it. [types/type-spec.md § Who owns a type-spec](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/types/type-spec.md#who-owns-a-type-spec) has
the split as a table, and [ownership-and-templates.md](Explanation-Ownership-and-templates) § "Where the the split as a table, and [ownership-and-templates.md](Explanation-Ownership-and-templates) § "Where the
file boundary used to strain" has what it cost to keep two audiences in one file for as long as file boundary used to strain" has what it cost to keep two audiences in one file for as long as
it did. it did.
+2 -2
@@ -1,10 +1,10 @@
*Generated from [`docs/explanation/model-and-effort-selection.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/docs/explanation/model-and-effort-selection.md) at v9.0.0 – edit the repository, not this wiki.* *Generated from [`docs/explanation/model-and-effort-selection.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/explanation/model-and-effort-selection.md) at v10.0.0 – edit the repository, not this wiki.*
# Choosing a Claude Code model and effort level # Choosing a Claude Code model and effort level
Claude Code exposes three choices this repo has an opinion on: which model a session itself Claude Code exposes three choices this repo has an opinion on: which model a session itself
runs as, what model a spawned subagent gets, and which `/code-review` effort level to pick. runs as, what model a spawned subagent gets, and which `/code-review` effort level to pick.
None of them are enforced anywhere - the gates in [instructions/gates.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/instructions/gates.md) None of them are enforced anywhere - the gates in [instructions/gates.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/instructions/gates.md)
are code precisely because a model cannot be talked out of them are code precisely because a model cannot be talked out of them
([why-gates-are-code.md](Explanation-Why-gates-are-code) makes that argument for gates; this page applies ([why-gates-are-code.md](Explanation-Why-gates-are-code) makes that argument for gates; this page applies
the same axis to who is holding the keyboard). What follows is a reference for making that choice the same axis to who is holding the keyboard). What follows is a reference for making that choice
+8 -8
@@ -1,11 +1,11 @@
*Generated from [`docs/explanation/ownership-and-templates.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/docs/explanation/ownership-and-templates.md) at v9.0.0 – edit the repository, not this wiki.* *Generated from [`docs/explanation/ownership-and-templates.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/explanation/ownership-and-templates.md) at v10.0.0 – edit the repository, not this wiki.*
# Ownership and Templates # Ownership and Templates
Chemenu ships two kinds of files side by side, and at a glance they look the same: both are Chemenu ships two kinds of files side by side, and at a glance they look the same: both are
plain markdown, both sit in the repo root or under `kb/`, both get read at session start. But a plain markdown, both sit in the repo root or under `kb/`, both get read at session start. But a
stack upgrade treats them completely differently. Some - [AGENTS.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/AGENTS.md), stack upgrade treats them completely differently. Some - [AGENTS.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/AGENTS.md),
[kb/CONTRACT.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/kb/CONTRACT.md), the per-stage contracts - are identical in every instance [kb/CONTRACT.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/kb/CONTRACT.md), the per-stage contracts - are identical in every instance
that runs this stack and are the next release's to replace (with one caveat about local edits, that runs this stack and are the next release's to replace (with one caveat about local edits,
below). Others - `USER.md`, below). Others - `USER.md`,
`SOUL.md`, `kb/CONVENTIONS.md`, `ENVIRONMENT.md` - describe one particular instance, and `SOUL.md`, `kb/CONVENTIONS.md`, `ENVIRONMENT.md` - describe one particular instance, and
@@ -27,7 +27,7 @@ overwriting them would silently erase a choice someone made on purpose.
The stack-owned files describe how the tool works. `kb/CONTRACT.md` opens by saying it holds The stack-owned files describe how the tool works. `kb/CONTRACT.md` opens by saying it holds
what `tools/wikitool` enforces or what follows mechanically from how it operates - see what `tools/wikitool` enforces or what follows mechanically from how it operates - see
[kb/CONTRACT.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/kb/CONTRACT.md), lines 10-13. That kind of statement doesn't vary by [kb/CONTRACT.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/kb/CONTRACT.md), lines 10-13. That kind of statement doesn't vary by
instance: the compiler behaves the same way regardless of who is running it, so the sentence instance: the compiler behaves the same way regardless of who is running it, so the sentence
describing that behavior can be copied byte-for-byte into every checkout without becoming describing that behavior can be copied byte-for-byte into every checkout without becoming
wrong anywhere. wrong anywhere.
@@ -35,7 +35,7 @@ wrong anywhere.
The instance-owned files describe a choice: which language pages are written in, what tone the The instance-owned files describe a choice: which language pages are written in, what tone the
agent takes, who the operator is, which git remote is authoritative, which MCP servers are agent takes, who the operator is, which git remote is authoritative, which MCP servers are
reachable. None of that follows from the tool's mechanics - two instances of the identical reachable. None of that follows from the tool's mechanics - two instances of the identical
stack can answer all of these differently and both be correct. [AGENTS.md § Personalization](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/AGENTS.md#personalization) stack can answer all of these differently and both be correct. [AGENTS.md § Personalization](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/AGENTS.md#personalization)
frames the split the same way for `kb/CONTRACT.md` versus `kb/CONVENTIONS.md`: "the split is by frames the split the same way for `kb/CONTRACT.md` versus `kb/CONVENTIONS.md`: "the split is by
who may change the sentence, not by what it is about." A rule about page structure could in who may change the sentence, not by what it is about." A rule about page structure could in
principle have been written per-instance too, but then every instance answering "not German" to principle have been written per-instance too, but then every instance answering "not German" to
@@ -61,7 +61,7 @@ the file itself already answers that.
For a while the boundary was written down as a list of paths - once in `dist_cmd.py`, once in For a while the boundary was written down as a list of paths - once in `dist_cmd.py`, once in
the merge procedure a private instance was told to run by hand, and once in the check that the merge procedure a private instance was told to run by hand, and once in the check that
procedure ended with. Three copies of one fact, which is the shape [AGENTS.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/AGENTS.md) procedure ended with. Three copies of one fact, which is the shape [AGENTS.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/AGENTS.md)
invariant 8 exists to forbid, and they drifted exactly as predicted: the hand-run procedure was invariant 8 exists to forbid, and they drifted exactly as predicted: the hand-run procedure was
still naming three paths after the collection contracts had moved to the instance's side of the still naming three paths after the collection contracts had moved to the instance's side of the
line, so it discarded upstream changes to files it had never heard of, while its own final check line, so it discarded upstream changes to files it had never heard of, while its own final check
@@ -83,7 +83,7 @@ The mechanism for instance-owned content is a `.template` file the distribution
of the real one - `USER.md.template`, `SOUL.md.template`, `kb/CONVENTIONS.md.template`, of the real one - `USER.md.template`, `SOUL.md.template`, `kb/CONVENTIONS.md.template`,
`ENVIRONMENT.md.template`. An alternative would have been to ship nothing at all and let a `ENVIRONMENT.md.template`. An alternative would have been to ship nothing at all and let a
brand-new instance start from a blank page. The template exists because a blank page doesn't brand-new instance start from a blank page. The template exists because a blank page doesn't
tell [instructions/setup-instance.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/instructions/setup-instance.md) what shape the answer tell [instructions/setup-instance.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/instructions/setup-instance.md) what shape the answer
should take, and it gives nothing for a validator to check afterward. should take, and it gives nothing for a validator to check afterward.
A template carries a placeholder value - a sentinel - in the fields that need a real answer. A template carries a placeholder value - a sentinel - in the fields that need a real answer.
@@ -91,7 +91,7 @@ Setup interviews the operator and replaces the sentinel with what they actually
gives `doctor` a mechanical way to tell "personalized" from "not yet": a file that still gives `doctor` a mechanical way to tell "personalized" from "not yet": a file that still
contains the sentinel hasn't been through setup, regardless of whether the file exists. That's contains the sentinel hasn't been through setup, regardless of whether the file exists. That's
also why `ENVIRONMENT.md` only warrants a WARN rather than a FAIL when absent - see also why `ENVIRONMENT.md` only warrants a WARN rather than a FAIL when absent - see
[AGENTS.md § Environment](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/AGENTS.md#environment) - while a missing or unfilled [AGENTS.md § Environment](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/AGENTS.md#environment) - while a missing or unfilled
`USER.md`/`SOUL.md`/`kb/CONVENTIONS.md` is a harder failure: `ENVIRONMENT.md` describes one `USER.md`/`SOUL.md`/`kb/CONVENTIONS.md` is a harder failure: `ENVIRONMENT.md` describes one
checkout among possibly several and is gitignored for that reason, so its absence is a normal checkout among possibly several and is gitignored for that reason, so its absence is a normal
state rather than a sign setup was skipped. state rather than a sign setup was skipped.
+13 -11
@@ -1,16 +1,16 @@
*Generated from [`docs/explanation/pipeline-rationale.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/docs/explanation/pipeline-rationale.md) at v9.0.0 – edit the repository, not this wiki.* *Generated from [`docs/explanation/pipeline-rationale.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/explanation/pipeline-rationale.md) at v10.0.0 – edit the repository, not this wiki.*
# Why the pipeline has four stages # Why the pipeline has four stages
Chemenu could, in principle, be one directory: drop a file in, ask a question, get an answer Chemenu could, in principle, be one directory: drop a file in, ask a question, get an answer
computed fresh each time. It isn't built that way. The pipeline in computed fresh each time. It isn't built that way. The pipeline in
[AGENTS.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/AGENTS.md#routing) - `raw/` -> `[types/ + tools/]` -> `kb/` -> `reports/`, with [AGENTS.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/AGENTS.md#routing) - `raw/` -> `[types/ + tools/]` -> `kb/` -> `reports/`, with
`work/` alongside rather than inside it - separates *material* from *meaning* from `work/` alongside rather than inside it - separates *material* from *meaning* from
*byproduct*, and each seam exists because collapsing it costs something specific. *byproduct*, and each seam exists because collapsing it costs something specific.
## Why raw material stays untouched ## Why raw material stays untouched
[raw/CONTRACT.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/raw/CONTRACT.md) keeps a source exactly as it arrived. The reasoning is [raw/CONTRACT.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/raw/CONTRACT.md) keeps a source exactly as it arrived. The reasoning is
simple once stated: the moment someone "cleans up" or reformats a source on the way in, the simple once stated: the moment someone "cleans up" or reformats a source on the way in, the
thing later claims get checked against is no longer the thing that was actually said. An thing later claims get checked against is no longer the thing that was actually said. An
immutable `raw/` means a citation always resolves to the original, not to somebody's tidied immutable `raw/` means a citation always resolves to the original, not to somebody's tidied
@@ -21,9 +21,11 @@ not yet reviewed and not yet a citation target, so its being gitignored and read
ingest session does not weaken anything - the boundary is the promotion into `raw/` itself, not ingest session does not weaken anything - the boundary is the promotion into `raw/` itself, not
the moment a human happened to drop a file somewhere. the moment a human happened to drop a file somewhere.
Once a document can arrive from *outside* - the MCP server's optional `submit` tool - "a human Once a document can arrive from *outside* - the MCP server's optional `submit` tool, a folder a
happened to drop a file somewhere" stops describing how everything reaches `incoming/`, so the sync client fills, a program of the checkout - "a human happened to drop a file somewhere" stops
near side of the boundary gets a stage of its own rather than a second meaning. `mcp-upload/` describing how everything reaches `incoming/`, so the near side of the boundary gets a stage of its
own rather than a second meaning. Every such channel ends in the same `quarantine/`, because what
the boundary protects against is the lack of a reviewer, not the transport. `quarantine/`
holds what nobody has looked at yet; `incoming/` holds what someone has. Two arrows, two holds what nobody has looked at yet; `incoming/` holds what someone has. Two arrows, two
different things being granted: `upload accept` grants *trust* (a human read the material and different things being granted: `upload accept` grants *trust* (a human read the material and
took responsibility for it), `raw accept` grants *immutability* (it becomes a citation target took responsibility for it), `raw accept` grants *immutability* (it becomes a citation target
@@ -33,19 +35,19 @@ promotion into `raw/` quietly doubling as the review step it cannot perform.
## Why extraction happens once, through a schema ## Why extraction happens once, through a schema
[types/type-spec.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/types/type-spec.md) is what stands between a raw file and a `kb/` page: [types/type-spec.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/types/type-spec.md) is what stands between a raw file and a `kb/` page:
a type-spec defines what a conforming instance of a page looks like, and the compiler a type-spec defines what a conforming instance of a page looks like, and the compiler
(`tools/wikitool`) applies it. The alternative - every query re-reading and re-interpreting the (`tools/wikitool`) applies it. The alternative - every query re-reading and re-interpreting the
source on demand - would mean paying the cost of understanding the material every single time, source on demand - would mean paying the cost of understanding the material every single time,
and getting a slightly different answer each time depending on how the question was phrased. and getting a slightly different answer each time depending on how the question was phrased.
Extracting once, against a fixed schema, turns "re-read and re-guess" into "look up what was Extracting once, against a fixed schema, turns "re-read and re-guess" into "look up what was
already compiled." That is the "never re-derive, always compile" principle from already compiled." That is the "never re-derive, always compile" principle from
[AGENTS.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/AGENTS.md): understanding a source is expensive and worth doing exactly once, [AGENTS.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/AGENTS.md): understanding a source is expensive and worth doing exactly once,
after which it becomes a cheap, stable lookup. after which it becomes a cheap, stable lookup.
## Why a `kb/` page has to stand on its own ## Why a `kb/` page has to stand on its own
[kb/CONTRACT.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/kb/CONTRACT.md) sets the bar for the compiled layer: a page should answer a [kb/CONTRACT.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/kb/CONTRACT.md) sets the bar for the compiled layer: a page should answer a
future question without sending the reader back to the source it came from. That's the payoff future question without sending the reader back to the source it came from. That's the payoff
of compiling in the first place - if every answer still bottomed out in "go re-read the raw of compiling in the first place - if every answer still bottomed out in "go re-read the raw
file," the `kb/` layer would just be a pointer with extra steps, and the cost of extraction file," the `kb/` layer would just be a pointer with extra steps, and the cost of extraction
@@ -54,7 +56,7 @@ consistent to query: the work of understanding is already sitting there, done.
## Why `reports/` doesn't need to be maintained ## Why `reports/` doesn't need to be maintained
[reports/CONTRACT.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/reports/CONTRACT.md) treats most of what lands in `reports/` - [reports/CONTRACT.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/reports/CONTRACT.md) treats most of what lands in `reports/` -
lint output, telemetry traces - as disposable. The structural content of a lint report can be lint output, telemetry traces - as disposable. The structural content of a lint report can be
recomputed from the tree at any commit, so keeping an old copy around would just be a second recomputed from the tree at any commit, so keeping an old copy around would just be a second
version of something the tool can already answer on demand, and a second copy is exactly the version of something the tool can already answer on demand, and a second copy is exactly the
@@ -66,7 +68,7 @@ distinction between what's recomputable and what isn't.
## Where `work/` fits ## Where `work/` fits
[work/CONTRACT.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/work/CONTRACT.md) describes a workshop, not a fifth pipeline stage: a [work/CONTRACT.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/work/CONTRACT.md) describes a workshop, not a fifth pipeline stage: a
place for the notes, extracts and open decisions of a task that spans more than one session, on place for the notes, extracts and open decisions of a task that spans more than one session, on
its way toward becoming a `kb/` page. It sits beside the raw -> kb -> reports flow rather than its way toward becoming a `kb/` page. It sits beside the raw -> kb -> reports flow rather than
inside it - closer in spirit to a desk than to a conveyor belt. inside it - closer in spirit to a desk than to a conveyor belt.
+8 -8
@@ -1,10 +1,10 @@
*Generated from [`docs/explanation/telemetry-and-evaluation.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/docs/explanation/telemetry-and-evaluation.md) at v9.0.0 – edit the repository, not this wiki.* *Generated from [`docs/explanation/telemetry-and-evaluation.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/explanation/telemetry-and-evaluation.md) at v10.0.0 – edit the repository, not this wiki.*
# Telemetry and evaluation # Telemetry and evaluation
How the stack observes what an agent did, and how that record turns into a score. This page explains How the stack observes what an agent did, and how that record turns into a score. This page explains
the design and points at the code. The rules an agent follows live in the design and points at the code. The rules an agent follows live in
[tools/CONTRACT.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/tools/CONTRACT.md) and [reports/CONTRACT.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/reports/CONTRACT.md). [tools/CONTRACT.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/tools/CONTRACT.md) and [reports/CONTRACT.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/reports/CONTRACT.md).
Turning tracing on and reading a session back is a how-to: Turning tracing on and reading a session back is a how-to:
[trace-and-score-sessions.md](How-to-Trace-and-score-sessions). [trace-and-score-sessions.md](How-to-Trace-and-score-sessions).
@@ -93,7 +93,7 @@ sides on the same id.
## The trace ## The trace
One JSON object per line, appended to `reports/telemetry/<session>/trace.jsonl`. The contract is One JSON object per line, appended to `reports/telemetry/<session>/trace.jsonl`. The contract is
[tools/chemenu/telemetry/schema.py](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/tools/chemenu/telemetry/schema.py). [tools/chemenu/telemetry/schema.py](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/tools/chemenu/telemetry/schema.py).
| Field | Meaning | | Field | Meaning |
|---|---| |---|---|
@@ -128,7 +128,7 @@ needs, and charging for a `search` would discourage the one habit that lowers co
## Whether it runs at all ## Whether it runs at all
The default depends on how the tree got here, not on one hard-coded switch. The default depends on how the tree got here, not on one hard-coded switch.
[tools/chemenu/telemetry/policy.py](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/tools/chemenu/telemetry/policy.py) is the one place that [tools/chemenu/telemetry/policy.py](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/tools/chemenu/telemetry/policy.py) is the one place that
resolves it, so `wikitool doctor`, the writer and the MCP server's start-up guard all answer the resolves it, so `wikitool doctor`, the writer and the MCP server's start-up guard all answer the
same question the same way: same question the same way:
@@ -144,8 +144,8 @@ where the traces are the stack's own measuring instrument. An instance commits t
first `publish`, so a clone of it on a second machine carries the stamp and defaults to off too. It first `publish`, so a clone of it on a second machine carries the stamp and defaults to off too. It
is a consuming instance, not a measuring stand. is a consuming instance, not a measuring stand.
**Turning it on for a distributed instance** takes a per-checkout `.wikitool-telemetry.json` at the **Turning it on for a distributed instance** takes a `telemetry` section in the per-checkout `.wikitool-local.json` at the
root, gitignored like `.wikitool-remotes.json` and for the same reason. The consent to write root, which is gitignored and for the same reason as every other setting that belongs to one checkout. The consent to write
prompts in cleartext to *this* disk belongs to the checkout, not the corpus, so a second clone must prompts in cleartext to *this* disk belongs to the checkout, not the corpus, so a second clone must
not inherit it silently. The setup instruction asks about it; nothing writes it automatically. Its not inherit it silently. The setup instruction asks about it; nothing writes it automatically. Its
shape is in the [configuration reference](Reference-Configuration#telemetry). shape is in the [configuration reference](Reference-Configuration#telemetry).
@@ -173,7 +173,7 @@ not on the existing history.
Prompts and assistant replies **are** recorded in cleartext, locally. A failure taxonomy cannot be Prompts and assistant replies **are** recorded in cleartext, locally. A failure taxonomy cannot be
read out of hashes, and building one is the first phase of any evaluation work. Four guards make read out of hashes, and building one is the first phase of any evaluation work. Four guards make
that defensible, all of them in that defensible, all of them in
[tools/chemenu/telemetry/scrub.py](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/tools/chemenu/telemetry/scrub.py), so there is one place to [tools/chemenu/telemetry/scrub.py](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/tools/chemenu/telemetry/scrub.py), so there is one place to
audit: audit:
1. **Secret scrubbing.** Tokens, keys, `Authorization:` headers and `SECRET=` assignments are 1. **Secret scrubbing.** Tokens, keys, `Authorization:` headers and `SECRET=` assignments are
@@ -182,7 +182,7 @@ audit:
3. **A kill switch.** `WIKI_TRACE_CONTENT=0` keeps only `<field>_length` and `<field>_sha256`. The 3. **A kill switch.** `WIKI_TRACE_CONTENT=0` keeps only `<field>_length` and `<field>_sha256`. The
digest is computed either way, so traces stay comparable. digest is computed either way, so traces stay comparable.
4. **`raw/` contents never enter a trace at all.** That text is data, not instruction 4. **`raw/` contents never enter a trace at all.** That text is data, not instruction
([AGENTS.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/AGENTS.md) invariant 4), and a trace gets read back later. Callers record a path ([AGENTS.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/AGENTS.md) invariant 4), and a trace gets read back later. Callers record a path
and a digest. and a digest.
Nothing leaves the machine. `reports/` is gitignored, no exporter is configured, and where a vendor Nothing leaves the machine. `reports/` is gitignored, no exporter is configured, and where a vendor
+1 -1
@@ -1,4 +1,4 @@
*Generated from [`docs/explanation/version-model.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/docs/explanation/version-model.md) at v9.0.0 – edit the repository, not this wiki.* *Generated from [`docs/explanation/version-model.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/explanation/version-model.md) at v10.0.0 – edit the repository, not this wiki.*
# Why the stack version splits compatibility from migration # Why the stack version splits compatibility from migration
+5 -5
@@ -1,11 +1,11 @@
*Generated from [`docs/explanation/why-gates-are-code.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/docs/explanation/why-gates-are-code.md) at v9.0.0 – edit the repository, not this wiki.* *Generated from [`docs/explanation/why-gates-are-code.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/explanation/why-gates-are-code.md) at v10.0.0 – edit the repository, not this wiki.*
# Why gates are code # Why gates are code
Chemenu's hard limits - the gates [AGENTS.md § Gates](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/AGENTS.md#gates) lists - live inside Chemenu's hard limits - the gates [AGENTS.md § Gates](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/AGENTS.md#gates) lists - live inside
`tools/wikitool`, not in a paragraph of instructions an agent reads and follows. The rules `tools/wikitool`, not in a paragraph of instructions an agent reads and follows. The rules
themselves, and what to do when one trips, are in [AGENTS.md § Gates](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/AGENTS.md#gates) and themselves, and what to do when one trips, are in [AGENTS.md § Gates](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/AGENTS.md#gates) and
[instructions/gates.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/instructions/gates.md). This page is only about the design choice [instructions/gates.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/instructions/gates.md). This page is only about the design choice
underneath them: why code, and why these mechanisms in particular. underneath them: why code, and why these mechanisms in particular.
<!-- wikitool:toc --> <!-- wikitool:toc -->
@@ -46,7 +46,7 @@ That's not a per-push judgment, it's a standing property of the checkout - true
every publish that checkout will ever attempt, not just this one. A confirm token would let an every publish that checkout will ever attempt, not just this one. A confirm token would let an
agent clear it once and then treat the answer as settled, which is exactly backwards for a agent clear it once and then treat the answer as settled, which is exactly backwards for a
question whose answer shouldn't move at all mid-session. The only way past it is the user question whose answer shouldn't move at all mid-session. The only way past it is the user
editing `.wikitool-remotes.json` directly, outside the gate's own flow. editing the `remotes` section of `.wikitool-local.json` directly, outside the gate's own flow.
The Upload Review Gate asks the Mass-Update Gate's own question - *is this change right?* - at The Upload Review Gate asks the Mass-Update Gate's own question - *is this change right?* - at
the opposite end of its size range: one file from a stranger instead of a changeset from the the opposite end of its size range: one file from a stranger instead of a changeset from the
+3 -1
@@ -1,4 +1,4 @@
*Generated from [`docs/explanation/README.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/docs/explanation/README.md) at v9.0.0 – edit the repository, not this wiki.* *Generated from [`docs/explanation/README.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/explanation/README.md) at v10.0.0 – edit the repository, not this wiki.*
# Explanation # Explanation
@@ -20,5 +20,7 @@ lives in `AGENTS.md` or a `CONTRACT.md`, and a page here only gives its reasons.
and a migration question separately and a migration question separately
- [knowledge-and-commitment.md](Explanation-Knowledge-and-commitment) - why commitments live in a task - [knowledge-and-commitment.md](Explanation-Knowledge-and-commitment) - why commitments live in a task
tracker rather than in `kb/` tracker rather than in `kb/`
- [configuration-tiers.md](Explanation-Configuration-tiers) - why settings sit in three tiers, and what
keeping secrets out of the tree does not protect against
- [model-and-effort-selection.md](Explanation-Model-and-effort-selection) - which model and effort suit - [model-and-effort-selection.md](Explanation-Model-and-effort-selection) - which model and effort suit
which kind of session, for the harness that has to choose which kind of session, for the harness that has to choose
+6 -6
@@ -1,4 +1,4 @@
*Generated from [`README.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/README.md) at v9.0.0 – edit the repository, not this wiki.* *Generated from [`README.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/README.md) at v10.0.0 – edit the repository, not this wiki.*
# Chemenu - Personal IT Knowledge Base # Chemenu - Personal IT Knowledge Base
@@ -53,7 +53,7 @@ The human documentation lives under `docs/`, sorted by what you want from it:
- [docs/reference/](Reference) - looking up: commands, exit codes, skills, page - [docs/reference/](Reference) - looking up: commands, exit codes, skills, page
types, configuration. The command reference is types, configuration. The command reference is
[docs/reference/commands/](Reference-Commands); agents use the same records in [docs/reference/commands/](Reference-Commands); agents use the same records in
[tools/CONTRACT.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/tools/CONTRACT.md). [tools/CONTRACT.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/tools/CONTRACT.md).
- [docs/explanation/](Explanation) - understanding: why the stack is built the way - [docs/explanation/](Explanation) - understanding: why the stack is built the way
it is. it is.
@@ -74,7 +74,7 @@ takes a sample document that ships with it through an ingest and a cited answer,
## Changelog ## Changelog
Changes to the wiki stack (schema, skills, `wikitool`, READMEs) are tracked in Changes to the wiki stack (schema, skills, `wikitool`, READMEs) are tracked in
[`CHANGES.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/CHANGES.md), not in an inline version history here. [`CHANGES.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/CHANGES.md), not in an inline version history here.
## Resources ## Resources
@@ -88,12 +88,12 @@ Chemenu is dual-licensed, because it is two things in one repository.
| Half | Licence | File | | Half | Licence | File |
|------|---------|------| |------|---------|------|
| The stack — `tools/`, `types/` | GNU AGPL-3.0-or-later | [LICENSE](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/LICENSE) | | The stack — `tools/`, `types/` | GNU AGPL-3.0-or-later | [LICENSE](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/LICENSE) |
| The content — `kb/`, `raw/`, `instructions/`, the `CONTRACT.md` layer, and the prose documents at the root | CC-BY-4.0 | [LICENSE-CONTENT](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/LICENSE-CONTENT) | | The content — `kb/`, `raw/`, `instructions/`, the `CONTRACT.md` layer, and the prose documents at the root | CC-BY-4.0 | [LICENSE-CONTENT](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/LICENSE-CONTENT) |
The boundary between the two is not a list someone maintains by hand: it is the The boundary between the two is not a list someone maintains by hand: it is the
file plan `tools/wikitool dist export` already computes, so it cannot drift out file plan `tools/wikitool dist export` already computes, so it cannot drift out
of agreement with what actually ships. [NOTICE](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/NOTICE) states that, and carries of agreement with what actually ships. [NOTICE](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/NOTICE) states that, and carries
the attribution for the vendored [Commonplace](https://github.com/zby/commonplace) the attribution for the vendored [Commonplace](https://github.com/zby/commonplace)
research base. research base.
+164
@@ -0,0 +1,164 @@
*Generated from [`docs/how-to/back-up-to-a-synced-folder.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/how-to/back-up-to-a-synced-folder.md) at v10.0.0 – edit the repository, not this wiki.*
# Back up to a synced folder
Back an instance up into a folder a sync client uploads - OneDrive, or any other client that keeps a
local folder in step with a server - when there is no git server to push to. The instance itself
stays in a folder the sync client does not touch; the synced folder receives a git repository to
push to and a copy of the files on this machine that are not in git. What the settings look like:
[configuration reference](Reference-Configuration#backup-to-a-synced-folder).
<!-- wikitool:toc -->
## Contents
- [Why the instance does not live in the synced folder](#why-the-instance-does-not-live-in-the-synced-folder)
- [Set it up](#set-it-up)
- [What is backed up, and what is not](#what-is-backed-up-and-what-is-not)
- [Restore](#restore)
- [One device writes](#one-device-writes)
<!-- /wikitool:toc -->
## Why the instance does not live in the synced folder
Putting the whole instance - `.git` included - into the synced folder looks like the simplest
backup, and fails in three ways:
- **Locks.** On Windows, OneDrive holds a file it is uploading open for a moment. `git gc` and
`git repack` then fail to delete or replace a file under `.git` ("unlink failed").
- **Placeholders.** With Files On-Demand, the client offloads files it considers unused and leaves a
placeholder behind; git then reads objects that first have to be downloaded, or are not there
when the machine is offline.
- **Conflict copies.** As soon as a second device syncs the same folder, two writers meet inside
`.git`, and the client resolves that by making copies of the files both changed - a repository
git can no longer read.
So the instance lives outside the synced folder, and the synced folder gets a layout git writes to
in a way a sync client handles well:
```
<target>/git/ a bare repository - origin of the instance; publish pushes to it
<target>/<machine>/ a copy of this machine's files that are not in git, plus .chemenu-backup.json
```
Git writes into the bare repository only on a push, and then mostly adds new files that never
change again.
`tools/wikitool doctor` reports `sync-folder` as a `WARN` when the instance's git directory lies in
a synced folder. On Windows, check that line even if you never chose such a folder: when Documents
or Desktop is redirected into OneDrive (Known Folder Move), an instance under Documents lies in
OneDrive without looking like it. Move the instance to a folder outside it - `C:\Wiki\<name>`, say -
before you go on.
## Set it up
1. **Pick the target**: a folder inside the synced folder, for example
`C:\Users\<you>\OneDrive - <company>\Chemenu-Backup`. It must not lie inside the instance.
2. **Write the `backup` section** into `.wikitool-local.json` at the root of the instance - by hand,
since its fields decide what leaves this machine and `config set` refuses them:
```json
{
"backup": {
"target": "C:\\Users\\<you>\\OneDrive - <company>\\Chemenu-Backup",
"machine_data": true,
"machine_name": "WORK-LAPTOP",
"extra": [".claude/settings.local.json"]
}
}
```
Backslashes in a JSON string are doubled. `machine_name` defaults to the computer's host name;
`extra` lists further paths inside the instance to copy, and can be left out.
3. **Tell the agent:**
> Set up the backup target from the `backup` section.
The agent runs `tools/wikitool backup init`. It creates `<target>/git` as a bare repository and
makes it `origin` - unless the instance already has an `origin` that points elsewhere: then it
changes nothing and shows you both URLs, and whether `origin` moves is yours to decide
(`git remote set-url origin "<target>\git"`).
4. **Allow the push target**, if your `.wikitool-local.json` has a `remotes` section: `backup init`
then prints that section with `<target>\git` added. Copy it into the file yourself - the list is
the Publish-Remote Gate, and no agent edits it. Without a `remotes` section there is nothing to do.
5. **Publish.** The next `tools/wikitool publish` pushes the whole history to `<target>/git` and,
with `machine_data` on, copies the machine data into `<target>/<machine>/`. From then on every
publish that pushes does both. `tools/wikitool backup run` copies the machine data on its own,
whenever you want it current without publishing - after dropping a file into `incoming/`, for
instance, which is otherwise only copied by the next publish.
6. **Check it:** `tools/wikitool doctor`. The `backup` line names the target and how old the last
copy of the machine data is; a `WARN` there names what is missing.
If a publish reports a `WARN` that the machine-data backup failed, the commit and the push have
still happened. Fix what the line names - a synced folder that is not there, a folder that is not
writable - and run `tools/wikitool backup run`. Another publish does not help: it has nothing new to
push.
## What is backed up, and what is not
| Path | Backed up | Why |
|------|-----------|-----|
| Everything committed (`kb/`, `raw/`, `wikitool.json`, ...) | yes, in `<target>/git` | that is what `publish` pushes |
| `.wikitool-local.json` | yes | the settings of this checkout |
| `ENVIRONMENT.md` | yes, if present | written by hand |
| Everything in `incoming/` but `.gitkeep` | yes | sources waiting there are backed up nowhere else |
| The `extra` paths | yes | what you named |
| `quarantine/` | no | material that arrived through an intake channel and that nobody has reviewed yet |
| `reports/` | no | regenerated by `lint`; telemetry holds prompts |
| `.wikitool-tools.json`, `tools/.venv/`, the published skills | no | the preflight and `instructions sync` rebuild them |
| The secrets file | **never** | it lies outside the instance, and a backup reads nothing outside it |
**Private values reach the synced folder.** `.wikitool-local.json` is copied as it is, so a plain
private value in it - an internal host name, an internal URL - ends up in OneDrive. A secret does
not: a secret is never in that file, only a reference to it, and the secrets file it resolves
through (`%APPDATA%\chemenu\<instance_id>\secrets.json`, with the sessions `raw login` stored)
lies outside the instance. An `extra` path that leads outside the instance - through `..` or a
link - is refused, so no setting can copy it either.
In `<target>/<machine>/` a run writes and deletes only what is its own: it removes a file only when
its previous run copied it and the instance no longer has it (a source `raw accept` moved out of
`incoming/`, say), and refuses a folder that is not empty and has no `.chemenu-backup.json`, rather
than copying into somebody else's folder. A link inside a copied directory is skipped and named,
never followed.
## Restore
On the same machine or a new one:
1. **Clone the repository and check it:**
```bash
git clone "<target>/git" "<new folder>"
cd "<new folder>"
git fsck --full
```
The clone is outside the synced folder, like the original; its `origin` is the bare repository
already.
2. **Copy the machine data back**: everything in `<target>/<machine>/` except
`.chemenu-backup.json`, into the new folder, keeping the paths. On Windows:
`robocopy "<target>\<machine>" "<new folder>" /E /XF .chemenu-backup.json`; elsewhere:
`rsync -a --exclude .chemenu-backup.json "<target>/<machine>/" "<new folder>/"`.
3. **Bootstrap the checkout** - tell the agent:
> Bootstrap this checkout.
It follows [instructions/bootstrap.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/instructions/bootstrap.md): the preflight, the
published skills, `doctor`.
4. **Provide the secrets again.** They were never in the backup. Put each value back where its
reference reads it - 1Password, the secrets file, or a `WIKITOOL_SECRET_<NAME>` variable
([use-1password-for-settings.md](How-to-Use-1password-for-settings)) - and sign in again with
`raw login` for each host that needs it. `doctor`'s `settings-secrets` line names every reference
that does not resolve yet.
`doctor` must then run through without a `FAIL`.
## One device writes
Only one device publishes to `<target>/git`. Two devices pushing into the same synced folder are two
writers again, only one step removed - and the sync client resolves that with conflict copies, not
with a merge. An instance on a second device gets a target of its own. The sync client is a backup
here, not a way to work on one instance from two devices.
A folder the sync client uploads can also hold a read-only version of selected pages, for readers
outside the repository: [Export pages to a synced folder](How-to-Export-to-a-synced-folder). It is a
separate folder with its own settings, and never the backup target.
+3 -3
@@ -1,4 +1,4 @@
*Generated from [`docs/how-to/browse-with-obsidian.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/docs/how-to/browse-with-obsidian.md) at v9.0.0 – edit the repository, not this wiki.* *Generated from [`docs/how-to/browse-with-obsidian.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/how-to/browse-with-obsidian.md) at v10.0.0 – edit the repository, not this wiki.*
# Browse the wiki in Obsidian # Browse the wiki in Obsidian
@@ -25,7 +25,7 @@ keeps consistent across the whole wiki. A hand edit can quietly break them. Rena
file in Obsidian is the clearest case: a title is the page's only identifier, so it also appears in file in Obsidian is the clearest case: a title is the page's only identifier, so it also appears in
every wikilink and citation that points at the page. A rename goes through every wikilink and citation that points at the page. A rename goes through
`tools/wikitool rename`, which updates every reference. The full procedure is `tools/wikitool rename`, which updates every reference. The full procedure is
[instructions/page-lifecycle.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/instructions/page-lifecycle.md). [instructions/page-lifecycle.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/instructions/page-lifecycle.md).
Generated files - `kb/index.md`, `kb/log.md`, `kb/provenance.md` and every `INDEX.md` - are Generated files - `kb/index.md`, `kb/log.md`, `kb/provenance.md` and every `INDEX.md` - are
rebuilt by the tool and never edited by hand. rebuilt by the tool and never edited by hand.
@@ -45,7 +45,7 @@ Knowing the naming rules makes the vault easier to find your way in:
page's title by case alone. A file's whole path below the instance root stays at 160 characters page's title by case alone. A file's whole path below the instance root stays at 160 characters
or fewer, so a Windows checkout works without long paths. `wikitool new` and `wikitool rename` or fewer, so a Windows checkout works without long paths. `wikitool new` and `wikitool rename`
refuse titles that break these rules, and `wikitool lint` reports existing ones. refuse titles that break these rules, and `wikitool lint` reports existing ones.
[kb/CONTRACT.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/kb/CONTRACT.md) has the full rule. [kb/CONTRACT.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/kb/CONTRACT.md) has the full rule.
## Letting the structure grow ## Letting the structure grow
+1 -1
@@ -1,4 +1,4 @@
*Generated from [`docs/how-to/capture-repository-docs.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/docs/how-to/capture-repository-docs.md) at v9.0.0 – edit the repository, not this wiki.* *Generated from [`docs/how-to/capture-repository-docs.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/how-to/capture-repository-docs.md) at v10.0.0 – edit the repository, not this wiki.*
# Capture documentation from a git repository # Capture documentation from a git repository
+139
@@ -0,0 +1,139 @@
*Generated from [`docs/how-to/collect-from-a-synced-folder.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/how-to/collect-from-a-synced-folder.md) at v10.0.0 – edit the repository, not this wiki.*
# Collect documents from a synced folder
Documents that are not typed or fetched at this terminal can reach the wiki through an intake
channel. Besides the MCP `submit` tool there are two channels that this checkout *pulls* from: a
folder that a sync client such as OneDrive fills (handler `folder`), and a program of your own
(handler `command`). Both end in the quarantine `quarantine/`, exactly like a submission through the
server, and both wait there for a human to run `wikitool upload accept` - the Upload Review Gate
stands between every channel and `incoming/`.
This guide creates a `folder` channel, explains what `upload collect` leaves alone and why, and shows
a `command` channel with an example script. The field reference is in the
[configuration reference](Reference-Configuration#intake-channels); the review is in
[ingesting a source](How-to-Ingest-a-source#a-document-submitted-from-outside).
<!-- wikitool:toc -->
## Contents
- [Create the folder channel](#create-the-folder-channel)
- [What stays in the folder, and why](#what-stays-in-the-folder-and-why)
- [`settle_seconds` and what it cannot promise](#settle_seconds-and-what-it-cannot-promise)
- [Deleting in a synced folder deletes everywhere](#deleting-in-a-synced-folder-deletes-everywhere)
- [A command channel](#a-command-channel)
- [Afterwards](#afterwards)
<!-- /wikitool:toc -->
## Create the folder channel
Channels are not written by `config set`: every field of the `intake` section opens a path into the
checkout, so it is yours to edit by hand in `.wikitool-local.json`.
```json
{
"intake": {
"channels": {
"onedrive": {
"handler": "folder",
"path": "/home/you/OneDrive/wiki-inbox",
"settle_seconds": 60,
"max_bytes": 10485760,
"allowed_extensions": [".pdf", ".docx", ".md", ".txt"]
}
}
}
}
```
`path` has to be absolute and lie outside the instance. Check it with `tools/wikitool doctor`, then
pull:
```bash
tools/wikitool upload collect # every pull channel
tools/wikitool upload collect onedrive # only this one
tools/wikitool upload list
```
A collected file is stored unchanged in `quarantine/<id>/` with a manifest that names the channel,
the handler and the original path. A folder knows nobody who dropped a file, so the submitter is
recorded as `unknown`, with the handler as its source: do not trust the name or content of such a
file any more than that of a stranger's.
## What stays in the folder, and why
`upload collect` reads the top level of the folder only, and it removes one kind of thing: a file it
has just stored. Everything else stays where it is and is reported with its reason:
| Entry | Why it stays |
|---|---|
| A subdirectory | Nothing descends into it, and nothing deletes a directory. |
| A name starting with `.` or `~$`, a hidden or system file | Editors and sync clients keep temporary files like these next to the real one. |
| A symbolic link | Following it could leave the folder. |
| An extension outside `allowed_extensions`, or a file above `max_bytes` | The same limits as for `submit`. |
| A file younger than `settle_seconds` | It may still be arriving. |
| A file that changed while it was being read or copied | The source is removed only if it still has the size and sha256 that were stored. |
A file whose sha256 already waits in the quarantine is reported as a duplicate, with the id of the
existing submission, and is removed from the folder.
## `settle_seconds` and what it cannot promise
A sync client writes a file in pieces, so a file that is half there looks like a complete, smaller
one. `settle_seconds` (default 60) skips files whose modification or creation time is younger than
that, and the check that the source did not change during the copy catches the rest. Neither can see
a client that pauses for longer than the interval in the middle of a file and then resumes: such a
file is stored incomplete, and the review is where you notice it. Raise the value if your client
works over a slow link.
## Deleting in a synced folder deletes everywhere
A file removed from a folder that OneDrive syncs is removed on every device that syncs it. That is
what a collected file should do - it is now in the quarantine - but it also means a collect on one
machine empties the folder for all of them. The client's recycle bin keeps the file for a while; the
quarantine copy, which is never synced, is the one to rely on. Collect from one machine only.
## A command channel
A `command` channel runs a program of your own, which fetches documents from somewhere that has no
synced folder - a mailbox, a share, a scanner. The program is named in `commands` and referenced from
the channel; the section stays valid only if every reference resolves.
```json
{
"intake": {
"commands": {
"scan-mail": ["/home/you/bin/fetch-attachments.sh"]
},
"channels": {
"mail": { "handler": "command", "command": "scan-mail" }
}
}
}
```
The program is started without a shell and with stdin closed, with an empty staging directory under
`quarantine/.staging/` appended as its last argument. It has 300 seconds. It leaves the files it
wants to hand over directly in that directory:
```bash
#!/bin/sh
# fetch-attachments.sh <staging-dir>
set -eu
out="$1"
rclone copy remote:wiki-inbox "$out" --max-age 1d
```
A program that exits non-zero, does not start or times out fails its channel: nothing is taken, the
staging directory is removed, and the last 2000 characters of its stderr are shown. Other channels
still run, and `upload collect` exits 1 if any channel failed. With no pull channel configured it
exits 0 with "nothing to collect".
## Afterwards
The agent does the same at the start of an ingest when you name no file: `upload collect`, then
`upload list`, then a report of what waits. It never runs `upload accept` for you.
The other direction - a read-only version of selected pages written out to a synced folder - is
[Export pages to a synced folder](How-to-Export-to-a-synced-folder). An export is never a source: the
export marker keeps it out of `raw/`.
+4 -2
@@ -1,4 +1,4 @@
*Generated from [`docs/how-to/export-guidelines.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/docs/how-to/export-guidelines.md) at v9.0.0 – edit the repository, not this wiki.* *Generated from [`docs/how-to/export-guidelines.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/how-to/export-guidelines.md) at v10.0.0 – edit the repository, not this wiki.*
# Export guidelines to captured repositories # Export guidelines to captured repositories
@@ -52,5 +52,7 @@ did not see.
## Related ## Related
- [Guideline export commands](Reference-Commands-Guideline-export) - [Export commands](Reference-Commands-Exports)
- [Export pages to a synced folder](How-to-Export-to-a-synced-folder) - the other export, a read-only
version of `kb/` pages
- [Why the gates are code](Explanation-Why-gates-are-code) - [Why the gates are code](Explanation-Why-gates-are-code)
+157
@@ -0,0 +1,157 @@
*Generated from [`docs/how-to/export-to-a-synced-folder.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/how-to/export-to-a-synced-folder.md) at v10.0.0 – edit the repository, not this wiki.*
# Export to a synced folder
Give a reader outside the repository a read-only version of part of `kb/`: the selected pages,
written as files into a folder - typically one a sync client such as OneDrive uploads, so that a
Microsoft 365 tenant can index them. The export is derived. It is regenerated from the committed
pages on every run, nobody edits it, and nothing in it flows back into the wiki. What the settings
look like: [configuration reference](Reference-Configuration#export-to-a-folder).
<!-- wikitool:toc -->
## Contents
- [Pick the pages and the format](#pick-the-pages-and-the-format)
- [Set it up](#set-it-up)
- [What a run does to the folder](#what-a-run-does-to-the-folder)
- [Export after every publish](#export-after-every-publish)
- [Convert the pages with a program of your own](#convert-the-pages-with-a-program-of-your-own)
- [What does not come back in](#what-does-not-come-back-in)
- [Related](#related)
<!-- /wikitool:toc -->
## Pick the pages and the format
- **Which pages.** Only what a target's `include` selects is written; there is no "everything". A
selection is a list of `search` predicates that must all hold - `tags=shared`, `kind=system`,
`!draft` - and a page that fits any selection of the target is exported. Try a selection with
`tools/wikitool search --field tags=shared` first.
- **Which format.** Two ways, equally valid:
- `markdown` writes the pages as `.md` files. It needs nothing but the folder.
- `command` hands those files to a converter of your own and exports what it produces - Word
documents with `pandoc`, say.
Whether a Microsoft 365 tenant indexes `.md` files for Copilot is not settled for every tenant.
Try a small `markdown` target first; if the tenant does not pick the files up, the same pages go
through a `command` target as a format it does.
## Set it up
1. **Pick the folder**: a folder inside the synced folder, for example
`C:\Users\<you>\OneDrive - <company>\Chemenu-Lesefassung`. It must be empty or not exist yet,
and it must lie outside the instance, outside every other export target, outside every `intake`
folder and outside `backup.target`.
2. **Write the `export` section** into `.wikitool-local.json` at the root of the instance - by hand,
since its fields decide what leaves this machine and which program runs, and `config set`
refuses them:
```json
{
"export": {
"targets": {
"shared": {
"handler": "markdown",
"path": "C:\\Users\\<you>\\OneDrive - <company>\\Chemenu-Lesefassung",
"include": [["tags=shared"]],
"frontmatter": "summary",
"links": "relative",
"after_publish": false
}
}
}
}
```
`frontmatter` decides what follows the export marker in each file: `minimal` nothing, `summary`
(the default) the title, type, summary and modified date, `full` the page's whole frontmatter.
`links: relative` (the default) turns a link to an exported page into a relative Markdown link;
`links: text` leaves the page's name. A link to a page that is not exported is always plain text.
3. **Tell the agent:**
> Export the pages.
The agent runs `tools/wikitool export run`. It reports per target which pages are new, which
changed and which were removed, and how many stayed as they were. Name a target to run only that
one: `tools/wikitool export run shared`.
4. **Check it:** `tools/wikitool doctor`. The `export` line lists the targets; a `FAIL` there names
what is wrong with the section.
The export reads the committed pages. A `kb/` or `types/` with uncommitted changes is refused, so
every file names the commit its pages come from. Publish first.
## What a run does to the folder
The folder holds the pages and one file of the stack's own, `.wikitool-export.json`, which lists
what the last run wrote.
- **Only its own files.** A run removes a file only when it is listed and no longer exported, and a
folder only when the run created it. Anything else in the folder stays as it is.
- **Nobody edits an export.** A listed file that someone changed stops the target before anything is
written, and the message names the file. Move or delete it and run again. A listed file that is
missing is written again.
- **A folder that is not an export is refused.** A folder that holds files and no manifest is
somebody's folder; the run writes nothing into it.
- **An empty selection is an error.** A run that finds no page does not delete the earlier export.
- **An unchanged export stays untouched.** A run that has nothing to change writes nothing, not even
a modification time - the sync client has nothing to upload.
- **A growing selection needs no confirmation.** New pages are reported as new; read the list
before the sync client uploads them.
Targets are independent: one that fails is reported with its reason and the others still run; the
command then exits 1.
## Export after every publish
With `"after_publish": true` on a target, every `tools/wikitool publish` that committed refreshes
it afterwards - with `--no-push` too, since the export reads the commit, not the remote. If that
export fails, `publish` prints a `WARN` with the reason and still exits 0: the commit and the push
stand. Fix what the line names and run `tools/wikitool export run`; another `publish` does not
repeat it. A malformed `export` section stops `publish` before it commits, but only when some target
sets `after_publish`.
## Convert the pages with a program of your own
A target with `"handler": "command"` names an entry of `commands`:
```json
{
"export": {
"targets": {
"as-word": {
"handler": "command",
"command": "pandoc-docx",
"path": "C:\\Users\\<you>\\OneDrive - <company>\\Chemenu-Word",
"include": [["tags=shared"]]
}
},
"commands": {
"pandoc-docx": ["pwsh", "-NoProfile", "-File", "C:\\tools\\pandoc-docx.ps1"]
}
}
}
```
The pages are rendered into a staging directory first. The program runs without a shell, with stdin
closed and a limit of 300 seconds, and gets two more arguments: the directory with the Markdown
files and an empty directory for its output. On exit 0 the regular files it left there (below any
subdirectory, symlinks left out and reported) become the content of the target; the manifest and
all the rules above apply to them as to Markdown. Any other end - a non-zero exit, a timeout, a
program that does not start - leaves the target exactly as it was and prints the end of the
program's stderr. The staging directory is always removed.
## What does not come back in
An export is never a source. Every Markdown file starts with a frontmatter block whose first key is
`wikitool_export`, and `tools/wikitool raw accept` and `raw capture` refuse a file that carries it.
A converter may lose that key, so `raw accept` also refuses any file in `incoming/` that is byte
for byte one of the files in the manifest of a configured target - and warns, without refusing,
when a target has no manifest yet or the `export` section is broken.
## Related
- [Configuration reference](Reference-Configuration#export-to-a-folder)
- [Export commands](Reference-Commands-Exports)
- [Export guidelines to captured repositories](How-to-Export-guidelines) - the other export, one file
pushed into a repository
- [Back up to a synced folder](How-to-Back-up-to-a-synced-folder) - the same kind of folder, for the
backup instead
+130
@@ -0,0 +1,130 @@
*Generated from [`docs/how-to/fetch-pages-behind-a-login.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/how-to/fetch-pages-behind-a-login.md) at v10.0.0 – edit the repository, not this wiki.*
# Fetch pages behind a login
Let `tools/wikitool raw fetch` fetch the articles of a website you are subscribed to, with your own
login: you sign in once in a browser window, and the wiki fetches that website's pages with the
session your sign-in left until it expires. No password is stored, and the agent never sees one.
The same page covers websites whose pages only appear in a browser, and handing a website to a
fetch program of your own.
Whether a website's terms allow you to fetch your subscription this way is yours to check; the
stack does not check it and offers no advice on it.
Without any of this, a page behind a login can still be saved from your browser and handed to
`raw fetch --html` ([Ingest a source](How-to-Ingest-a-source)) - one page at a time, by hand.
<!-- wikitool:toc -->
## Contents
- [1. Install the browser component](#1-install-the-browser-component)
- [2. Describe the website's login](#2-describe-the-websites-login)
- [3. Route the website through your session](#3-route-the-website-through-your-session)
- [4. Sign in](#4-sign-in)
- [5. Ingest as usual](#5-ingest-as-usual)
- [Pages that only appear in a browser](#pages-that-only-appear-in-a-browser)
- [A fetch program of your own](#a-fetch-program-of-your-own)
- [Where the session is kept](#where-the-session-is-kept)
<!-- /wikitool:toc -->
## 1. Install the browser component
Signing in needs a real browser window, so it needs the optional browser component: a Python
package and a Chromium of its own, installed into the instance's `tools/.venv`. You install it -
the agent never does:
```bash
tools/preflight.sh --with browser
```
In PowerShell 7: `pwsh -NoProfile -ExecutionPolicy Bypass -File tools/preflight.ps1 --with browser`.
Later preflight runs keep it up to date. `tools/wikitool doctor` reports it on its `browser` line.
## 2. Describe the website's login
The website needs a site handler that says where you sign in and how a page shows that it arrived
signed in. Add a `session` block to the website's handler in `sites/`, or write a handler just for
it ([Write a site handler](How-to-Write-a-site-handler)):
```json
{
"host": "news.example.com",
"summary": "A subscription news site: articles need the subscriber's session.",
"session": {
"login": "https://news.example.com/login",
"signed_in": {"tag": "a", "class": "logout"},
"signed_out": {"class": "paywall-teaser"}
}
}
```
`signed_in` is something only a signed-in page carries - a logout link, an account menu.
`signed_out` is something only a page that was not carries - the teaser box that asks you to
subscribe. One of the two is enough; open a saved article and a saved teaser and pick an element
that tells them apart. [sites/CONTRACT.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/sites/CONTRACT.md) has the exact format.
## 3. Route the website through your session
The handler alone changes nothing. Tell the agent:
> Route news.example.com through my session.
It runs `tools/wikitool config set fetch.routes '{"news.example.com": "session"}'`, keeping any
host already listed. The setting is committed with the instance, so every clone fetches the
website the same way - each with its own user's session.
## 4. Sign in
Run this yourself - it opens a browser window and waits for you:
```bash
tools/wikitool raw login news.example.com
```
Sign in as you always do, captcha and second factor included, then close the window. The session
is stored outside the instance's folder. Sign in again the same way whenever the wiki asks.
## 5. Ingest as usual
Give the agent the article's URL as for any web page ([Ingest a source](How-to-Ingest-a-source)).
The capture's header names the website whose session was used (`session: news.example.com`),
never the session itself.
When your session has expired, or the page came back as a teaser, the agent stops: `raw fetch`
writes nothing and asks you to run `raw login` again. Nothing is kept from a teaser in silence.
## Pages that only appear in a browser
Some websites send an empty frame and draw the text with JavaScript; `raw fetch` then captures
almost nothing. Add `"render": true` to the website's handler: its pages are then fetched through
the browser component, with your session where the website has one, and the header says
`rendered: true`. The `.html` is then the page as the browser drew it, not what the server sent -
which is why it happens only where a handler says so.
## A fetch program of your own
A website can instead go to a program you run - a fetch service on another machine, say. Add it to
`.wikitool-local.json` by hand, since it names a program to run and `config set` does not write
such a field:
```json
{
"schema": 1,
"fetch": {"fetchers": {"homelab": ["/usr/local/bin/fetch-page", "--html"]}}
}
```
and route the host to it: `tools/wikitool config set fetch.routes '{"intranet.example.org": "homelab"}'`.
The program gets the URL as its last argument and prints the page's HTML; anything else - an exit
other than 0, no output - stops `raw fetch` with the program's error output, and the built-in
fetch is not tried instead. The `fetch` section is in
[docs/reference/configuration.md](Reference-Configuration#fetch-routes).
## Where the session is kept
In the secrets file in your own configuration directory - `~/.config/chemenu/<instance_id>/secrets.json`,
`%APPDATA%\chemenu\<instance_id>\secrets.json` on Windows - under `session_<host>`, readable by
you only, outside every working tree. It never reaches a commit or a `wikitool` output. It does
not keep an agent with a shell in your account from reading it, any more than any other secret
there: [configuration-tiers.md](Explanation-Configuration-tiers) says what keeping secrets
out of the tree protects against and what it does not.
+34 -6
@@ -1,4 +1,4 @@
*Generated from [`docs/how-to/ingest-a-source.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/docs/how-to/ingest-a-source.md) at v9.0.0 – edit the repository, not this wiki.* *Generated from [`docs/how-to/ingest-a-source.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/how-to/ingest-a-source.md) at v10.0.0 – edit the repository, not this wiki.*
# Ingest a source # Ingest a source
@@ -7,6 +7,17 @@ entity and concept pages it touches, cross-referenced and published. The agent d
the `wiki-ingest` skill ([skill catalogue](Reference-Skills)). This page covers what you hand the `wiki-ingest` skill ([skill catalogue](Reference-Skills)). This page covers what you hand
it and where it waits for you. it and where it waits for you.
<!-- wikitool:toc -->
## Contents
- [What you prepare](#what-you-prepare)
- [What you say](#what-you-say)
- [Where the agent asks you](#where-the-agent-asks-you)
- [What you get back](#what-you-get-back)
- [A document submitted from outside](#a-document-submitted-from-outside)
- [Related](#related)
<!-- /wikitool:toc -->
## What you prepare ## What you prepare
Put the material into `incoming/` at the root of the instance. You don't have to classify it or Put the material into `incoming/` at the root of the instance. You don't have to classify it or
@@ -18,6 +29,17 @@ pick a directory:
- **A web page** needs no download of your own. Name the URL to the agent, and - **A web page** needs no download of your own. Name the URL to the agent, and
`tools/wikitool raw fetch` puts two files into `incoming/`: the HTML exactly as received, and a `tools/wikitool raw fetch` puts two files into `incoming/`: the HTML exactly as received, and a
text derived from it with a header recording where and when it was fetched. text derived from it with a header recording where and when it was fetched.
For the pages of a website the stack knows - Microsoft Learn, for one - the tool fetches the
same page in the website's standard form (the English one, whatever language your link was in),
leaves the page's menus, sign-in notice and feedback footer out of the derived text, and prints
`CHECK` lines for what it could not do for you: the agent acts on those before it accepts the
capture. Your instance can switch such a handler off, choose another language and add hint lines
of its own per website; see [the configuration reference](Reference-Configuration).
Some pages are an index of further pages - the overview of a PowerShell module on Microsoft Learn
lists its cmdlet pages. The tool then says so in a `CHECK` line, and `raw fetch <url> --scope`
fetches the page and those pages as one folder, `incoming/<name>/`, which is accepted whole like
any other folder. It respects the website's `robots.txt`, pauses between requests and refuses a
page that offers more than the instance's `max_pages`.
- **A document that is not text** - a PDF, a Word, PowerPoint or Excel file, a mail (`.eml` or - **A document that is not text** - a PDF, a Word, PowerPoint or Excel file, a mail (`.eml` or
Outlook `.msg`) or a ZIP archive - is Outlook `.msg`) or a ZIP archive - is
put into `incoming/` as it is. `tools/wikitool raw derive incoming/<file>` turns it into a folder with the put into `incoming/` as it is. `tools/wikitool raw derive incoming/<file>` turns it into a folder with the
@@ -27,7 +49,10 @@ pick a directory:
unpacked into a folder instead, and a mail's attachments land beside it. A scanned PDF has no unpacked into a folder instead, and a mail's attachments land beside it. A scanned PDF has no
text to derive - the tool warns, and there is no OCR. text to derive - the tool warns, and there is no OCR.
- **A page behind a paywall or a login** is saved from your browser into `incoming/`, HTML only. The - **A page behind a paywall or a login** is saved from your browser into `incoming/`, HTML only. The
agent derives the same text from that file with `raw fetch --html`. agent derives the same text from that file with `raw fetch --html`. For a website you read often,
sign in once instead and let the wiki fetch it with your session:
[Fetch pages behind a login](How-to-Fetch-pages-behind-a-login). When that session has expired the
agent stops and asks you to run `tools/wikitool raw login <host>` - it never signs in itself.
Where a file ends up in `raw/` is computed by `tools/wikitool raw accept`: a `YYYY/MM` shard for the Where a file ends up in `raw/` is computed by `tools/wikitool raw accept`: a `YYYY/MM` shard for the
day it was accepted. So is whether several files of one source get bundled. You never choose either. day it was accepted. So is whether several files of one source get bundled. You never choose either.
@@ -49,7 +74,7 @@ what is waiting and names the entry the next bare `Ingest` will take.
- **Before promoting the file into `raw/`**, the agent asks how faithful the capture is - **Before promoting the file into `raw/`**, the agent asks how faithful the capture is
(`fidelity`) and what the material may claim about its subject (`authority`). Both are recorded (`fidelity`) and what the material may claim about its subject (`authority`). Both are recorded
once and never guessed: you know the answers now, and nobody can recover them later. once and never guessed: you know the answers now, and nobody can recover them later.
[raw/CONTRACT.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/raw/CONTRACT.md) defines the values. [raw/CONTRACT.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/raw/CONTRACT.md) defines the values.
- **Before writing pages**, the agent presents the key takeaways and asks which points matter most - **Before writing pages**, the agent presents the key takeaways and asks which points matter most
and what to emphasise. The pages follow your answer. and what to emphasise. The pages follow your answer.
- **When it publishes**, a source that touches ten or more files stops at the Mass-Update Gate - **When it publishes**, a source that touches ten or more files stops at the Mass-Update Gate
@@ -70,17 +95,20 @@ Every ten sources, the agent suggests a lint run - see [Maintain the wiki](How-t
## A document submitted from outside ## A document submitted from outside
When the [MCP read server](How-to-Set-up-the-mcp-server) has its optional `submit` tool turned on, When the [MCP read server](How-to-Set-up-the-mcp-server) has its optional `submit` tool turned on,
documents can arrive from another machine. They land in the quarantine `mcp-upload/`, not in documents can arrive from another machine. A folder a sync client fills and a program of the
checkout are two more ways in ([Collect documents from a synced
folder](How-to-Collect-from-a-synced-folder)). All of them land in the quarantine `quarantine/`, not in
`incoming/`, and nothing else reads them there. Before an ingest can start, a human has to promote `incoming/`, and nothing else reads them there. Before an ingest can start, a human has to promote
the submission: the submission:
```bash ```bash
tools/wikitool upload collect # pull the folder and command channels into the quarantine
tools/wikitool upload list # what is waiting tools/wikitool upload list # what is waiting
tools/wikitool upload show <id> # one submission's manifest: who sent it, size, checksum tools/wikitool upload show <id> # one submission's manifest: who sent it, size, checksum
``` ```
Ask the agent to review the queue, and it follows Ask the agent to review the queue, and it follows
[instructions/ingest-queue.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/instructions/ingest-queue.md). Accepting a submission stops at [instructions/ingest-queue.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/instructions/ingest-queue.md). Accepting a submission stops at
the Upload Review Gate (exit 42), which shows you the manifest and the `--confirm` line. Once you the Upload Review Gate (exit 42), which shows you the manifest and the `--confirm` line. Once you
approve, the file is in `incoming/`, and from there it is ingested like any other. Rejecting it needs approve, the file is in `incoming/`, and from there it is ingested like any other. Rejecting it needs
no clearance. The file is deleted, and its checksum and your reason stay in the ledger. no clearance. The file is deleted, and its checksum and your reason stay in the ledger.
@@ -89,5 +117,5 @@ no clearance. The file is deleted, and its checksum and your reason stay in the
- [Capture documentation from a repository](How-to-Capture-repository-docs) - for material that lives - [Capture documentation from a repository](How-to-Capture-repository-docs) - for material that lives
in git and will change. in git and will change.
- [raw/CONTRACT.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/raw/CONTRACT.md) - what belongs in `raw/`, and why its content is data, - [raw/CONTRACT.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/raw/CONTRACT.md) - what belongs in `raw/`, and why its content is data,
never instructions. never instructions.
+8 -3
@@ -1,4 +1,4 @@
*Generated from [`docs/how-to/maintain-the-wiki.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/docs/how-to/maintain-the-wiki.md) at v9.0.0 – edit the repository, not this wiki.* *Generated from [`docs/how-to/maintain-the-wiki.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/how-to/maintain-the-wiki.md) at v10.0.0 – edit the repository, not this wiki.*
# Maintain the wiki # Maintain the wiki
@@ -15,7 +15,7 @@ Lint the wiki
Do this periodically. The agent suggests it on its own every ten sources. The full schedule - which Do this periodically. The agent suggests it on its own every ten sources. The full schedule - which
task runs how often, and with which command - is the task runs how often, and with which command - is the
[maintenance schedule](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/tools/CONTRACT.md#maintenance-schedule) in `tools/CONTRACT.md`, next [maintenance schedule](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/tools/CONTRACT.md#maintenance-schedule) in `tools/CONTRACT.md`, next
to the command records it depends on. to the command records it depends on.
## What you get back ## What you get back
@@ -60,7 +60,12 @@ tools/wikitool sources trace --page "HA Integration" # which raw files this p
needs. A refusal usually means the task should be split, not pushed through. needs. A refusal usually means the task should be split, not pushed through.
- **No reachable remote.** Without `--no-push`, `publish` refuses with exit 1 before committing when - **No reachable remote.** Without `--no-push`, `publish` refuses with exit 1 before committing when
the remote is not configured or cannot be reached. An instance that is meant to stay local passes the remote is not configured or cannot be reached. An instance that is meant to stay local passes
`--no-push` on every publish. `--no-push` on every publish - or, without a git server, pushes to a synced folder instead:
[back-up-to-a-synced-folder.md](How-to-Back-up-to-a-synced-folder).
- **A failed machine-data backup.** With a backup into a synced folder, a publish that pushed also
copies this machine's files that are not in git. If that copy fails, `publish` prints a `WARN`
line and still succeeds - the commit and the push stand. Fix what the line names, then run
`tools/wikitool backup run`; another publish does not repeat the copy.
Every gate, and which command it belongs to: [exit codes and gates](Reference-Exit-codes). Every gate, and which command it belongs to: [exit codes and gates](Reference-Exit-codes).
Why they are enforced in code: [why-gates-are-code.md](Explanation-Why-gates-are-code). Why they are enforced in code: [why-gates-are-code.md](Explanation-Why-gates-are-code).
+1 -1
@@ -1,4 +1,4 @@
*Generated from [`docs/how-to/query-the-wiki.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/docs/how-to/query-the-wiki.md) at v9.0.0 – edit the repository, not this wiki.* *Generated from [`docs/how-to/query-the-wiki.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/how-to/query-the-wiki.md) at v10.0.0 – edit the repository, not this wiki.*
# Query the wiki # Query the wiki
+3 -3
@@ -1,4 +1,4 @@
*Generated from [`docs/how-to/run-the-weekly-review.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/docs/how-to/run-the-weekly-review.md) at v9.0.0 – edit the repository, not this wiki.* *Generated from [`docs/how-to/run-the-weekly-review.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/how-to/run-the-weekly-review.md) at v10.0.0 – edit the repository, not this wiki.*
# Run the weekly review # Run the weekly review
@@ -20,8 +20,8 @@ syncing: [knowledge-and-commitment.md](Explanation-Knowledge-and-commitment).
## What you prepare ## What you prepare
A task tracker, configured for this checkout in `.wikitool-tasks.json`. Super Productivity and A task tracker, configured in the settings (the `tasks` section and the section of its provider). Super Productivity and
CalDAV (for example Nextcloud Tasks) are supported. The file's shape, and the difference between a CalDAV (for example Nextcloud Tasks) are supported. Their shape, and the difference between a
tracker the wiki can only read and one it can write to, is in the tracker the wiki can only read and one it can write to, is in the
[configuration reference](Reference-Configuration#task-tracker). Without a tracker, the [configuration reference](Reference-Configuration#task-tracker). Without a tracker, the
review has nothing to join against. That is a valid state, though, and the pages work without one. review has nothing to join against. That is a valid state, though, and the pages work without one.
+11 -10
@@ -1,4 +1,4 @@
*Generated from [`docs/how-to/set-up-the-mcp-server.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/docs/how-to/set-up-the-mcp-server.md) at v9.0.0 – edit the repository, not this wiki.* *Generated from [`docs/how-to/set-up-the-mcp-server.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/how-to/set-up-the-mcp-server.md) at v10.0.0 – edit the repository, not this wiki.*
# Set up the MCP read server # Set up the MCP read server
@@ -7,7 +7,7 @@ machine - another agent, a chat client, a service - can ask it the same question
answers. This page covers what you set up around the server: the client, the authentication in answers. This page covers what you set up around the server: the client, the authentication in
front of it, the optional `submit` path, and how to check that it works. Installing, starting and front of it, the optional `submit` path, and how to check that it works. Installing, starting and
syncing the server itself is syncing the server itself is
[instructions/mcp-read-server.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/instructions/mcp-read-server.md), which an agent follows [instructions/mcp-read-server.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/instructions/mcp-read-server.md), which an agent follows
and which is readable for you as well. and which is readable for you as well.
<!-- wikitool:toc --> <!-- wikitool:toc -->
@@ -162,18 +162,19 @@ reviewed yet.
## Optional: the submit path ## Optional: the submit path
Without this, `submit` does not exist as a tool - not unused, but not registered. A file Without this, `submit` does not exist as a tool - not unused, but not registered. A
`.wikitool-upload.json` in the served corpus turns it on. Its fields, defaults and what a malformed channel with the handler `mcp` in the `intake` section of the served checkout's
file does are in the [configuration reference](Reference-Configuration#mcp-submissions); a `.wikitool-local.json` turns it on. Its fields, defaults and what a malformed section does are in
broken file never means "no limits", it stops the server from starting. the [configuration reference](Reference-Configuration#intake-channels); a broken section never
means "no limits", it stops the server from starting.
**With `submit` on, the middleware has a second duty.** It has to set the identity header itself, on **With `submit` on, the middleware has a second duty.** It has to set the identity header itself, on
every authenticated request, and discard any copy the client sent. The server trusts that header as every authenticated request, and discard any copy the client sent. The server trusts that header as
the value for `submitter`. A header the client could set itself would be a claim, not an identity. the value for `submitter`. A header the client could set itself would be a claim, not an identity.
If `identity_header` names a header the middleware does not set, every submission is refused for If the channel's `identity_header` names a header the middleware does not set, every submission is refused for
lack of an identity. lack of an identity.
Submitted files land in `mcp-upload/<id>/`, which is gitignored and read by no other command. From Submitted files land in `quarantine/<id>/`, which is gitignored and read by no other command. From
there a human promotes or rejects them, in the same checkout as the server rather than in its there a human promotes or rejects them, in the same checkout as the server rather than in its
process. That review is part of process. That review is part of
[ingesting a source](How-to-Ingest-a-source#a-document-submitted-from-outside). [ingesting a source](How-to-Ingest-a-source#a-document-submitted-from-outside).
@@ -230,7 +231,7 @@ the client starts a different Python than the instance's. Give the client the ab
**`"commit": null` in every answer**: the served tree has uncommitted changes. Either the sync is not **`"commit": null` in every answer**: the served tree has uncommitted changes. Either the sync is not
running, or something writes into the corpus that has no business there. The five read tools never running, or something writes into the corpus that has no business there. The five read tools never
write. `submit`, when it is on, writes only to `mcp-upload/`, which is gitignored and so is never a write. `submit`, when it is on, writes only to `quarantine/`, which is gitignored and so is never a
cause of `"commit": null` either. cause of `"commit": null` either.
**`commit` names an old revision**: the sync timer is not running. **`commit` names an old revision**: the sync timer is not running.
@@ -248,6 +249,6 @@ Deployment specifics - the cluster, ingress hosts, the secret store, the GitOps
private infrastructure, and this repository is public. private infrastructure, and this repository is public.
There is no container image for the server yet. Until there is, the path above - the venv, There is no container image for the server yet. Until there is, the path above - the venv,
`python -m chemenu.mcp`, a proxy in front - is the complete one. With `submit` on, `mcp-upload/` `python -m chemenu.mcp`, a proxy in front - is the complete one. With `submit` on, `quarantine/`
has to survive restarts the same way the rest of the checkout does. Otherwise a submitted file that has to survive restarts the same way the rest of the checkout does. Otherwise a submitted file that
nobody has reviewed yet loses its quarantine. nobody has reviewed yet loses its quarantine.
+10 -10
@@ -1,4 +1,4 @@
*Generated from [`docs/how-to/trace-and-score-sessions.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/docs/how-to/trace-and-score-sessions.md) at v9.0.0 – edit the repository, not this wiki.* *Generated from [`docs/how-to/trace-and-score-sessions.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/how-to/trace-and-score-sessions.md) at v10.0.0 – edit the repository, not this wiki.*
# Trace and score agent sessions # Trace and score agent sessions
@@ -23,13 +23,13 @@ score means are covered in [telemetry-and-evaluation.md](Explanation-Telemetry-a
**Turn tracing on, if it is off.** It is on in a clone of the origin repository and off in an **Turn tracing on, if it is off.** It is on in a clone of the origin repository and off in an
instance installed from a release. `tools/wikitool doctor` reports which state this checkout is in, instance installed from a release. `tools/wikitool doctor` reports which state this checkout is in,
and why. To turn it on for one checkout, create `.wikitool-telemetry.json` at the root: and why. To turn it on for one checkout, set it in the per-checkout settings:
```json ```bash
{ "enabled": true } tools/wikitool config set telemetry.enabled true
``` ```
The file and its other keys are in the [configuration reference](Reference-Configuration#telemetry). This writes the `telemetry` section of `.wikitool-local.json`. The section and its other keys are in the [configuration reference](Reference-Configuration#telemetry).
Everything stays on this machine: traces land under `reports/telemetry/`, which is gitignored, and Everything stays on this machine: traces land under `reports/telemetry/`, which is gitignored, and
no exporter is configured. no exporter is configured.
@@ -40,7 +40,7 @@ yourself keeps one task in one trace:
export WIKITOOL_SESSION_ID="my-task" export WIKITOOL_SESSION_ID="my-task"
``` ```
[instructions/session-setup.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/instructions/session-setup.md) has the line for each shell, [instructions/session-setup.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/instructions/session-setup.md) has the line for each shell,
and what to do on a harness that starts a fresh shell for every command. and what to do on a harness that starts a fresh shell for every command.
## Reading a session back ## Reading a session back
@@ -110,7 +110,7 @@ guarantee: an agent that reorders the flags gets around it.
### Copilot CLI ### Copilot CLI
[.github/hooks/wiki-trace.json](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/.github/hooks/wiki-trace.json) is committed, so a clone [.github/hooks/wiki-trace.json](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/.github/hooks/wiki-trace.json) is committed, so a clone
brings its own tracing. Eleven events go to `tools/trace_ingest.py`. `disableAllHooks` in brings its own tracing. Eleven events go to `tools/trace_ingest.py`. `disableAllHooks` in
`.github/copilot/settings.json` turns them off without deleting anything. `userPromptSubmitted` is `.github/copilot/settings.json` turns them off without deleting anything. `userPromptSubmitted` is
among them, so `clearance-ended-the-turn` can be scored on Copilot CLI with no change. among them, so `clearance-ended-the-turn` can be scored on Copilot CLI with no change.
@@ -146,7 +146,7 @@ hooks:
VS Code Chat has no hooks, so nothing observes a session while it runs. Its chronicle store VS Code Chat has no hooks, so nothing observes a session while it runs. Its chronicle store
(`session-store.db`, the same schema as Copilot CLI's) keeps sessions, turns and touched files, and (`session-store.db`, the same schema as Copilot CLI's) keeps sessions, turns and touched files, and
[tools/import_chronicle.py](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/tools/import_chronicle.py) rebuilds a trace from it afterwards: [tools/import_chronicle.py](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/tools/import_chronicle.py) rebuilds a trace from it afterwards:
```bash ```bash
tools/import_chronicle.py --dry-run # this repository's sessions tools/import_chronicle.py --dry-run # this repository's sessions
@@ -160,8 +160,8 @@ that a file was touched, not that a tool was about to run.
### Mistral Vibe ### Mistral Vibe
[.vibe/hooks.toml](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/.vibe/hooks.toml) declares the three hooks Vibe has, and [.vibe/hooks.toml](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/.vibe/hooks.toml) declares the three hooks Vibe has, and
[.vibe/config.toml](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/.vibe/config.toml) keeps the telemetry settings in the repository rather [.vibe/config.toml](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/.vibe/config.toml) keeps the telemetry settings in the repository rather
than in someone's shell profile. Both were checked against the installed CLI's own loader than in someone's shell profile. Both were checked against the installed CLI's own loader
(`mistral-vibe 2.24.2`) rather than against its documentation. What that turned up: (`mistral-vibe 2.24.2`) rather than against its documentation. What that turned up:
+11 -11
@@ -1,4 +1,4 @@
*Generated from [`docs/how-to/troubleshoot.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/docs/how-to/troubleshoot.md) at v9.0.0 – edit the repository, not this wiki.* *Generated from [`docs/how-to/troubleshoot.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/how-to/troubleshoot.md) at v10.0.0 – edit the repository, not this wiki.*
# Troubleshoot an instance # Troubleshoot an instance
@@ -20,7 +20,7 @@ tools/preflight.sh
In PowerShell 7 on Windows, the call is In PowerShell 7 on Windows, the call is
`pwsh -NoProfile -ExecutionPolicy Bypass -File tools/preflight.ps1`. The `-ExecutionPolicy Bypass` `pwsh -NoProfile -ExecutionPolicy Bypass -File tools/preflight.ps1`. The `-ExecutionPolicy Bypass`
applies to that one process only and changes no setting. applies to that one process only and changes no setting.
[instructions/preflight.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/instructions/preflight.md) is the procedure, and its output says [instructions/preflight.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/instructions/preflight.md) is the procedure, and its output says
what to install if anything is missing. what to install if anything is missing.
## The agent offers no skills (`wiki-ingest`, `wiki-query`, ...) ## The agent offers no skills (`wiki-ingest`, `wiki-query`, ...)
@@ -37,7 +37,7 @@ Then restart the agent session. A harness reads its skills only when the session
`USER.md` or `SOUL.md` is missing, or still carries the sentinel line from its template. A renamed `USER.md` or `SOUL.md` is missing, or still carries the sentinel line from its template. A renamed
template does not count as a filled one. Ask the agent to run the personalization step of template does not count as a filled one. Ask the agent to run the personalization step of
[instructions/setup-instance.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/instructions/setup-instance.md). For a further clone of an [instructions/setup-instance.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/instructions/setup-instance.md). For a further clone of an
older instance, that is the only step to catch up on. older instance, that is the only step to catch up on.
## `doctor` reports `environment: WARN` ## `doctor` reports `environment: WARN`
@@ -48,11 +48,11 @@ a valid state.
## `doctor` reports `telemetry: WARN` ## `doctor` reports `telemetry: WARN`
`.wikitool-telemetry.json` holds something telemetry does not read as written: unreadable JSON, a The `telemetry` section of `.wikitool-local.json` holds something telemetry does not read as
value that is not an object, an `enabled` that is not `true` or `false`, a cap that is not a written: an unreadable settings file, a value that is not an object, an `enabled` that is not `true` or `false`, a cap that is not a
positive whole number, or a key it does not know. Telemetry itself never stops on this. It ignores positive whole number, or a key it does not know. Telemetry itself never stops on this. It ignores
the file or the value, or reads `"no"` as on, and the line names the field. Correct the file to the the file or the value, or reads `"no"` as on, and the line names the field. Correct the section to the
shape in the [configuration reference](Reference-Configuration#telemetry), or delete it to shape in the [configuration reference](Reference-Configuration#telemetry), or remove it to
get the default back. get the default back.
## `new` stops with "No author configured" ## `new` stops with "No author configured"
@@ -65,19 +65,19 @@ export `WIKI_AUTHOR` ([configuration](Reference-Configuration)).
Expected when ten or more files are counted, for example on the very first commit of a new instance Expected when ten or more files are counted, for example on the very first commit of a new instance
or after a lint pass. It is not an error. It asks that a human see the output: the complete file or after a lint pass. It is not an error. It asks that a human see the output: the complete file
list and the exact `--confirm <token>` line that publishes once you approve. list and the exact `--confirm <token>` line that publishes once you approve.
[instructions/gates.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/instructions/gates.md) has the details. [instructions/gates.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/instructions/gates.md) has the details.
## `publish` ends with exit 42 (Publish-Remote Gate) ## `publish` ends with exit 42 (Publish-Remote Gate)
This checkout has a `.wikitool-remotes.json`, and the remote being pushed to is not listed in it. This checkout has a `remotes.allowed_push_urls` list in `.wikitool-local.json`, and the remote being pushed to is not listed in it.
Also not an error: the output names the push URL it would write to and the URLs that are allowed. Also not an error: the output names the push URL it would write to and the URLs that are allowed.
Unlike the Mass-Update Gate, there is **no token and no flag** here. If the target really is right, Unlike the Mass-Update Gate, there is **no token and no flag** here. If the target really is right,
you add its URL to the file yourself. An agent that edits the file to get past the refusal is you add its URL to that list yourself. An agent that edits the list to get past the refusal is
opening a gate on its own initiative, which it must never do. opening a gate on its own initiative, which it must never do.
## Setup or an update fails, and you want to report it ## Setup or an update fails, and you want to report it
Ask the agent to follow [instructions/bug-report.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/instructions/bug-report.md), or call Ask the agent to follow [instructions/bug-report.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/instructions/bug-report.md), or call
`tools/bugreport` yourself. It works the same from Bash, Git Bash and PowerShell. It finds a `tools/bugreport` yourself. It works the same from Bash, Git Bash and PowerShell. It finds a
Python 3.8 or newer by itself, skipping the Store aliases `python3` points at on Windows. It needs Python 3.8 or newer by itself, skipping the Store aliases `python3` points at on Windows. It needs
neither the preflight nor a working `wikitool`, and if it finds no Python, it says why. neither the preflight nor a working `wikitool`, and if it finds no Python, it says why.
+11 -2
@@ -1,10 +1,10 @@
*Generated from [`docs/how-to/update-an-instance.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/docs/how-to/update-an-instance.md) at v9.0.0 – edit the repository, not this wiki.* *Generated from [`docs/how-to/update-an-instance.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/how-to/update-an-instance.md) at v10.0.0 – edit the repository, not this wiki.*
# Update an instance # Update an instance
Bring an instance installed from a release up to a newer release of the stack: the tools, types, Bring an instance installed from a release up to a newer release of the stack: the tools, types,
instructions and contracts. Your pages stay yours. The agent carries out the upgrade by following instructions and contracts. Your pages stay yours. The agent carries out the upgrade by following
[instructions/upgrade-instance.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/instructions/upgrade-instance.md), which sets the order of [instructions/upgrade-instance.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/instructions/upgrade-instance.md), which sets the order of
the steps, what each one decides, and where the session has to restart. This page covers the the steps, what each one decides, and where the session has to restart. This page covers the
decision before it, and what you'll see along the way. decision before it, and what you'll see along the way.
@@ -95,6 +95,15 @@ old form. `doctor` keeps warning until the migration is done, and a later sessio
`tools/wikitool migrate status`. The agent never records a migration it did not carry out, because `tools/wikitool migrate status`. The agent never records a migration it did not carry out, because
the content's version would then describe a shape the pages are not in. the content's version would then describe a shape the pages are not in.
**A migration can also be owed per checkout.** Most migrations rewrite pages: they are done once,
and every other clone of the instance receives the result with its next pull. The 10.0.0 migration
moves settings instead, out of gitignored files that belong to one checkout, so a pull does not
carry it. After another clone has pulled the new release, run `tools/wikitool config migrate`
there too. Until then `doctor` reports each leftover file as a `FAIL`, and a command that needs
one of those settings stops with exit 42 and names that command. `migrate done` is still recorded
only once per instance
([10.0.0-settings-tiers.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/instructions/migrations/10.0.0-settings-tiers.md)).
If a step fails for a reason that isn't obvious, the agent stops and offers you a bug report rather If a step fails for a reason that isn't obvious, the agent stops and offers you a bug report rather
than improvising ([Troubleshoot](How-to-Troubleshoot)). than improvising ([Troubleshoot](How-to-Troubleshoot)).
+60
@@ -0,0 +1,60 @@
*Generated from [`docs/how-to/use-1password-for-settings.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/how-to/use-1password-for-settings.md) at v10.0.0 – edit the repository, not this wiki.*
# Use 1Password for settings
Keep secrets and private values (host names, internal URLs) out of the working tree by storing
them in 1Password and leaving only an `op://` reference in the settings. The committed
`wikitool.json` then works on every machine signed in to the same 1Password account, and in CI.
What the files and references are: [configuration reference](Reference-Configuration#tiered-settings).
Why, and what this does not protect against: [configuration-tiers.md](Explanation-Configuration-tiers).
## On a desktop
1. **Install the 1Password CLI** (`op`) and turn on its integration with the 1Password desktop
app, so `op` asks the app instead of keeping a session of its own. 1Password's own
documentation describes both steps for each platform.
2. **Run the preflight again** - `tools/preflight.sh`, or in PowerShell 7
`pwsh -NoProfile -ExecutionPolicy Bypass -File tools/preflight.ps1`. It records `op` in
`.wikitool-tools.json` and shows it as `OK`; without `op` the line reads `ABSENT`, which is no
problem until a reference needs it.
3. **Make a vault of its own for this instance**, and put each secret or private value into an
item there. A vault of its own limits what an unlocked `op` can reach.
4. **Copy each value's reference** (`op://<vault>/<item>/<field>`) - the 1Password app offers it
on the field - and tell the agent which setting it belongs to:
> Set `<section>.<field>` to the reference `op://<vault>/<item>/<field>`.
The agent runs `tools/wikitool config set <section>.<field> --ref op://<vault>/<item>/<field>`,
which writes the reference into `wikitool.json`. Never paste the value itself into the chat:
the agent should never see it.
5. **Check it:** `tools/wikitool doctor`. The `settings-secrets` line says how many references
resolve. When the desktop app asks to approve access, that is the prompt doing its job - a
locked app makes the line a `WARN`, and a command that needs the value stops with exit 42 until
you unlock it.
## Without 1Password
A reference by name works the same way without `op`:
- set the reference: `tools/wikitool config set <section>.<field> --ref <name>`;
- put the value into `WIKITOOL_SECRET_<NAME>` (the name upper-cased) in the environment, or into
the secrets file as `{"<name>": "<value>"}`: `~/.config/chemenu/<instance_id>/secrets.json`
(`$XDG_CONFIG_HOME` instead of `~/.config` when it is set), readable by you only
(`chmod 600`) - on Windows `%APPDATA%\chemenu\<instance_id>\secrets.json`. The `instance_id`
is in `wikitool.json`.
## In CI
There are two ways, and which one fits depends on the form of the references:
- **`op` itself, with a service-account token** - for settings that hold `op://` references.
Install `op` in the job, run the preflight so it records it, and set `OP_SERVICE_ACCOUNT_TOKEN`;
the references then resolve exactly as on a desktop.
- **1Password's action resolves the values first** - for settings that hold references by name.
`1password/load-secrets-action` with a service-account token reads an `op://` reference into a
variable before `wikitool` starts; name the variable `WIKITOOL_SECRET_<NAME>` and use
`{"ref": "<name>"}` in the settings. This repository's own CI does this, in
`.gitea/workflows/ci.yml`. An `op://` reference in the settings is not resolved this way: it
still needs `op`.
Give the service account access to the instance's own vault only.
+87
@@ -0,0 +1,87 @@
*Generated from [`docs/how-to/write-a-site-handler.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/how-to/write-a-site-handler.md) at v10.0.0 – edit the repository, not this wiki.*
# Write a site handler
Teach `tools/wikitool raw fetch` how one website serves its pages: the standard form of its URLs,
the page furniture to leave out of the derived text, and a hint or two for whoever reads the
capture. A handler is a JSON file in `sites/` at the root of the instance - data, not a program -
so every clone fetches the website the same way.
The stack already ships handlers, for `learn.microsoft.com` among others. They are the same kind of
file, so the quickest start is to copy one:
```bash
cp tools/chemenu/sites/handlers/learn-microsoft.json sites/my-website.json
```
The file name without `.json` is the handler's name: lower-case letters, digits and hyphens.
## 1. Find out what the website does
Fetch a page of the website and read the derived `.md`:
```bash
tools/wikitool raw fetch <url>
```
Two things are worth a handler:
- **The same page under several URLs** - a language segment, a tracking parameter. A rewrite
turns every spelling into one standard form, so two people ingesting the page get the same source.
- **Page furniture in the derived text** - a sign-in notice, an action bar, a feedback footer. Open
the saved `.html`, find the element that holds it, and note its `id`, `class` or an attribute.
## 2. Write the file
A small handler that drops a cookie banner and a footer on one host:
```json
{
"host": "docs.example.org",
"summary": "Drops the cookie banner and the feedback footer.",
"drop": [
{"id": "cookie-banner"},
{"class": "feedback", "inside": "footer"}
]
}
```
`host` is required and matched exactly, in lower case. `path` narrows the handler to a part of the
website; among the handlers of a host the longest matching path wins. The other fields - `rewrites`,
`checks`, `parent` - and what each selector may say are in [sites/CONTRACT.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/sites/CONTRACT.md).
A page that is an index - a module overview, a table of contents - can name its subpages, so that
`raw fetch <url> --scope` fetches it and them as one bundle in `incoming/<name>/`:
```json
"subpages": {"links": {"tag": "a", "inside": "td"}}
```
The links are the `<a>` elements inside a table cell; only those on the same host and under the
page's own path count. A page with such a handler prints a `CHECK` line with their number when it
is fetched without `--scope`.
## 3. Try it
Fetch a page again, or derive a saved one without a fetch:
```bash
tools/wikitool raw fetch <url>
tools/wikitool raw fetch --html <saved.html> --url <url>
```
The header of the derived text then says `site: <name> (instance)`, and the furniture is gone. A
check shows up as a `CHECK` line when it applies to the page.
`tools/wikitool doctor` lists the handlers of the instance and fails on a file that cannot be used,
naming the file and the reason. Such a file also stops a `raw fetch` of its own host with exit 1,
before anything is fetched or written, so a handler that did not apply never leaves furniture in a
source unnoticed.
## Replace or extend a stack handler
A handler of the instance for the same host and path as a stack handler **replaces** it. To keep
what the stack knows and add to it, name it as `parent` and give the new file a `path` of its own.
To switch a handler off without deleting it, list its name under `disabled` in the `sites` section
of the settings - see [Configuration](Reference-Configuration#site-handlers).
+12 -1
@@ -1,4 +1,4 @@
*Generated from [`docs/how-to/README.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/docs/how-to/README.md) at v9.0.0 – edit the repository, not this wiki.* *Generated from [`docs/how-to/README.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/how-to/README.md) at v10.0.0 – edit the repository, not this wiki.*
# How-to guides # How-to guides
@@ -12,8 +12,13 @@ Working with the wiki:
- [Ingest a source](How-to-Ingest-a-source) - a file, a folder, a web page, or a document submitted - [Ingest a source](How-to-Ingest-a-source) - a file, a folder, a web page, or a document submitted
through the MCP server through the MCP server
- [Write a site handler](How-to-Write-a-site-handler) - teach `raw fetch` how a website serves its pages
- [Fetch pages behind a login](How-to-Fetch-pages-behind-a-login) - with your own session, through a
browser, or through a fetch program of your own
- [Capture documentation from a git repository](How-to-Capture-repository-docs) - and keep it current - [Capture documentation from a git repository](How-to-Capture-repository-docs) - and keep it current
- [Export guidelines to captured repositories](How-to-Export-guidelines) - [Export guidelines to captured repositories](How-to-Export-guidelines)
- [Export to a synced folder](How-to-Export-to-a-synced-folder) - a read-only version of selected pages
for a reader outside the repository, OneDrive and Microsoft 365 included
- [Query the wiki](How-to-Query-the-wiki) - asking, and searching yourself - [Query the wiki](How-to-Query-the-wiki) - asking, and searching yourself
- [Maintain the wiki](How-to-Maintain-the-wiki) - lint, provenance, and the gates on the way to a publish - [Maintain the wiki](How-to-Maintain-the-wiki) - lint, provenance, and the gates on the way to a publish
- [Run the weekly review](How-to-Run-the-weekly-review) - commitments in a task tracker against the - [Run the weekly review](How-to-Run-the-weekly-review) - commitments in a task tracker against the
@@ -27,5 +32,11 @@ Running the instance:
- [Set up the MCP read server](How-to-Set-up-the-mcp-server) - for a consumer that is not a terminal - [Set up the MCP read server](How-to-Set-up-the-mcp-server) - for a consumer that is not a terminal
on this machine on this machine
- [Trace and score agent sessions](How-to-Trace-and-score-sessions) - [Trace and score agent sessions](How-to-Trace-and-score-sessions)
- [Use 1Password for settings](How-to-Use-1password-for-settings) - secrets and private values out
of the tree, on the desktop and in CI
- [Collect documents from a synced folder](How-to-Collect-from-a-synced-folder) - a folder OneDrive and
the like fill, or a program of your own, as an intake channel into the quarantine
- [Back up to a synced folder](How-to-Back-up-to-a-synced-folder) - OneDrive and the like as the
backup of an instance without a git server, and restoring from it
Installing a new instance is [INSTALL.md](Install) at the repository root. Installing a new instance is [INSTALL.md](Install) at the repository root.
+21 -4
@@ -1,4 +1,4 @@
*Generated from [`INSTALL.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/INSTALL.md) at v9.0.0 – edit the repository, not this wiki.* *Generated from [`INSTALL.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/INSTALL.md) at v10.0.0 – edit the repository, not this wiki.*
# Install a Chemenu instance # Install a Chemenu instance
@@ -7,7 +7,7 @@ way to get one: an agent installs the **latest release** into an empty folder yo
agent runs the steps, following `instructions/setup-instance.md` from that same release. This agent runs the steps, following `instructions/setup-instance.md` from that same release. This
page covers what you prepare beforehand, the sentence you give the agent, what it asks you, and page covers what you prepare beforehand, the sentence you give the agent, what it asks you, and
what to do when it stops. The full command reference is in what to do when it stops. The full command reference is in
[tools/CONTRACT.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/tools/CONTRACT.md). [tools/CONTRACT.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/tools/CONTRACT.md).
The optional **MCP read server** is set up separately, once the instance runs: The optional **MCP read server** is set up separately, once the instance runs:
[docs/how-to/set-up-the-mcp-server.md](How-to-Set-up-the-mcp-server). It serves the same [docs/how-to/set-up-the-mcp-server.md](How-to-Set-up-the-mcp-server). It serves the same
@@ -31,6 +31,21 @@ preflight names the install command for your system. The list is generated from
- **ripgrep (rg)** - **ripgrep (rg)**
<!-- /wikitool:prerequisites --> <!-- /wikitool:prerequisites -->
Optional: the **1Password CLI (`op`)**, if you want secrets and private values in the settings to
come from 1Password ([use-1password-for-settings.md](How-to-Use-1password-for-settings)).
The preflight records it when it finds it and never asks for it.
Optional: the **browser component**, if you want the wiki to fetch pages of a website you are
subscribed to with your own login, or pages that only appear in a browser
([fetch-pages-behind-a-login.md](How-to-Fetch-pages-behind-a-login)). It is a Python
package plus a Chromium of its own, a few hundred megabytes, installed into the instance's
`tools/.venv` and nowhere else. You install it yourself, once the instance runs:
`tools/preflight.sh --with browser` (PowerShell 7:
`pwsh -NoProfile -ExecutionPolicy Bypass -File tools/preflight.ps1 --with browser`). Every later
preflight run keeps it up to date. On Linux, the browser may need system libraries the preflight
cannot install; Playwright's message then names them, for you or whoever administers the machine.
Native Windows is not yet verified for it.
You also need: You also need:
- **An agent harness**: Claude Code, GitHub Copilot (in VS Code or as a CLI), Codex CLI or - **An agent harness**: Claude Code, GitHub Copilot (in VS Code or as a CLI), Codex CLI or
@@ -87,7 +102,9 @@ repository:
needed). needed).
- <!-- setup-question: remote --> **Remote** - for an empty clone, only the confirmation that - <!-- setup-question: remote --> **Remote** - for an empty clone, only the confirmation that
`origin` is right; otherwise a URL, if you want to push to a server. Without a remote the `origin` is right; otherwise a URL, if you want to push to a server. Without a remote the
instance stays local, and every `publish` runs with `--no-push`. instance stays local, and every `publish` runs with `--no-push`. Without a git server but with a
synced folder (OneDrive and the like), the agent offers a backup into that folder instead:
[docs/how-to/back-up-to-a-synced-folder.md](How-to-Back-up-to-a-synced-folder).
- <!-- setup-question: kb-language --> **Language and tone of the pages** - they go into - <!-- setup-question: kb-language --> **Language and tone of the pages** - they go into
`kb/CONVENTIONS.md`, plus `kb/<name>/COLLECTION.md` per collection. Ready-made profiles, `kb/CONVENTIONS.md`, plus `kb/<name>/COLLECTION.md` per collection. Ready-made profiles,
including a complete German one, are in `instructions/kb-profiles.md`. Decide this **before the including a complete German one, are in `instructions/kb-profiles.md`. Decide this **before the
@@ -177,7 +194,7 @@ tools/wikitool doctor
This checks in one call: dependencies, author resolution, git identity/branch/remote, published This checks in one call: dependencies, author resolution, git identity/branch/remote, published
skills, structure (collection contracts, generated files), personalization (`USER.md`/`SOUL.md` skills, structure (collection contracts, generated files), personalization (`USER.md`/`SOUL.md`
present **and** filled in), the optional environment note (`ENVIRONMENT.md`), the task tracker present **and** filled in), the optional environment note (`ENVIRONMENT.md`), the task tracker
(`.wikitool-tasks.json` - if it is missing, that is `OK`; if a provider is configured, also (the `tasks` section of the settings - if it is missing, that is `OK`; if a provider is configured, also
whether its read path is ready and its API is answering right now, neither ever a `FAIL`) and the whether its read path is ready and its API is answering right now, neither ever a `FAIL`) and the
session id. `OK`/`WARN` are harmless (a missing remote, for example, is a valid end state); only a session id. `OK`/`WARN` are harmless (a missing remote, for example, is a valid end state); only a
`FAIL` ends with exit 1, and every line names its own fix command. `FAIL` ends with exit 1, and every line names its own fix command.
+1 -1
@@ -1,4 +1,4 @@
*Generated from [`docs/reference/commands/catalog-and-log.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/docs/reference/commands/catalog-and-log.md) at v9.0.0 – edit the repository, not this wiki.* *Generated from [`docs/reference/commands/catalog-and-log.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/reference/commands/catalog-and-log.md) at v10.0.0 – edit the repository, not this wiki.*
# Catalog and log # Catalog and log
+124
@@ -0,0 +1,124 @@
*Generated from [`docs/reference/commands/configuration.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/reference/commands/configuration.md) at v10.0.0 – edit the repository, not this wiki.*
# Configuration
Writing the tiered settings in `wikitool.json` and `.wikitool-local.json`.
<!-- wikitool:toc -->
## Contents
- [`config set`](#config-set)
- [`config migrate`](#config-migrate)
<!-- /wikitool:toc -->
<!-- wikitool:reference-commands -->
## `config set`
Set one value of the tiered settings, in the file its tier names.
**Synopsis**
- `wikitool config set <section>.<field> <value>`
- `wikitool config set <section>.<field> --ref <name|op://vault/item/field>`
**Options**
```text
Arguments:
key <section>.<field> [required]
value The plain value; a string as given, anything else as JSON.
Options:
--ref <str> A reference instead of a value: <name> or
op://<vault>/<item>/<field>.
-h, --help Show this message and exit.
```
**Examples**
```bash
tools/wikitool config set tasks.stalled_waiting_days 14
tools/wikitool config set caldav.app_password --ref caldav_app_password
tools/wikitool config set caldav.username --ref op://Chemenu/caldav/username
```
**Behaviour**
- Writes a plain value of a field of tier `instance` into `wikitool.json`, and one of tier `checkout` - a private field's included - into `.wikitool-local.json`.
- With `--ref`, writes a reference instead of a value into `wikitool.json`: `<name>` resolves through `WIKITOOL_SECRET_<NAME>`, then the secrets file in the user's configuration directory; `op://<vault>/<item>/<field>` resolves through the 1Password CLI. Only a secret or private field takes one.
- Removes the same key from the other file, so a value never has two places, and says so.
- Creates `wikitool.json` with a fresh `instance_id` the first time it writes anything; the secrets file is found by that id.
- Checks both files as they would stand after the write against every registered section, and writes nothing when that fails.
- Prints the key and the file it wrote, never the value. Resolves no reference.
**Properties**
- Changes files: yes
- Idempotent: yes
- Atomic: Each file is replaced in one rename; both are checked before either is written
- Counts toward the session call budget: yes
- Can reach the network: no
**Exit codes**
- `0` - success
- `1` - The key names no registered section or field, or is not `<section>.<field>`
- `1` - A plain value given for a field of tier `secret`
- `1` - `--ref` given for a field that takes a plain value, or a malformed reference
- `1` - The value is not of the field's type, or the files would not pass their check afterwards
- `42` - The field names a program to run or affects a gate
**See also**
- `wikitool doctor` - whether both files pass and every reference resolves
- `docs/reference/configuration.md` - the files, the tiers and the references
- `docs/how-to/use-1password-for-settings.md` - setting 1Password up as the source
## `config migrate`
Move the settings out of the per-feature files of earlier versions into the tiered settings.
**Synopsis**
- `wikitool config migrate`
**Options**
```text
-h, --help Show this message and exit.
```
**Examples**
```bash
tools/wikitool config migrate
```
**Behaviour**
- Moves the four per-feature settings files of earlier versions - telemetry, remotes, upload and the task tracker, each named in the migration document - into the sections `telemetry`, `remotes`, `upload`, and `tasks`, `superproductivity` and `caldav`: each value into the file its field's tier names, a secret's value into the secrets file with only a reference in `wikitool.json`.
- Every setting keeps its effective value, defaults included: the old content is read with the same checks the new sections use, and a field the old file did not set is not written.
- Run once per checkout, since the old files and `.wikitool-local.json` belong to one checkout. `.wikitool-tools.json` stays as it is.
- Prints each key and the file it went to, plus anything the old file held that was not read before and is not carried over - never a value.
- A second run, or a run with no old file, prints that there is nothing to move and changes no file.
- A key the target files already hold with a different value stops the move before anything is written: decide which one stands, remove the other, run again.
**Properties**
- Changes files: yes
- Idempotent: yes
- Atomic: Everything is computed and checked in memory first; the secrets file, `wikitool.json` and `.wikitool-local.json` are each replaced in one rename, read back and checked, and only then are the old files removed. Any failure before that puts every target back to the bytes it had and leaves the old files as they were
- Counts toward the session call budget: yes
- Can reach the network: no
**Exit codes**
- `0` - success
- `1` - An old file does not pass the check the new section applies, a target key holds a different value, or the files would not pass their check afterwards
**See also**
- `wikitool doctor` - reports every leftover old file as a `FAIL`
- `docs/reference/configuration.md` - the sections the files became
- `instructions/migrations/10.0.0-settings-tiers.md` - the migration this command belongs to
<!-- /wikitool:reference-commands -->
+1 -1
@@ -1,4 +1,4 @@
*Generated from [`docs/reference/commands/content-migrations.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/docs/reference/commands/content-migrations.md) at v9.0.0 – edit the repository, not this wiki.* *Generated from [`docs/reference/commands/content-migrations.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/reference/commands/content-migrations.md) at v10.0.0 – edit the repository, not this wiki.*
# Content migrations # Content migrations
+1 -1
@@ -1,4 +1,4 @@
*Generated from [`docs/reference/commands/distribution-and-versioning.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/docs/reference/commands/distribution-and-versioning.md) at v9.0.0 – edit the repository, not this wiki.* *Generated from [`docs/reference/commands/distribution-and-versioning.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/reference/commands/distribution-and-versioning.md) at v10.0.0 – edit the repository, not this wiki.*
# Distribution and versioning # Distribution and versioning
+144
@@ -0,0 +1,144 @@
*Generated from [`docs/reference/commands/exports.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/reference/commands/exports.md) at v10.0.0 – edit the repository, not this wiki.*
# Exports
Derived, read-only versions of `kb/`: the generated `GUIDELINES.md` for the repositories the wiki has captured, and the pages exported into configured folders.
<!-- wikitool:toc -->
## Contents
- [`export guidelines`](#export-guidelines)
- [`export run`](#export-run)
<!-- /wikitool:toc -->
<!-- wikitool:reference-commands -->
## `export guidelines`
Render the guideline pages into one generated `GUIDELINES.md`, or push it into the captured repositories that opted in (**Guideline Push Gate**).
**Synopsis**
- `wikitool export guidelines [--field <predicate> ...] [--kind/--subtype/--collection/--tag <v>]` - Prints the file to stdout; no network.
- `wikitool export guidelines <predicates> --push [--bundle <raw-bundle> ...] [--confirm TOKEN]` - Writes it into the captured repositories, behind the gate.
**Options**
```text
-f, --field <str> Frontmatter predicate, repeatable (AND) - the same forms
as `search --field`.
--kind <str> Shorthand for --field kind=<value>.
--subtype <str> Shorthand for --field subtype=<value>.
--collection <str> Shorthand for --field collection=<value>.
--tag <str> Shorthand for --field tags=<value>.
--push Write the file into the captured repositories (Guideline
Push Gate).
--bundle <path> Only the repository of this captured bundle under raw/;
repeatable.
--confirm <str> The token from a prior exit 42, once a human has approved
it.
-h, --help Show this message and exit.
```
**Examples**
```bash
tools/wikitool export guidelines --tag guideline
tools/wikitool export guidelines --tag guideline --push
tools/wikitool export guidelines --confirm <token> --push --field tags=guideline # re-run after exit 42, once the user approved every diff
```
**Behaviour**
- Selects pages with `search`'s own predicates and no text: `--field` (repeatable, AND), `--kind`, `--subtype`, `--collection`, `--tag`. Which filter selects this instance's guidelines is written down in `kb/CONVENTIONS.md`.
- Renders mechanically: line 1 is `<!-- wikitool:export kind=guidelines instance=<origin URL without userinfo, or local> commit=<newest commit touching a selected page> - generated, do not edit by hand -->`; then each page sorted by title, as `# <title>` and its text without frontmatter, the generated links and footnote blocks, citation markers (`[^id]`, legacy `^[[...]]`), with `[[Title|Text]]` as `Text` and `[[Title]]` as `Title`. A leading H1 of the page is replaced by the title heading. Code is never rewritten. LF endings, no timestamp: the same pages give the same bytes.
- Refuses before printing or pushing anything: no predicate, no match, an unknown field, any page under `kb/` with unreadable frontmatter, uncommitted changes in `kb/` or `types/` (untracked files included), or no git checkout.
- `--push` targets every captured repository - each `(repo, ref)` from a `_capture.json` under `raw/`, `--bundle` narrowing it. A target is skipped with its reason when its ref rule is a tag pattern, it cannot be reached or has no such branch, its root has no `GUIDELINES.md` (not opted in), its `GUIDELINES.md` has no export header (hand-written), or the header names another `instance=`. A header line alone is the opt-in.
- Guideline Push Gate: `--push` without a matching `--confirm` fetches and builds everything, pushes nothing, and exits 42 with each target's status (`new`, `changed`, `unchanged`, `skipped`), the diff for every target it would write, and the re-run line. The token digests URL, branch, old tip and new blob of every target to write.
- With a matching token, each target gets exactly one commit on the fetched tip changing only `GUIDELINES.md`, authored as the instance checkout's `user.name`/`user.email`, pushed without force; one line per target: `unchanged`, `written <old> -> <new>`, `skipped (<reason>)`, `rejected (<reason>)` or `failed (<reason>)`.
- Nothing to write (every target unchanged or skipped) ends with exit 0 and no gate. A second run after a successful one finds every target unchanged.
- The target repository's own `AGENTS.md` points at the file (Claude Code: `@GUIDELINES.md`); that belongs to the repository, not to this command.
**Properties**
- Changes files: yes
- Idempotent: yes
- Atomic: Per target - one commit pushed without force, or nothing; targets already written stay written when a later one fails
- Counts toward the session call budget: yes
- Can reach the network: yes
- Gates: guideline-push - see [exit codes and gates](Reference-Exit-codes)
**Exit codes**
- `0` - success
- `0` - A target was skipped - tag rule, not reachable, not opted in, hand-written file, another instance's file
- `1` - No predicate, no page matches, a malformed predicate or an unknown field
- `1` - A page under `kb/` has unreadable frontmatter, `kb/` or `types/` has uncommitted changes, or the instance is not a git checkout
- `1` - `--push` with no `user.name` or `user.email` in the instance checkout's git configuration - checked before any target is fetched
- `1` - `--bundle` names no captured bundle under `raw/`, or `--bundle`/`--confirm` was given without `--push`
- `1` - A target rejected the push (its branch moved since the fetch) or a git call failed - the other targets were still served
- `42` - Guideline Push Gate: `--push` without `--confirm`, or with a token that does not match the current set of targets
**See also**
- `wikitool search` - the same predicates, to preview the selection
- `wikitool raw capture` - how a repository becomes a target
- `instructions/gates.md` - the gate procedure
## `export run`
Write the derived, read-only reading copy of `kb/` into the folders the `export` section configures - the selected pages as Markdown, or converted by a program of this checkout.
**Synopsis**
- `wikitool export run [<target> ...]` - Every configured target, or the named ones. Needs an `export` section in `.wikitool-local.json`; the user writes it by hand.
**Options**
```text
Arguments:
[TARGET]... Targets to run; default: every configured target.
Options:
-h, --help Show this message and exit.
```
**Examples**
```bash
tools/wikitool export run
tools/wikitool export run work-notes
```
**Behaviour**
- An export is derived: regenerated from `kb/` on every run, never edited, never read back into the wiki. Each target names its pages with an allowlist of `search --field` predicates (`include`: a list of selections, AND within one, OR across them), a handler (`markdown`, or `command` for the operator's converter) and the folder (`path`, outside the instance). Nothing is exported that no selection names.
- Every Markdown file starts with a frontmatter block whose `wikitool_export` line says `kind=pages instance=<origin URL without userinfo, or local> commit=<newest commit touching a selected page>`, followed by `title`, `type`, `summary`, `modified` where the page has them (`frontmatter: summary`, the default; `minimal` keeps only the marker, `full` the whole frontmatter), then the page text. A file keeps its path below `kb/`. A wikilink to an exported page becomes a relative Markdown link (`links: relative`, the default) or plain text (`links: text`); a link to a page that is not exported is always plain text. Footnote markers stay; code is never rewritten. LF endings, no timestamp: the same pages give the same bytes.
- Handler `command`: the Markdown is rendered into a staging directory, the program of `export.commands` runs with no shell, stdin closed and a timeout of 300 s, and receives the Markdown directory and an empty output directory as its last two arguments. On exit 0 the regular files in the output directory (recursively) become the target's content; any other end leaves the target unchanged and prints the end of its stderr. The staging directory is always removed.
- The folder is only ever touched by what it holds in its manifest, `.wikitool-export.json`: the files an earlier run wrote, each with its sha256. A run removes only manifest files that are no longer exported (and folders it created, once empty), rewrites a manifest file that is missing, leaves every other file byte for byte as it was, and writes nothing - not even a modification time - for a file that did not change. A run that changes nothing writes nothing at all.
- Refuses a target, before writing anything into it: a non-empty folder with no manifest (it is somebody's, not an export), a manifest file that was changed in the folder since the last run (naming it), a foreign file where an exported one belongs, an empty selection (it would delete the earlier export), an unknown field in a predicate. A missing folder is created.
- Refuses everything, before any target: uncommitted changes in `kb/` or `types/` (untracked files included), no git checkout, a page under `kb/` with unreadable frontmatter, an unknown target name, a broken `export` section.
- Output per target: `new` (with the page titles), `changed`, `removed`, the count `unchanged`, or `failed` with the reason. A page that newly enters a selection is reported as new, not gated. Exit 0 when every target ran; exit 1 when any failed - the others were still served.
- A target with `after_publish: true` is also refreshed by every `publish` that committed, `--no-push` included; a failure there is a warning, the commit stands, and this command is the repeat.
- `raw accept` refuses a file whose sha256 a target's manifest lists, and one that carries the marker: an export never goes back into `raw/`.
**Properties**
- Changes files: yes
- Idempotent: yes
- Atomic: Per file within a target (a temporary file in the target, then a move over its place), the manifest last; targets are independent, so a failed target does not undo another
- Counts toward the session call budget: yes
- Can reach the network: no
**Exit codes**
- `0` - success
- `1` - No `export` section, an unknown target name, or a broken section (a predicate that does not parse, a path inside the instance or in the way of another target, an `intake` folder or the backup target)
- `1` - `kb/` or `types/` has uncommitted changes, the instance is not a git checkout, or a page under `kb/` has unreadable frontmatter
- `1` - A target failed: a foreign folder, a file changed in the target, an empty selection, an unknown field, a converter that did not exit 0
**See also**
- `wikitool search` - the same predicates, to preview a selection
- `wikitool backup run` - the same kind of folder, for this machine's own files
- `docs/how-to/export-to-a-synced-folder.md`
<!-- /wikitool:reference-commands -->
+6 -4
@@ -1,4 +1,4 @@
*Generated from [`docs/reference/commands/finding-and-checking.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/docs/reference/commands/finding-and-checking.md) at v9.0.0 – edit the repository, not this wiki.* *Generated from [`docs/reference/commands/finding-and-checking.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/reference/commands/finding-and-checking.md) at v10.0.0 – edit the repository, not this wiki.*
# Finding and checking # Finding and checking
@@ -187,7 +187,8 @@ tools/wikitool review --json
- **someday-stale**: a someday/maybe item untouched for longer than `thresholds.someday_stale_months`. - **someday-stale**: a someday/maybe item untouched for longer than `thresholds.someday_stale_months`.
- A value a provider genuinely cannot supply - a `WAITING` item with no `follow_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. - A value a provider genuinely cannot supply - a `WAITING` item with no `follow_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. - Thresholds come from the tracker configuration, never from the schema.
- The tracker configuration is read from `.wikitool-tasks.json`, or from the file `WIKITOOL_TASKS_CONFIG` names 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". - The tracker configuration is read from the `tasks`, `superproductivity` and `caldav` sections of the settings, or from the profile `WIKITOOL_TASKS_CONFIG` names 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".
- The tracker settings file of an earlier version exits 42 and names `tools/wikitool config migrate`; so does a secret source that needs unlocking.
- Text output is one `[check] project: message` line per finding, preceded by a `Source:` line naming which access path answered and, for `superproductivity`'s `access: "snapshot"`, the snapshot's age. `--json` carries the same findings plus `checks_run`/`checks_skipped`/`kb_project_count`/`complete`/`source` (`{"kind": ..., "detail": ...}` or `null`). - Text output is one `[check] project: message` line per finding, preceded by a `Source:` line naming which access path answered and, for `superproductivity`'s `access: "snapshot"`, the snapshot's age. `--json` carries the same findings plus `checks_run`/`checks_skipped`/`kb_project_count`/`complete`/`source` (`{"kind": ..., "detail": ...}` or `null`).
- 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. - 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. - Re-reads everything fresh on every call, so nothing is ever stale to re-fetch.
@@ -204,9 +205,10 @@ tools/wikitool review --json
**Exit codes** **Exit codes**
- `0` - success - `0` - success
- `1` - No `.wikitool-tasks.json`, or a malformed one - a clear "no tracker configured" message - `1` - No `tasks` section in the settings, or a malformed one - a clear "no tracker configured" message
- `1` - `WIKITOOL_TASKS_CONFIG` names a file that does not exist or is broken - `1` - `WIKITOOL_TASKS_CONFIG` names a profile 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 - `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
- `42` - The tracker settings file of an earlier version is still there, or a secret source (1Password) needs unlocking
**See also** **See also**
+114 -5
@@ -1,14 +1,17 @@
*Generated from [`docs/reference/commands/git.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/docs/reference/commands/git.md) at v9.0.0 – edit the repository, not this wiki.* *Generated from [`docs/reference/commands/git.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/reference/commands/git.md) at v10.0.0 – edit the repository, not this wiki.*
# Git # Git
Pulling in the remote and publishing a change - the only way a change leaves this checkout. Pulling in the remote and publishing a change - the only way a change leaves this checkout - and
backing the checkout up into a synced folder.
<!-- wikitool:toc --> <!-- wikitool:toc -->
## Contents ## Contents
- [`sync`](#sync) - [`sync`](#sync)
- [`publish`](#publish) - [`publish`](#publish)
- [`backup init`](#backup-init)
- [`backup run`](#backup-run)
<!-- /wikitool:toc --> <!-- /wikitool:toc -->
<!-- wikitool:reference-commands --> <!-- wikitool:reference-commands -->
@@ -128,9 +131,12 @@ tools/wikitool publish --confirm-rebase <token> --message "ingest: docker-cheats
- 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 after `git 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. - 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 after `git 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 files `wikitool` generates itself (`kb/index.md`, `kb/log.md`, `kb/provenance.md`, every `INDEX.md` under `kb/`). The refusal line accounts for both, by reason. - Never counted and never shown for approval, but committed like everything else: anything under `work/`, and the files `wikitool` generates itself (`kb/index.md`, `kb/log.md`, `kb/provenance.md`, every `INDEX.md` under `kb/`). The refusal line accounts for both, by reason.
- The `--confirm` token 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. - The `--confirm` token 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.json` and the push URL of `--remote` is not listed in it, exits 42 before the reconcile fetches anything. The URL is read with `git 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. - Publish-Remote Gate: when `.wikitool-local.json` carries `remotes.allowed_push_urls` and the push URL of `--remote` is not listed in it, exits 42 before the reconcile fetches anything. The URL is read with `git remote get-url --push`, so a repointed remote does not pass on its name. An absent section means unrestricted; a malformed one is an error, not permission.
- The remotes settings file of an earlier version is not read: while it exists this exits 42 and names `tools/wikitool config migrate`, so a restriction is never lost silently.
- `--path` (repeatable) scopes the whole operation - gate count, staging and commit - to that subtree, with one exception: uncommitted generated files outside `--path` (`kb/index.md`, `kb/log.md`, `kb/provenance.md`, every `INDEX.md` under `kb/`) are staged and committed too, and a line names them - whether a reconcile or the change itself wrote them. They stay out of the gate count, and the `--confirm` token and the re-run line cover the `--path` given. Every other file outside `--path` is left as it is. - `--path` (repeatable) scopes the whole operation - gate count, staging and commit - to that subtree, with one exception: uncommitted generated files outside `--path` (`kb/index.md`, `kb/log.md`, `kb/provenance.md`, every `INDEX.md` under `kb/`) are staged and committed too, and a line names them - whether a reconcile or the change itself wrote them. They stay out of the gate count, and the `--confirm` token and the re-run line cover the `--path` given. Every other file outside `--path` is left as it is.
- Commit message: `--message`, then a `Files changed:` paragraph listing every committed path. When `--message` ends in a paragraph git reads as a trailer block (`Co-Authored-By:` and the like), that block stays the last paragraph and the list goes in front of it, since git reads trailers from the last paragraph only. `git interpret-trailers` decides whether there is such a block, and the list moves only when git reads the same trailers from the result; otherwise it is appended at the end. `--message` is not part of any gate token. - Commit message: `--message`, then a `Files changed:` paragraph listing every committed path. When `--message` ends in a paragraph git reads as a trailer block (`Co-Authored-By:` and the like), that block stays the last paragraph and the list goes in front of it, since git reads trailers from the last paragraph only. `git interpret-trailers` decides whether there is such a block, and the list moves only when git reads the same trailers from the result; otherwise it is appended at the end. `--message` is not part of any gate token.
- Machine-data backup: with a `backup` section in `.wikitool-local.json` whose `machine_data` is true, a call that pushed then mirrors this machine's files that are not in git into `<target>/<machine>/` - the same step as `backup run`. A failure there prints a `WARN` line with the reason and still exits 0, since the commit and the push stand. No backup with `--no-push`, after a failed push, or when there was nothing to push. A malformed `backup` section stops the call with exit 1 before anything is committed, `--no-push` included.
- Export after publish: every target of the `export` section with `after_publish: true` is refreshed once a commit was made - the same step as `export run`, `--no-push` included, since the export reads the committed `kb/`. A failure there prints a `WARN` line with the reason and still exits 0, since the commit stands; `export run` repeats it. A malformed `export` section stops the call with exit 1 before anything is committed, but only when some target sets `after_publish`.
- After a successful commit or push whose changed files include `tools/`, `types/`, `instructions/`, `AGENTS.md` or a path ending in `CONTRACT.md`, prints one reminder line: the phase past this point (an issue-body rewrite, `docs/` staleness, a changelog entry's accuracy) is not covered by `docs verify`, `instructions verify` or `pytest`. It is not a gate: no exit code change, nothing to clear, and silent for an ordinary content publish. - After a successful commit or push whose changed files include `tools/`, `types/`, `instructions/`, `AGENTS.md` or a path ending in `CONTRACT.md`, prints one reminder line: the phase past this point (an issue-body rewrite, `docs/` staleness, a changelog entry's accuracy) is not covered by `docs verify`, `instructions verify` or `pytest`. It is not a gate: no exit code change, nothing to clear, and silent for an ordinary content publish.
**Properties** **Properties**
@@ -145,18 +151,121 @@ tools/wikitool publish --confirm-rebase <token> --message "ingest: docker-cheats
**Exit codes** **Exit codes**
- `0` - success - `0` - success
- `0` - The machine-data backup after the push failed: a `WARN` line names the reason, and the commit and the push stand
- `0` - The export after the commit failed: a `WARN` line names the target and the reason, and the commit stands
- `1` - git failed - `git add`, `git commit`, `git push`, or the reconcile's automatic rebase (a conflict outside the generated files, an edited `kb/log.md`, or an uncommitted change on a path the incoming commits change) - `1` - git failed - `git add`, `git commit`, `git push`, or the reconcile's automatic rebase (a conflict outside the generated files, an edited `kb/log.md`, or an uncommitted change on a path the incoming commits change)
- `1` - The push target (`--branch`) is not the checked-out branch, or HEAD is detached; the unborn branch of a fresh `git init` is not this case - `1` - The push target (`--branch`) is not the checked-out branch, or HEAD is detached; the unborn branch of a fresh `git init` is not this case
- `1` - No `--no-push`, and the remote is not configured or cannot be reached; nothing was committed - `1` - No `--no-push`, and the remote is not configured or cannot be reached; nothing was committed
- `1` - `--yes`/`-y` was passed - the flag does not exist and fails with an explicit error - `1` - `--yes`/`-y` was passed - the flag does not exist and fails with an explicit error
- `1` - `.wikitool-remotes.json` is unreadable or has no usable `allowed_push_urls` list - `1` - The settings files do not pass their check, `remotes.allowed_push_urls` is not a list of strings, the `backup` section is malformed, or the `export` section is malformed while a target sets `after_publish`; nothing was committed
- `42` - The remotes settings file of an earlier version is still there
- `42` - Mass-Update Gate: `--threshold` (default 10) or more counted files would be committed, or the `--confirm` token does not match this changeset - `42` - Mass-Update Gate: `--threshold` (default 10) or more counted files would be committed, or the `--confirm` token 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` - 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.json` exists and the push URL of `--remote` is not listed in it, or `--remote` resolves to no push URL - `42` - Publish-Remote Gate: `remotes.allowed_push_urls` is set and the push URL of `--remote` is not listed in it, or `--remote` resolves to no push URL
**See also** **See also**
- `wikitool sync` - the same reconcile on its own, without committing or pushing - `wikitool sync` - the same reconcile on its own, without committing or pushing
- `instructions/gates.md` - the gate procedure - `instructions/gates.md` - the gate procedure
- `instructions/setup-instance.md` - the first publish of a new instance - `instructions/setup-instance.md` - the first publish of a new instance
- `wikitool backup run` - the machine-data backup on its own
- `wikitool export run` - the page export on its own
## `backup init`
Create the backup target's bare repository and make it `origin` when there is none.
**Synopsis**
- `wikitool backup init`
**Options**
```text
-h, --help Show this message and exit.
```
**Examples**
```bash
tools/wikitool backup init
```
**Behaviour**
- Reads the `backup` section of `.wikitool-local.json` and creates `<target>/` and `<target>/git`, the latter as a bare repository (`git init --bare -b main`). An existing bare repository there, or an empty directory, is used as it is.
- Sets `origin` to `<target>/git` when the checkout has no `origin`. An `origin` that already pushes there is left as it is; one that pushes anywhere else stops the command.
- When `remotes.allowed_push_urls` exists and lacks the URL `origin` pushes to, prints the `remotes` section with that URL added, for the user to copy into `.wikitool-local.json`. It never writes that file: the field is a gate.
- Pushes nothing: the next `publish` sends the history to the new `origin`.
**Properties**
- Changes files: yes
- Idempotent: yes
- Atomic: Every precondition is checked first; a refusal changes nothing
- Counts toward the session call budget: yes
- Can reach the network: no
**Exit codes**
- `0` - success
- `1` - No `backup` section in `.wikitool-local.json`, or a malformed one - a target that is not absolute or lies inside the instance, a `machine_name` that cleans to nothing or to `git`, an `extra` path that resolves outside the instance or into `.git/`
- `1` - `<target>/git` exists and is not a bare repository; nothing was changed
- `1` - `origin` already pushes to another URL; the message names both and nothing was changed
**See also**
- `wikitool backup run` - the machine-data half of the backup
- `wikitool publish` - pushes to the bare repository and runs the machine-data backup after it
- `docs/how-to/back-up-to-a-synced-folder.md` - the whole procedure, restoring included
## `backup run`
Mirror this machine's files that are not in git into `<target>/<machine>/`.
**Synopsis**
- `wikitool backup run`
**Options**
```text
-h, --help Show this message and exit.
```
**Examples**
```bash
tools/wikitool backup run
```
**Behaviour**
- Mirrors `.wikitool-local.json`, `ENVIRONMENT.md` if present, every file in `incoming/` but `.gitkeep`, and the `extra` paths of the `backup` section into `<target>/<machine>/`, then writes the manifest `.chemenu-backup.json` there: time, stack version, machine name, and per file its relative path, size and sha256.
- Never mirrors `quarantine/`, `reports/`, `.wikitool-tools.json`, `tools/.venv/`, the published skills or `.git/` - unless an `extra` path names one of the first ones. The secrets file lies outside the instance and is never read.
- Writes and deletes only under `<target>/<machine>/`. Deletes only a file the previous manifest lists and the instance no longer has; a file there the manifest does not list stays as it is. A file whose size and sha256 match the previous manifest is not rewritten.
- A symbolic link or junction inside a mirrored directory is skipped and named in the output, never followed.
- `publish` runs the same step after every push when `machine_data` is true.
**Properties**
- Changes files: yes
- Idempotent: yes
- Atomic: Per file - each is written to a temporary file and renamed, the manifest last; an interrupted run leaves whole files and the previous manifest
- Counts toward the session call budget: yes
- Can reach the network: no
**Exit codes**
- `0` - success
- `1` - No `backup` section in `.wikitool-local.json`, or a malformed one - a target that is not absolute or lies inside the instance, a `machine_name` that cleans to nothing or to `git`, an `extra` path that resolves outside the instance or into `.git/`
- `1` - `machine_data` is false
- `1` - `<target>/<machine>/` is not empty and has no manifest, or its manifest cannot be read; nothing was written
- `1` - The target is not a directory (the synced folder is missing or not mounted), or a write failed
**See also**
- `wikitool backup init` - sets up the target's bare repository
- `wikitool doctor` - the `backup` line reports the age of the last run
- `docs/how-to/back-up-to-a-synced-folder.md` - the whole procedure, restoring included
<!-- /wikitool:reference-commands --> <!-- /wikitool:reference-commands -->
-79
@@ -1,79 +0,0 @@
*Generated from [`docs/reference/commands/guideline-export.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/docs/reference/commands/guideline-export.md) at v9.0.0 – edit the repository, not this wiki.*
# Guideline export
Writing the generated `GUIDELINES.md` into the repositories the wiki has captured.
<!-- wikitool:reference-commands -->
## `export guidelines`
Render the guideline pages into one generated `GUIDELINES.md`, or push it into the captured repositories that opted in (**Guideline Push Gate**).
**Synopsis**
- `wikitool export guidelines [--field <predicate> ...] [--kind/--subtype/--collection/--tag <v>]` - Prints the file to stdout; no network.
- `wikitool export guidelines <predicates> --push [--bundle <raw-bundle> ...] [--confirm TOKEN]` - Writes it into the captured repositories, behind the gate.
**Options**
```text
-f, --field <str> Frontmatter predicate, repeatable (AND) - the same forms
as `search --field`.
--kind <str> Shorthand for --field kind=<value>.
--subtype <str> Shorthand for --field subtype=<value>.
--collection <str> Shorthand for --field collection=<value>.
--tag <str> Shorthand for --field tags=<value>.
--push Write the file into the captured repositories (Guideline
Push Gate).
--bundle <path> Only the repository of this captured bundle under raw/;
repeatable.
--confirm <str> The token from a prior exit 42, once a human has approved
it.
-h, --help Show this message and exit.
```
**Examples**
```bash
tools/wikitool export guidelines --tag guideline
tools/wikitool export guidelines --tag guideline --push
tools/wikitool export guidelines --confirm <token> --push --field tags=guideline # re-run after exit 42, once the user approved every diff
```
**Behaviour**
- Selects pages with `search`'s own predicates and no text: `--field` (repeatable, AND), `--kind`, `--subtype`, `--collection`, `--tag`. Which filter selects this instance's guidelines is written down in `kb/CONVENTIONS.md`.
- Renders mechanically: line 1 is `<!-- wikitool:export kind=guidelines instance=<origin URL without userinfo, or local> commit=<newest commit touching a selected page> - generated, do not edit by hand -->`; then each page sorted by title, as `# <title>` and its text without frontmatter, the generated links and footnote blocks, citation markers (`[^id]`, legacy `^[[...]]`), with `[[Title|Text]]` as `Text` and `[[Title]]` as `Title`. A leading H1 of the page is replaced by the title heading. Code is never rewritten. LF endings, no timestamp: the same pages give the same bytes.
- Refuses before printing or pushing anything: no predicate, no match, an unknown field, any page under `kb/` with unreadable frontmatter, uncommitted changes in `kb/` or `types/` (untracked files included), or no git checkout.
- `--push` targets every captured repository - each `(repo, ref)` from a `_capture.json` under `raw/`, `--bundle` narrowing it. A target is skipped with its reason when its ref rule is a tag pattern, it cannot be reached or has no such branch, its root has no `GUIDELINES.md` (not opted in), its `GUIDELINES.md` has no export header (hand-written), or the header names another `instance=`. A header line alone is the opt-in.
- Guideline Push Gate: `--push` without a matching `--confirm` fetches and builds everything, pushes nothing, and exits 42 with each target's status (`new`, `changed`, `unchanged`, `skipped`), the diff for every target it would write, and the re-run line. The token digests URL, branch, old tip and new blob of every target to write.
- With a matching token, each target gets exactly one commit on the fetched tip changing only `GUIDELINES.md`, authored as the instance checkout's `user.name`/`user.email`, pushed without force; one line per target: `unchanged`, `written <old> -> <new>`, `skipped (<reason>)`, `rejected (<reason>)` or `failed (<reason>)`.
- Nothing to write (every target unchanged or skipped) ends with exit 0 and no gate. A second run after a successful one finds every target unchanged.
- The target repository's own `AGENTS.md` points at the file (Claude Code: `@GUIDELINES.md`); that belongs to the repository, not to this command.
**Properties**
- Changes files: yes
- Idempotent: yes
- Atomic: Per target - one commit pushed without force, or nothing; targets already written stay written when a later one fails
- Counts toward the session call budget: yes
- Can reach the network: yes
- Gates: guideline-push - see [exit codes and gates](Reference-Exit-codes)
**Exit codes**
- `0` - success
- `0` - A target was skipped - tag rule, not reachable, not opted in, hand-written file, another instance's file
- `1` - No predicate, no page matches, a malformed predicate or an unknown field
- `1` - A page under `kb/` has unreadable frontmatter, `kb/` or `types/` has uncommitted changes, or the instance is not a git checkout
- `1` - `--push` with no `user.name` or `user.email` in the instance checkout's git configuration - checked before any target is fetched
- `1` - `--bundle` names no captured bundle under `raw/`, or `--bundle`/`--confirm` was given without `--push`
- `1` - A target rejected the push (its branch moved since the fetch) or a git call failed - the other targets were still served
- `42` - Guideline Push Gate: `--push` without `--confirm`, or with a token that does not match the current set of targets
**See also**
- `wikitool search` - the same predicates, to preview the selection
- `wikitool raw capture` - how a repository becomes a target
- `instructions/gates.md` - the gate procedure
<!-- /wikitool:reference-commands -->
+14 -6
@@ -1,4 +1,4 @@
*Generated from [`docs/reference/commands/instance-health.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/docs/reference/commands/instance-health.md) at v9.0.0 – edit the repository, not this wiki.* *Generated from [`docs/reference/commands/instance-health.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/reference/commands/instance-health.md) at v10.0.0 – edit the repository, not this wiki.*
# Instance health # Instance health
@@ -29,7 +29,7 @@ tools/wikitool doctor --json
**Behaviour** **Behaviour**
- Checks dependencies (Python, ripgrep), author resolution, stack version, git identity/branch/remote, published skills, the kb/raw/reports/work/instructions structure, and generated files. - Checks dependencies (Python, ripgrep), author resolution, stack version, git identity/branch/remote, published skills, the kb/raw/reports/work/sites/instructions structure, and generated files.
- Import filters (`import-filter-<name>`): one line for each `raw derive` format that reads through a package (PDF, Outlook `.msg`) - a `WARN` naming the package when it is missing, since that costs only its own format; fixed by running `tools/preflight.sh` again. - Import filters (`import-filter-<name>`): one line for each `raw derive` format that reads through a package (PDF, Outlook `.msg`) - a `WARN` naming the package when it is missing, since that costs only its own format; fixed by running `tools/preflight.sh` again.
- Tool paths (`tool-paths`): `.wikitool-tools.json` written by a preflight that finished, every tool `tools/prerequisites.txt` names for this platform recorded, and every recorded path still there - a `FAIL` otherwise, fixed by running `tools/preflight.sh` again (PowerShell 7: `tools/preflight.ps1`). - Tool paths (`tool-paths`): `.wikitool-tools.json` written by a preflight that finished, every tool `tools/prerequisites.txt` names for this platform recorded, and every recorded path still there - a `FAIL` otherwise, fixed by running `tools/preflight.sh` again (PowerShell 7: `tools/preflight.ps1`).
- Install folder (`install-dir`): on Windows with long paths off, a `FAIL` when the folder holding `tools/` is longer than `tools/prerequisites.txt` allows (95 characters) - the limit the preflight enforces before it sets anything up. - Install folder (`install-dir`): on Windows with long paths off, a `FAIL` when the folder holding `tools/` is longer than `tools/prerequisites.txt` allows (95 characters) - the limit the preflight enforces before it sets anything up.
@@ -37,12 +37,19 @@ tools/wikitool doctor --json
- Personalization: `USER.md`/`SOUL.md` present **and** filled - a file still carrying the template's sentinel is a `FAIL`. - Personalization: `USER.md`/`SOUL.md` present **and** filled - a file still carrying the template's sentinel is a `FAIL`.
- KB conventions: `kb/CONVENTIONS.md` present, unsentinelled, and naming all three tool-owned section headings - a `FAIL` on any of the three. - KB conventions: `kb/CONVENTIONS.md` present, unsentinelled, and naming all three tool-owned section headings - a `FAIL` on any of the three.
- Environment note: `ENVIRONMENT.md` is optional, so absent is `OK`; a still-templated one is a `WARN`. - Environment note: `ENVIRONMENT.md` is optional, so absent is `OK`; a still-templated one is a `WARN`.
- MCP `submit` tool: whether `.wikitool-upload.json` is present, absent or malformed, its limits, and how many submissions wait in `mcp-upload/`. Absent is `OK` and means the write path does not exist at all; malformed is a `FAIL`. - Legacy settings (`legacy-settings`): one `FAIL` for each per-feature settings file of an earlier version (telemetry, remotes, upload, task tracker) that is still there. Nothing reads them any more, so the settings they hold are not in effect; the fix is `tools/wikitool config migrate`.
- Task tracker: `.wikitool-tasks.json` present, absent or malformed - absent is `OK` (no tracker configured), malformed is a `FAIL`. When `WIKITOOL_TASKS_CONFIG` is set, that file is read instead and the finding names it; a set variable that names no file is a `FAIL`, never `OK`. - Synced folder (`sync-folder`): a `WARN` when the git directory, links and junctions resolved, lies under a folder named by `OneDrive`, `OneDriveCommercial` or `OneDriveConsumer` (compared without regard to case on Windows) or under `~/Library/CloudStorage/` (macOS); `OK` otherwise, never a `FAIL`.
- Backup (`backup`): the `backup` section absent is `OK`, malformed is a `FAIL`. Configured, a `WARN` when `<target>/git` is not a bare repository, `origin` does not push there, or - with `machine_data` true - no machine-data backup exists yet; otherwise `OK` with the age of the last machine-data backup.
- Page export (`export`): the `export` section absent is `OK`, malformed is a `FAIL`; otherwise `OK` listing each target with its handler, folder and whether it runs after `publish`.
- Intake channels (`upload-intake`): the channels of the `intake` section with their handler, and how many submissions wait in `quarantine/`. Absent is `OK` and means no channel - no `submit` tool, nothing to collect; malformed is a `FAIL`, and so is an `mcp-upload/` of an earlier version that still holds submissions (the message names the command that moves it). A `folder` channel whose `path` does not exist is a `WARN`.
- Task tracker: the `tasks` section present, absent or malformed - absent is `OK` (no tracker configured), malformed is a `FAIL`. When `WIKITOOL_TASKS_CONFIG` is set, the profile it names is read instead of the `tasks`, `superproductivity` and `caldav` sections and the finding names it; a set variable that names no file is a `FAIL`, never `OK`.
- For a configured `superproductivity` provider, the configured `access` path's own state: `access: "api"` reports whether its local REST API answers `GET /health` with 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 a `FAIL`. - For a configured `superproductivity` provider, the configured `access` path's own state: `access: "api"` reports whether its local REST API answers `GET /health` with 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 a `FAIL`.
- For a configured `caldav` provider, whether the server is reachable and Basic auth succeeds - never a `FAIL`; only a broken config block is. - For a configured `caldav` provider, whether the server is reachable and Basic auth succeeds - never a `FAIL`; only a broken config block is.
- Settings (`settings-instance`, `settings-checkout`, `settings-secrets`): one line per tier. `wikitool.json` and `.wikitool-local.json` each present or absent - absent is `OK` - and a `FAIL` naming file, key and the right tier when a value sits in the wrong file, a secret or a committed private field holds a plain value, or a value fails its field; a section this version does not read is a `WARN`. For the references: how many resolve, never a value - an unresolvable one is a `FAIL`, one 1Password will not hand out until a human unlocks it a `WARN`.
- Session id source: `OK` for `WIKITOOL_SESSION_ID` or a registered harness variable, `WARN` only for the bare parent-pid fallback. - Session id source: `OK` for `WIKITOOL_SESSION_ID` or a registered harness variable, `WARN` only for the bare parent-pid fallback.
- Telemetry: on or off and why - installation-form default, `.wikitool-telemetry.json`, or `WIKI_TRACE` - and the current session's count and byte total against both caps; never a `FAIL`. A `WARN` when `.wikitool-telemetry.json` is not read as written - unreadable JSON, not an object, `enabled` not a JSON boolean, a cap not a positive integer, or an unknown key - naming the field; telemetry itself drops or reinterprets such a value without a word, so this is where it shows. - Telemetry: on or off and why - installation-form default, the `telemetry` section of `.wikitool-local.json`, or `WIKI_TRACE` - and the current session's count and byte total against both caps; never a `FAIL`. A `WARN` when that section is not read as written - unreadable JSON, not an object, `enabled` not a JSON boolean, a cap not a positive integer, or an unknown key - naming the field; telemetry itself drops or reinterprets such a value without a word, so this is where it shows.
- Site handlers (`site-handlers`): every file in `sites/` is read against the handler schema and the rules the schema cannot state; `FAIL` for a file that cannot be used, naming the file and the reason, and `OK` listing the instance's handlers, each one that replaces a stack handler (the same host and path) named as such.
- Browser component (`browser`): whether the optional Playwright component and its browser are installed in the venv - `OK` either way, never a `FAIL`; a `WARN` only when it is missing and the instance needs it: a host `fetch.routes` sends through the session (`raw login`), or a site handler that declares `render`. The user installs it with `tools/preflight.sh --with browser`.
- Exits 1 only on a `FAIL`; a missing remote, session id or `VERSION` is a `WARN`, not a fault. - Exits 1 only on a `FAIL`; a missing remote, session id or `VERSION` is a `WARN`, not a fault.
- Read-only and exempt from the Iteration Budget Gate. - Read-only and exempt from the Iteration Budget Gate.
@@ -63,7 +70,8 @@ tools/wikitool doctor --json
- `instructions/setup-instance.md` - the setup steps most findings point back to - `instructions/setup-instance.md` - the setup steps most findings point back to
- `instructions/preflight.md` - what `tool-paths` and `install-dir` point back to - `instructions/preflight.md` - what `tool-paths` and `install-dir` point back to
- `docs/reference/configuration.md` - the per-checkout configuration files - `docs/reference/configuration.md` - the per-checkout configuration files and the tiered settings
- `docs/explanation/telemetry-and-evaluation.md` - telemetry state and caps - `docs/explanation/telemetry-and-evaluation.md` - telemetry state and caps
- `docs/how-to/back-up-to-a-synced-folder.md` - what `sync-folder` and `backup` point to
- `instructions/session-setup.md` - setting `WIKITOOL_SESSION_ID` - `instructions/session-setup.md` - setting `WIKITOOL_SESSION_ID`
<!-- /wikitool:reference-commands --> <!-- /wikitool:reference-commands -->
+1 -1
@@ -1,4 +1,4 @@
*Generated from [`docs/reference/commands/links-and-citations.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/docs/reference/commands/links-and-citations.md) at v9.0.0 – edit the repository, not this wiki.* *Generated from [`docs/reference/commands/links-and-citations.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/reference/commands/links-and-citations.md) at v10.0.0 – edit the repository, not this wiki.*
# Links and citations # Links and citations
+17 -14
@@ -1,4 +1,4 @@
*Generated from [`docs/reference/commands/pages.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/docs/reference/commands/pages.md) at v9.0.0 – edit the repository, not this wiki.* *Generated from [`docs/reference/commands/pages.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/reference/commands/pages.md) at v10.0.0 – edit the repository, not this wiki.*
# Pages # Pages
@@ -73,7 +73,7 @@ tools/wikitool new project --name "Homelab migration" --set responsibility=infra
- `--set` is repeatable, and comma-separated values fill array fields. An element that itself contains a comma is written `\,`, or passed as its own repeated `--set` for that field - repeating an array field appends. - `--set` is repeatable, and comma-separated values fill array fields. An element that itself contains a comma is written `\,`, or passed as its own repeated `--set` for that field - repeating an array field appends.
- A capture field the type-spec requires (a source's `fidelity`/`authority`) must be passed with `--set`; `new` never guesses it and refuses `unknown` for it. - A capture field the type-spec requires (a source's `fidelity`/`authority`) must be passed with `--set`; `new` never guesses it and refuses `unknown` for it.
- Produces structurally correct frontmatter and a body skeleton only - the prose (Description, Summary, judgment calls about relationships) is written afterwards. - 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.json` configuring 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`: with the `tasks` section of the settings configuring 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 state `review`'s check 3 reports - never a page with no tracker project. - `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 state `review`'s check 3 reports - never a page with no tracker project.
- `new project`: a name already taken, case-insensitively, in `kb/` or the tracker is refused outright, naming where it was found, and creates nothing. For `caldav` the tracker check covers every list in the account, not only the ones counted as projects. - `new project`: a name already taken, case-insensitively, in `kb/` or the tracker is refused outright, naming where it was found, and creates nothing. For `caldav` the 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's `access: "snapshot"`) refuses entirely with exit 1, naming the `access: "api"` instance to use instead - neither the tracker project nor the page is created, and `--resume` behaves the same. - `new project`: a provider whose configured access path has no write path (Super Productivity's `access: "snapshot"`) refuses entirely with exit 1, naming the `access: "api"` instance to use instead - neither the tracker project nor the page is created, and `--resume` behaves the same.
@@ -156,10 +156,10 @@ tools/wikitool task new --title "Read the qmd README" --inbox --notes "from [[So
- `--inbox` files into the tracker's own inbox. An item filed there never appears in `review`, since every one of its checks reaches items through a project name. - `--inbox` files into the tracker's own inbox. An item filed there never appears in `review`, since every one of its checks reaches items through a project name.
- `--waiting` sets the WAITING status `review`'s waiting-overdue check reads. `--follow-up-at` is refused without `--waiting` - it is never a due date on its own. - `--waiting` sets the WAITING status `review`'s waiting-overdue check reads. `--follow-up-at` is refused without `--waiting` - it is never a due date on its own.
- `--notes` carries a freetext backref (e.g. to the `kb/` source page the item came from), stored verbatim, never parsed. - `--notes` carries a freetext backref (e.g. to the `kb/` source page the item came from), stored verbatim, never parsed.
- The tracker configuration is read from `.wikitool-tasks.json`, or from the file `WIKITOOL_TASKS_CONFIG` names 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". - The tracker configuration is read from the `tasks`, `superproductivity` and `caldav` sections of the settings, or from the profile `WIKITOOL_TASKS_CONFIG` names 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.json` fails immediately with a "no tracker configured" message. - No `tasks` section fails 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 the `access: "api"` instance to use instead. - A provider whose configured access path has no write path (Super Productivity's `access: "snapshot"`) refuses entirely with exit 1, naming the `access: "api"` instance to use instead.
- **Never exits 42.** A `--project` matching no tracker project, or `--waiting` against a provider that cannot represent it right now (Super Productivity: the `waiting` tag does not exist yet, and its API cannot create tags), are ordinary exit-1 refusals that create nothing. - **Exits 42 only for the settings** (the tracker settings file of an earlier version, a locked secret source). A `--project` matching no tracker project, or `--waiting` against a provider that cannot represent it right now (Super Productivity: the `waiting` tag 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. - Not idempotent: every successful run creates another item.
**Properties** **Properties**
@@ -173,12 +173,13 @@ tools/wikitool task new --title "Read the qmd README" --inbox --notes "from [[So
**Exit codes** **Exit codes**
- `0` - success - `0` - success
- `1` - No `.wikitool-tasks.json` - no tracker configured - `1` - No `tasks` section in the settings - no tracker configured
- `1` - `WIKITOOL_TASKS_CONFIG` names a file that does not exist or is broken - `1` - `WIKITOOL_TASKS_CONFIG` names a profile that does not exist or is broken
- `1` - Neither or both of `--project`/`--inbox`, or a `--follow-up-at` without `--waiting` or not `YYYY-MM-DD` - `1` - Neither or both of `--project`/`--inbox`, or a `--follow-up-at` without `--waiting` or not `YYYY-MM-DD`
- `1` - A `--project` name matching no tracker project - `1` - A `--project` name matching no tracker project
- `1` - `--waiting` against a provider with no way to represent it right now (Super Productivity: the `waiting` tag does not exist) - `1` - `--waiting` against a provider with no way to represent it right now (Super Productivity: the `waiting` tag does not exist)
- `1` - A read-only access path (Super Productivity's `access: "snapshot"`) - `1` - A read-only access path (Super Productivity's `access: "snapshot"`)
- `42` - The tracker settings file of an earlier version is still there, or a secret source (1Password) needs unlocking
**See also** **See also**
@@ -229,8 +230,9 @@ tools/wikitool task list --project "Homelab migration"
- `0` - success - `0` - success
- `0` - A `--project` matching no tracker project - prints "No open items", not an error - `0` - A `--project` matching no tracker project - prints "No open items", not an error
- `1` - No `.wikitool-tasks.json` - no tracker configured - `1` - No `tasks` section in the settings - no tracker configured
- `1` - `WIKITOOL_TASKS_CONFIG` names a file that does not exist or is broken - `1` - `WIKITOOL_TASKS_CONFIG` names a profile that does not exist or is broken
- `42` - The tracker settings file of an earlier version is still there, or a secret source (1Password) needs unlocking
**See also** **See also**
@@ -264,10 +266,10 @@ tools/wikitool task close --id <item-id>
- Marks one tracker item done. It never deletes or moves an item - the only closing write this stack makes. - Marks one tracker item done. It never deletes or moves an item - the only closing write this stack makes.
- `--id` is the provider's own item id, from `task list` or a `review` finding - never a title. - `--id` is the provider's own item id, from `task list` or a `review` finding - never a title.
- The tracker configuration is read from `.wikitool-tasks.json`, or from the file `WIKITOOL_TASKS_CONFIG` names 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". - The tracker configuration is read from the `tasks`, `superproductivity` and `caldav` sections of the settings, or from the profile `WIKITOOL_TASKS_CONFIG` names 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.json` fails with a "no tracker configured" message. - No `tasks` section fails 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 the `access: "api"` instance to use instead. - A provider whose configured access path has no write path (Super Productivity's `access: "snapshot"`) refuses entirely with exit 1, naming the `access: "api"` instance to use instead.
- **Never exits 42.** - **Exits 42 only for the settings** (the tracker settings file of an earlier version, a locked secret source).
**Properties** **Properties**
@@ -280,10 +282,11 @@ tools/wikitool task close --id <item-id>
**Exit codes** **Exit codes**
- `0` - success - `0` - success
- `1` - No `.wikitool-tasks.json` - no tracker configured - `1` - No `tasks` section in the settings - no tracker configured
- `1` - `WIKITOOL_TASKS_CONFIG` names a file that does not exist or is broken - `1` - `WIKITOOL_TASKS_CONFIG` names a profile that does not exist or is broken
- `1` - An `--id` matching no tracker item right now - `1` - An `--id` matching no tracker item right now
- `1` - A read-only access path (Super Productivity's `access: "snapshot"`) - `1` - A read-only access path (Super Productivity's `access: "snapshot"`)
- `42` - The tracker settings file of an earlier version is still there, or a secret source (1Password) needs unlocking
**See also** **See also**
+1 -1
@@ -1,4 +1,4 @@
*Generated from [`docs/reference/commands/provenance.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/docs/reference/commands/provenance.md) at v9.0.0 – edit the repository, not this wiki.* *Generated from [`docs/reference/commands/provenance.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/reference/commands/provenance.md) at v10.0.0 – edit the repository, not this wiki.*
# Provenance # Provenance
+145 -14
@@ -1,4 +1,4 @@
*Generated from [`docs/reference/commands/raw-material-and-uploads.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/docs/reference/commands/raw-material-and-uploads.md) at v9.0.0 – edit the repository, not this wiki.* *Generated from [`docs/reference/commands/raw-material-and-uploads.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/reference/commands/raw-material-and-uploads.md) at v10.0.0 – edit the repository, not this wiki.*
# Raw material and uploads # Raw material and uploads
@@ -8,11 +8,13 @@ Bringing new material into `raw/`: fetching and capturing it, seeing what is wai
## Contents ## Contents
- [`raw fetch`](#raw-fetch) - [`raw fetch`](#raw-fetch)
- [`raw login`](#raw-login)
- [`raw derive`](#raw-derive) - [`raw derive`](#raw-derive)
- [`raw capture`](#raw-capture) - [`raw capture`](#raw-capture)
- [`raw status`](#raw-status) - [`raw status`](#raw-status)
- [`raw pending`](#raw-pending) - [`raw pending`](#raw-pending)
- [`raw accept`](#raw-accept) - [`raw accept`](#raw-accept)
- [`upload collect`](#upload-collect)
- [`upload list`](#upload-list) - [`upload list`](#upload-list)
- [`upload show`](#upload-show) - [`upload show`](#upload-show)
- [`upload accept`](#upload-accept) - [`upload accept`](#upload-accept)
@@ -22,11 +24,12 @@ Bringing new material into `raw/`: fetching and capturing it, seeing what is wai
<!-- wikitool:reference-commands --> <!-- wikitool:reference-commands -->
## `raw fetch` ## `raw fetch`
Capture a web page the user names into `incoming/`: the HTML as received plus a derived text, for `raw accept` to promote. Capture a web page the user names into `incoming/`: the HTML as received plus a derived text, for `raw accept` to promote. Pages of a known website are fetched in the website's standard form and cleaned of its page furniture; a host the instance routes so is fetched with the user's own session or through an external fetcher.
**Synopsis** **Synopsis**
- `wikitool raw fetch <url> [--name <stem>]` - Fetch the page and write `incoming/<stem>.html` and `incoming/<stem>.md` - `wikitool raw fetch <url> [--name <stem>]` - Fetch the page and write `incoming/<stem>.html` and `incoming/<stem>.md`
- `wikitool raw fetch <url> --scope [--name <stem>]` - Fetch the page and the subpages its site handler offers, and write the folder `incoming/<stem>/` - a `.html` + `.md` pair per page and the manifest `_scope.json`
- `wikitool raw fetch --html incoming/<file>.html --url <url>` - Derive the `.md` from a page a human saved from their browser - no network access - `wikitool raw fetch --html incoming/<file>.html --url <url>` - Derive the `.md` from a page a human saved from their browser - no network access
**Options** **Options**
@@ -40,6 +43,8 @@ Options:
a browser) instead of fetching - no network access a browser) instead of fetching - no network access
--url <str> With --html: the page's URL, for the header and relative links --url <str> With --html: the page's URL, for the header and relative links
--name <str> File stem in incoming/ instead of the one computed from the URL --name <str> File stem in incoming/ instead of the one computed from the URL
--scope Also fetch the subpages the site handler offers for this page,
into the folder incoming/<stem>/
-h, --help Show this message and exit. -h, --help Show this message and exit.
``` ```
@@ -48,18 +53,24 @@ Options:
```bash ```bash
tools/wikitool raw fetch https://example.org/blog/post tools/wikitool raw fetch https://example.org/blog/post
tools/wikitool raw fetch https://example.org/ --name example-start tools/wikitool raw fetch https://example.org/ --name example-start
tools/wikitool raw fetch https://learn.microsoft.com/en-us/powershell/module/oslicense/ --scope
tools/wikitool raw fetch --html incoming/post.html --url https://example.org/blog/post tools/wikitool raw fetch --html incoming/post.html --url https://example.org/blog/post
``` ```
**Behaviour** **Behaviour**
- Writes into `incoming/` only, never into `raw/`: `raw accept` promotes both files afterwards, in one call, as one bundle under `raw/<YYYY>/<MM>/<stem>/`. - Writes into `incoming/` only, never into `raw/`: `raw accept` promotes both files afterwards, in one call, as one bundle under `raw/<YYYY>/<MM>/<stem>/`.
- The `.html` holds the response body byte for byte. The `.md` starts with a fixed header block (`fetched_by`, `url`, `final_url`, `retrieved`, `http_status`, `content_type`, `charset`, `title`, `derived_from`) followed by the derived text. - The `.html` holds the response body byte for byte. The `.md` starts with a fixed header block (`fetched_by`, `url`, `final_url`, `retrieved`, `http_status`, `content_type`, `charset`, `title`, `derived_from`) followed by the derived text. A page of a website with a site handler adds `site`, and `requested_url` when the URL fetched is not the one typed.
- Character set, in this order: byte-order mark, the HTTP `Content-Type` charset, a `<meta>` declaration in the first 4 KiB, UTF-8. Undecodable bytes are replaced with U+FFFD and reported; the header names the charset and where it came from. - Character set, in this order: byte-order mark, the HTTP `Content-Type` charset, a `<meta>` declaration in the first 4 KiB, UTF-8. Undecodable bytes are replaced with U+FFFD and reported; the header names the charset and where it came from.
- Derived text: the content root is `<main>`, else the single `<article>`, else `<body>`; `script`, `style`, `noscript`, `nav`, `header`, `footer`, `aside`, `form`, `template` and `svg` are dropped below it. Headings, lists, code, tables and links (made absolute) become Markdown. The same bytes always give the same text. - Derived text: the content root is `<main>`, else the single `<article>`, else `<body>`; `script`, `style`, `noscript`, `nav`, `header`, `footer`, `aside`, `form`, `template` and `svg` are dropped below it. Headings, lists, code, tables and links (made absolute) become Markdown. The same bytes always give the same text.
- Site handlers: for the pages of a website the stack or the instance knows (the stack's today: `learn.microsoft.com`; the instance's are JSON files in `sites/`), `raw fetch <url>` fetches the standard form of the URL (the locale segment becomes `en-us`, the `view=` query stays) and `<url>` as typed goes into the header as `requested_url`; `url` is the form fetched. The handler also removes the page furniture the website renders around the article from the derived text - never from the `.html` - and prints hints that apply to this page as `CHECK` lines: act on one before accepting. `--html --url <url>` removes the same furniture but keeps the URL as typed. A URL no handler claims is handled as before. The header names an instance's handler as `<name> (instance)`. A handler file of the instance that cannot be used, and names the URL's host or no readable host, stops the fetch before anything is fetched or written. The `sites` section of the settings switches handlers off, sets the locale and adds hint lines of the instance's own per host.
- `--scope` is for a page whose site handler offers subpages - the stack's today: a PowerShell module's overview page on `learn.microsoft.com`. The handler names them from the page's own HTML: the links it selects that lead to the same host, under the path of the page itself (or the prefix the handler sets); no other link is followed, and a subpage's own links never are. Without `--scope` the one page is fetched as before and a `CHECK` line says how many subpages it offers. With it, `incoming/<stem>/` holds the page and every subpage as a `<name>.html` + `<name>.md` pair - the `.html` the response byte for byte, the `.md` derived as for a single page, `<name>` from the subpage's URL - and `_scope.json`: the URL, the retrieval time and the `sha256` of the `.html` of each page. `raw accept incoming/<stem>` takes the folder as one bundle.
- `--scope` is bounded. The `robots.txt` of the host is honoured for the subpages (not for the page itself): one it forbids refuses the whole fetch and names the URL. More subpages than `sites.max_pages` (200) refuses it before the first subpage is requested; `sites.pause_seconds` (1) is the pause before each subpage request. Both are settings of the `instance` tier. Everything is checked before the first write: the path budget under `raw/<YYYY>/<MM>/`, a folder of that name already in `incoming/` or `raw/`. A failing request leaves `incoming/` as it was.
- The stem is the URL's last path segment without its extension, else its host name - ASCII, lowercase, `-`-separated, at most 60 characters; `--name` overrides it. - The stem is the URL's last path segment without its extension, else its host name - ASCII, lowercase, `-`-separated, at most 60 characters; `--name` overrides it.
- A `text/plain` or `text/markdown` response is stored as received as `.txt`/`.md`; any other non-HTML response (PDF, image, ...) as received under the extension of its content type. Neither gets a derived file or a header. - A `text/plain` or `text/markdown` response is stored as received as `.txt`/`.md`; any other non-HTML response (PDF, image, ...) as received under the extension of its content type. Neither gets a derived file or a header.
- Only `http`/`https`, on every redirect hop too. 30 s for the whole transfer, 25 MiB at most. No cookies, no JavaScript: a derived text under 200 characters is written, with a warning to read it before accepting. - Only `http`/`https`, on every redirect hop too. 30 s for the whole transfer, 25 MiB at most. No cookies and no JavaScript unless the host is routed or its handler says so (below): a derived text under 200 characters is written, with a warning to read it before accepting.
- Fetch routes: the `fetch` section of the settings sends a host's pages another way. `session` fetches them with the cookies of the session the user stored with `raw login`, still with the built-in fetch and the bytes as received; the header adds `session: <host>` and never the session's content. The host's site handler names how a page shows it arrived signed in: no stored session, or a page without that sign (an expired session, a teaser), stops with exit 42 and writes nothing. Any other route names an external fetcher of this checkout: run without a shell with the URL as its last argument (120 s, 25 MiB), its stdout is the `.html` byte for byte, and the header says `fetched_by: wikitool raw fetch (fetcher: <name>)` and carries no `http_status`. A fetcher that fails is never replaced by the built-in fetch. A host no route names is fetched as before.
- Rendered pages: a site handler that declares `render` has its pages fetched through a headless browser - with the session, when the host has one - and the `.html` is the page as the browser drew it, not the bytes the server sent; the header says `rendered: true`. No page is rendered without that declaration, and an external fetcher's page never is. It needs the optional browser component; without it the fetch stops with exit 42. `--scope` fetches every page of the bundle the same way, and `_scope.json` names the `session`, `fetcher` or `rendered` it used.
- `--html` derives the `.md` beside a page saved to `incoming/` from a logged-in browser - the way past a paywall or a script-rendered page. Its header carries `derived` (when the text was derived) instead of `retrieved`, `final_url`, `http_status` and `content_type`, and the `.html` is left untouched. - `--html` derives the `.md` beside a page saved to `incoming/` from a logged-in browser - the way past a paywall or a script-rendered page. Its header carries `derived` (when the text was derived) instead of `retrieved`, `final_url`, `http_status` and `content_type`, and the `.html` is left untouched.
- Success prints the `raw accept` line for the written files and the `source_url` for the source page. - Success prints the `raw accept` line for the written files and the `source_url` for the source page.
@@ -67,7 +78,7 @@ tools/wikitool raw fetch --html incoming/post.html --url https://example.org/blo
- Changes files: yes - Changes files: yes
- Idempotent: no - Idempotent: no
- Atomic: Yes for what it leaves behind - one or two new files in `incoming/`, created exclusively; a failure on the second removes the first - Atomic: Yes for what it leaves behind - one or two new files in `incoming/`, created exclusively; a failure on the second removes the first. With `--scope`: all of the folder or none of it - the pages are fetched into a temporary place and moved in as a whole
- Counts toward the session call budget: yes - Counts toward the session call budget: yes
- Can reach the network: yes - Can reach the network: yes
@@ -75,16 +86,79 @@ tools/wikitool raw fetch --html incoming/post.html --url https://example.org/blo
- `0` - success - `0` - success
- `1` - The URL is not `http`/`https` (also after a redirect), or neither or both of `<url>` and `--html` were given - `1` - The URL is not `http`/`https` (also after a redirect), or neither or both of `<url>` and `--html` were given
- `1` - The `sites` section of the settings is malformed, `disabled` names a handler that does not exist, or a file in `sites/` cannot be used and names this URL's host (or none can be read)
- `1` - A target file already exists in `incoming/` - `1` - A target file already exists in `incoming/`
- `1` - The server answered with an HTTP error, could not be reached, took longer than 30 s, or sent more than 25 MiB - `1` - The server answered with an HTTP error, could not be reached, took longer than 30 s, or sent more than 25 MiB
- `1` - The `fetch` section is malformed, routes the host through the session though no site handler of the host names a login page, or names a fetcher this checkout does not define
- `1` - The external fetcher could not be started, timed out, ended with an exit other than 0, printed nothing or more than 25 MiB
- `1` - raw fetch --scope: No site handler offers subpages for the URL, the page offers none or more than `sites.max_pages`, `robots.txt` forbids a subpage or cannot be read, a subpage cannot be fetched, answers something other than HTML or leaves the host, the folder `incoming/<stem>/` exists, or a path would be over the budget
- `1` - raw fetch --html: `--url` is missing, the file is not directly in `incoming/` or does not exist, or `incoming/<stem>.md` already exists - `1` - raw fetch --html: `--url` is missing, the file is not directly in `incoming/` or does not exist, or `incoming/<stem>.md` already exists
- `42` - The host is fetched with the user's session and none is stored, or the page arrived without the sign of being signed in that its site handler names (the session expired)
- `42` - The site handler declares `render` and the optional browser component is not installed
**See also** **See also**
- `raw/CONTRACT.md` "Getting a URL in: `raw fetch`" - the rules and why - `raw/CONTRACT.md` "Getting a URL in: `raw fetch`" - the rules and why
- `sites/CONTRACT.md` - the instance's own handler files
- `docs/reference/configuration.md` - the `sites` section, which tunes the site handlers, and the `fetch` section, which routes a host
- `wikitool raw login` - the user signs in once for a host routed through the session
- `wikitool raw accept` - promotes the written files into `raw/` - `wikitool raw accept` - promotes the written files into `raw/`
- `instructions/wiki-ingest/SKILL.md` - where a URL to ingest starts - `instructions/wiki-ingest/SKILL.md` - where a URL to ingest starts
## `raw login`
Let the user sign in to a website once, in a visible browser window, and store the session it leaves as a secret outside the working tree, for `raw fetch` to fetch that website's pages with. Only the user runs it.
**Synopsis**
- `wikitool raw login <host>`
**Options**
```text
Arguments:
host The website's host, such as www.example.org [required]
Options:
-h, --help Show this message and exit.
```
**Examples**
```bash
tools/wikitool raw login www.heise.de
```
**Behaviour**
- Opens the login page the host's site handler names (its `session` block) in a visible browser window and waits until the user has signed in - captcha and second factor included - and closed the window, at most 15 minutes.
- Stores only the browser state the window leaves (cookies and storage), never a password, under `session_<host>` (every run of other characters as one `_`) in the secrets file of the instance, outside the working tree and readable by its owner only. `WIKITOOL_SECRET_SESSION_<HOST>` answers before the file, as for any named secret. The session is never written to 1Password.
- Needs the host routed through the session first (`fetch.routes`, set with `wikitool config set`), which also gives the instance the id its secrets file is found by.
- Needs the optional browser component, which the user installs with the preflight's `--with browser` option.
- Prints no part of the session. Signing in again replaces the stored session.
**Properties**
- Changes files: yes
- Idempotent: yes
- Atomic: Yes - the secrets file is replaced in one rename, with every other entry kept
- Counts toward the session call budget: yes
- Can reach the network: yes
**Exit codes**
- `0` - success
- `1` - The host is not routed through the session in `fetch.routes`, no enabled site handler of the host names a login page, or the `fetch` settings are malformed
- `1` - The browser could not be started, or the window closed before any state could be read
- `42` - The optional browser component is not installed
**See also**
- `wikitool raw fetch` - fetches the pages of a routed host with the stored session
- `raw/CONTRACT.md` "Getting a URL in: `raw fetch`" - fetching behind a login
- `sites/CONTRACT.md` - a handler's `session` block
- `docs/reference/configuration.md` - the `fetch` section
## `raw derive` ## `raw derive`
Derive reading text from a PDF, e-mail, Office file or web page in `incoming/`, or unpack a ZIP, and leave the original beside it, for `raw accept` to promote. Derive reading text from a PDF, e-mail, Office file or web page in `incoming/`, or unpack a ZIP, and leave the original beside it, for `raw accept` to promote.
@@ -190,7 +264,7 @@ tools/wikitool raw capture --update raw/2026/10/service-docs
- `--ref` is a branch name (`main`) or a tag pattern (`v*`, any of `*?[`), which names the newest matching tag by version order. It resolves to one commit - for an annotated tag, the commit it points at. - `--ref` is a branch name (`main`) or a tag pattern (`v*`, any of `*?[`), which names the newest matching tag by version order. It resolves to one commit - for an annotated tag, the commit it points at.
- The commit is fetched by ref name, shallowly, into a bare cache repository per URL under `tools/.wikitool_capture/` (gitignored). Files are read from it as blobs, never through a checkout, so they are byte-identical to the repository's - no line-ending conversion, no filter. - The commit is fetched by ref name, shallowly, into a bare cache repository per URL under `tools/.wikitool_capture/` (gitignored). Files are read from it as blobs, never through a checkout, so they are byte-identical to the repository's - no line-ending conversion, no filter.
- `--path` globs (repeatable) select files by their path in the repository, as git's `:(glob)` pathspec does: `*`, `?` and `[...]` do not cross `/`; `**` as a whole segment spans any number of directories; a glob without wildcards also takes everything below it as a directory. Files keep their repository paths inside the bundle. - `--path` globs (repeatable) select files by their path in the repository, as git's `:(glob)` pathspec does: `*`, `?` and `[...]` do not cross `/`; `**` as a whole segment spans any number of directories; a glob without wildcards also takes everything below it as a directory. Files keep their repository paths inside the bundle.
- Never captured, each named in the output with its reason: a file whose first line starts with `<!-- wikitool:export`, a symlink, a submodule, a file over 25 MiB, a path with a segment starting with `.`, a file named `_capture.json`, and a Git LFS pointer. - Never captured, each named in the output with its reason: a file whose first line starts with `<!-- wikitool:export` or a leading frontmatter block with a `wikitool_export:` key, a symlink, a submodule, a file over 25 MiB, a path with a segment starting with `.`, a file named `_capture.json`, and a Git LFS pointer.
- `incoming/<bundle>/_capture.json` records `schema`, `repo`, `ref`, `commit`, `paths`, `captured` (UTC), `fidelity`, `authority` and `files`. It is the only declaration that and how this instance follows the repository. - `incoming/<bundle>/_capture.json` records `schema`, `repo`, `ref`, `commit`, `paths`, `captured` (UTC), `fidelity`, `authority` and `files`. It is the only declaration that and how this instance follows the repository.
- Only `ssh://`, `https://` and the scp form `user@host:path`; a URL with a password or token in it is refused, since the manifest is committed. Git runs with the host's own credentials and configuration, restricted to those protocols, and never prompts: a repository that asks for credentials is reported as unreachable. Each git call times out after 120 s. - Only `ssh://`, `https://` and the scp form `user@host:path`; a URL with a password or token in it is refused, since the manifest is committed. Git runs with the host's own credentials and configuration, restricted to those protocols, and never prompts: a repository that asks for credentials is reported as unreachable. Each git call times out after 120 s.
- `--update <raw-bundle>` takes URL, ref rule and globs from that bundle's manifest and writes the current state to `incoming/<same bundle name>/`; `--fidelity`/`--authority` default to the manifest's values, and passing one is the way to correct it. - `--update <raw-bundle>` takes URL, ref rule and globs from that bundle's manifest and writes the current state to `incoming/<same bundle name>/`; `--fidelity`/`--authority` default to the manifest's values, and passing one is the way to correct it.
@@ -294,7 +368,7 @@ tools/wikitool raw pending --json
**Behaviour** **Behaviour**
- A candidate is a top-level entry of `incoming/`: a single `file`, a `bundle` of top-level files sharing a stem (a `raw fetch` pair), or a `folder` with every file below it. Dotfiles, empty directories and their contents are none; `mcp-upload/` is outside `incoming/` and never listed. - A candidate is a top-level entry of `incoming/`: a single `file`, a `bundle` of top-level files sharing a stem (a `raw fetch` pair), or a `folder` with every file below it. Dotfiles, empty directories and their contents are none; `quarantine/` is outside `incoming/` and never listed.
- Order: oldest first by modification time. A bundle or folder counts as new as its newest file; a tie goes by name. The mtime is when a document last changed only if it was copied with its timestamps kept (`cp -p`, `rsync -a`, an unpacked archive) - for a download or a `raw fetch` it is merely when it was dropped. - Order: oldest first by modification time. A bundle or folder counts as new as its newest file; a tie goes by name. The mtime is when a document last changed only if it was copied with its timestamps kept (`cp -p`, `rsync -a`, an unpacked archive) - for a download or a `raw fetch` it is merely when it was dropped.
- Each candidate shows its path(s), kind, file count and mtime, and whether `raw accept` would take it as it stands - the same checks, minus `--fidelity`/`--authority`. One it would refuse is listed with the reason and skipped: it needs a human. - Each candidate shows its path(s), kind, file count and mtime, and whether `raw accept` would take it as it stands - the same checks, minus `--fidelity`/`--authority`. One it would refuse is listed with the reason and skipped: it needs a human.
- The default is the first candidate `raw accept` would take, and the output names it. - The default is the first candidate `raw accept` would take, and the output names it.
@@ -405,7 +479,7 @@ tools/wikitool raw accept incoming/chemenu --replaces-bundle raw/2026/10/chemenu
- `0` - success - `0` - success
- `1` - raw accept: A file does not exist or is not directly in `incoming/`; 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: A file does not exist or is not directly in `incoming/`; 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 incoming/<folder>: The folder is not directly in `incoming/`, is combined with another argument, `--page` or `--replaces`, holds no file, or holds a hidden entry, a symlink or a special file; a target path is over the budget; or the folder name is already occupied under `raw/` - `1` - raw accept incoming/<folder>: The folder is not directly in `incoming/`, is combined with another argument, `--page` or `--replaces`, holds no file, or holds a hidden entry, a symlink or a special file; a target path is over the budget; or the folder name is already occupied under `raw/`
- `1` - A file - given directly, inside a folder, or as `--replaces`/`--replaces-bundle` material - is a guideline export: its first line, after an optional BOM, starts with `<!-- wikitool:export` - `1` - A file - given directly, inside a folder, or as `--replaces`/`--replaces-bundle` material - is an export of this wiki: it carries the export marker (a first line starting with `<!-- wikitool:export`, or a `wikitool_export:` key in a leading frontmatter block), or it is byte for byte a file in the manifest of a configured `export` target
- `1` - raw accept: `--fidelity`/`--authority` is missing, or names `unknown` or a value outside the schema's enum - `1` - raw accept: `--fidelity`/`--authority` is missing, or names `unknown` or 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: The target name is already occupied anywhere under `raw/` by something the call does not own
- `1` - raw accept: `--page` names an unknown page or one with no `raw_files:` yet, an existing `raw_files:` entry is missing on disk, a file to be moved has more than one owning page, or `--page` would overwrite an already-set `fidelity`/`authority` with a different value - `1` - raw accept: `--page` names an unknown page or one with no `raw_files:` yet, an existing `raw_files:` entry is missing on disk, a file to be moved has more than one owning page, or `--page` would overwrite an already-set `fidelity`/`authority` with a different value
@@ -424,9 +498,64 @@ tools/wikitool raw accept incoming/chemenu --replaces-bundle raw/2026/10/chemenu
- `wikitool types describe source` - the capture field values - `wikitool types describe source` - the capture field values
- `wikitool new source` - the source page for a promoted file - `wikitool new source` - the source page for a promoted file
## `upload collect`
Pull material from the configured `folder` and `command` intake channels into the quarantine - never past it.
**Synopsis**
- `wikitool upload collect [<channel>...]`
**Options**
```text
Arguments:
names... Channels to collect from; all pulled channels when none is named
Options:
-h, --help Show this message and exit.
```
**Examples**
```bash
tools/wikitool upload collect
tools/wikitool upload collect onedrive
```
**Behaviour**
- Without arguments, collects from every channel of the `intake` section whose handler is `folder` or `command`; with names, from those channels only. A name no channel has, or the name of the `mcp` channel (which is pushed, not pulled), exits 1 before anything happens.
- Everything a channel delivers lands in `quarantine/<id>/` unchanged, with a manifest that names `channel`, `handler` and `origin`; the submitter is `unknown`. Nothing reaches `incoming/` or `raw/` - that is `upload accept`, behind its gate, and an agent may run `collect` because collecting gives no trust.
- `folder`: only the top level of `path`. A file is taken once its modification time and, where the platform has it, its creation time are older than `settle_seconds`, its extension and size fit, and its name passes the filename rules. It is removed from the folder only after its copy is fully written and the source still has the size and sha256 that were read; if it changed meanwhile, nothing is kept and it stays. A file whose sha256 already waits in the quarantine is reported as a duplicate and removed from the folder, because its content is kept. Subdirectories, hidden files (names starting with `.` or `~$`, or the hidden or system attribute on Windows), symbolic links, other extensions and oversized files are left untouched and reported.
- `command`: the program of `commands.<name>` runs without a shell, with stdin closed, a 300 s timeout and an empty staging directory under `quarantine/.staging/` as its last argument. On exit 0 each regular file at the top of that directory with an allowed extension and size becomes a submission; on anything else nothing is taken and the end of stderr is printed. The staging directory is always removed.
- Channels are independent: when one fails the others still run, and the exit status is 1 if any channel or file failed. With no pulled channel it prints `nothing to collect` and exits 0.
- Exits 1, before it does anything, while an `mcp-upload/` of an earlier version still holds submissions, and when the `intake` section is malformed.
- Not idempotent: a second run finds the folder emptied.
**Properties**
- Changes files: yes
- Idempotent: no
- Atomic: No - per file: one copy into `quarantine/<id>/` (manifest last), then one removal of the source; a channel's failure does not stop the others
- Counts toward the session call budget: yes
- Can reach the network: no
**Exit codes**
- `0` - success
- `1` - Unknown channel name, an `mcp` channel as argument, a malformed `intake` section, or an `mcp-upload/` that still holds submissions
- `1` - A channel or a file failed (folder missing, program exited non-zero, timed out or could not be started, a source that could not be removed)
**See also**
- `wikitool upload list` - what waits for review afterwards
- `wikitool upload accept` - the gate out of the quarantine
- `docs/how-to/collect-from-a-synced-folder.md`
## `upload list` ## `upload list`
List every MCP submission currently waiting in the quarantine (`mcp-upload/`). List every submission currently waiting in the quarantine (`quarantine/`), whatever channel it came through.
**Synopsis** **Synopsis**
@@ -448,8 +577,9 @@ tools/wikitool upload list --json
**Behaviour** **Behaviour**
- Lists every submission waiting in `mcp-upload/`, oldest id first: id, filename, size, submitter. - Lists every submission waiting in `quarantine/`, oldest id first: id, filename, size, channel, submitter.
- Only ever non-empty when `.wikitool-upload.json` opts the checkout into the MCP server's `submit` tool. - Only ever non-empty when the `intake` section of `.wikitool-local.json` declares a channel.
- Exits 1, before it does anything, while an `mcp-upload/` of an earlier version still holds submissions - the message names the command that moves it.
- A submission directory with a corrupt manifest is silently skipped. - A submission directory with a corrupt manifest is silently skipped.
- Never fails; read-only and safe to retry freely. - Never fails; read-only and safe to retry freely.
@@ -497,7 +627,8 @@ tools/wikitool upload show <id>
**Behaviour** **Behaviour**
- 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. - Prints one submission's manifest in full: filename, size, sha256, submitter, submitter source (the header name or the handler, not a claim the header was honest), submission time, and where it came from: channel, handler, origin (the source file's path, `command:<name>` or `mcp:submit`).
- A submitter of `unknown` means the channel has no sender to trust - read channel and origin instead.
- What a reviewer reads before `upload accept`. - What a reviewer reads before `upload accept`.
- Read-only. - Read-only.
@@ -547,7 +678,7 @@ tools/wikitool upload accept <id> --confirm <token> # re-run after exit 42, o
**Behaviour** **Behaviour**
- Promotes a submission's file from `mcp-upload/<id>/` into `incoming/`, deletes the quarantine directory, and appends an `accepted` event to `mcp-upload/ledger.jsonl`. - Promotes a submission's file from `quarantine/<id>/` into `incoming/`, deletes the quarantine directory, and appends an `accepted` event to `quarantine/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. - 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. - 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. - An occupied `incoming/<filename>` is an ordinary validation error, not the gate.
@@ -564,7 +695,7 @@ tools/wikitool upload accept <id> --confirm <token> # re-run after exit 42, o
**Exit codes** **Exit codes**
- `0` - success - `0` - success
- `1` - Unknown or malformed submission id, or the submission's file is missing from `mcp-upload/<id>/` - `1` - Unknown or malformed submission id, or the submission's file is missing from `quarantine/<id>/`
- `1` - `incoming/<filename>` already exists - `1` - `incoming/<filename>` already exists
- `42` - Upload Review Gate: `--confirm` is absent or does not match the manifest's current token - `42` - Upload Review Gate: `--confirm` is absent or does not match the manifest's current token
+1 -1
@@ -1,4 +1,4 @@
*Generated from [`docs/reference/commands/telemetry.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/docs/reference/commands/telemetry.md) at v9.0.0 – edit the repository, not this wiki.* *Generated from [`docs/reference/commands/telemetry.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/reference/commands/telemetry.md) at v10.0.0 – edit the repository, not this wiki.*
# Telemetry # Telemetry
+7 -4
@@ -1,4 +1,4 @@
*Generated from [`docs/reference/commands/types-instructions-and-docs.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/docs/reference/commands/types-instructions-and-docs.md) at v9.0.0 – edit the repository, not this wiki.* *Generated from [`docs/reference/commands/types-instructions-and-docs.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/reference/commands/types-instructions-and-docs.md) at v10.0.0 – edit the repository, not this wiki.*
# Types, instructions and docs # Types, instructions and docs
@@ -140,6 +140,7 @@ tools/wikitool instructions sync
**Behaviour** **Behaviour**
- Publishes every `instructions/<name>/SKILL.md` into `.agents/skills/` and `.claude/skills/` as **copies**, and deletes published skills whose source is gone. - Publishes every `instructions/<name>/SKILL.md` into `.agents/skills/` and `.claude/skills/` as **copies**, and deletes published skills whose source is gone.
- Each published `SKILL.md` is its source plus one generated line directly under the frontmatter: the `language:` of `kb/CONVENTIONS.md` and a pointer to `AGENTS.md` § Conversation language, so the agent has the value in context when the task begins. The frontmatter and every other file are copied unchanged. With no language declared, the `SKILL.md` is copied unchanged too, and the success message says so.
- Both targets are gitignored, so a fresh clone runs this once. - Both targets are gitignored, so a fresh clone runs this once.
- Re-running repairs a drifted copy: the source always wins. - Re-running repairs a drifted copy: the source always wins.
- `--force` is required only to replace a target directory that is not a published skill at all (no `SKILL.md` in it). - `--force` is required only to replace a target directory that is not a published skill at all (no `SKILL.md` in it).
@@ -188,7 +189,7 @@ tools/wikitool instructions verify
- Flat instructions validate against `types/instruction.schema.yaml`, and each `SKILL.md` carries the frontmatter its harness reads. - Flat instructions validate against `types/instruction.schema.yaml`, and each `SKILL.md` carries the frontmatter its harness reads.
- No `SKILL.md` carries a relative markdown link: `sync` copies it to a different depth than the source, so a `SKILL.md` references 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"). - No `SKILL.md` carries a relative markdown link: `sync` copies it to a different depth than the source, so a `SKILL.md` references 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. - Every published copy is what `sync` would write from its source: byte-identical, except for the generated language line in `SKILL.md` that the current `language:` of `kb/CONVENTIONS.md` calls for. 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: true` must instead not be linked from AGENTS.md, CLAUDE.md or a skill. - No instruction is left that nothing references; one marked `manual: true` must 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`). - Nothing under `instructions/dev/` is referenced from outside it; a `<!-- dist:strip-start/end -->` block is exempt (`instructions/CONTRACT.md`).
- Read-only. - Read-only.
@@ -206,7 +207,7 @@ tools/wikitool instructions verify
- `0` - success - `0` - success
- `1` - Nothing found under `instructions/` at all, or a malformed instruction or `SKILL.md` - `1` - Nothing found under `instructions/` at all, or a malformed instruction or `SKILL.md`
- `1` - A `SKILL.md` carries a relative markdown link - `1` - A `SKILL.md` carries a relative markdown link
- `1` - A published copy drifted from its source - `1` - A published copy drifted from its source - including a language line that belongs to an earlier `language:` (the message names it as such)
- `1` - An instruction nothing references, or a `manual: true` one that IS linked from AGENTS.md, CLAUDE.md or a skill and so risks running implicitly - `1` - An instruction nothing references, or a `manual: true` one 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 a `dist:strip` block - `1` - Something under `instructions/dev/` is referenced from outside it and outside a `dist:strip` block
@@ -285,7 +286,8 @@ tools/wikitool docs verify
- Collections: every directory under `kb/` has a `COLLECTION.md` and no directory outside it does; every collection declares `profile:` and a `required_by_stack:` that agrees with the stack's own list. - Collections: every directory under `kb/` has a `COLLECTION.md` and no directory outside it does; every collection declares `profile:` and a `required_by_stack:` that agrees with the stack's own list.
- Types: every type the stack lists (currently `source` and `project`) has a type-spec of that name whose schema requires the field the stack list names (`raw_files:`/`state:`); every file under `types/` declaring `type: types/type-spec.md` validates against `types/type-spec.schema.yaml`; every subtype template `types/<type>.<value>.md` (any `<value>` but `guidance`) sits beside a type-spec declaring `subtype_field:`, names a value that field's schema enum allows, and carries no frontmatter; no pre-migration `type: entity` blocks are left in the contracts. - Types: every type the stack lists (currently `source` and `project`) has a type-spec of that name whose schema requires the field the stack list names (`raw_files:`/`state:`); every file under `types/` declaring `type: types/type-spec.md` validates against `types/type-spec.schema.yaml`; every subtype template `types/<type>.<value>.md` (any `<value>` but `guidance`) sits beside a type-spec declaring `subtype_field:`, names a value that field's schema enum allows, and carries no frontmatter; no pre-migration `type: entity` blocks 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. - `kb/CONVENTIONS.md`, if it exists at all, names all three tool-owned section headings; every stage contract is present.
- The `.gitignore` canaries clear in both directions: nothing ignored under `raw/`/`kb/`, `incoming/` ignored, everything ignored under `reports/` and the published skill directories. - The `.gitignore` canaries clear in both directions: nothing ignored under `raw/`/`kb/`, `incoming/` ignored, everything ignored under `reports/` and the published skill directories, `.wikitool-local.json` ignored and `wikitool.json` tracked.
- A `wikitool.json` at the root holds every value in the place its tier allows - no secret and no private field as plain text, no `checkout` field at all except as a reference - and a well-formed `schema` and `instance_id`. The local file is not read.
- No `.md`/`.template` file `dist export` would 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. - No `.md`/`.template` file `dist export` would 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 toc` covers carries the current table-of-contents region for its own headings - missing and stale are one check. - Every reference file `docs toc` covers 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 `#anchor` suffix 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. - Every relative markdown link in one of those reference files resolves to an existing file. A target's `#anchor` suffix 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.
@@ -314,6 +316,7 @@ tools/wikitool docs verify
- `1` - The `<!-- wikitool:commands -->` region of `tools/CONTRACT.md` is stale - `1` - The `<!-- wikitool:commands -->` region of `tools/CONTRACT.md` is stale
- `1` - A type-spec's own frontmatter fails its schema - `1` - A type-spec's own frontmatter fails its schema
- `1` - A subtype template `types/<type>.<value>.md` has no type-spec with a `subtype_field:` beside it, names a value outside that field's enum, or carries frontmatter - `1` - A subtype template `types/<type>.<value>.md` has no type-spec with a `subtype_field:` beside it, names a value outside that field's enum, or carries frontmatter
- `1` - `wikitool.json` holds a secret or private value as plain text, or a value its tier puts elsewhere
- `1` - A shipped `.md`/`.template` cites an issue number - `1` - A shipped `.md`/`.template` cites an issue number
- `1` - A region of the human reference under `docs/reference/` is stale - `1` - A region of the human reference under `docs/reference/` is stale
- `1` - A page of the human reference is missing or lacks its marker pair, or a page under `docs/reference/commands/` belongs to no group - `1` - A page of the human reference is missing or lacks its marker pair, or a page under `docs/reference/commands/` belongs to no group
@@ -1,4 +1,4 @@
*Generated from [`docs/reference/commands/workshop-runs-and-session-budget.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/docs/reference/commands/workshop-runs-and-session-budget.md) at v9.0.0 – edit the repository, not this wiki.* *Generated from [`docs/reference/commands/workshop-runs-and-session-budget.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/reference/commands/workshop-runs-and-session-budget.md) at v10.0.0 – edit the repository, not this wiki.*
# Workshop runs and session budget # Workshop runs and session budget
+20 -7
@@ -1,4 +1,4 @@
*Generated from [`docs/reference/commands/README.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/docs/reference/commands/README.md) at v9.0.0 – edit the repository, not this wiki.* *Generated from [`docs/reference/commands/README.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/reference/commands/README.md) at v10.0.0 – edit the repository, not this wiki.*
# Command reference # Command reference
@@ -17,12 +17,13 @@ collected in [exit codes and gates](Reference-Exit-codes).
- [Provenance](#provenance) - [Provenance](#provenance)
- [Raw material and uploads](#raw-material-and-uploads) - [Raw material and uploads](#raw-material-and-uploads)
- [Git](#git) - [Git](#git)
- [Guideline export](#guideline-export) - [Exports](#exports)
- [Workshop runs and session budget](#workshop-runs-and-session-budget) - [Workshop runs and session budget](#workshop-runs-and-session-budget)
- [Types, instructions and docs](#types-instructions-and-docs) - [Types, instructions and docs](#types-instructions-and-docs)
- [Telemetry](#telemetry) - [Telemetry](#telemetry)
- [Distribution and versioning](#distribution-and-versioning) - [Distribution and versioning](#distribution-and-versioning)
- [Content migrations](#content-migrations) - [Content migrations](#content-migrations)
- [Configuration](#configuration)
- [Instance health](#instance-health) - [Instance health](#instance-health)
<!-- /wikitool:toc --> <!-- /wikitool:toc -->
@@ -80,13 +81,15 @@ Full records: [provenance.md](Reference-Commands-Provenance)
Full records: [raw-material-and-uploads.md](Reference-Commands-Raw-material-and-uploads) Full records: [raw-material-and-uploads.md](Reference-Commands-Raw-material-and-uploads)
- [`raw fetch`](Reference-Commands-Raw-material-and-uploads#raw-fetch) - Capture a web page the user names into `incoming/`: the HTML as received plus a derived text, for `raw accept` to promote. - [`raw fetch`](Reference-Commands-Raw-material-and-uploads#raw-fetch) - Capture a web page the user names into `incoming/`: the HTML as received plus a derived text, for `raw accept` to promote. Pages of a known website are fetched in the website's standard form and cleaned of its page furniture; a host the instance routes so is fetched with the user's own session or through an external fetcher.
- [`raw login`](Reference-Commands-Raw-material-and-uploads#raw-login) - Let the user sign in to a website once, in a visible browser window, and store the session it leaves as a secret outside the working tree, for `raw fetch` to fetch that website's pages with. Only the user runs it.
- [`raw derive`](Reference-Commands-Raw-material-and-uploads#raw-derive) - Derive reading text from a PDF, e-mail, Office file or web page in `incoming/`, or unpack a ZIP, and leave the original beside it, for `raw accept` to promote. - [`raw derive`](Reference-Commands-Raw-material-and-uploads#raw-derive) - Derive reading text from a PDF, e-mail, Office file or web page in `incoming/`, or unpack a ZIP, and leave the original beside it, for `raw accept` to promote.
- [`raw capture`](Reference-Commands-Raw-material-and-uploads#raw-capture) - Capture documentation from a git repository into `incoming/<bundle>/`, with a manifest naming the repository, ref rule and commit, for `raw accept` to promote. - [`raw capture`](Reference-Commands-Raw-material-and-uploads#raw-capture) - Capture documentation from a git repository into `incoming/<bundle>/`, with a manifest naming the repository, ref rule and commit, for `raw accept` to promote.
- [`raw status`](Reference-Commands-Raw-material-and-uploads#raw-status) - Report which captured bundles under `raw/` have fallen behind their repository, with each changed file as `A`/`M`/`D`. - [`raw status`](Reference-Commands-Raw-material-and-uploads#raw-status) - Report which captured bundles under `raw/` have fallen behind their repository, with each changed file as `A`/`M`/`D`.
- [`raw pending`](Reference-Commands-Raw-material-and-uploads#raw-pending) - List what waits in `incoming/`, oldest first, and name the entry an ingest without an argument takes next. - [`raw pending`](Reference-Commands-Raw-material-and-uploads#raw-pending) - List what waits in `incoming/`, oldest first, and name the entry an ingest without an argument takes next.
- [`raw accept`](Reference-Commands-Raw-material-and-uploads#raw-accept) - Promote one or more files, or one folder, from `incoming/` into `raw/`. - [`raw accept`](Reference-Commands-Raw-material-and-uploads#raw-accept) - Promote one or more files, or one folder, from `incoming/` into `raw/`.
- [`upload list`](Reference-Commands-Raw-material-and-uploads#upload-list) - List every MCP submission currently waiting in the quarantine (`mcp-upload/`). - [`upload collect`](Reference-Commands-Raw-material-and-uploads#upload-collect) - Pull material from the configured `folder` and `command` intake channels into the quarantine - never past it.
- [`upload list`](Reference-Commands-Raw-material-and-uploads#upload-list) - List every submission currently waiting in the quarantine (`quarantine/`), whatever channel it came through.
- [`upload show`](Reference-Commands-Raw-material-and-uploads#upload-show) - Print one submission's manifest in full. - [`upload show`](Reference-Commands-Raw-material-and-uploads#upload-show) - Print one submission's manifest in full.
- [`upload accept`](Reference-Commands-Raw-material-and-uploads#upload-accept) - **Upload Review Gate:** promote a submission's file from quarantine into `incoming/`. - [`upload accept`](Reference-Commands-Raw-material-and-uploads#upload-accept) - **Upload Review Gate:** promote a submission's file from quarantine into `incoming/`.
- [`upload reject`](Reference-Commands-Raw-material-and-uploads#upload-reject) - Delete a submission's material, keeping only its ledger trail. - [`upload reject`](Reference-Commands-Raw-material-and-uploads#upload-reject) - Delete a submission's material, keeping only its ledger trail.
@@ -97,12 +100,15 @@ Full records: [git.md](Reference-Commands-Git)
- [`sync`](Reference-Commands-Git#sync) - Fetch `<remote>/<branch>` and bring the local branch up to date with it. - [`sync`](Reference-Commands-Git#sync) - Fetch `<remote>/<branch>` and bring the local branch up to date with it.
- [`publish`](Reference-Commands-Git#publish) - Reconcile with `<remote>/<branch>`, then stage all changes, commit, and push. - [`publish`](Reference-Commands-Git#publish) - Reconcile with `<remote>/<branch>`, then stage all changes, commit, and push.
- [`backup init`](Reference-Commands-Git#backup-init) - Create the backup target's bare repository and make it `origin` when there is none.
- [`backup run`](Reference-Commands-Git#backup-run) - Mirror this machine's files that are not in git into `<target>/<machine>/`.
## Guideline export ## Exports
Full records: [guideline-export.md](Reference-Commands-Guideline-export) Full records: [exports.md](Reference-Commands-Exports)
- [`export guidelines`](Reference-Commands-Guideline-export#export-guidelines) - Render the guideline pages into one generated `GUIDELINES.md`, or push it into the captured repositories that opted in (**Guideline Push Gate**). - [`export guidelines`](Reference-Commands-Exports#export-guidelines) - Render the guideline pages into one generated `GUIDELINES.md`, or push it into the captured repositories that opted in (**Guideline Push Gate**).
- [`export run`](Reference-Commands-Exports#export-run) - Write the derived, read-only reading copy of `kb/` into the folders the `export` section configures - the selected pages as Markdown, or converted by a program of this checkout.
## Workshop runs and session budget ## Workshop runs and session budget
@@ -159,6 +165,13 @@ Full records: [content-migrations.md](Reference-Commands-Content-migrations)
- [`migrate done`](Reference-Commands-Content-migrations#migrate-done) - Record one migration as applied, advancing `kb_version` in `.wikitool-kb.json`. - [`migrate done`](Reference-Commands-Content-migrations#migrate-done) - Record one migration as applied, advancing `kb_version` in `.wikitool-kb.json`.
- [`migrate baseline`](Reference-Commands-Content-migrations#migrate-baseline) - Declare `kb_version` once, for an instance predating `.wikitool-kb.json`. - [`migrate baseline`](Reference-Commands-Content-migrations#migrate-baseline) - Declare `kb_version` once, for an instance predating `.wikitool-kb.json`.
## Configuration
Full records: [configuration.md](Reference-Commands-Configuration)
- [`config set`](Reference-Commands-Configuration#config-set) - Set one value of the tiered settings, in the file its tier names.
- [`config migrate`](Reference-Commands-Configuration#config-migrate) - Move the settings out of the per-feature files of earlier versions into the tiered settings.
## Instance health ## Instance health
Full records: [instance-health.md](Reference-Commands-Instance-health) Full records: [instance-health.md](Reference-Commands-Instance-health)
+577 -175
@@ -1,17 +1,19 @@
*Generated from [`docs/reference/configuration.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/docs/reference/configuration.md) at v9.0.0 – edit the repository, not this wiki.* *Generated from [`docs/reference/configuration.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/reference/configuration.md) at v10.0.0 – edit the repository, not this wiki.*
# Configuration # Configuration
The environment variables `wikitool` reads, and the per-checkout files an operator may create at the The environment variables `wikitool` reads and the two tiered settings files an operator may create at
root of an instance. None of them is needed for an instance to work: an instance installed from a the root of an instance. None of them is needed for an instance to work: an instance installed from a
release runs against the origin's public release feed with no configuration at all. release runs against the origin's public release feed with no configuration at all.
The shape of each file - its fields, what an absent or a malformed file means, an example - is Every setting of a feature - telemetry, the publish allowlist, the task tracker, the intake
generated from the record `wikitool` itself reads the file with, so it cannot describe a field the channels - is a section of those two files, and the shape of each section - its fields, their tier, what
tool does not take. What a setting does around it is written by hand. an absent or a malformed section means, an example - is generated from the record `wikitool` itself
reads it with, so it cannot describe a field the tool does not take. What a setting does around it is
written by hand.
Three more files at the root are written by `wikitool` and never by hand: `.wikitool-release.json`, Three more files at the root are written by `wikitool` and never by hand: `.wikitool-release.json`,
`.wikitool-kb.json` and `.wikitool-tools.json` ([AGENTS.md invariant 1](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/AGENTS.md#invariants)). `.wikitool-kb.json` and `.wikitool-tools.json` ([AGENTS.md invariant 1](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/AGENTS.md#invariants)).
The last one has its own section [below](#tool-paths), because the preflight that writes it is The last one has its own section [below](#tool-paths), because the preflight that writes it is
something you run. something you run.
@@ -19,13 +21,20 @@ something you run.
## Contents ## Contents
- [Environment variables](#environment-variables) - [Environment variables](#environment-variables)
- [Telemetry](#telemetry)
- [Tool paths](#tool-paths) - [Tool paths](#tool-paths)
- [Tiered settings](#tiered-settings)
- [Telemetry](#telemetry)
- [Allowed push URLs](#allowed-push-urls) - [Allowed push URLs](#allowed-push-urls)
- [Task tracker](#task-tracker) - [Task tracker](#task-tracker)
- [Super Productivity](#super-productivity) - [Super Productivity](#super-productivity)
- [CalDAV](#caldav) - [CalDAV](#caldav)
- [MCP submissions](#mcp-submissions) - [Profiles](#profiles)
- [Intake channels](#intake-channels)
- [Site handlers](#site-handlers)
- [Fetch routes](#fetch-routes)
- [Backup to a synced folder](#backup-to-a-synced-folder)
- [Export to a folder](#export-to-a-folder)
- [Files of earlier versions](#files-of-earlier-versions)
<!-- /wikitool:toc --> <!-- /wikitool:toc -->
## Environment variables ## Environment variables
@@ -33,60 +42,26 @@ something you run.
| Variable | Purpose | Fallback | | Variable | Purpose | Fallback |
|----------|---------|----------| |----------|---------|----------|
| `WIKI_AUTHOR` | Overrides the author name of new source pages | `git config user.name`. Without either, `new` stops with an `ERROR` | | `WIKI_AUTHOR` | Overrides the author name of new source pages | `git config user.name`. Without either, `new` stops with an `ERROR` |
| `WIKITOOL_SESSION_ID` | Scopes the Iteration Budget Gate to a task rather than a terminal, and names the directory a trace lands in | A session variable the harness sets itself, where one is known (for example `CLAUDE_CODE_SESSION_ID`), else the parent process id ([instructions/session-setup.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/instructions/session-setup.md)) | | `WIKITOOL_SESSION_ID` | Scopes the Iteration Budget Gate to a task rather than a terminal, and names the directory a trace lands in | A session variable the harness sets itself, where one is known (for example `CLAUDE_CODE_SESSION_ID`), else the parent process id ([instructions/session-setup.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/instructions/session-setup.md)) |
| `WIKITOOL_UPDATE_URL` | The release feed `version check`, `version notes` and `dist upgrade --latest` ask | The value in `.wikitool-release.json`, else the origin's feed | | `WIKITOOL_UPDATE_URL` | The release feed `version check`, `version notes` and `dist upgrade --latest` ask | The value in `.wikitool-release.json`, else the origin's feed |
| `WIKITOOL_UPDATE_TOKEN` | A Gitea token for the release feed, sent only to downloads on the feed's own host | None. Not needed against the origin, only against a feed in a private repository | | `WIKITOOL_UPDATE_TOKEN` | A Gitea token for the release feed, sent only to downloads on the feed's own host | None. Not needed against the origin, only against a feed in a private repository |
| `WIKITOOL_TASKS_CONFIG` | Path to a tracker configuration that `task`, `review` and `doctor` read instead of `.wikitool-tasks.json`, for running one checkout against several trackers in turn | `.wikitool-tasks.json` at the root. A path to a file that does not exist is an error, never "no tracker configured" | | `WIKITOOL_TASKS_CONFIG` | Path to a tracker profile that `task`, `review` and `doctor` read instead of the `tasks`, `superproductivity` and `caldav` sections of the settings, for running one checkout against several trackers in turn ([below](#profiles)) | The sections in `wikitool.json` and `.wikitool-local.json`. A path to a file that does not exist is an error, never "no tracker configured" |
| `WIKITOOL_SECRET_<NAME>` | The value behind a settings reference `{"ref": "<name>"}`, for CI and containers - `<NAME>` is the name upper-cased | The secrets file in the user's configuration directory ([below](#tiered-settings)) |
| `CHEMENU_ROOT` | Which corpus the package works on, for a caller outside the checkout itself | The checkout the package sits in. `tools/wikitool` behaves the same without it | | `CHEMENU_ROOT` | Which corpus the package works on, for a caller outside the checkout itself | The checkout the package sits in. `tools/wikitool` behaves the same without it |
| `WIKI_TRACE` | `0`/`1` turns telemetry off or on, beating both the default and `.wikitool-telemetry.json` | The installation-form default ([below](#telemetry)) | | `WIKI_TRACE` | `0`/`1` turns telemetry off or on, beating both the default and the `telemetry` section | The installation-form default ([below](#telemetry)) |
| `WIKI_TRACE_DIR` | Writes traces somewhere other than `reports/telemetry/` | `reports/telemetry/` | | `WIKI_TRACE_DIR` | Writes traces somewhere other than `reports/telemetry/` | `reports/telemetry/` |
| `WIKI_TRACE_CONTENT` | `0` keeps only lengths and digests of prompts and replies instead of their text | Text is kept | | `WIKI_TRACE_CONTENT` | `0` keeps only lengths and digests of prompts and replies instead of their text | Text is kept |
| `WIKI_TRACE_MAX_CONTENT` | Per-attribute content cap in characters | 60 KiB | | `WIKI_TRACE_MAX_CONTENT` | Per-attribute content cap in characters | 60 KiB |
| `WIKI_TRACE_MAX_SESSION_BYTES` | Byte cap per session trace; beats the file's `max_session_bytes` | 5 MiB | | `WIKI_TRACE_MAX_SESSION_BYTES` | Byte cap per session trace; beats the section's `max_session_bytes` | 5 MiB |
| `WIKI_TRACE_KEEP_SESSIONS` | How many session directories retention keeps; beats the file's `keep_sessions` | 250 | | `WIKI_TRACE_KEEP_SESSIONS` | How many session directories retention keeps; beats the section's `keep_sessions` | 250 |
## Telemetry
**Telemetry is off in an instance.** Every instance carries the `.wikitool-release.json` of its
release, and that is how `chemenu.telemetry.policy` recognises that nobody asked for telemetry. The
same holds for every further clone of the instance, because the file is committed with the first
publish. Only a clone of the origin repository, used to develop the stack, has no stamp. There the
traces are the instrument the stack measures itself with, and they are **on**.
To turn the default around, create `.wikitool-telemetry.json` at the root. It is per checkout and
gitignored, with no `.template`, like `.wikitool-remotes.json`.
<!-- wikitool:reference-config-telemetry -->
| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `schema` | any | no | - | Accepted and not read. Conventionally `1`; no version of this file is told apart by it yet. |
| `enabled` | boolean | no | the installation-form default | Telemetry on or off. `WIKI_TRACE` beats it in both directions. Any other value is read by its truthiness, so `"no"` means on. |
| `max_session_bytes` | positive integer | no | `5242880` | Byte cap per session trace. `WIKI_TRACE_MAX_SESSION_BYTES` beats it. A number written as a string is accepted. |
| `keep_sessions` | positive integer | no | `250` | How many session directories retention keeps. `WIKI_TRACE_KEEP_SESSIONS` beats it. A number written as a string is accepted. |
- **Absent:** The installation-form default applies: on in a clone of the origin repository, off in an instance.
- **Malformed:** Never an error. Unreadable JSON or a non-object counts as an absent file, and a cap that is not a positive integer as an absent cap. `doctor` reports each such deviation as a `WARN` under `telemetry`, naming the field.
- **Unknown keys:** Ignored; `doctor` reports them as a `WARN`.
Example:
```json
{
"enabled": true,
"max_session_bytes": 5242880,
"keep_sessions": 250
}
```
<!-- /wikitool:reference-config-telemetry -->
`wikitool doctor` reports the current state: on or off, why, and how much has been recorded against
both caps. What the caps do and what never reaches a trace:
[telemetry-and-evaluation.md](Explanation-Telemetry-and-evaluation).
## Tool paths ## Tool paths
**The preflight writes this file, not you.** `.wikitool-tools.json` at the root holds the absolute **The preflight writes this file, not you.** `.wikitool-tools.json` at the root holds the absolute
paths of Python, git and ripgrep, as the preflight found them on this machine. `wikitool` starts git paths of Python, git and ripgrep, as the preflight found them on this machine - and of the
1Password CLI `op` when it is installed. `op` is optional: the preflight records it when it finds
it, reports `ABSENT` when it does not, and never asks for it. Only an `op://` reference in the
[tiered settings](#tiered-settings) needs it. `wikitool` starts git
and rg from there instead of from `PATH`. It is per checkout and gitignored, because a path on one and rg from there instead of from `PATH`. It is per checkout and gitignored, because a path on one
machine says nothing about the next. machine says nothing about the next.
@@ -96,85 +71,202 @@ preflight. If a tool sits somewhere the preflight does not look, name it:
`pwsh -NoProfile -ExecutionPolicy Bypass -File tools/preflight.ps1 --set rg=<path>`. Never edit the `pwsh -NoProfile -ExecutionPolicy Bypass -File tools/preflight.ps1 --set rg=<path>`. Never edit the
file by hand. `doctor` reports under `tool-paths` whether every path still works. file by hand. `doctor` reports under `tool-paths` whether every path still works.
## Allowed push URLs ## Tiered settings
`.wikitool-remotes.json` at the root restricts where `publish` may push, per checkout and Two files at the root hold the settings of the features that read them this way, and every field
gitignored. declares the tier it belongs to:
<!-- wikitool:reference-config-remotes --> | Tier | File | Holds |
| Field | Type | Required | Default | Description | |------|------|-------|
|-------|------|----------|---------|-------------| | `instance` | `wikitool.json`, committed | What belongs to the corpus and is the same in every clone |
| `schema` | any | no | - | Accepted and not read. Conventionally `1`; no version of this file is told apart by it yet. | | `checkout` | `.wikitool-local.json`, gitignored | What is true only for this clone on this machine |
| `allowed_push_urls` | array of strings | yes | - | The push URLs `publish` may write to, compared with the URL the remote resolves to for a push - not with its name. An empty list allows no push at all. | | `secret` | neither | Tokens, passwords and website sessions - both files hold only a reference, and a session from `raw login` none ([below](#fetch-routes)) |
- **Absent:** Any remote is allowed; the Publish-Remote Gate does not apply. Each file is a JSON object of sections, one per feature, beside two keys of its own:
- **Malformed:** `publish` stops with an error before committing anything. A broken file is never read as "no restriction".
- **Unknown keys:** Ignored. ```json
{
"schema": 1,
"instance_id": "6f1c0e52-3d1b-4b8e-9a51-2f0e7d1c9b44",
"<section>": {"<field>": "<value>"}
}
```
`instance_id` sits in `wikitool.json` only. The first `wikitool config set` creates the file and
the id; the id names the directory of the secrets file below. The sections are [telemetry](#telemetry),
[remotes](#allowed-push-urls), [tasks, superproductivity and caldav](#task-tracker) and
[intake](#intake-channels), [sites](#site-handlers), [fetch](#fetch-routes) and
[backup](#backup-to-a-synced-folder) and [export](#export-to-a-folder). `config set` names the sections it knows, and `doctor` reports a
section it does not read as a `WARN`.
**One place per value.** A value in the file its tier does not name is an error naming the file,
the key and the right tier - never an override. The same holds for a key set in both files.
**References.** A field of tier `secret` takes only a reference; a field marked private - a host
name, an internal URL - takes a plain value in `.wikitool-local.json` or a reference in either
file. No other field takes one.
| Reference | Resolves through |
|-----------|------------------|
| `{"ref": "<name>"}` - lower-case letters, digits and single `_` | `WIKITOOL_SECRET_<NAME>`, else the key `<name>` in the secrets file |
| `{"ref": "op://<vault>/<item>/<field>"}` | `op read`, the 1Password CLI the preflight recorded |
The secrets file is `~/.config/chemenu/<instance_id>/secrets.json` (`$XDG_CONFIG_HOME` instead of
`~/.config` when it is set; `%APPDATA%\chemenu\<instance_id>\secrets.json` on Windows), a JSON object of
names to strings. Outside Windows it must be readable by its owner only (`chmod 600`); a file
others can read is refused.
A reference is resolved when a command asks for that value, never when the files are read. A name
nobody provides, an `op` that is not installed, or an item 1Password does not have ends the command
with exit 1; a 1Password that is locked, signed out or waiting for an approval ends it with exit 42,
for a human to unlock. Setting 1Password up: [use-1password-for-settings.md](How-to-Use-1password-for-settings).
**Never in the committed file:** a field that names a program to run or affects a gate - not even
as a reference. `config set` does not write such a field either (exit 42); the user edits
`.wikitool-local.json` by hand, and `doctor` checks it afterwards. The fields marked `gate` or
`names a command` in the tables below are these: the publish allowlist, the intake channels and the
external fetchers.
`wikitool doctor` reports one line per tier - `settings-instance`, `settings-checkout`,
`settings-secrets` - and for the references only whether they resolve. `docs verify` holds a
committed `wikitool.json` to the placement rules above, a plain secret or private value above
all. Neither file ships with `dist export`. Why the tiers exist, and what keeping secrets out of
the tree does not protect against: [configuration-tiers.md](Explanation-Configuration-tiers).
## Telemetry
**Telemetry is off in an instance.** Every instance carries the `.wikitool-release.json` of its
release, and that is how `chemenu.telemetry.policy` recognises that nobody asked for telemetry. The
same holds for every further clone of the instance, because the file is committed with the first
publish. Only a clone of the origin repository, used to develop the stack, has no stamp. There the
traces are the instrument the stack measures itself with, and they are **on**.
To turn the default around, add a `telemetry` section to `.wikitool-local.json`, by hand or with
`wikitool config set telemetry.enabled true`. The consent to write prompts to this disk belongs to
the checkout, so the section has no instance-tier field and no gate.
<!-- wikitool:reference-config-telemetry -->
| Field | Tier | Type | Required | Default | Description |
|-------|------|------|----------|---------|-------------|
| `enabled` | checkout | boolean | no | the installation-form default | Telemetry on or off. `WIKI_TRACE` beats it in both directions. Any other value is read by its truthiness, so `"no"` means on. |
| `max_session_bytes` | checkout | positive integer | no | `5242880` | Byte cap per session trace. `WIKI_TRACE_MAX_SESSION_BYTES` beats it. A number written as a string is accepted. |
| `keep_sessions` | checkout | positive integer | no | `250` | How many session directories retention keeps. `WIKI_TRACE_KEEP_SESSIONS` beats it. A number written as a string is accepted. |
- **Absent:** The installation-form default applies: on in a clone of the origin repository, off in an instance.
- **Malformed:** The hook that reads it never fails on it: a value it cannot use counts as absent, and an unreadable `.wikitool-local.json` as an absent section. `doctor` reports each such deviation as a `WARN` under `telemetry`, naming the field.
Example: Example:
```json ```json
{ {
"schema": 1, "telemetry": {
"enabled": true,
"max_session_bytes": 5242880,
"keep_sessions": 250
}
}
```
<!-- /wikitool:reference-config-telemetry -->
`wikitool doctor` reports the current state: on or off, why, and how much has been recorded against
both caps. What the caps do and what never reaches a trace:
[telemetry-and-evaluation.md](Explanation-Telemetry-and-evaluation).
The telemetry hook runs on every tool call and stays silent, so it reads only this section, leniently,
and never exits 42: a `.wikitool-telemetry.json` left over from an earlier version is ignored there
([below](#files-of-earlier-versions)).
## Allowed push URLs
The `remotes` section of `.wikitool-local.json` restricts where `publish` may push.
<!-- wikitool:reference-config-remotes -->
| Field | Tier | Type | Required | Default | Description |
|-------|------|------|----------|---------|-------------|
| `allowed_push_urls` | checkout, gate | array of strings | yes | - | The push URLs `publish` may write to, compared with the URL the remote resolves to for a push - not with its name. An empty list allows no push at all. |
- **Absent:** Any remote is allowed; the Publish-Remote Gate does not apply.
- **Malformed:** `publish` stops with an error before committing anything. A broken section is never read as "no restriction".
Example:
```json
{
"remotes": {
"allowed_push_urls": [ "allowed_push_urls": [
"<url>" "<url>"
] ]
} }
}
``` ```
<!-- /wikitool:reference-config-remotes --> <!-- /wikitool:reference-config-remotes -->
With the file, `publish` refuses with exit 42 (the Publish-Remote Gate) before pushing to a URL not With the section, `publish` refuses with exit 42 (the Publish-Remote Gate) before pushing to a URL not
on the list. Only you edit this file. The gate has no token and no flag, and an agent that edits the on the list. Only you edit it: the field is marked `gate`, so `config set` exits 42 for it. The gate
list to get past a refusal is opening a gate on its own initiative. has no token and no flag, and an agent that edits the list to get past a refusal is opening a gate on
its own initiative.
## Task tracker ## Task tracker
**Optional.** The weekly review (`tools/wikitool review`, skill `gtd-weekly-review`) checks the **Optional.** The weekly review (`tools/wikitool review`, skill `gtd-weekly-review`) checks the
project pages under `kb/gtd/` against a task tracker. Which tracker that is goes in project pages under `kb/gtd/` against a task tracker. Which tracker that is, and its connection
`.wikitool-tasks.json` at the root. Like the telemetry and remotes files, it is per checkout and has details, are three sections: `tasks` (which provider, the review's thresholds), and the section of the
no `.template`, and it is **gitignored as soon as a token is in it**. provider chosen, `superproductivity` or `caldav`. What is the same in every clone - the provider, the
thresholds, the list names - sits in `wikitool.json`; the host, the user name and the folder this
machine uses in `.wikitool-local.json`; a token or password is a reference to a secret, never a value.
<!-- wikitool:reference-config-tasks --> <!-- wikitool:reference-config-tasks -->
| Field | Type | Required | Default | Description | | Field | Tier | Type | Required | Default | Description |
|-------|------|----------|---------|-------------| |-------|------|------|----------|---------|-------------|
| `schema` | any | no | - | Accepted and not read. Conventionally `1`; no version of this file is told apart by it yet. | | `provider` | instance | string | yes | - | Which adapter the review goes through. |
| `provider` | string | yes | - | Which adapter the review goes through. The provider's own settings sit in a section of the same name. | | `stalled_waiting_days` | instance | positive integer | yes | - | A waiting-for item older than this many days counts as overdue. A number written as a string is accepted. |
| `thresholds` | object | yes | - | The weekly review's three thresholds. | | `unpaged_project_weeks` | instance | positive integer | yes | - | A tracker project with no `kb/` page is reported once it is older than this many weeks. A number written as a string is accepted. |
| `thresholds.stalled_waiting_days` | positive integer | yes | - | A waiting-for item older than this many days counts as overdue. A number written as a string is accepted. | | `someday_stale_months` | instance | positive integer | yes | - | A someday/maybe item untouched for this many months counts as stale. A number written as a string is accepted. |
| `thresholds.unpaged_project_weeks` | positive integer | yes | - | A tracker project with no `kb/` page is reported once it is older than this many weeks. A number written as a string is accepted. |
| `thresholds.someday_stale_months` | positive integer | yes | - | A someday/maybe item untouched for this many months counts as stale. A number written as a string is accepted. |
- **Absent:** No tracker is configured - a valid state. The weekly review has nothing to check against. - **Absent:** No tracker is configured - a valid state. The weekly review has nothing to check against.
- **Malformed:** An error, and `doctor` reports `FAIL`. A broken file is never read as "no tracker configured". - **Malformed:** An error, and `doctor` reports `FAIL`. A broken section is never read as "no tracker configured".
- **Unknown keys:** Ignored, so the section of a provider not chosen right now can stay in the file.
**The `superproductivity` section.** Connection settings for the Super Productivity adapter. Example:
| Field | Type | Required | Default | Description | ```json
|-------|------|----------|---------|-------------| {
| `access` | string | yes | - | Which way in: `api`, the local REST API (reads the current state and carries the write path), or `snapshot`, the newest backup snapshot (read-only). | "tasks": {
"provider": "superproductivity",
"stalled_waiting_days": 14,
"unpaged_project_weeks": 3,
"someday_stale_months": 5
}
}
```
<!-- /wikitool:reference-config-tasks -->
With `access: "api"`: The thresholds are configuration, not schema. A value the provider cannot supply at all - a
waiting item with no follow-up date, a tracker project with no creation date - is not skipped silently
but reported as its own finding (`waiting_no_follow_up`/`project_age_unknown`).
| Field | Type | Required | Default | Description | ### Super Productivity
|-------|------|----------|---------|-------------|
| `api_base_url` | string | no | `"http://127.0.0.1:3876"` | Where the app's REST API listens. An empty value means the default. |
| `api_token` | string | yes, not empty | - | The token from the app's settings. |
With `access: "snapshot"`: <!-- wikitool:reference-config-superproductivity -->
| Field | Tier | Type | Required | Default | Description |
|-------|------|------|----------|---------|-------------|
| `access` | instance | string | yes | - | Which way in: `api`, the local REST API (reads the current state and carries the write path), or `snapshot`, the newest backup snapshot (read-only). |
| `api_base_url` | checkout, private | string | no | `"http://127.0.0.1:3876"` | With `access: "api"`: where the app's REST API listens. An empty value means the default. |
| `api_token` | secret | string | no | - | With `access: "api"`, required: the token from the app's settings. Only ever a reference. |
| `backups_dir` | checkout | string | no | - | With `access: "snapshot"`, required: the directory the app writes its backup snapshots to; `~` is expanded. |
| Field | Type | Required | Default | Description | - **Absent:** Read only when `tasks.provider` is `superproductivity`.
|-------|------|----------|---------|-------------| - **Malformed:** An error when it is read: a field of the other access path beside `access`, or a missing `api_token` or `backups_dir`, stops the command with exit 1.
| `backups_dir` | string | yes, not empty | - | The directory the app writes its backup snapshots to; `~` is expanded. |
- **Unknown keys:** Rejected: a section names only one access path's own fields, so a `backups_dir` beside `access: "api"` is an error, not ignored. Examples:
```json ```json
{ {
"superproductivity": { "superproductivity": {
"access": "api", "access": "api",
"api_base_url": "http://127.0.0.1:3876", "api_base_url": "http://127.0.0.1:3876",
"api_token": "..." "api_token": {
"ref": "sp_token"
}
} }
} }
``` ```
@@ -187,62 +279,9 @@ With `access: "snapshot"`:
} }
} }
``` ```
<!-- /wikitool:reference-config-superproductivity -->
**The `caldav` section.** Connection settings for the CalDAV adapter. `access` is **mandatory and has no fallback**. The fields of the other access path are an error, not ignored: a `backups_dir` beside `access: "api"` stops the command with exit 1, as does an `api_token` missing from it. It chooses between two mutually exclusive ways in:
| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `url` | string | yes, not empty | - | The account's CalDAV calendar home set. A trailing `/` is added when missing. |
| `username` | string | yes, not empty | - | The login name, which can differ from the user id in the URL. |
| `app_password` | string | yes, not empty | - | An app password is recommended over the account password. |
| `inbox_list` | string | yes, not empty | - | The name of the one inbox list. |
| `someday_list` | string | yes, not empty | - | The name of the one someday/maybe list. |
| `exclude_lists` | array of strings | no | `[]` | Existing plain task lists that are not projects. |
- **Unknown keys:** Rejected.
```json
{
"caldav": {
"url": "https://<host>/remote.php/dav/calendars/<user>/",
"username": "<login>",
"app_password": "...",
"inbox_list": "Inbox",
"someday_list": "Someday",
"exclude_lists": [
"..."
]
}
}
```
A complete example:
```json
{
"schema": 1,
"provider": "superproductivity",
"thresholds": {
"stalled_waiting_days": 14,
"unpaged_project_weeks": 3,
"someday_stale_months": 5
},
"superproductivity": {
"access": "api",
"api_base_url": "http://127.0.0.1:3876",
"api_token": "..."
}
}
```
<!-- /wikitool:reference-config-tasks -->
The `thresholds` block is configuration, not schema. A value the provider cannot supply at all - a
waiting item with no follow-up date, a tracker project with no creation date - is not skipped silently
but reported as its own finding (`waiting_no_follow_up`/`project_age_unknown`).
### Super Productivity
`access` is **mandatory and has no fallback**. It chooses between two mutually exclusive ways in:
- **`access: "snapshot"`** for an instance run headless: it reads only the newest backup snapshot - **`access: "snapshot"`** for an instance run headless: it reads only the newest backup snapshot
under `backups_dir`. That works without the app running, but only reads. The tracker cannot be under `backups_dir`. That works without the app running, but only reads. The tracker cannot be
@@ -250,7 +289,7 @@ but reported as its own finding (`waiting_no_follow_up`/`project_age_unknown`).
- **`access: "api"`** for a desktop instance: it talks only to the local REST API. That answers only - **`access: "api"`** for a desktop instance: it talks only to the local REST API. That answers only
while the app runs, but it gives the current state and carries the write path. `api_token` is while the app runs, but it gives the current state and carries the write path. `api_token` is
required, because every endpoint except `GET /health` demands `Authorization: Bearer <token>`. required (as a reference), because every endpoint except `GET /health` demands `Authorization: Bearer <token>`.
What can write: What can write:
@@ -265,6 +304,40 @@ from there. `tools/wikitool task list --project` only reads, so it answers with
### CalDAV ### CalDAV
<!-- wikitool:reference-config-caldav -->
| Field | Tier | Type | Required | Default | Description |
|-------|------|------|----------|---------|-------------|
| `url` | checkout, private | string | yes, not empty | - | The account's CalDAV calendar home set. A trailing `/` is added when missing. |
| `username` | checkout, private | string | yes, not empty | - | The login name, which can differ from the user id in the URL. |
| `app_password` | secret | string | yes, not empty | - | An app password is recommended over the account password. Only ever a reference. |
| `inbox_list` | instance | string | yes, not empty | - | The name of the one inbox list. |
| `someday_list` | instance | string | yes, not empty | - | The name of the one someday/maybe list. |
| `exclude_lists` | instance | array of strings | no | `[]` | Existing plain task lists that are not projects. |
- **Absent:** Read only when `tasks.provider` is `caldav`.
Example:
```json
{
"caldav": {
"url": {
"ref": "op://Chemenu/caldav/url"
},
"username": "<login>",
"app_password": {
"ref": "caldav_app_password"
},
"inbox_list": "Inbox",
"someday_list": "Someday",
"exclude_lists": [
"..."
]
}
}
```
<!-- /wikitool:reference-config-caldav -->
`caldav` is the standards-based second adapter (RFC 4791/5545). It is verified against Nextcloud `caldav` is the standards-based second adapter (RFC 4791/5545). It is verified against Nextcloud
Tasks, with iOS *Reminders* as the mobile client, and built for that split: tick items off on the Tasks, with iOS *Reminders* as the mobile client, and built for that split: tick items off on the
phone, maintain them at the desk. Unlike Super Productivity, there is only one way in. CalDAV is phone, maintain them at the desk. Unlike Super Productivity, there is only one way in. CalDAV is
@@ -291,44 +364,373 @@ since it was read (an ETag conflict) is not written, and the command stops with
ever deleted. The lists of finished projects stay after archiving, because the adapter never deletes ever deleted. The lists of finished projects stay after archiving, because the adapter never deletes
a list. Remove them by hand in Nextcloud when you want to. a list. Remove them by hand in Nextcloud when you want to.
## MCP submissions ### Profiles
`.wikitool-upload.json` in the served corpus turns on the MCP read server's optional `submit` tool. `WIKITOOL_TASKS_CONFIG` points at a profile: a file in the format of `.wikitool-local.json` that holds
What the authentication in front of the server has to guarantee before you create it is in only the sections `tasks`, `superproductivity` and `caldav` (and `schema`, and `live_test_version`
[Set up the MCP read server](How-to-Set-up-the-mcp-server#optional-the-submit-path). for the live suite). While it is set, `task`, `review` and `doctor` read those three sections from the
profile instead of from `wikitool.json` and `.wikitool-local.json`, so one checkout can be run against
several trackers in turn. A profile is checked like the local file, except that a plain instance-tier
value is allowed in it; a secret is a reference there too, never a value. The live suite takes its
throwaway tokens from `WIKITOOL_SECRET_<NAME>`.
<!-- wikitool:reference-config-upload --> ## Intake channels
| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `schema` | any | no | - | Accepted and not read. Conventionally `1`; no version of this file is told apart by it yet. |
| `identity_header` | string | no | `"X-Forwarded-User"` | The request header the authenticating middleware sets to the submitter's identity on every request. An empty value means the default. |
| `max_bytes` | positive integer | yes | - | Largest single submission, in bytes. A number written as a string is accepted. |
| `allowed_extensions` | array of strings | yes | - | The file extensions `submit` accepts, each starting with `.`. Compared case-insensitively. Entries are read as strings and lower-cased; duplicates collapse. |
| `quota` | object | yes | - | Per-submitter limits over any rolling 24 hours. |
| `quota.submissions_per_day` | positive integer | yes | - | Submissions one submitter may make. A number written as a string is accepted. |
| `quota.bytes_per_day` | positive integer | yes | - | Bytes one submitter may submit. A number written as a string is accepted. |
- **Absent:** The `submit` tool is not registered: the write path does not exist. The `intake` section of `.wikitool-local.json` declares the ways a document gets into the
- **Malformed:** An error. The MCP server does not start and `doctor` reports `FAIL`. A broken file is never read as "no limits". quarantine `quarantine/` from somewhere other than this terminal. Each channel has a name and a
- **Unknown keys:** Ignored. handler:
| Handler | Source | How it arrives |
|---|---|---|
| `mcp` | The MCP read server's optional `submit` tool | Pushed by a remote caller. At most one channel has this handler. |
| `folder` | A directory outside the instance that a sync client fills (OneDrive and the like) | Pulled by `wikitool upload collect`; what is taken is removed from the folder. |
| `command` | A program of this checkout, named in `commands` | Pulled by `wikitool upload collect`; the program leaves its files in an empty staging directory. |
All of them end in the same place: a submission nobody has looked at, which `wikitool upload accept`
promotes into `incoming/` after a human has cleared the Upload Review Gate. What the authentication
in front of the server has to guarantee for the `mcp` handler is in
[Set up the MCP read server](How-to-Set-up-the-mcp-server#optional-the-submit-path); how to
collect from a synced folder or a program is in
[Collect documents from a synced folder](How-to-Collect-from-a-synced-folder). Every field is
marked `gate` or `names a command`: the channels are the user's to set by hand, and `config set`
exits 42 for them.
<!-- wikitool:reference-config-intake -->
| Field | Tier | Type | Required | Default | Description |
|-------|------|------|----------|---------|-------------|
| `channels` | checkout, gate | object | yes | - | Channel name (lower-case letters, digits and hyphens) to channel. The name goes into the manifest of what the channel delivers and is the argument of `upload collect`. Every channel has `handler` (`mcp`, `folder` or `command`), `max_bytes` (the largest single file) and `allowed_extensions` (each starting with `.`, compared case-insensitively). Then per handler: `mcp` takes `identity_header` (the request header the authenticating middleware sets to the submitter's identity, default `X-Forwarded-User`) and, required, `quota_submissions_per_day` and `quota_bytes_per_day` (per submitter, over any rolling 24 hours); `folder` takes `path` (required: an absolute path outside the instance, read without descending into subdirectories) and `settle_seconds` (default 60: a file whose modification or creation time is younger is left alone); `command` takes `command` (required: the name of an entry of `commands`). At most one channel has the handler `mcp`. A field the handler does not take is an error. |
| `commands` | checkout, names a command | object | no | `{}` | Command name to command: an array of the program and its arguments, run by the `command` handler without a shell, with stdin closed and a timeout of 300 s, and a staging directory appended as the last argument. Exit 0 turns each regular file at the top level of that directory into a submission; anything else takes nothing over and prints the end of the program's stderr. |
- **Absent:** No channel: the `submit` tool is not registered and `upload collect` has nothing to collect. A valid state; `doctor` reports `OK`.
- **Malformed:** An error. The MCP server does not start, `upload collect` stops with exit 1 and `doctor` reports `FAIL`. A broken section is never read as "no channel".
Example: Example:
```json ```json
{ {
"schema": 1, "intake": {
"channels": {
"mcp": {
"handler": "mcp",
"identity_header": "X-Forwarded-User", "identity_header": "X-Forwarded-User",
"max_bytes": 10485760, "max_bytes": 10485760,
"allowed_extensions": [ "allowed_extensions": [
".md", ".md",
".pdf" ".pdf"
], ],
"quota": { "quota_submissions_per_day": 20,
"submissions_per_day": 20, "quota_bytes_per_day": 52428800
"bytes_per_day": 52428800 },
"onedrive": {
"handler": "folder",
"path": "C:\\Users\\<user>\\OneDrive - <company>\\Chemenu-Eingang",
"max_bytes": 52428800,
"allowed_extensions": [
".pdf",
".docx",
".msg",
".eml",
".md"
],
"settle_seconds": 60
},
"scanner": {
"handler": "command",
"command": "scanner-pull",
"max_bytes": 52428800,
"allowed_extensions": [
".pdf"
]
}
},
"commands": {
"scanner-pull": [
"pwsh",
"-NoProfile",
"-File",
"C:\\tools\\scanner-pull.ps1"
]
}
} }
} }
``` ```
<!-- /wikitool:reference-config-upload --> <!-- /wikitool:reference-config-intake -->
Size and quota are your deployment's decision, not defaults worth copying blindly. Size and quota are your deployment's decision, not defaults worth copying blindly. A field that does
not belong to the channel's handler is an error, not ignored: `identity_header` and the `quota_*`
fields belong to `mcp`, `path` and `settle_seconds` to `folder`, `command` to `command`. A `folder`
`path` must be absolute and lie outside the instance, and `upload collect` reads only its top level.
## Site handlers
`tools/wikitool raw fetch` knows how some websites serve their pages. For a URL of such a website a
*site handler* fetches the page in the website's standard form, leaves the page furniture out of the
derived text and prints hints about the page as `CHECK` lines; today there are two, both for
`learn.microsoft.com`: `learn-microsoft` for every page of the host and
`learn-microsoft-powershell-module` for a PowerShell module's overview page, which offers the cmdlet
pages it lists as subpages for `raw fetch --scope`. Handlers are JSON files: the stack's, and the instance's own under `sites/`
(see [How to write a site handler](How-to-Write-a-site-handler)).
The `sites` section is what the instance decides about the handlers, and it is optional: without it every
handler applies, in the locale `en-us`.
- `disabled` switches handlers off by name. A page of that website is then handled as any other:
fetched under the URL as typed, with its page furniture in the derived text. A name no handler has
is an error, so a typo cannot leave a handler on. Switching off a special handler leaves the
general one of the same website in force.
- `locale` is the language segment a handler for a multi-language website puts into the URL it
fetches. The header of the derived text keeps the URL as typed under `requested_url`.
- `notes` adds hint lines of the instance's own, printed after a fetch or an `--html` derivation of
a page of that host, whether or not a handler exists for it. An entry names a `host` (exact, lower
case), optionally a `path` (a regular expression matched against the start of the URL path, after
the standard form was applied) and one to three single-line `lines`. A host and path pattern appears
once; an entry whose `path` is left out applies to the whole host. A line is plain text and is
output as it is, so it is for a reminder to the reader of the capture, never for an instruction
the tool should carry out.
- `max_pages` and `pause_seconds` bound `raw fetch --scope`: the most subpages one page may offer
before the fetch is refused, and the pause before each subpage request, which keeps the load on
the website low.
<!-- wikitool:reference-config-sites -->
| Field | Tier | Type | Required | Default | Description |
|-------|------|------|----------|---------|-------------|
| `disabled` | instance | array of strings | no | `[]` | The names of the site handlers that do not apply. `raw fetch` then treats a page of that website as it does any other. A name no handler has is an error when `raw fetch` reads the section, so a typo cannot leave a handler on. |
| `locale` | instance | string | no | `"en-us"` | The locale a handler for a multi-language website fetches, in the website's own spelling and lower case. An empty value means the default. |
| `notes` | instance | array of objects | no | `[]` | Hint lines of the instance's own, printed as `CHECK` lines after a fetch or an `--html` derivation of a page of that host. Each entry has `host` (exact, lower case), an optional `path` (a regular expression matched against the start of the URL path) and `lines` (one to 3 single-line strings of at most 300 characters). A host and path pattern appears once. |
| `max_pages` | instance | positive integer | no | `200` | The most subpages `raw fetch --scope` fetches for one page. A page that offers more is refused before the first subpage is requested. A number written as a string is accepted. |
| `pause_seconds` | instance | number | no | `1` | The pause, in seconds, before each subpage request of `raw fetch --scope`. `0` means none. A number written as a string is accepted. |
- **Absent:** Every handler applies, with the locale `en-us` and no further hint lines.
- **Malformed:** An error when `raw fetch` reads it: it stops with exit 1 before fetching anything. A broken section is never read as "no handlers".
Example:
```json
{
"sites": {
"disabled": [
"learn-microsoft-powershell-module"
],
"locale": "en-us",
"max_pages": 200,
"pause_seconds": 1,
"notes": [
{
"host": "learn.microsoft.com",
"path": "^/[a-z-]+/azure/",
"lines": [
"Check the Azure retirement notice at the top of the page."
]
}
]
}
}
```
<!-- /wikitool:reference-config-sites -->
All three fields are instance-tier: what a website needs cleaned is the same in every clone, and none
of them names a program or opens a gate. A section that does not read correctly stops `raw fetch`
with exit 1 before it fetches anything.
## Fetch routes
`tools/wikitool raw fetch` fetches every page the same way unless the `fetch` section says
otherwise for its host: anonymously, with no cookie and no browser. `routes` maps a host to another
way, and is instance-tier, so every clone fetches the host the same way:
- `"session"` fetches the host's pages with the session the user stored with
`tools/wikitool raw login <host>`, through the same built-in fetch. The host needs a site handler
with a `session` block - its login page and how a page shows it arrived signed in
([sites/CONTRACT.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/sites/CONTRACT.md)). No stored session, or a page without that sign,
stops the fetch with exit 42 for the user to sign in again. The session is a secret: it is
stored in the secrets file [above](#tiered-settings) under `session_<host>` (every run of other
characters as one `_`, so `www.example.org` is `session_www_example_org`), or comes from
`WIKITOOL_SECRET_SESSION_<HOST>`, and no settings field refers to it. It is never written to
1Password.
- Any other value names a fetcher of `fetchers`: a program of this checkout, run without a shell
with the URL appended. `fetchers` names a program to run, so it lives only in
`.wikitool-local.json`, written by hand - `config set` refuses it with exit 42, and a fetcher in
`wikitool.json` is an error. A route to a fetcher this checkout does not define stops a fetch of
that host with exit 1, so a clone without the program fails loudly instead of fetching
anonymously.
The whole procedure, with the browser component it needs for signing in:
[fetch-pages-behind-a-login.md](How-to-Fetch-pages-behind-a-login).
<!-- wikitool:reference-config-fetch -->
| Field | Tier | Type | Required | Default | Description |
|-------|------|------|----------|---------|-------------|
| `routes` | instance | object | no | `{}` | Host (exact, lower case) to route: `"session"` fetches with the user's session - the host needs a site handler that names its login page - and any other value names a fetcher of `fetchers`. A host not listed is fetched as before. |
| `fetchers` | checkout, names a command | object | no | `{}` | Fetcher name to command: an array of the program and its arguments, run without a shell with the URL appended. Exit 0 and the page's HTML on stdout is a fetch; anything else stops `raw fetch` with exit 1 and the program's stderr. |
- **Absent:** Every page is fetched by the built-in anonymous fetch, as before.
- **Malformed:** An error when `raw fetch` or `raw login` reads it: the command stops with exit 1 before fetching anything. A route to a fetcher this checkout does not define stops a fetch of that host the same way.
Example:
```json
{
"fetch": {
"routes": {
"www.heise.de": "session",
"intranet.example.org": "homelab"
},
"fetchers": {
"homelab": [
"/usr/local/bin/fetch-page",
"--html"
]
}
}
}
```
<!-- /wikitool:reference-config-fetch -->
## Backup to a synced folder
The `backup` section of `.wikitool-local.json` backs this checkout up into a folder a sync client
uploads - OneDrive and the like - while the working tree itself stays outside it. The target gets
two kinds of content:
```
<target>/git/ bare repository, the checkout's origin; publish pushes here
<target>/<machine>/ mirror of this machine's files that are not in git, plus .chemenu-backup.json
```
`tools/wikitool backup init` creates `<target>/git` and makes it `origin` when the checkout has none.
With `machine_data` on, every `publish` that pushed mirrors `.wikitool-local.json`, `ENVIRONMENT.md`,
what waits in `incoming/` and the `extra` paths into `<target>/<machine>/`; `tools/wikitool backup
run` does the same on its own. The secrets file is never among them - it lies outside the instance,
and a backup reads nothing outside it. Plain private values in `.wikitool-local.json`, an internal
host name for one, do reach the synced folder.
<!-- wikitool:reference-config-backup -->
| Field | Tier | Type | Required | Default | Description |
|-------|------|------|----------|---------|-------------|
| `target` | checkout, gate | string | yes | - | The backup folder: an absolute path outside the instance. `backup init` creates `<target>/git` in it, `backup run` writes only to `<target>/<machine>/`. |
| `machine_data` | checkout, gate | boolean | no | `false` | Mirror this machine's files after every `publish` that pushed, and with `backup run`: `.wikitool-local.json`, `ENVIRONMENT.md`, what waits in `incoming/`, and `extra`. |
| `machine_name` | checkout | string | no | the host name | The name of this machine's folder under the target. Every character outside letters, digits, `-` and `_` becomes `-`, and leading and trailing `-` are dropped; a name that is empty afterwards, or `git` in any case, is an error. |
| `extra` | checkout, gate | array of strings | no | `[]` | Further files or directories to mirror, relative to the instance. One that resolves outside the instance, or into `.git/`, is an error. |
- **Absent:** No backup: `publish` behaves as without it, and `backup init` and `backup run` stop with exit 1 naming the section.
- **Malformed:** An error. `publish` stops with exit 1 before it commits anything, `backup init` and `backup run` stop with exit 1, and `doctor` reports `FAIL`. A broken section is never read as "no backup".
Example:
```json
{
"backup": {
"target": "C:\\Users\\<user>\\OneDrive - <company>\\Chemenu-Backup",
"machine_data": true,
"machine_name": "WORK-LAPTOP",
"extra": [
".claude/settings.local.json"
]
}
}
```
<!-- /wikitool:reference-config-backup -->
`target`, `machine_data` and `extra` decide what leaves this machine, so they are marked `gate`:
`config set` exits 42 for them, and only you write them. The procedure, restoring included:
[back-up-to-a-synced-folder.md](How-to-Back-up-to-a-synced-folder).
## Export to a folder
The `export` section of `.wikitool-local.json` declares folders that hold a derived, read-only
version of `kb/` - for a reader outside the repository, such as a Microsoft 365 tenant that indexes
a OneDrive folder. An export is regenerated from the committed pages on every run and is never
edited; what someone changes in the folder is refused or overwritten, never merged back. Each
target has a handler: `markdown` writes the selected pages as Markdown files, `command` renders
them into a staging directory and hands that to a converter of this checkout.
<!-- wikitool:reference-config-export -->
| Field | Tier | Type | Required | Default | Description |
|-------|------|------|----------|---------|-------------|
| `targets` | checkout, gate | object | yes | - | Target name (lower-case letters, digits and hyphens) to target; the name is the argument of `export run`. Every target has `handler` (`markdown`: the pages as Markdown files; `command`: the same Markdown run through a converter of this checkout), `path` (an absolute path outside the instance; it must not lie in or contain another target, an `intake` folder or `backup.target`) and `include` (an array of selections, each an array of `search --field` predicates: all predicates of a selection must hold, and a page that fits any selection is exported; there is no "everything"). Optional: `frontmatter` (`minimal`: only the export marker; `summary`, the default: also `title`, `type`, `summary` and `modified` where the page has them; `full`: the page's whole frontmatter), `links` (`relative`, the default: a wikilink to an exported page becomes a relative Markdown link; `text`: plain text; a link to a page that is not exported is always plain text) and `after_publish` (default false: refresh this target after every `publish` that committed). The handler `command` takes `command` (required: the name of an entry of `commands`). A field the handler does not take is an error. |
| `commands` | checkout, names a command | object | no | `{}` | Command name to command: an array of the program and its arguments, run by the `command` handler without a shell, with stdin closed and a timeout of 300 s. The last two arguments appended are the directory holding the Markdown and an empty output directory. Exit 0 makes the regular files in the output directory the target's content; anything else leaves the target unchanged and prints the end of the program's stderr. |
- **Absent:** No export: `export run` stops with exit 1 naming the section, `publish` exports nothing and `doctor` reports `OK`.
- **Malformed:** An error. `export run` stops with exit 1 and `doctor` reports `FAIL`; `publish` stops with exit 1 before it commits anything, but only when some target sets `after_publish`. A broken section is never read as "no export".
Example:
```json
{
"export": {
"targets": {
"work-notes": {
"handler": "markdown",
"path": "C:\\Users\\<user>\\OneDrive - <company>\\Chemenu-Lesefassung",
"include": [
[
"kind=system"
],
[
"tags=shared",
"!draft"
]
],
"frontmatter": "summary",
"links": "relative",
"after_publish": true
},
"as-word": {
"handler": "command",
"command": "pandoc-docx",
"path": "C:\\Users\\<user>\\OneDrive - <company>\\Chemenu-Word",
"include": [
[
"kind=system"
]
]
}
},
"commands": {
"pandoc-docx": [
"pwsh",
"-NoProfile",
"-File",
"C:\\tools\\pandoc-docx.ps1"
]
}
}
}
```
<!-- /wikitool:reference-config-export -->
Within one target, `include` is a list of selections, each a list of `search` predicates (`tags=docs`,
`entity_type=system`, `!source_url`): a page is exported when it satisfies every predicate of at least
one selection. Only what `include` selects is written - there is no way to exclude by default. The
syntax is checked when the section is read, the field names against the corpus when `export run`
runs. `path` must not lie in or contain the instance, another target, an `intake` folder or
`backup.target`.
Every Markdown file starts with a frontmatter block whose first key is `wikitool_export`, carrying
the kind, the instance and the commit of the newest selected page. `frontmatter` decides what follows:
`minimal` nothing, `summary` the title, type, summary and modified date, `full` the page's whole
frontmatter. Wikilinks between exported pages become relative Markdown links (`links: relative`) or
plain text (`links: text`); a link to a page that is not exported is always plain text. Footnote
markers and code stay as they are.
A target folder holds `.wikitool-export.json`, which lists the files a run wrote with their digests.
A run removes only listed files that are no longer exported, creates only folders it needs and
removes only those it created, and leaves every other file untouched. It stops before writing for a
listed file someone changed, for a folder that is not empty and has no manifest, and for a selection
that comes up empty. A run that finds nothing to change writes nothing, not even a modification time.
`targets` and `commands` decide what leaves this checkout and which program runs, so they are marked
`gate` and `names a command`: `config set` exits 42 for them, and only you write them. `raw accept`
also refuses a file whose sha256 is listed in the manifest of a configured target, for an export
that went through a converter and lost its marker. The procedure:
[export-to-a-synced-folder.md](How-to-Export-to-a-synced-folder).
## Files of earlier versions
Until version 10 every feature above had a file of its own at the root: `.wikitool-telemetry.json`,
`.wikitool-remotes.json`, `.wikitool-upload.json` and `.wikitool-tasks.json`. Nothing reads them any
more. A command that needs the setting such a file held stops with exit 42 and names
`tools/wikitool config migrate`, `doctor` reports each one as a `FAIL`, and only the telemetry hook
ignores it. `config migrate` moves the content into the sections above (the upload file becomes the channel `mcp` of `intake`), each value into the file its
tier names and each secret into the secrets file, and then removes the old files; it is run once per
checkout. `.wikitool-tools.json` is not among them and stays. The migration step for an instance is
[10.0.0-settings-tiers.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/instructions/migrations/10.0.0-settings-tiers.md).
+14 -5
@@ -1,4 +1,4 @@
*Generated from [`docs/reference/exit-codes.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/docs/reference/exit-codes.md) at v9.0.0 – edit the repository, not this wiki.* *Generated from [`docs/reference/exit-codes.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/reference/exit-codes.md) at v10.0.0 – edit the repository, not this wiki.*
# Exit codes and gates # Exit codes and gates
@@ -6,13 +6,13 @@ Which `wikitool` commands can stop and wait for a person - exit 42 - and for whi
from the command records. from the command records.
<!-- wikitool:reference-exit-codes --> <!-- wikitool:reference-exit-codes -->
What each exit code means, and how a caller reacts to it, is [AGENTS.md § Tool error contract](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/AGENTS.md#tool-error-contract). What a gate is and why it is enforced in code is [AGENTS.md § Gates](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/AGENTS.md#gates). This page lists which commands declare a gate, and every cause for which a command exits 42. What each exit code means, and how a caller reacts to it, is [AGENTS.md § Tool error contract](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/AGENTS.md#tool-error-contract). What a gate is and why it is enforced in code is [AGENTS.md § Gates](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/AGENTS.md#gates). This page lists which commands declare a gate, and every cause for which a command exits 42.
## Gates by command ## Gates by command
### guideline-push ### guideline-push
- [`export guidelines`](Reference-Commands-Guideline-export#export-guidelines) - [`export guidelines`](Reference-Commands-Exports#export-guidelines)
### mass-update ### mass-update
@@ -34,10 +34,19 @@ What each exit code means, and how a caller reacts to it, is [AGENTS.md § Tool
## Exit 42 causes ## Exit 42 causes
- [`new`](Reference-Commands-Pages#new): new project: The provider cannot create the project itself (Super Productivity's `access: "api"`); the output says what a human has to create - [`new`](Reference-Commands-Pages#new): new project: The provider cannot create the project itself (Super Productivity's `access: "api"`); the output says what a human has to create
- [`task new`](Reference-Commands-Pages#task-new): The tracker settings file of an earlier version is still there, or a secret source (1Password) needs unlocking
- [`task list`](Reference-Commands-Pages#task-list): The tracker settings file of an earlier version is still there, or a secret source (1Password) needs unlocking
- [`task close`](Reference-Commands-Pages#task-close): The tracker settings file of an earlier version is still there, or a secret source (1Password) needs unlocking
- [`review`](Reference-Commands-Finding-and-checking#review): The tracker settings file of an earlier version is still there, or a secret source (1Password) needs unlocking
- [`raw fetch`](Reference-Commands-Raw-material-and-uploads#raw-fetch): The host is fetched with the user's session and none is stored, or the page arrived without the sign of being signed in that its site handler names (the session expired)
- [`raw fetch`](Reference-Commands-Raw-material-and-uploads#raw-fetch): The site handler declares `render` and the optional browser component is not installed
- [`raw login`](Reference-Commands-Raw-material-and-uploads#raw-login): The optional browser component is not installed
- [`upload accept`](Reference-Commands-Raw-material-and-uploads#upload-accept): Upload Review Gate: `--confirm` is absent or does not match the manifest's current token - [`upload accept`](Reference-Commands-Raw-material-and-uploads#upload-accept): Upload Review Gate: `--confirm` is absent or does not match the manifest's current token
- [`sync`](Reference-Commands-Git#sync): Rebase-Review Gate: `<remote>/<branch>` moved and both sides changed the same file, generated files aside; the output lists the upstream commits, the overlapping files and their diff - [`sync`](Reference-Commands-Git#sync): Rebase-Review Gate: `<remote>/<branch>` moved and both sides changed the same file, generated files aside; the output lists the upstream commits, the overlapping files and their diff
- [`publish`](Reference-Commands-Git#publish): The remotes settings file of an earlier version is still there
- [`publish`](Reference-Commands-Git#publish): Mass-Update Gate: `--threshold` (default 10) or more counted files would be committed, or the `--confirm` token does not match this changeset - [`publish`](Reference-Commands-Git#publish): Mass-Update Gate: `--threshold` (default 10) or more counted files would be committed, or the `--confirm` token does not match this changeset
- [`publish`](Reference-Commands-Git#publish): 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 - [`publish`](Reference-Commands-Git#publish): 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
- [`publish`](Reference-Commands-Git#publish): Publish-Remote Gate: `.wikitool-remotes.json` exists and the push URL of `--remote` is not listed in it, or `--remote` resolves to no push URL - [`publish`](Reference-Commands-Git#publish): Publish-Remote Gate: `remotes.allowed_push_urls` is set and the push URL of `--remote` is not listed in it, or `--remote` resolves to no push URL
- [`export guidelines`](Reference-Commands-Guideline-export#export-guidelines): Guideline Push Gate: `--push` without `--confirm`, or with a token that does not match the current set of targets - [`export guidelines`](Reference-Commands-Exports#export-guidelines): Guideline Push Gate: `--push` without `--confirm`, or with a token that does not match the current set of targets
- [`config set`](Reference-Commands-Configuration#config-set): The field names a program to run or affects a gate
<!-- /wikitool:reference-exit-codes --> <!-- /wikitool:reference-exit-codes -->
+1 -1
@@ -1,4 +1,4 @@
*Generated from [`docs/reference/page-types.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/docs/reference/page-types.md) at v9.0.0 – edit the repository, not this wiki.* *Generated from [`docs/reference/page-types.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/reference/page-types.md) at v10.0.0 – edit the repository, not this wiki.*
# Page types # Page types
+1 -1
@@ -1,4 +1,4 @@
*Generated from [`docs/reference/skills.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/docs/reference/skills.md) at v9.0.0 – edit the repository, not this wiki.* *Generated from [`docs/reference/skills.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/reference/skills.md) at v10.0.0 – edit the repository, not this wiki.*
# Skill catalogue # Skill catalogue
+1 -1
@@ -1,4 +1,4 @@
*Generated from [`docs/reference/README.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/docs/reference/README.md) at v9.0.0 – edit the repository, not this wiki.* *Generated from [`docs/reference/README.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/reference/README.md) at v10.0.0 – edit the repository, not this wiki.*
# Reference # Reference
+2 -2
@@ -1,4 +1,4 @@
*Generated from [`docs/tutorials/first-source-to-first-answer.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/docs/tutorials/first-source-to-first-answer.md) at v9.0.0 – edit the repository, not this wiki.* *Generated from [`docs/tutorials/first-source-to-first-answer.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/tutorials/first-source-to-first-answer.md) at v10.0.0 – edit the repository, not this wiki.*
# From your first source to your first answer # From your first source to your first answer
@@ -86,7 +86,7 @@ verbatim, reporting
``` ```
The agent never guesses these two. Only you know them, and only now: once the file is in the wiki, The agent never guesses these two. Only you know them, and only now: once the file is in the wiki,
nobody can tell any more where it came from. [raw/CONTRACT.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/raw/CONTRACT.md#capture-fields-fidelity-and-authority) nobody can tell any more where it came from. [raw/CONTRACT.md](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/raw/CONTRACT.md#capture-fields-fidelity-and-authority)
lists every value, for the sources you add later. lists every value, for the sources you add later.
## 5. Let the agent publish ## 5. Let the agent publish
+1 -1
@@ -1,4 +1,4 @@
*Generated from [`docs/tutorials/sample-source.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/docs/tutorials/sample-source.md) at v9.0.0 – edit the repository, not this wiki.* *Generated from [`docs/tutorials/sample-source.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/tutorials/sample-source.md) at v10.0.0 – edit the repository, not this wiki.*
# Home lab backups - how they work now # Home lab backups - how they work now
+1 -1
@@ -1,4 +1,4 @@
*Generated from [`docs/tutorials/README.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v9.0.0/docs/tutorials/README.md) at v9.0.0 – edit the repository, not this wiki.* *Generated from [`docs/tutorials/README.md`](https://gitea.nehmer.net/torben/chemenu/src/tag/v10.0.0/docs/tutorials/README.md) at v10.0.0 – edit the repository, not this wiki.*
# Tutorials # Tutorials
+9 -1
@@ -4,9 +4,13 @@
* [From your first source to your first answer](Tutorials-First-source-to-first-answer) * [From your first source to your first answer](Tutorials-First-source-to-first-answer)
* [Home lab backups - how they work now](Tutorials-Sample-source) * [Home lab backups - how they work now](Tutorials-Sample-source)
* [How-to guides](How-to) * [How-to guides](How-to)
* [Back up to a synced folder](How-to-Back-up-to-a-synced-folder)
* [Browse the wiki in Obsidian](How-to-Browse-with-obsidian) * [Browse the wiki in Obsidian](How-to-Browse-with-obsidian)
* [Capture documentation from a git repository](How-to-Capture-repository-docs) * [Capture documentation from a git repository](How-to-Capture-repository-docs)
* [Collect documents from a synced folder](How-to-Collect-from-a-synced-folder)
* [Export guidelines to captured repositories](How-to-Export-guidelines) * [Export guidelines to captured repositories](How-to-Export-guidelines)
* [Export to a synced folder](How-to-Export-to-a-synced-folder)
* [Fetch pages behind a login](How-to-Fetch-pages-behind-a-login)
* [Ingest a source](How-to-Ingest-a-source) * [Ingest a source](How-to-Ingest-a-source)
* [Maintain the wiki](How-to-Maintain-the-wiki) * [Maintain the wiki](How-to-Maintain-the-wiki)
* [Query the wiki](How-to-Query-the-wiki) * [Query the wiki](How-to-Query-the-wiki)
@@ -15,14 +19,17 @@
* [Trace and score agent sessions](How-to-Trace-and-score-sessions) * [Trace and score agent sessions](How-to-Trace-and-score-sessions)
* [Troubleshoot an instance](How-to-Troubleshoot) * [Troubleshoot an instance](How-to-Troubleshoot)
* [Update an instance](How-to-Update-an-instance) * [Update an instance](How-to-Update-an-instance)
* [Use 1Password for settings](How-to-Use-1password-for-settings)
* [Write a site handler](How-to-Write-a-site-handler)
* [Reference](Reference) * [Reference](Reference)
* [Command reference](Reference-Commands) * [Command reference](Reference-Commands)
* [Catalog and log](Reference-Commands-Catalog-and-log) * [Catalog and log](Reference-Commands-Catalog-and-log)
* [Configuration](Reference-Commands-Configuration)
* [Content migrations](Reference-Commands-Content-migrations) * [Content migrations](Reference-Commands-Content-migrations)
* [Distribution and versioning](Reference-Commands-Distribution-and-versioning) * [Distribution and versioning](Reference-Commands-Distribution-and-versioning)
* [Exports](Reference-Commands-Exports)
* [Finding and checking](Reference-Commands-Finding-and-checking) * [Finding and checking](Reference-Commands-Finding-and-checking)
* [Git](Reference-Commands-Git) * [Git](Reference-Commands-Git)
* [Guideline export](Reference-Commands-Guideline-export)
* [Instance health](Reference-Commands-Instance-health) * [Instance health](Reference-Commands-Instance-health)
* [Links and citations](Reference-Commands-Links-and-citations) * [Links and citations](Reference-Commands-Links-and-citations)
* [Pages](Reference-Commands-Pages) * [Pages](Reference-Commands-Pages)
@@ -37,6 +44,7 @@
* [Skill catalogue](Reference-Skills) * [Skill catalogue](Reference-Skills)
* [Explanation](Explanation) * [Explanation](Explanation)
* [Architecture](Explanation-Architecture) * [Architecture](Explanation-Architecture)
* [Configuration tiers](Explanation-Configuration-tiers)
* [Why knowledge and commitments are two layers](Explanation-Knowledge-and-commitment) * [Why knowledge and commitments are two layers](Explanation-Knowledge-and-commitment)
* [Language Boundaries](Explanation-Language-boundaries) * [Language Boundaries](Explanation-Language-boundaries)
* [Choosing a Claude Code model and effort level](Explanation-Model-and-effort-selection) * [Choosing a Claude Code model and effort level](Explanation-Model-and-effort-selection)