feat: INSTALL.md held to the installation instructions - prerequisites lists generated from the manifest, setup questions checked by docs verify (#154)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnAJ7Z3CpVD3PRbN73QtU2 Files changed: - CHANGES.md - INSTALL.md - VERSION - instructions/dev/doc-pull-through.md - instructions/dev/stack-close/SKILL.md - instructions/setup-instance.md - tools/CONTRACT.md - tools/README.md - tools/chemenu/cli_contract.py - tools/chemenu/commands/docs_verify.py - tools/chemenu/install_doc.py - tools/chemenu/tests/test_install_doc.py
This commit is contained in:
1 parent
b33088f64e
commit
c77bda2004
12 files changed
+633
-69
No files matched your search
+30
-1
@@ -59,7 +59,7 @@ concern - readable here, never shipped as something to parse.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 8.0.0-beta.20 - 2026-10-01 - Installation only from a release, into an empty folder; upstream merge/verify and the other install paths removed (#153)
|
## 8.0.0-beta.21 - 2026-10-02 - INSTALL.md an die Installationsinstruktionen gekoppelt: Voraussetzungen generiert, Setup-Fragen geprüft
|
||||||
|
|
||||||
**Author:** Torben Nehmer
|
**Author:** Torben Nehmer
|
||||||
|
|
||||||
@@ -95,6 +95,7 @@ concern - readable here, never shipped as something to parse.
|
|||||||
- Preflight as a release asset: download, verify and unpack the stack, then run the tree preflight
|
- Preflight as a release asset: download, verify and unpack the stack, then run the tree preflight
|
||||||
- trace-hook.ps1: Copilot hooks no longer open Windows' choose-an-app dialog
|
- trace-hook.ps1: Copilot hooks no longer open Windows' choose-an-app dialog
|
||||||
- Windows-Portabilität: Pfadtrenner, Zeilenenden, Encoding und Locks
|
- Windows-Portabilität: Pfadtrenner, Zeilenenden, Encoding und Locks
|
||||||
|
- INSTALL.md an die Installationsinstruktionen gekoppelt: Voraussetzungen generiert, Setup-Fragen geprüft
|
||||||
|
|
||||||
**Low impact**
|
**Low impact**
|
||||||
- version bump no longer points at version release in its output
|
- version bump no longer points at version release in its output
|
||||||
@@ -132,6 +133,34 @@ concern - readable here, never shipped as something to parse.
|
|||||||
- preflight.ps1: the asset-mode error helper is Exit-Asset, so PSScriptAnalyzer passes
|
- preflight.ps1: the asset-mode error helper is Exit-Asset, so PSScriptAnalyzer passes
|
||||||
<!-- /wikitool:bumps -->
|
<!-- /wikitool:bumps -->
|
||||||
|
|
||||||
|
### INSTALL.md held to the installation instructions (#154)
|
||||||
|
|
||||||
|
The installation procedure has one source, the instructions under `instructions/`; `INSTALL.md`
|
||||||
|
is the human guide beside it, in the instance's language, and since #153 it no longer retells
|
||||||
|
the steps. One shared file was rejected (D12): an agent reads every sentence as an instruction,
|
||||||
|
and the two readers need different things. What the two still share is two enumerable lists,
|
||||||
|
and both are now checked by `docs verify`:
|
||||||
|
|
||||||
|
- **Prerequisites.** `INSTALL.md` carries one generated region per platform value of
|
||||||
|
`tools/prerequisites.txt` - `<!-- wikitool:prerequisites -->` for every platform,
|
||||||
|
`<!-- wikitool:prerequisites-windows -->` for Windows only - rendered as label and minimum
|
||||||
|
version, without the manifest's English reason field. The new `wikitool docs prerequisites
|
||||||
|
[--apply]` rewrites them; it never places a missing region, since where a list belongs is the
|
||||||
|
human guide's decision, and reports it instead. A tool added to the manifest fails `docs
|
||||||
|
verify` until the region is regenerated.
|
||||||
|
- **Setup questions.** Every place `instructions/setup-instance.md` asks the user something
|
||||||
|
carries `<!-- setup-question: <key> -->` (eight today: `identity`, `remote`, `kb-language`,
|
||||||
|
`domain`, `personalization`, `environment`, `telemetry`, `task-tracker`), and the matching
|
||||||
|
bullet in `INSTALL.md` § "Was der Agent dich fragt" carries the same marker. `docs verify`
|
||||||
|
compares the two sets in both directions. Markers rather than a frontmatter list: they sit
|
||||||
|
where the question is asked, visible to whoever adds the next one, and the instruction schema
|
||||||
|
stays closed.
|
||||||
|
|
||||||
|
The prose that no check reads is session work. `instructions/dev/doc-pull-through.md` gains rows
|
||||||
|
mapping the installation instructions to `INSTALL.md` and `dev-setup.md` to `DEVELOPMENT.md`,
|
||||||
|
applied before the publish; `stack-close` step 3 reads the pair again after it and files a
|
||||||
|
deviation as a follow-up issue.
|
||||||
|
|
||||||
### Installation only from a release, into an empty folder; upstream merge/verify and the other install paths removed (#153)
|
### Installation only from a release, into an empty folder; upstream merge/verify and the other install paths removed (#153)
|
||||||
|
|
||||||
The install run analysed in Gitea #140 failed on an instruction that contradicted itself, and the
|
The install run analysed in Gitea #140 failed on an instruction that contradicted itself, and the
|
||||||
|
|||||||
+48
-30
@@ -18,10 +18,19 @@ Demo-Korpus und keine Instanz; sie steht in [DEVELOPMENT.md](DEVELOPMENT.md).
|
|||||||
|
|
||||||
## Was vorher da sein muss
|
## Was vorher da sein muss
|
||||||
|
|
||||||
- **Python 3.11 oder neuer, git und [ripgrep](https://github.com/BurntSushi/ripgrep) (`rg`).**
|
Diese Programme prüft der Preflight, bevor irgendein `wikitool`-Befehl läuft. Installieren musst
|
||||||
Die maßgebliche Liste steht in `tools/prerequisites.txt`; der Preflight prüft sie, bevor
|
du sie selbst - der Agent tut es nie, auch nicht mit deiner Zustimmung. Wo eines fehlt, nennt
|
||||||
irgendein `wikitool`-Befehl läuft. Installieren musst du selbst - der Agent tut es nie, auch
|
der Preflight den Installationsbefehl für dein System. Die Liste wird aus
|
||||||
nicht mit deiner Zustimmung.
|
`tools/prerequisites.txt` erzeugt, derselben Datei, die der Preflight liest:
|
||||||
|
|
||||||
|
<!-- wikitool:prerequisites -->
|
||||||
|
- **Python** ≥ 3.11
|
||||||
|
- **Git**
|
||||||
|
- **ripgrep (rg)**
|
||||||
|
<!-- /wikitool:prerequisites -->
|
||||||
|
|
||||||
|
Dazu:
|
||||||
|
|
||||||
- **Ein Agent-Harness**: Claude Code, GitHub Copilot (in VS Code oder als CLI), Codex CLI oder
|
- **Ein Agent-Harness**: Claude Code, GitHub Copilot (in VS Code oder als CLI), Codex CLI oder
|
||||||
Mistral Vibe.
|
Mistral Vibe.
|
||||||
- **Ein leeres Verzeichnis**, in dem die Instanz liegen soll, und dein Harness darin geöffnet.
|
- **Ein leeres Verzeichnis**, in dem die Instanz liegen soll, und dein Harness darin geöffnet.
|
||||||
@@ -29,10 +38,16 @@ Demo-Korpus und keine Instanz; sie steht in [DEVELOPMENT.md](DEVELOPMENT.md).
|
|||||||
also genau richtig - liegt dein Repo `torben/nathan` etwa in `~/src/nathan`, installierst du
|
also genau richtig - liegt dein Repo `torben/nathan` etwa in `~/src/nathan`, installierst du
|
||||||
dorthin, und der Agent übernimmt dessen `origin` als Ziel für `publish`.
|
dorthin, und der Agent übernimmt dessen `origin` als Ziel für `publish`.
|
||||||
|
|
||||||
Unter Windows zusätzlich:
|
Unter Windows zusätzlich, ebenfalls vom Preflight geprüft:
|
||||||
|
|
||||||
- **PowerShell 7** (`pwsh`), in VS Code als Standardterminal eingestellt. Windows PowerShell 5.1
|
<!-- wikitool:prerequisites-windows -->
|
||||||
reicht nicht, und WSL ist nicht vorgesehen.
|
- **PowerShell 7 (pwsh)** ≥ 7
|
||||||
|
<!-- /wikitool:prerequisites-windows -->
|
||||||
|
|
||||||
|
Und außerdem:
|
||||||
|
|
||||||
|
- **PowerShell 7 als Standardterminal in VS Code.** Windows PowerShell 5.1 reicht nicht, und WSL
|
||||||
|
ist nicht vorgesehen.
|
||||||
- **Execution Policy `RemoteSigned`** - auf vielen Rechnern ab Werk gesetzt
|
- **Execution Policy `RemoteSigned`** - auf vielen Rechnern ab Werk gesetzt
|
||||||
(`Get-ExecutionPolicy -List` zeigt es).
|
(`Get-ExecutionPolicy -List` zeigt es).
|
||||||
- **Git for Windows.** Es bringt Git Bash mit, in dem Claude Code seine Befehle ausführt.
|
- **Git for Windows.** Es bringt Git Bash mit, in dem Claude Code seine Befehle ausführt.
|
||||||
@@ -64,29 +79,32 @@ Die Liste aller Releases: <https://gitea.nehmer.net/torben/chemenu/releases>. Da
|
|||||||
|
|
||||||
Raten darf der Agent keine dieser Antworten, und keine übernimmt er aus einem anderen Repo:
|
Raten darf der Agent keine dieser Antworten, und keine übernimmt er aus einem anderen Repo:
|
||||||
|
|
||||||
- **Autor-Identität** - Name und E-Mail für `git config`. Das ist zugleich der Autorname jeder
|
- <!-- setup-question: identity --> **Autor-Identität** - Name und E-Mail für `git config`. Das ist
|
||||||
künftig angelegten Wiki-Seite (`$WIKI_AUTHOR` überschreibt ihn bei Bedarf).
|
zugleich der Autorname jeder künftig angelegten Wiki-Seite (`$WIKI_AUTHOR` überschreibt ihn bei
|
||||||
- **Remote** - bei einem leeren Klon nur die Bestätigung, dass `origin` stimmt; sonst eine URL,
|
Bedarf).
|
||||||
wenn du auf einen Server pushen willst. Ohne Remote bleibt die Instanz lokal, und jedes
|
- <!-- setup-question: remote --> **Remote** - bei einem leeren Klon nur die Bestätigung, dass
|
||||||
`publish` läuft mit `--no-push`.
|
`origin` stimmt; sonst eine URL, wenn du auf einen Server pushen willst. Ohne Remote bleibt die
|
||||||
- **Sprache und Ton der Seiten** - sie landen in `kb/CONVENTIONS.md`, dazu je Collection
|
Instanz lokal, und jedes `publish` läuft mit `--no-push`.
|
||||||
`kb/<name>/COLLECTION.md`. Fertige Profile, darunter ein vollständiges deutsches, hält
|
- <!-- setup-question: kb-language --> **Sprache und Ton der Seiten** - sie landen in
|
||||||
`instructions/kb-profiles.md` bereit. Entscheide das **vor dem ersten Ingest**: Danach ist ein
|
`kb/CONVENTIONS.md`, dazu je Collection `kb/<name>/COLLECTION.md`. Fertige Profile, darunter ein
|
||||||
Wechsel der Abschnittsnamen eine Migration jeder bestehenden Seite. Titel, Wikilink-Ziele,
|
vollständiges deutsches, hält `instructions/kb-profiles.md` bereit. Entscheide das **vor dem
|
||||||
Zitat-IDs, Schema-Werte, Tags, Befehle und Pfade folgen keiner Sprache - `Act Runner` heißt in
|
ersten Ingest**: Danach ist ein Wechsel der Abschnittsnamen eine Migration jeder bestehenden
|
||||||
jeder Instanz `Act Runner`.
|
Seite. Titel, Wikilink-Ziele, Zitat-IDs, Schema-Werte, Tags, Befehle und Pfade folgen keiner
|
||||||
- **Anwendungsgebiet** - woraus dieses Wiki seine Quellen zieht. Daraus schlägt der Agent eine
|
Sprache - `Act Runner` heißt in jeder Instanz `Act Runner`.
|
||||||
`source_type`-Liste vor (bei einem Verein etwa Satzung, Protokoll, Spielbericht). Das ist ein
|
- <!-- setup-question: domain --> **Anwendungsgebiet** - woraus dieses Wiki seine Quellen zieht.
|
||||||
Startpunkt, keine Festlegung: Später wird sie an echtem Bestand korrigiert
|
Daraus schlägt der Agent eine `source_type`-Liste vor (bei einem Verein etwa Satzung, Protokoll,
|
||||||
(`instructions/evolve-subtypes.md`).
|
Spielbericht). Das ist ein Startpunkt, keine Festlegung: Später wird sie an echtem Bestand
|
||||||
- **Personalisierung** - wer diese Instanz bedient (`USER.md`) und wie sie klingt (`SOUL.md`).
|
korrigiert (`instructions/evolve-subtypes.md`).
|
||||||
Der Agent interviewt dich entlang der Vorlagen und schreibt deine Antworten wörtlich mit. Zwei
|
- <!-- setup-question: personalization --> **Personalisierung** - wer diese Instanz bedient
|
||||||
Fragen beantwortest nur du: den **Namen der Persona** und die **Themen, die bewusst draußen
|
(`USER.md`) und wie sie klingt (`SOUL.md`). Der Agent interviewt dich entlang der Vorlagen und
|
||||||
bleiben**.
|
schreibt deine Antworten wörtlich mit. Zwei Fragen beantwortest nur du: den **Namen der Persona**
|
||||||
- **Umgebung** (optional) - Harness, MCP-Server, Remotes, damit spätere Sitzungen nicht erneut
|
und die **Themen, die bewusst draußen bleiben**.
|
||||||
fragen. „Weiß ich nicht“ ist eine gültige Antwort.
|
- <!-- setup-question: environment --> **Umgebung** (optional) - Harness, MCP-Server, Remotes,
|
||||||
- **Telemetrie** - standardmäßig aus; der Agent fragt nur, ob du sie einschalten willst.
|
damit spätere Sitzungen nicht erneut fragen. „Weiß ich nicht“ ist eine gültige Antwort.
|
||||||
- **Aufgaben-Tracker** (optional) - siehe [Konfiguration](#konfiguration).
|
- <!-- setup-question: telemetry --> **Telemetrie** - standardmäßig aus; der Agent fragt nur, ob du
|
||||||
|
sie einschalten willst.
|
||||||
|
- <!-- setup-question: task-tracker --> **Aufgaben-Tracker** (optional) - siehe
|
||||||
|
[Konfiguration](#konfiguration).
|
||||||
|
|
||||||
Am Ende legt der Agent den ersten Commit an. Dabei hält das Mass-Update-Gate an (Exit 42), weil
|
Am Ende legt der Agent den ersten Commit an. Dabei hält das Mass-Update-Gate an (Exit 42), weil
|
||||||
eine neue Instanz aus weit mehr als zehn Dateien besteht. Das ist erwartet: Der Agent zeigt dir
|
eine neue Instanz aus weit mehr als zehn Dateien besteht. Das ist erwartet: Der Agent zeigt dir
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ touched; a row that does not apply needs no action.
|
|||||||
| A task-tracker adapter (`tools/chemenu/tasks/`), its recorded fixtures, or the live suite | [instructions/dev/tracker-testing.md](tracker-testing.md), and `MANIFEST.json` beside the fixtures when they were re-recorded |
|
| A task-tracker adapter (`tools/chemenu/tasks/`), its recorded fixtures, or the live suite | [instructions/dev/tracker-testing.md](tracker-testing.md), and `MANIFEST.json` beside the fixtures when they were re-recorded |
|
||||||
| A skill's own step sequence or catalogue | The skill's `SKILL.md` source under `instructions/<name>/` or `instructions/dev/<name>/` |
|
| A skill's own step sequence or catalogue | The skill's `SKILL.md` source under `instructions/<name>/` or `instructions/dev/<name>/` |
|
||||||
| A per-checkout configuration file an instance owns (`.wikitool-tasks.json`, `.wikitool-telemetry.json`, `.wikitool-remotes.json`, `.wikitool-upload.json`) | [INSTALL.md](../../INSTALL.md) § Konfiguration, where an operator looks the shape up; the [setup-instance.md](../setup-instance.md) decision point that offers it during setup; and `doctor`'s own row in [tools/CONTRACT.md](../../tools/CONTRACT.md), since `doctor` is what reports the file's state |
|
| A per-checkout configuration file an instance owns (`.wikitool-tasks.json`, `.wikitool-telemetry.json`, `.wikitool-remotes.json`, `.wikitool-upload.json`) | [INSTALL.md](../../INSTALL.md) § Konfiguration, where an operator looks the shape up; the [setup-instance.md](../setup-instance.md) decision point that offers it during setup; and `doctor`'s own row in [tools/CONTRACT.md](../../tools/CONTRACT.md), since `doctor` is what reports the file's state |
|
||||||
|
| An installation instruction - [preflight.md](../preflight.md), [setup-instance.md](../setup-instance.md), [bootstrap.md](../bootstrap.md), [upgrade-instance.md](../upgrade-instance.md) - or `tools/prerequisites.txt` | [INSTALL.md](../../INSTALL.md), the human guide to the same procedure. Read it against the instruction: what to prepare, the sentence for the agent, what the agent asks, where it stops and why. `docs verify` checks only the two enumerable overlaps - the prerequisites lists, which `wikitool docs prerequisites --apply` regenerates from the manifest, and the setup questions: a question the agent asks the user carries `<!-- setup-question: <key> -->` where it is asked in `setup-instance.md`, and `INSTALL.md` § "Was der Agent dich fragt" names it with the same marker. Every other sentence is this session's to compare. `INSTALL.md` does not retell the steps, so a change to their order or wording alone moves nothing there |
|
||||||
|
| [dev-setup.md](dev-setup.md) | [DEVELOPMENT.md](../../DEVELOPMENT.md), read against it the same way - nothing checks this pair at all |
|
||||||
| A new page type the stack requires, or a new collection | Its type-spec and `COLLECTION.md` (both as the `.template` an instance adopts), the collection table in [kb/CONTRACT.md](../../kb/CONTRACT.md), and **both adoption paths**: [setup-instance.md](../setup-instance.md) for a fresh instance and [upgrade-instance.md](../upgrade-instance.md) for an existing one, where an unadopted template is what `docs verify` refuses |
|
| A new page type the stack requires, or a new collection | Its type-spec and `COLLECTION.md` (both as the `.template` an instance adopts), the collection table in [kb/CONTRACT.md](../../kb/CONTRACT.md), and **both adoption paths**: [setup-instance.md](../setup-instance.md) for a fresh instance and [upgrade-instance.md](../upgrade-instance.md) for an existing one, where an unadopted template is what `docs verify` refuses |
|
||||||
|
|
||||||
3. **A heading you changed means a table of contents to regenerate - by the tool, never by
|
3. **A heading you changed means a table of contents to regenerate - by the tool, never by
|
||||||
|
|||||||
@@ -105,6 +105,14 @@ and a fresh subagent starts without the session's context).
|
|||||||
behaviour one of these documents describes, update it now; if none did, say so rather than
|
behaviour one of these documents describes, update it now; if none did, say so rather than
|
||||||
leaving the question unasked.
|
leaving the question unasked.
|
||||||
|
|
||||||
|
**If the published diff touches an installation instruction, read the human guide against
|
||||||
|
it once more.** Which instructions those are and which human document answers for each is
|
||||||
|
the pull-through table's row in `instructions/dev/doc-pull-through.md` - `stack-dev` step 5
|
||||||
|
applied it before the publish; this is the second reading, after. A deviation found here is
|
||||||
|
filed as a follow-up issue naming both files and the sentence that disagrees, rather than
|
||||||
|
fixed in this phase: the pull-through before the publish missed it, and that miss is worth a
|
||||||
|
record of its own.
|
||||||
|
|
||||||
**If that update moved a `##`/`###` heading, the file's table of contents is now stale** -
|
**If that update moved a `##`/`###` heading, the file's table of contents is now stale** -
|
||||||
regenerate it with `tools/wikitool docs toc --apply`, never by editing the list. The region
|
regenerate it with `tools/wikitool docs toc --apply`, never by editing the list. The region
|
||||||
is generated (AGENTS.md invariant 1), `docs verify` fails on stale exactly as on missing, and
|
is generated (AGENTS.md invariant 1), `docs verify` fails on stale exactly as on missing, and
|
||||||
|
|||||||
@@ -102,9 +102,9 @@ one it needs before that.
|
|||||||
checked-out branch matches the target branch (default `main`) and refuses otherwise, so that
|
checked-out branch matches the target branch (default `main`) and refuses otherwise, so that
|
||||||
the wrong branch is never published.
|
the wrong branch is never published.
|
||||||
|
|
||||||
2. **Decision point - identity.** Ask the user for their name and email address; never guess
|
2. <!-- setup-question: identity --> **Decision point - identity.** Ask the user for their name and
|
||||||
them, and never quietly carry them over from another repository (that is a different person
|
email address; never guess them, and never quietly carry them over from another repository (that
|
||||||
and a different project):
|
is a different person and a different project):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git config user.name "<name>"
|
git config user.name "<name>"
|
||||||
@@ -115,9 +115,9 @@ one it needs before that.
|
|||||||
resolves `author:` from `$WIKI_AUTHOR` (an override) or else from `git config user.name`, and
|
resolves `author:` from `$WIKI_AUTHOR` (an override) or else from `git config user.name`, and
|
||||||
aborts with `ERROR` when both are missing - there is no silent placeholder.
|
aborts with `ERROR` when both are missing - there is no silent placeholder.
|
||||||
|
|
||||||
3. **Decision point - remote.** An empty clone already has one: show the user `git remote -v`
|
3. <!-- setup-question: remote --> **Decision point - remote.** An empty clone already has one:
|
||||||
and confirm that `origin` is where this instance is to be published. Otherwise ask for a
|
show the user `git remote -v` and confirm that `origin` is where this instance is to be
|
||||||
remote URL; a purely local repo is a valid end state:
|
published. Otherwise ask for a remote URL; a purely local repo is a valid end state:
|
||||||
- Given: `git remote add origin <url>`
|
- Given: `git remote add origin <url>`
|
||||||
- Not given: stay local - then **every** later `tools/wikitool publish` needs a `--no-push`
|
- Not given: stay local - then **every** later `tools/wikitool publish` needs a `--no-push`
|
||||||
(which also drops its branch check, see step 1). Without it, `publish` ends with exit 1
|
(which also drops its branch check, see step 1). Without it, `publish` ends with exit 1
|
||||||
@@ -153,10 +153,11 @@ one it needs before that.
|
|||||||
`dist upgrade` improves it directly, without the type-spec that links it needing to be
|
`dist upgrade` improves it directly, without the type-spec that links it needing to be
|
||||||
touched. `types/type-spec.md` § "Anatomy of a type" has the shape.
|
touched. `types/type-spec.md` § "Anatomy of a type" has the shape.
|
||||||
|
|
||||||
2. Ask the user for the KB language. `kb/CONVENTIONS.md.template` defaults to **English**;
|
2. <!-- setup-question: kb-language --> Ask the user for the KB language.
|
||||||
[kb-profiles.md](kb-profiles.md) additionally holds a complete German profile. The
|
`kb/CONVENTIONS.md.template` defaults to **English**; [kb-profiles.md](kb-profiles.md)
|
||||||
profile catalogue is a **palette, not an enum**: what gets adopted is the text *into* the
|
additionally holds a complete German profile. The profile catalogue is a **palette, not an
|
||||||
instance file, not a reference to the catalogue.
|
enum**: what gets adopted is the text *into* the instance file, not a reference to the
|
||||||
|
catalogue.
|
||||||
|
|
||||||
3. Write `kb/CONVENTIONS.md` from `kb/CONVENTIONS.md.template`, filled in along the chosen
|
3. Write `kb/CONVENTIONS.md` from `kb/CONVENTIONS.md.template`, filled in along the chosen
|
||||||
profile - language, section names, naming forms, tone, relationship labels, hedging rule -
|
profile - language, section names, naming forms, tone, relationship labels, hedging rule -
|
||||||
@@ -166,13 +167,13 @@ one it needs before that.
|
|||||||
4. For a language other than German: delete `german-terminology.md` or replace it with your
|
4. For a language other than German: delete `german-terminology.md` or replace it with your
|
||||||
own vocabulary - it is material belonging to the German profile, not to the stack.
|
own vocabulary - it is material belonging to the German profile, not to the stack.
|
||||||
|
|
||||||
5. Ask the user about the subject area and derive a `source_type` proposal from it.
|
5. <!-- setup-question: domain --> Ask the user about the subject area and derive a
|
||||||
[kb-profiles.md](kb-profiles.md) holds two worked domain profiles as illustration. The
|
`source_type` proposal from it. [kb-profiles.md](kb-profiles.md) holds two worked domain
|
||||||
proposal is a **starting point, not a commitment** - at setup time the operator has zero
|
profiles as illustration. The proposal is a **starting point, not a commitment** - at setup
|
||||||
sources and is guessing a taxonomy before having seen a single file, which is the worst
|
time the operator has zero sources and is guessing a taxonomy before having seen a single
|
||||||
possible moment to pin an enum down. Carrying out the proposal means setting the enum in
|
file, which is the worst possible moment to pin an enum down. Carrying out the proposal means
|
||||||
`types/source.schema.yaml` **and** the matching `layout:` line per value in
|
setting the enum in `types/source.schema.yaml` **and** the matching `layout:` line per value
|
||||||
`types/source.md` in the same edit - one without the other leaves a value with no target
|
in `types/source.md` in the same edit - one without the other leaves a value with no target
|
||||||
directory. The visible catch-all (`unclassified`) survives every proposal; it is not a
|
directory. The visible catch-all (`unclassified`) survives every proposal; it is not a
|
||||||
dumping ground but the slot for a source whose category is not settled yet. Extending the
|
dumping ground but the slot for a source whose category is not settled yet. Extending the
|
||||||
list later, or emptying that slot: [evolve-subtypes.md](evolve-subtypes.md) - not part of
|
list later, or emptying that slot: [evolve-subtypes.md](evolve-subtypes.md) - not part of
|
||||||
@@ -207,11 +208,11 @@ one it needs before that.
|
|||||||
`docs verify` additionally checks `profile:` and `required_by_stack:` on every
|
`docs verify` additionally checks `profile:` and `required_by_stack:` on every
|
||||||
`COLLECTION.md`.
|
`COLLECTION.md`.
|
||||||
|
|
||||||
5. **Decision point - personalization.** The release ships `USER.md.template` and
|
5. <!-- setup-question: personalization --> **Decision point - personalization.** The release ships
|
||||||
`SOUL.md.template`, but no filled-in versions: who operates this instance and how it sounds
|
`USER.md.template` and `SOUL.md.template`, but no filled-in versions: who operates this instance
|
||||||
is the property of this instance alone and is never carried over from anywhere else. Both
|
and how it sounds is the property of this instance alone and is never carried over from anywhere
|
||||||
files are read in **every** session from now on, so they come into being here - not later,
|
else. Both files are read in **every** session from now on, so they come into being here - not
|
||||||
when the occasion arises.
|
later, when the occasion arises.
|
||||||
|
|
||||||
Procedure, once each for `USER.md` and `SOUL.md`:
|
Procedure, once each for `USER.md` and `SOUL.md`:
|
||||||
|
|
||||||
@@ -248,9 +249,9 @@ one it needs before that.
|
|||||||
tools/wikitool instructions sync
|
tools/wikitool instructions sync
|
||||||
```
|
```
|
||||||
|
|
||||||
7. **Decision point - record the environment.** The release ships `ENVIRONMENT.md.template`:
|
7. <!-- setup-question: environment --> **Decision point - record the environment.** The release
|
||||||
harness, published skills, reachable MCP servers, connectors, git remotes, where CI runs.
|
ships `ENVIRONMENT.md.template`: harness, published skills, reachable MCP servers, connectors,
|
||||||
Constants a session would otherwise ask about every time.
|
git remotes, where CI runs. Constants a session would otherwise ask about every time.
|
||||||
|
|
||||||
Unlike step 5, this step is **optional** and not an interview. Whatever can be read off the
|
Unlike step 5, this step is **optional** and not an interview. Whatever can be read off the
|
||||||
checkout itself (`git remote -v`, the running harness, the skills just published) the agent
|
checkout itself (`git remote -v`, the running harness, the skills just published) the agent
|
||||||
@@ -262,10 +263,10 @@ one it needs before that.
|
|||||||
`environment: absent (optional)` in step 12, not a `FAIL`. The file is gitignored and enters
|
`environment: absent (optional)` in step 12, not a `FAIL`. The file is gitignored and enters
|
||||||
no commit - it describes this checkout, not the repo.
|
no commit - it describes this checkout, not the repo.
|
||||||
|
|
||||||
8. **Decision point - telemetry.** Every instance installed from a release carries a
|
8. <!-- setup-question: telemetry --> **Decision point - telemetry.** Every instance installed from
|
||||||
`.wikitool-release.json` and starts with telemetry **off**; nobody asked for it, and nobody
|
a release carries a `.wikitool-release.json` and starts with telemetry **off**; nobody asked for
|
||||||
reads `EVALS.md` before the first file is written anyway. This step only asks whether the
|
it, and nobody reads `EVALS.md` before the first file is written anyway. This step only asks
|
||||||
operator wants to reverse that.
|
whether the operator wants to reverse that.
|
||||||
|
|
||||||
Ask the user once: telemetry on? If yes, create `.wikitool-telemetry.json` in the repo root
|
Ask the user once: telemetry on? If yes, create `.wikitool-telemetry.json` in the repo root
|
||||||
(per checkout, gitignored, no `.template` - like `.wikitool-remotes.json`):
|
(per checkout, gitignored, no `.template` - like `.wikitool-remotes.json`):
|
||||||
@@ -284,12 +285,13 @@ one it needs before that.
|
|||||||
never a `FAIL`, since both directions are a valid state. More on this:
|
never a `FAIL`, since both directions are a valid state. More on this:
|
||||||
[EVALS.md](../EVALS.md) § "Whether it runs at all".
|
[EVALS.md](../EVALS.md) § "Whether it runs at all".
|
||||||
|
|
||||||
9. **Decision point - task tracker.** The instance ships the `project` type and the collection
|
9. <!-- setup-question: task-tracker --> **Decision point - task tracker.** The instance ships the
|
||||||
its type-spec's `base_dir:` names (`kb/gtd/` here), so committed initiatives have a page from
|
`project` type and the collection its type-spec's `base_dir:` names (`kb/gtd/` here), so
|
||||||
the start. What they do *not* have until this step is the other half of the weekly review:
|
committed initiatives have a page from the start. What they do *not* have until this step is the
|
||||||
the tracker that owns the open items, which `tools/wikitool review` joins those pages against
|
other half of the weekly review: the tracker that owns the open items, which `tools/wikitool
|
||||||
over the project name. No tracker configured is a legitimate end state - the pages work
|
review` joins those pages against over the project name. No tracker configured is a legitimate
|
||||||
alone, `review` simply says so and refuses - so ask rather than assume.
|
end state - the pages work alone, `review` simply says so and refuses - so ask rather than
|
||||||
|
assume.
|
||||||
|
|
||||||
Ask the user once: is there a task tracker to connect? If yes, create `.wikitool-tasks.json`
|
Ask the user once: is there a task tracker to connect? If yes, create `.wikitool-tasks.json`
|
||||||
in the repo root (per checkout, no `.template`, **gitignored once it holds a token** - like
|
in the repo root (per checkout, no `.template`, **gitignored once it holds a token** - like
|
||||||
|
|||||||
@@ -132,6 +132,7 @@ instructions verify read idempotent budget:counted exit:0,1
|
|||||||
instructions list read idempotent budget:counted exit:0 List the flat instructions with their descriptions.
|
instructions list read idempotent budget:counted exit:0 List the flat instructions with their descriptions.
|
||||||
docs verify read idempotent budget:counted exit:0,1 Check the docs that mirror the code.
|
docs verify read idempotent budget:counted exit:0,1 Check the docs that mirror the code.
|
||||||
docs toc write idempotent budget:counted exit:0 Create, refresh or remove the generated table-of-contents region.
|
docs toc write idempotent budget:counted exit:0 Create, refresh or remove the generated table-of-contents region.
|
||||||
|
docs prerequisites write idempotent budget:counted exit:0,1 Regenerate `INSTALL.md`'s prerequisites lists from `tools/prerequisites.txt`.
|
||||||
docs contract write idempotent budget:counted exit:0,1 Regenerate `tools/CONTRACT.md`'s `<!-- wikitool:commands -->` region.
|
docs contract write idempotent budget:counted exit:0,1 Regenerate `tools/CONTRACT.md`'s `<!-- wikitool:commands -->` region.
|
||||||
eval sessions read idempotent budget:exempt exit:0 List the sessions that have a trace under `reports/telemetry/`.
|
eval sessions read idempotent budget:exempt exit:0 List the sessions that have a trace under `reports/telemetry/`.
|
||||||
eval score read idempotent budget:exempt exit:0,1 Score one traced session.
|
eval score read idempotent budget:exempt exit:0,1 Score one traced session.
|
||||||
@@ -2182,6 +2183,9 @@ Check the docs that mirror the code.
|
|||||||
- 1 A shipped `.md`/`.template` cites an issue number
|
- 1 A shipped `.md`/`.template` cites an issue number
|
||||||
- 1 A reference file's table-of-contents region is missing or stale
|
- 1 A reference file's table-of-contents region is missing or stale
|
||||||
- 1 A reference file's relative markdown link does not resolve to an existing file
|
- 1 A reference file's relative markdown link does not resolve to an existing file
|
||||||
|
- 1 An `INSTALL.md` prerequisites region is stale
|
||||||
|
- 1 An `INSTALL.md` prerequisites region is missing, or names a platform no tool has
|
||||||
|
- 1 A setup question is marked in one of `instructions/setup-instance.md` and `INSTALL.md` but not the other
|
||||||
|
|
||||||
**ON FAILURE**
|
**ON FAILURE**
|
||||||
|
|
||||||
@@ -2191,6 +2195,9 @@ Check the docs that mirror the code.
|
|||||||
- A shipped `.md`/`.template` cites an issue number -> Say what was decided instead of pointing at where, or move the pointer behind a `<!-- dist:strip-start/end -->` block
|
- A shipped `.md`/`.template` cites an issue number -> Say what was decided instead of pointing at where, or move the pointer behind a `<!-- dist:strip-start/end -->` block
|
||||||
- A reference file's table-of-contents region is missing or stale -> Run `docs toc --apply`, then re-run
|
- A reference file's table-of-contents region is missing or stale -> Run `docs toc --apply`, then re-run
|
||||||
- A reference file's relative markdown link does not resolve to an existing file -> Fix the `../` count or the target's name
|
- A reference file's relative markdown link does not resolve to an existing file -> Fix the `../` count or the target's name
|
||||||
|
- An `INSTALL.md` prerequisites region is stale -> Run `docs prerequisites --apply`, then re-run
|
||||||
|
- An `INSTALL.md` prerequisites region is missing, or names a platform no tool has -> Add the marker pair where that list belongs (or remove the orphaned region and its introducing prose), then run `docs prerequisites --apply`
|
||||||
|
- A setup question is marked in one of `instructions/setup-instance.md` and `INSTALL.md` but not the other -> Describe the question for the human in `INSTALL.md` with the same marker, or remove the bullet for a question no longer asked
|
||||||
|
|
||||||
**NEVER**
|
**NEVER**
|
||||||
|
|
||||||
@@ -2207,12 +2214,14 @@ Check the docs that mirror the code.
|
|||||||
- 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.
|
||||||
|
- `INSTALL.md` carries one generated `<!-- wikitool:prerequisites -->` region per platform value of `tools/prerequisites.txt` (`prerequisites-<platform>` for a platform-specific one), each current; and the `<!-- setup-question: <key> -->` markers in `instructions/setup-instance.md` and `INSTALL.md` name the same set of keys, so a question the agent asks is never one the human guide leaves out, nor the reverse.
|
||||||
- Read-only.
|
- Read-only.
|
||||||
|
|
||||||
**SEE ALSO**
|
**SEE ALSO**
|
||||||
|
|
||||||
- `wikitool docs toc` - regenerates tables of contents
|
- `wikitool docs toc` - regenerates tables of contents
|
||||||
- `wikitool docs contract` - regenerates the commands region
|
- `wikitool docs contract` - regenerates the commands region
|
||||||
|
- `wikitool docs prerequisites` - regenerates `INSTALL.md`'s prerequisites lists
|
||||||
- `wikitool instructions verify` - the same kind of check for `instructions/`
|
- `wikitool instructions verify` - the same kind of check for `instructions/`
|
||||||
|
|
||||||
#### `docs toc`
|
#### `docs toc`
|
||||||
@@ -2257,6 +2266,51 @@ Create, refresh or remove the generated table-of-contents region.
|
|||||||
|
|
||||||
- `wikitool docs verify` - checks every region is current
|
- `wikitool docs verify` - checks every region is current
|
||||||
|
|
||||||
|
#### `docs prerequisites`
|
||||||
|
|
||||||
|
Regenerate `INSTALL.md`'s prerequisites lists from `tools/prerequisites.txt`.
|
||||||
|
|
||||||
|
**SYNOPSIS**
|
||||||
|
|
||||||
|
- `wikitool docs prerequisites [--apply]`
|
||||||
|
|
||||||
|
**PROPERTIES**
|
||||||
|
|
||||||
|
- effect: write
|
||||||
|
- idempotent: yes
|
||||||
|
- atomic: Yes - every region is rewritten in one file write
|
||||||
|
- budget: counted
|
||||||
|
- network: no
|
||||||
|
|
||||||
|
**EXAMPLES**
|
||||||
|
|
||||||
|
- `tools/wikitool docs prerequisites`
|
||||||
|
- `tools/wikitool docs prerequisites --apply`
|
||||||
|
|
||||||
|
**EXIT STATUS**
|
||||||
|
|
||||||
|
- 0 success
|
||||||
|
- 1 `INSTALL.md` is missing, or lacks a region the manifest calls for
|
||||||
|
|
||||||
|
**ON FAILURE**
|
||||||
|
|
||||||
|
- `INSTALL.md` is missing, or lacks a region the manifest calls for -> Not transient - add the marker pair the message names where that list belongs (restore the file if it is gone), then retry
|
||||||
|
|
||||||
|
**NEVER**
|
||||||
|
|
||||||
|
- Never hand-edit a prerequisites region - change `tools/prerequisites.txt` and re-run this.
|
||||||
|
|
||||||
|
**NOTES**
|
||||||
|
|
||||||
|
- Rewrites each `<!-- wikitool:prerequisites -->` region in `INSTALL.md` (tools every platform needs) and `<!-- wikitool:prerequisites-<platform> -->` region (tools only that platform needs) from the manifest: one list item per tool, its label and minimum version. The manifest's reason field stays out - it is English prose, and the region sits in a document that need not be.
|
||||||
|
- Never places a region: where a list belongs in the human guide is that guide's own decision. A region the manifest calls for but the file lacks is an error naming the marker pair to add.
|
||||||
|
- Dry-run by default (says whether the file would change); `--apply` writes.
|
||||||
|
- `docs verify` checks the result stays current.
|
||||||
|
|
||||||
|
**SEE ALSO**
|
||||||
|
|
||||||
|
- `wikitool docs verify` - checks every region is current
|
||||||
|
|
||||||
#### `docs contract`
|
#### `docs contract`
|
||||||
|
|
||||||
Regenerate `tools/CONTRACT.md`'s `<!-- wikitool:commands -->` region.
|
Regenerate `tools/CONTRACT.md`'s `<!-- wikitool:commands -->` region.
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ tools/
|
|||||||
toolpaths.py where git and rg are started from: .wikitool-tools.json, bare name only without the file
|
toolpaths.py where git and rg are started from: .wikitool-tools.json, bare name only without the file
|
||||||
filelock.py an exclusive lock on an open file, flock on POSIX and msvcrt on Windows - the only module that imports either
|
filelock.py an exclusive lock on an open file, flock on POSIX and msvcrt on Windows - the only module that imports either
|
||||||
prerequisites.py prerequisites.txt read from Python, plus the platform and long-path questions `doctor` asks
|
prerequisites.py prerequisites.txt read from Python, plus the platform and long-path questions `doctor` asks
|
||||||
|
install_doc.py INSTALL.md held to the instructions: its prerequisites lists generated from prerequisites.txt, its setup questions matched to setup-instance.md's markers
|
||||||
corpus_cache.py one parsed corpus per commit, never cached while the tree is dirty
|
corpus_cache.py one parsed corpus per commit, never cached while the tree is dirty
|
||||||
kb_scan.py page iteration/loading over kb/
|
kb_scan.py page iteration/loading over kb/
|
||||||
blocks.py generated regions in a page body, found by marker rather than by heading
|
blocks.py generated regions in a page body, found by marker rather than by heading
|
||||||
|
|||||||
@@ -270,7 +270,7 @@ GROUPS: tuple[tuple[str, tuple[str, ...]], ...] = (
|
|||||||
("Types, instructions and docs", (
|
("Types, instructions and docs", (
|
||||||
"types list", "types describe",
|
"types list", "types describe",
|
||||||
"instructions sync", "instructions verify", "instructions list",
|
"instructions sync", "instructions verify", "instructions list",
|
||||||
"docs verify", "docs toc", "docs contract",
|
"docs verify", "docs toc", "docs prerequisites", "docs contract",
|
||||||
)),
|
)),
|
||||||
("Telemetry", (
|
("Telemetry", (
|
||||||
"eval sessions", "eval score",
|
"eval sessions", "eval score",
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ from typing import Optional
|
|||||||
|
|
||||||
import typer
|
import typer
|
||||||
|
|
||||||
from chemenu import blocks, cli_contract, config, conventions, kb_collections, markdown_code, toc, toolpaths, version as version_mod
|
from chemenu import blocks, cli_contract, config, conventions, install_doc, kb_collections, markdown_code, toc, toolpaths, version as version_mod
|
||||||
from chemenu.commands import dist_cmd
|
from chemenu.commands import dist_cmd
|
||||||
from chemenu.commands._util import fail, rel_path, success
|
from chemenu.commands._util import fail, rel_path, success
|
||||||
|
|
||||||
@@ -1078,6 +1078,11 @@ def check_breaking_change_for_boundary() -> list[str]:
|
|||||||
"file. A target's `#anchor` suffix is stripped first, and code fences and inline code "
|
"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 "
|
"spans are masked before scanning, so link syntax shown as an example is not mistaken "
|
||||||
"for a real reference.",
|
"for a real reference.",
|
||||||
|
"`INSTALL.md` carries one generated `<!-- wikitool:prerequisites -->` region per "
|
||||||
|
"platform value of `tools/prerequisites.txt` (`prerequisites-<platform>` for a "
|
||||||
|
"platform-specific one), each current; and the `<!-- setup-question: <key> -->` markers "
|
||||||
|
"in `instructions/setup-instance.md` and `INSTALL.md` name the same set of keys, so a "
|
||||||
|
"question the agent asks is never one the human guide leaves out, nor the reverse.",
|
||||||
"Read-only.",
|
"Read-only.",
|
||||||
),
|
),
|
||||||
failures=(
|
failures=(
|
||||||
@@ -1108,6 +1113,22 @@ def check_breaking_change_for_boundary() -> list[str]:
|
|||||||
"file",
|
"file",
|
||||||
reaction="Fix the `../` count or the target's name",
|
reaction="Fix the `../` count or the target's name",
|
||||||
),
|
),
|
||||||
|
cli_contract.Failure(
|
||||||
|
cause="An `INSTALL.md` prerequisites region is stale",
|
||||||
|
reaction="Run `docs prerequisites --apply`, then re-run",
|
||||||
|
),
|
||||||
|
cli_contract.Failure(
|
||||||
|
cause="An `INSTALL.md` prerequisites region is missing, or names a platform no tool "
|
||||||
|
"has",
|
||||||
|
reaction="Add the marker pair where that list belongs (or remove the orphaned region "
|
||||||
|
"and its introducing prose), then run `docs prerequisites --apply`",
|
||||||
|
),
|
||||||
|
cli_contract.Failure(
|
||||||
|
cause="A setup question is marked in one of `instructions/setup-instance.md` and "
|
||||||
|
"`INSTALL.md` but not the other",
|
||||||
|
reaction="Describe the question for the human in `INSTALL.md` with the same marker, "
|
||||||
|
"or remove the bullet for a question no longer asked",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
examples=(
|
examples=(
|
||||||
"tools/wikitool docs verify",
|
"tools/wikitool docs verify",
|
||||||
@@ -1118,6 +1139,7 @@ def check_breaking_change_for_boundary() -> list[str]:
|
|||||||
see_also=(
|
see_also=(
|
||||||
"`wikitool docs toc` - regenerates tables of contents",
|
"`wikitool docs toc` - regenerates tables of contents",
|
||||||
"`wikitool docs contract` - regenerates the commands region",
|
"`wikitool docs contract` - regenerates the commands region",
|
||||||
|
"`wikitool docs prerequisites` - regenerates `INSTALL.md`'s prerequisites lists",
|
||||||
"`wikitool instructions verify` - the same kind of check for `instructions/`",
|
"`wikitool instructions verify` - the same kind of check for `instructions/`",
|
||||||
),
|
),
|
||||||
))
|
))
|
||||||
@@ -1139,6 +1161,8 @@ def verify():
|
|||||||
+ check_no_issue_references()
|
+ check_no_issue_references()
|
||||||
+ check_toc_regions()
|
+ check_toc_regions()
|
||||||
+ check_reference_targets()
|
+ check_reference_targets()
|
||||||
|
+ install_doc.check_prerequisite_regions()
|
||||||
|
+ install_doc.check_setup_questions()
|
||||||
)
|
)
|
||||||
|
|
||||||
if issues:
|
if issues:
|
||||||
@@ -1155,6 +1179,8 @@ def verify():
|
|||||||
f"no issue references in {len(shipped_prose())} shipped document(s) or command help, "
|
f"no issue references in {len(shipped_prose())} shipped document(s) or command help, "
|
||||||
f"tables of contents current and every link resolving on "
|
f"tables of contents current and every link resolving on "
|
||||||
f"{len(toc.target_files())} reference file(s), "
|
f"{len(toc.target_files())} reference file(s), "
|
||||||
|
f"{install_doc.INSTALL_DOC} in step with tools/prerequisites.txt and "
|
||||||
|
f"{install_doc.SETUP_INSTRUCTION}'s questions, "
|
||||||
f"{version_mod.CHANGES_FILENAME} documents version "
|
f"{version_mod.CHANGES_FILENAME} documents version "
|
||||||
f"{(config.ROOT / version_mod.VERSION_FILENAME).read_text(encoding='utf-8').strip()}."
|
f"{(config.ROOT / version_mod.VERSION_FILENAME).read_text(encoding='utf-8').strip()}."
|
||||||
)
|
)
|
||||||
@@ -1235,6 +1261,79 @@ def toc_command(
|
|||||||
typer.echo(f"\n{len(changed)} file(s) would change. Re-run with --apply to write.")
|
typer.echo(f"\n{len(changed)} file(s) would change. Re-run with --apply to write.")
|
||||||
|
|
||||||
|
|
||||||
|
@app.command("prerequisites")
|
||||||
|
@cli_contract.record(cli_contract.CommandRecord(
|
||||||
|
path="docs prerequisites",
|
||||||
|
summary="Regenerate `INSTALL.md`'s prerequisites lists from `tools/prerequisites.txt`.",
|
||||||
|
synopsis=(cli_contract.Variant(usage="docs prerequisites [--apply]"),),
|
||||||
|
properties=cli_contract.Properties(
|
||||||
|
effect=cli_contract.Effect.WRITE,
|
||||||
|
idempotent=cli_contract.Idempotent.YES,
|
||||||
|
atomic="Yes - every region is rewritten in one file write",
|
||||||
|
budget=cli_contract.Budget.COUNTED,
|
||||||
|
),
|
||||||
|
notes=(
|
||||||
|
"Rewrites each `<!-- wikitool:prerequisites -->` region in `INSTALL.md` (tools every "
|
||||||
|
"platform needs) and `<!-- wikitool:prerequisites-<platform> -->` region (tools only that "
|
||||||
|
"platform needs) from the manifest: one list item per tool, its label and minimum "
|
||||||
|
"version. The manifest's reason field stays out - it is English prose, and the region "
|
||||||
|
"sits in a document that need not be.",
|
||||||
|
"Never places a region: where a list belongs in the human guide is that guide's own "
|
||||||
|
"decision. A region the manifest calls for but the file lacks is an error naming the "
|
||||||
|
"marker pair to add.",
|
||||||
|
"Dry-run by default (says whether the file would change); `--apply` writes.",
|
||||||
|
"`docs verify` checks the result stays current.",
|
||||||
|
),
|
||||||
|
failures=(cli_contract.Failure(
|
||||||
|
cause="`INSTALL.md` is missing, or lacks a region the manifest calls for",
|
||||||
|
reaction="Not transient - add the marker pair the message names where that list "
|
||||||
|
"belongs (restore the file if it is gone), then retry",
|
||||||
|
),),
|
||||||
|
examples=(
|
||||||
|
"tools/wikitool docs prerequisites",
|
||||||
|
"tools/wikitool docs prerequisites --apply",
|
||||||
|
),
|
||||||
|
never=(
|
||||||
|
"Never hand-edit a prerequisites region - change `tools/prerequisites.txt` and re-run "
|
||||||
|
"this.",
|
||||||
|
),
|
||||||
|
see_also=(
|
||||||
|
"`wikitool docs verify` - checks every region is current",
|
||||||
|
),
|
||||||
|
))
|
||||||
|
def prerequisites_command(
|
||||||
|
apply: bool = typer.Option(False, "--apply", help="Write changes; default is dry-run (preview only)"),
|
||||||
|
):
|
||||||
|
"""Regenerate `INSTALL.md`'s prerequisites lists from `tools/prerequisites.txt`."""
|
||||||
|
from chemenu import prerequisites
|
||||||
|
|
||||||
|
path = install_doc.install_doc_path()
|
||||||
|
if not path.is_file():
|
||||||
|
fail(f"{install_doc.INSTALL_DOC} is missing.")
|
||||||
|
|
||||||
|
text = path.read_text(encoding="utf-8")
|
||||||
|
after, missing = install_doc.refresh(text, prerequisites.load_manifest())
|
||||||
|
if missing:
|
||||||
|
fail(
|
||||||
|
f"{install_doc.INSTALL_DOC} lacks "
|
||||||
|
+ ", ".join(f"`{blocks.open_marker(name)}`" for name in missing)
|
||||||
|
+ " - add each marker pair (with its closing marker) where that list belongs, "
|
||||||
|
"then re-run."
|
||||||
|
)
|
||||||
|
|
||||||
|
if after == text:
|
||||||
|
success(f"{install_doc.INSTALL_DOC}'s prerequisites lists are already current.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not apply:
|
||||||
|
typer.echo(f"{install_doc.INSTALL_DOC} would change.")
|
||||||
|
typer.echo("Re-run with --apply to write.")
|
||||||
|
return
|
||||||
|
|
||||||
|
path.write_text(after, encoding="utf-8", newline="\n")
|
||||||
|
success(f"Regenerated the prerequisites lists in {install_doc.INSTALL_DOC}.")
|
||||||
|
|
||||||
|
|
||||||
@app.command("contract")
|
@app.command("contract")
|
||||||
@cli_contract.record(cli_contract.CommandRecord(
|
@cli_contract.record(cli_contract.CommandRecord(
|
||||||
path="docs contract",
|
path="docs contract",
|
||||||
|
|||||||
@@ -0,0 +1,178 @@
|
|||||||
|
"""The two places `INSTALL.md` overlaps the installation instructions, held to them.
|
||||||
|
|
||||||
|
`INSTALL.md` is written for a human and in the instance's KB language;
|
||||||
|
`instructions/setup-instance.md` is written for an agent and in English. A
|
||||||
|
single file serving both was rejected: an agent reads every sentence as an
|
||||||
|
instruction, and the human needs preparation and decisions where the agent
|
||||||
|
needs steps and exit codes. So the human document does not retell the
|
||||||
|
procedure, and what it still shares with the instructions is two enumerable
|
||||||
|
lists - both checked here, so neither can drift silently:
|
||||||
|
|
||||||
|
1. **What the machine needs.** `tools/prerequisites.txt` is the one list; the
|
||||||
|
preflight and `doctor` read it. `INSTALL.md` carries it as a generated
|
||||||
|
region per platform value (`<!-- wikitool:prerequisites -->` for `all`,
|
||||||
|
`<!-- wikitool:prerequisites-<platform> -->` otherwise), rendered from the
|
||||||
|
label and minimum version only. The manifest's `why` field stays out: it is
|
||||||
|
English prose, and the region sits in a document that is not. A region is
|
||||||
|
never placed by the tool - where it goes is the human document's own
|
||||||
|
decision - so a missing one is reported, not appended.
|
||||||
|
2. **What the agent asks the user.** Every place `setup-instance.md` puts a
|
||||||
|
question to the user carries `<!-- setup-question: <key> -->`, and the
|
||||||
|
bullet in `INSTALL.md` that tells the human about it carries the same
|
||||||
|
marker. The two key sets must be equal: a question the human guide does not
|
||||||
|
mention catches them unprepared, and one it mentions that is no longer asked
|
||||||
|
is a claim about a procedure that no longer exists. The key is the shared
|
||||||
|
identifier precisely because the surrounding prose is in two languages.
|
||||||
|
|
||||||
|
Everything else in `INSTALL.md` is prose no check reads;
|
||||||
|
`instructions/dev/doc-pull-through.md` names it as session work.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from chemenu import blocks, config, prerequisites
|
||||||
|
|
||||||
|
INSTALL_DOC = "INSTALL.md"
|
||||||
|
SETUP_INSTRUCTION = "instructions/setup-instance.md"
|
||||||
|
|
||||||
|
REGION_PREFIX = "prerequisites"
|
||||||
|
|
||||||
|
QUESTION_RE = re.compile(r"<!-- setup-question: ([a-z][a-z0-9-]*) -->")
|
||||||
|
|
||||||
|
|
||||||
|
def install_doc_path() -> Path:
|
||||||
|
return config.ROOT / INSTALL_DOC
|
||||||
|
|
||||||
|
|
||||||
|
def setup_instruction_path() -> Path:
|
||||||
|
return config.ROOT / SETUP_INSTRUCTION
|
||||||
|
|
||||||
|
|
||||||
|
def region_name(platform: str) -> str:
|
||||||
|
"""`prerequisites` for the tools every platform needs, `prerequisites-<platform>`
|
||||||
|
for the ones only that platform does."""
|
||||||
|
return REGION_PREFIX if platform == "all" else f"{REGION_PREFIX}-{platform}"
|
||||||
|
|
||||||
|
|
||||||
|
def render_lines(tools: tuple[prerequisites.Tool, ...]) -> list[str]:
|
||||||
|
"""One list item per tool - label and minimum version, nothing in a language."""
|
||||||
|
return [
|
||||||
|
f"- **{tool.label}**" + (f" ≥ {tool.minimum}" if tool.minimum else "")
|
||||||
|
for tool in tools
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def expected_regions(manifest: prerequisites.Manifest) -> dict[str, str]:
|
||||||
|
"""Region name -> the whole region, markers included, in manifest order of
|
||||||
|
first appearance of each platform value."""
|
||||||
|
platforms: list[str] = []
|
||||||
|
for tool in manifest.tools:
|
||||||
|
if tool.platforms not in platforms:
|
||||||
|
platforms.append(tool.platforms)
|
||||||
|
regions = {}
|
||||||
|
for platform in platforms:
|
||||||
|
name = region_name(platform)
|
||||||
|
tools = tuple(t for t in manifest.tools if t.platforms == platform)
|
||||||
|
regions[name] = "\n".join(
|
||||||
|
[blocks.open_marker(name), *render_lines(tools), blocks.close_marker(name)]
|
||||||
|
)
|
||||||
|
return regions
|
||||||
|
|
||||||
|
|
||||||
|
def _region_span(text: str, name: str) -> tuple[int, int] | None:
|
||||||
|
"""Start and end offset of `name`'s region, markers included - without the
|
||||||
|
blank lines `blocks` takes along, so a refresh leaves the prose around it
|
||||||
|
exactly as it was."""
|
||||||
|
start = text.find(blocks.open_marker(name))
|
||||||
|
if start < 0:
|
||||||
|
return None
|
||||||
|
close = blocks.close_marker(name)
|
||||||
|
end = text.find(close, start)
|
||||||
|
if end < 0:
|
||||||
|
return None
|
||||||
|
return start, end + len(close)
|
||||||
|
|
||||||
|
|
||||||
|
def refresh(text: str, manifest: prerequisites.Manifest) -> tuple[str, list[str]]:
|
||||||
|
"""`text` with every prerequisites region it carries rewritten from the
|
||||||
|
manifest, plus the names of the regions it lacks.
|
||||||
|
|
||||||
|
A region the manifest no longer has a platform for is left alone and
|
||||||
|
reported by `check_prerequisite_regions`, not deleted here: the prose
|
||||||
|
introducing it ("Unter Windows zusätzlich:") would otherwise be left
|
||||||
|
standing over nothing.
|
||||||
|
"""
|
||||||
|
missing = []
|
||||||
|
for name, region in expected_regions(manifest).items():
|
||||||
|
span = _region_span(text, name)
|
||||||
|
if span is None:
|
||||||
|
missing.append(name)
|
||||||
|
continue
|
||||||
|
text = text[: span[0]] + region + text[span[1]:]
|
||||||
|
return text, missing
|
||||||
|
|
||||||
|
|
||||||
|
def _present_region_names(text: str) -> set[str]:
|
||||||
|
pattern = re.compile(
|
||||||
|
rf"<!-- wikitool:({re.escape(REGION_PREFIX)}(?:-[a-z0-9-]+)?) -->"
|
||||||
|
)
|
||||||
|
return set(pattern.findall(text))
|
||||||
|
|
||||||
|
|
||||||
|
def check_prerequisite_regions() -> list[str]:
|
||||||
|
"""`INSTALL.md` carries one current region per platform value of the manifest."""
|
||||||
|
path = install_doc_path()
|
||||||
|
if not path.is_file():
|
||||||
|
return [f"{INSTALL_DOC} is missing - the human installation guide is a stack file"]
|
||||||
|
text = path.read_text(encoding="utf-8")
|
||||||
|
manifest = prerequisites.load_manifest()
|
||||||
|
refreshed, missing = refresh(text, manifest)
|
||||||
|
|
||||||
|
issues = [
|
||||||
|
f"{INSTALL_DOC} has no `{blocks.open_marker(name)}` region - add the marker pair "
|
||||||
|
f"where that list belongs, then run `wikitool docs prerequisites --apply`"
|
||||||
|
for name in missing
|
||||||
|
]
|
||||||
|
issues += [
|
||||||
|
f"{INSTALL_DOC} carries `{blocks.open_marker(name)}`, but no tool in "
|
||||||
|
"tools/prerequisites.txt has that platform - remove the region and the prose "
|
||||||
|
"introducing it"
|
||||||
|
for name in sorted(_present_region_names(text) - set(expected_regions(manifest)))
|
||||||
|
]
|
||||||
|
if refreshed != text:
|
||||||
|
issues.append(
|
||||||
|
f"{INSTALL_DOC}'s prerequisites list no longer matches tools/prerequisites.txt - "
|
||||||
|
"run `wikitool docs prerequisites --apply`"
|
||||||
|
)
|
||||||
|
return issues
|
||||||
|
|
||||||
|
|
||||||
|
def question_keys(text: str) -> set[str]:
|
||||||
|
return set(QUESTION_RE.findall(text))
|
||||||
|
|
||||||
|
|
||||||
|
def check_setup_questions() -> list[str]:
|
||||||
|
"""Every question `setup-instance.md` asks is named in `INSTALL.md`, and
|
||||||
|
`INSTALL.md` names no question that is no longer asked."""
|
||||||
|
setup = setup_instruction_path()
|
||||||
|
install = install_doc_path()
|
||||||
|
if not setup.is_file() or not install.is_file():
|
||||||
|
# A missing INSTALL.md is check_prerequisite_regions' finding; a
|
||||||
|
# missing setup instruction leaves nothing to compare against.
|
||||||
|
return []
|
||||||
|
asked = question_keys(setup.read_text(encoding="utf-8"))
|
||||||
|
named = question_keys(install.read_text(encoding="utf-8"))
|
||||||
|
|
||||||
|
issues = [
|
||||||
|
f"{SETUP_INSTRUCTION} asks the user `{key}`, but {INSTALL_DOC} § \"Was der Agent dich "
|
||||||
|
f"fragt\" does not name it - add a bullet carrying `<!-- setup-question: {key} -->`"
|
||||||
|
for key in sorted(asked - named)
|
||||||
|
]
|
||||||
|
issues += [
|
||||||
|
f"{INSTALL_DOC} names the setup question `{key}`, which {SETUP_INSTRUCTION} no longer "
|
||||||
|
"asks - remove the bullet, or restore the marker where the question is asked"
|
||||||
|
for key in sorted(named - asked)
|
||||||
|
]
|
||||||
|
return issues
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
from typer.testing import CliRunner
|
||||||
|
|
||||||
|
from chemenu import config, install_doc, prerequisites
|
||||||
|
from chemenu.cli import app
|
||||||
|
|
||||||
|
runner = CliRunner()
|
||||||
|
|
||||||
|
MANIFEST = prerequisites.Manifest(
|
||||||
|
limits={},
|
||||||
|
tools=(
|
||||||
|
prerequisites.Tool("python", "3.11", "all", "Python", "why"),
|
||||||
|
prerequisites.Tool("git", None, "all", "Git", "why"),
|
||||||
|
prerequisites.Tool("pwsh", "7", "windows", "PowerShell 7 (pwsh)", "why"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
CURRENT = """# Installation
|
||||||
|
|
||||||
|
Vorher:
|
||||||
|
|
||||||
|
<!-- wikitool:prerequisites -->
|
||||||
|
- **Python** ≥ 3.11
|
||||||
|
- **Git**
|
||||||
|
<!-- /wikitool:prerequisites -->
|
||||||
|
|
||||||
|
Unter Windows zusätzlich:
|
||||||
|
|
||||||
|
<!-- wikitool:prerequisites-windows -->
|
||||||
|
- **PowerShell 7 (pwsh)** ≥ 7
|
||||||
|
<!-- /wikitool:prerequisites-windows -->
|
||||||
|
|
||||||
|
## Was der Agent dich fragt
|
||||||
|
|
||||||
|
- <!-- setup-question: identity --> **Autor-Identität** - Name und E-Mail.
|
||||||
|
"""
|
||||||
|
|
||||||
|
SETUP = """# Set up
|
||||||
|
|
||||||
|
2. <!-- setup-question: identity --> **Decision point - identity.** Ask the user.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def _tree(tmp_path, monkeypatch, install=CURRENT, setup=SETUP, manifest=MANIFEST):
|
||||||
|
monkeypatch.setattr(config, "ROOT", tmp_path)
|
||||||
|
monkeypatch.setattr(prerequisites, "load_manifest", lambda path=None: manifest)
|
||||||
|
(tmp_path / "instructions").mkdir()
|
||||||
|
(tmp_path / "INSTALL.md").write_text(install, encoding="utf-8")
|
||||||
|
(tmp_path / "instructions" / "setup-instance.md").write_text(setup, encoding="utf-8")
|
||||||
|
return tmp_path
|
||||||
|
|
||||||
|
|
||||||
|
def test_this_repos_install_doc_matches_the_manifest():
|
||||||
|
assert install_doc.check_prerequisite_regions() == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_this_repos_install_doc_names_every_setup_question():
|
||||||
|
assert install_doc.check_setup_questions() == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_this_repos_setup_instruction_marks_its_questions():
|
||||||
|
"""The check compares two sets; an instruction that lost every marker would
|
||||||
|
compare empty against empty and pass. Guard the real file's own count."""
|
||||||
|
text = install_doc.setup_instruction_path().read_text(encoding="utf-8")
|
||||||
|
assert install_doc.question_keys(text) >= {
|
||||||
|
"identity", "remote", "kb-language", "domain",
|
||||||
|
"personalization", "environment", "telemetry", "task-tracker",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_render_lines_are_label_and_minimum_only():
|
||||||
|
assert install_doc.render_lines(MANIFEST.tools) == [
|
||||||
|
"- **Python** ≥ 3.11",
|
||||||
|
"- **Git**",
|
||||||
|
"- **PowerShell 7 (pwsh)** ≥ 7",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_current_install_doc_passes(tmp_path, monkeypatch):
|
||||||
|
_tree(tmp_path, monkeypatch)
|
||||||
|
assert install_doc.check_prerequisite_regions() == []
|
||||||
|
assert install_doc.check_setup_questions() == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_manifest_tool_missing_from_install_doc_fails(tmp_path, monkeypatch):
|
||||||
|
"""Acceptance criterion: a tool in the manifest that INSTALL.md lacks fails `docs verify`."""
|
||||||
|
_tree(tmp_path, monkeypatch, install=CURRENT.replace("- **Git**\n", ""))
|
||||||
|
issues = install_doc.check_prerequisite_regions()
|
||||||
|
assert any("no longer matches tools/prerequisites.txt" in issue for issue in issues)
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_new_platform_without_a_region_fails(tmp_path, monkeypatch):
|
||||||
|
manifest = prerequisites.Manifest(
|
||||||
|
limits={},
|
||||||
|
tools=MANIFEST.tools + (prerequisites.Tool("brew", None, "macos", "Homebrew", "why"),),
|
||||||
|
)
|
||||||
|
_tree(tmp_path, monkeypatch, manifest=manifest)
|
||||||
|
issues = install_doc.check_prerequisite_regions()
|
||||||
|
assert any("<!-- wikitool:prerequisites-macos -->" in issue for issue in issues)
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_region_for_a_platform_no_tool_has_fails(tmp_path, monkeypatch):
|
||||||
|
manifest = prerequisites.Manifest(limits={}, tools=MANIFEST.tools[:2])
|
||||||
|
_tree(tmp_path, monkeypatch, manifest=manifest)
|
||||||
|
issues = install_doc.check_prerequisite_regions()
|
||||||
|
assert any("prerequisites-windows" in issue and "no tool" in issue for issue in issues)
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_missing_install_doc_is_reported(tmp_path, monkeypatch):
|
||||||
|
_tree(tmp_path, monkeypatch)
|
||||||
|
(tmp_path / "INSTALL.md").unlink()
|
||||||
|
assert any("missing" in issue for issue in install_doc.check_prerequisite_regions())
|
||||||
|
assert install_doc.check_setup_questions() == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_refresh_touches_nothing_outside_the_regions(tmp_path):
|
||||||
|
stale = CURRENT.replace("≥ 3.11", "≥ 3.9")
|
||||||
|
refreshed, missing = install_doc.refresh(stale, MANIFEST)
|
||||||
|
assert missing == []
|
||||||
|
assert refreshed == CURRENT
|
||||||
|
|
||||||
|
|
||||||
|
def test_refresh_is_idempotent():
|
||||||
|
once, _ = install_doc.refresh(CURRENT, MANIFEST)
|
||||||
|
twice, _ = install_doc.refresh(once, MANIFEST)
|
||||||
|
assert once == twice == CURRENT
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_question_the_install_doc_does_not_name_fails(tmp_path, monkeypatch):
|
||||||
|
"""Acceptance criterion: a new question in setup-instance.md that INSTALL.md
|
||||||
|
does not name fails `docs verify`."""
|
||||||
|
setup = SETUP + "\n3. <!-- setup-question: remote --> **Decision point - remote.**\n"
|
||||||
|
_tree(tmp_path, monkeypatch, setup=setup)
|
||||||
|
issues = install_doc.check_setup_questions()
|
||||||
|
assert len(issues) == 1
|
||||||
|
assert "`remote`" in issues[0] and "does not name it" in issues[0]
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_question_no_longer_asked_fails(tmp_path, monkeypatch):
|
||||||
|
install = CURRENT + "- <!-- setup-question: telemetry --> **Telemetrie**\n"
|
||||||
|
_tree(tmp_path, monkeypatch, install=install)
|
||||||
|
issues = install_doc.check_setup_questions()
|
||||||
|
assert len(issues) == 1
|
||||||
|
assert "`telemetry`" in issues[0] and "no longer asks" in issues[0]
|
||||||
|
|
||||||
|
|
||||||
|
def test_docs_prerequisites_is_a_dry_run_by_default(tmp_path, monkeypatch):
|
||||||
|
stale = CURRENT.replace("≥ 3.11", "≥ 3.9")
|
||||||
|
root = _tree(tmp_path, monkeypatch, install=stale)
|
||||||
|
result = runner.invoke(app, ["docs", "prerequisites"])
|
||||||
|
assert result.exit_code == 0, result.output
|
||||||
|
assert "would change" in result.output
|
||||||
|
assert (root / "INSTALL.md").read_text(encoding="utf-8") == stale
|
||||||
|
|
||||||
|
|
||||||
|
def test_docs_prerequisites_apply_rewrites_the_regions(tmp_path, monkeypatch):
|
||||||
|
root = _tree(tmp_path, monkeypatch, install=CURRENT.replace("≥ 3.11", "≥ 3.9"))
|
||||||
|
result = runner.invoke(app, ["docs", "prerequisites", "--apply"])
|
||||||
|
assert result.exit_code == 0, result.output
|
||||||
|
assert (root / "INSTALL.md").read_text(encoding="utf-8") == CURRENT
|
||||||
|
assert install_doc.check_prerequisite_regions() == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_docs_prerequisites_never_places_a_missing_region(tmp_path, monkeypatch):
|
||||||
|
without_windows = CURRENT.replace(
|
||||||
|
"<!-- wikitool:prerequisites-windows -->\n- **PowerShell 7 (pwsh)** ≥ 7\n"
|
||||||
|
"<!-- /wikitool:prerequisites-windows -->\n",
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
root = _tree(tmp_path, monkeypatch, install=without_windows)
|
||||||
|
result = runner.invoke(app, ["docs", "prerequisites", "--apply"])
|
||||||
|
assert result.exit_code == 1
|
||||||
|
assert "prerequisites-windows" in result.output
|
||||||
|
assert (root / "INSTALL.md").read_text(encoding="utf-8") == without_windows
|
||||||
Reference in new issue
Block a user