tools: command records, Distribution and versioning group - one line per cause, examples, prohibitions (#142)
Files changed: - CHANGES.md - VERSION - tools/CONTRACT.md - tools/chemenu/commands/dist_cmd.py - tools/chemenu/commands/version_cmd.py
This commit is contained in:
1 parent
df8ff2fa22
commit
9d6ca6b193
5 files changed
+709
-264
No files matched your search
+19
-1
@@ -59,7 +59,7 @@ concern - readable here, never shipped as something to parse.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 7.1.0-beta.8 - 2026-09-26 - Command records, Catalog and log group: bullets, examples, prohibitions
|
## 7.1.0-beta.9 - 2026-09-26 - Command records, Distribution and versioning group: one line per cause, examples, prohibitions
|
||||||
|
|
||||||
**Author:** Torben Nehmer
|
**Author:** Torben Nehmer
|
||||||
|
|
||||||
@@ -77,6 +77,7 @@ concern - readable here, never shipped as something to parse.
|
|||||||
- wikitool: usage lines name wikitool, and the -h acceptance checks become tests
|
- wikitool: usage lines name wikitool, and the -h acceptance checks become tests
|
||||||
- Command records, Git group: NOTES as bullets, one exit line per cause, examples and prohibitions
|
- Command records, Git group: NOTES as bullets, one exit line per cause, examples and prohibitions
|
||||||
- Command records, Catalog and log group: bullets, examples, prohibitions
|
- Command records, Catalog and log group: bullets, examples, prohibitions
|
||||||
|
- Command records, Distribution and versioning group: one line per cause, examples, prohibitions
|
||||||
<!-- /wikitool:bumps -->
|
<!-- /wikitool:bumps -->
|
||||||
|
|
||||||
### CalDAV task-tracker provider (Nextcloud Tasks, iOS Reminders); review reports unknown-value findings instead of skipping them
|
### CalDAV task-tracker provider (Nextcloud Tasks, iOS Reminders); review reports unknown-value findings instead of skipping them
|
||||||
@@ -206,6 +207,23 @@ prose: check the tail of `kb/log.md` before re-running after an uncertain outcom
|
|||||||
the command already did. `log status` no longer claims it "reports 0" for a missing log - it
|
the command already did. `log status` no longer claims it "reports 0" for a missing log - it
|
||||||
reports that nothing is logged yet, which is what it always printed.
|
reports that nothing is logged yet, which is what it always printed.
|
||||||
|
|
||||||
|
### Command records, Distribution and versioning group: one line per cause, examples, prohibitions
|
||||||
|
|
||||||
|
`dist export`, `dist upgrade` and the six `version` commands rewritten the same way; text only.
|
||||||
|
The long single-paragraph records - `dist upgrade` and `version bump` above all - are now one
|
||||||
|
bullet per behaviour, and each exit-1 cause carries its own reaction, taken from what the
|
||||||
|
command's own error message already tells the caller (`migrate baseline`, `migrate status`,
|
||||||
|
commit or stash, `upstream merge`). Three corrections to what the records claimed: `dist upgrade`
|
||||||
|
listed an equal source version both as a no-op success and as an exit-1 refusal - it is a
|
||||||
|
success, and EXIT STATUS now says so; `dist export` and `dist upgrade` gained the exit-1 causes
|
||||||
|
their code already had but their records omitted (a missing licence, an export plan leaking
|
||||||
|
instance data, a bad archive checksum); and `version regrade`'s NOTES, garbled when they were
|
||||||
|
moved over mechanically, read as sentences again. Two code comments changed with them: the one
|
||||||
|
above `dist export`'s `raw/`/`incoming/` anchors had claimed `.gitignore` drops `incoming/`,
|
||||||
|
which stopped being true when `!/incoming/.gitkeep` was added, and `dist upgrade`'s stamp write
|
||||||
|
now carries the reasoning for writing the stamp whole after `--keep-local`, which used to live
|
||||||
|
in the record.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 7.0.0 - 2026-09-22 - Task-Tracker-Anbindung: Vorhaben als Seitenart, Verpflichtungsschicht, Weekly Review als Read-Time-Join
|
## 7.0.0 - 2026-09-22 - Task-Tracker-Anbindung: Vorhaben als Seitenart, Verpflichtungsschicht, Weekly Review als Read-Time-Join
|
||||||
|
|||||||
+238
-23
@@ -1569,18 +1569,46 @@ Write a contentless, distributable copy of this repo's machinery.
|
|||||||
- budget: counted
|
- budget: counted
|
||||||
- network: no
|
- network: no
|
||||||
|
|
||||||
|
**EXAMPLES**
|
||||||
|
|
||||||
|
- `tools/wikitool dist export ../my-wiki --dry-run`
|
||||||
|
- `tools/wikitool dist export ../my-wiki`
|
||||||
|
|
||||||
**EXIT STATUS**
|
**EXIT STATUS**
|
||||||
|
|
||||||
- 0 success
|
- 0 success
|
||||||
- 1 Target exists and is not empty, is not a directory, or the tree has no readable `VERSION`
|
- 1 `<target>` exists and is not empty, or is not a directory
|
||||||
|
- 1 The tree has no readable `VERSION`
|
||||||
|
- 1 A licence file is missing from the source tree
|
||||||
|
- 1 The plan would carry this instance's own data (a filled personalization, conventions or page type-spec file)
|
||||||
|
|
||||||
**ON FAILURE**
|
**ON FAILURE**
|
||||||
|
|
||||||
- Target exists and is not empty, is not a directory, or the tree has no readable `VERSION` -> Point `<target>` at an empty (or new) directory and retry. Never merge into a non-empty one by hand
|
- `<target>` exists and is not empty, or is not a directory -> Point `<target>` at an empty (or new) directory and retry
|
||||||
|
- The tree has no readable `VERSION` -> Fix `VERSION`, then retry
|
||||||
|
- A licence file is missing from the source tree -> Restore it, then retry
|
||||||
|
- The plan would carry this instance's own data (a filled personalization, conventions or page type-spec file) -> Report it: it is an allowlist bug in `dist export`, not something to work around by deleting files from the target
|
||||||
|
|
||||||
|
**NEVER**
|
||||||
|
|
||||||
|
- Never merge an export into a non-empty directory by hand.
|
||||||
|
|
||||||
**NOTES**
|
**NOTES**
|
||||||
|
|
||||||
Write a contentless, distributable copy of this repo's machinery into an empty `<target>` directory: `AGENTS.md`/`README.md`/`EVALS.md` with any `dist:strip-start`...`dist:strip-end` marker region removed, `instructions/` (minus `instructions/dev/`), `types/` (the `root: kb` page type-specs and their schemas re-keyed as `.template`, the stack's own verbatim), `docs/` verbatim, `tools/` (no venv/caches), the `.github/hooks/`+`.vibe/` session-tracing config plus `.claude/settings.json`, `kb/CONTRACT.md` (no pages, no areas), the two flat anchors `raw/.gitkeep` and `incoming/.gitkeep` (both roots are flat now that a file's location under `raw/` is a date shard rather than a hand-picked type, so a fresh export no longer creates any type subdirectories under either root; `incoming/.gitkeep` is trackable and survives becoming a git repository, so a plain clone gets the directory without any bootstrap step re-creating it), `VERSION`, `USER.md.template`/`SOUL.md.template` plus `kb/CONVENTIONS.md.template` and each collection's contract re-keyed as `kb/<name>/COLLECTION.md.template` (the templates ship; the filled `USER.md`/`SOUL.md`/`kb/CONVENTIONS.md`/`kb/<name>/COLLECTION.md`/`types/<page-type>.md` never do - all of them bind their instance and none are the stack's to decide, and `find_leaks` refuses a plan carrying one), and a generated `.wikitool-release.json` stamp (version, export date, origin, and a sha256 per exported file - the base a later upgrade would compare against). The four origin options only fill stamp fields: `export` never calls git and cannot discover them. Refuses a non-empty target, and a tree with no `VERSION`. See `instructions/setup-instance.md`. One-way: there is no command that reconstructs a distributed instance into a dev instance - work on the stack in the origin repo (or a new dev instance exported from it) instead
|
- Writes a contentless, distributable copy of this repo's machinery into an empty or new `<target>` directory.
|
||||||
|
- Ships `AGENTS.md`/`README.md`/`EVALS.md` with any `dist:strip-start`...`dist:strip-end` marker region removed, `instructions/` (minus `instructions/dev/`), `types/` (the `root: kb` page type-specs and their schemas re-keyed as `.template`, the stack's own verbatim), `docs/` verbatim, `tools/` (no venv/caches), the `.github/hooks/`+`.vibe/` session-tracing config plus `.claude/settings.json`, `kb/CONTRACT.md` (no pages, no areas) and `VERSION`.
|
||||||
|
- Ships `raw/` and `incoming/` as flat roots, each with a `.gitkeep` and no subdirectories. `incoming/.gitkeep` is trackable and survives becoming a git repository, so a plain clone gets the directory without any bootstrap step.
|
||||||
|
- Ships templates, never the filled files: `USER.md.template`/`SOUL.md.template`, `kb/CONVENTIONS.md.template`, and each collection's contract re-keyed as `kb/<name>/COLLECTION.md.template`. The filled `USER.md`/`SOUL.md`/`kb/CONVENTIONS.md`/`kb/<name>/COLLECTION.md`/`types/<page-type>.md` bind their instance; `find_leaks` refuses a plan carrying one.
|
||||||
|
- Writes a generated `.wikitool-release.json` stamp: version, export date, origin, and a sha256 per exported file - the base a later upgrade compares against.
|
||||||
|
- The four origin options only fill stamp fields: `export` never calls git and cannot discover them.
|
||||||
|
- One-way: no command reconstructs a distributed instance into a dev instance - work on the stack in the origin repo, or in a new dev instance exported from it.
|
||||||
|
- `--dry-run` lists every file it would write, and writes nothing.
|
||||||
|
|
||||||
|
**SEE ALSO**
|
||||||
|
|
||||||
|
- `instructions/setup-instance.md` - what comes after the export
|
||||||
|
- `wikitool dist upgrade` - applies a later export to an existing instance
|
||||||
|
- `wikitool version show` - reads the stamp this writes
|
||||||
|
|
||||||
#### `dist upgrade`
|
#### `dist upgrade`
|
||||||
|
|
||||||
@@ -1594,22 +1622,71 @@ Apply a stack update `dist export` produced - the write half of `version check`.
|
|||||||
|
|
||||||
- effect: write
|
- effect: write
|
||||||
- idempotent: no
|
- idempotent: no
|
||||||
- atomic: **Yes for the refusal cases above - nothing is written.** Once writing starts it is a plain sequential file copy with no partial-state cleanup: an interruption mid-copy (killed process, disk full) can leave the tree part-old, part-new
|
- atomic: Yes for every refusal - nothing is written. Once writing starts it is a plain sequential file copy with no partial-state cleanup: an interruption mid-copy (killed process, disk full) can leave the tree part-old, part-new
|
||||||
- budget: counted
|
- budget: counted
|
||||||
- network: no
|
- network: no
|
||||||
|
|
||||||
|
**EXAMPLES**
|
||||||
|
|
||||||
|
- `tools/wikitool dist upgrade ../chemenu-7.1.0.tar.gz --dry-run`
|
||||||
|
- `tools/wikitool dist upgrade ../chemenu-7.1.0.tar.gz`
|
||||||
|
- `tools/wikitool dist upgrade ../chemenu-7.1.0.tar.gz --take-release tools/README.md`
|
||||||
|
|
||||||
**EXIT STATUS**
|
**EXIT STATUS**
|
||||||
|
|
||||||
- 0 success
|
- 0 success
|
||||||
- 1 Missing local `VERSION`/`.wikitool-release.json`(`files`)/`.wikitool-kb.json`, a migration already outstanding against the installed machinery, a dirty working tree, a source with no `VERSION`/stamp/`files` block, a source version that is older than, equal to, or (without `--pre`) a pre-release relative to the installed one, a `--take-release` path that is not classified as locally changed (the one refusal a `--dry-run` also raises), or one or more locally changed files that neither `--keep-local` nor a `--take-release` answers for
|
- 0 The source's version equals the installed one - a no-op success
|
||||||
|
- 1 Local `VERSION` missing, or no local `.wikitool-release.json` with a `files` block
|
||||||
|
- 1 `.wikitool-kb.json` is missing
|
||||||
|
- 1 A migration is already outstanding against the *installed* machinery
|
||||||
|
- 1 The working tree is dirty
|
||||||
|
- 1 `<source>` does not exist, fails its `.sha256`, or does not unpack to exactly one top-level directory
|
||||||
|
- 1 The source carries no `VERSION`, `.wikitool-release.json` or `files` block
|
||||||
|
- 1 The source's version is older than the installed one, or a pre-release without `--pre`
|
||||||
|
- 1 A `--take-release` path this run does not classify as locally changed - the one refusal a `--dry-run` also raises
|
||||||
|
- 1 Locally changed files that neither `--keep-local` nor a `--take-release` answers for; nothing was written
|
||||||
|
|
||||||
**ON FAILURE**
|
**ON FAILURE**
|
||||||
|
|
||||||
- Missing local `VERSION`/`.wikitool-release.json`(`files`)/`.wikitool-kb.json`, a migration already outstanding against the installed machinery, a dirty working tree, a source with no `VERSION`/stamp/`files` block, a source version that is older than, equal to, or (without `--pre`) a pre-release relative to the installed one, a `--take-release` path that is not classified as locally changed (the one refusal a `--dry-run` also raises), or one or more locally changed files that neither `--keep-local` nor a `--take-release` answers for -> For every refusal above: fix the named precondition and retry - none of them are transient. For a rejected `--take-release` path: correct it against the locally-changed list the refusal prints. For locally changed files, the refusal names all three answers with the re-run line filled in - `--take-release <path>` to write the release's version over it (which ends the divergence), `--keep-local` to leave them untouched (repeatable, and it reports the same files again on every subsequent run until they stop diverging), or reconcile by hand and retry. An interrupted write is not resumed automatically; compare the tree against the printed classification and finish or revert by hand
|
- Local `VERSION` missing, or no local `.wikitool-release.json` with a `files` block -> Not transient - fix the named precondition and retry. A checkout with shared git history takes stack updates with `wikitool upstream merge` instead
|
||||||
|
- `.wikitool-kb.json` is missing -> Run `wikitool migrate baseline <version>`, then retry
|
||||||
|
- A migration is already outstanding against the *installed* machinery -> Finish it first - `wikitool migrate status` names it - then retry
|
||||||
|
- The working tree is dirty -> Commit or stash first, then retry
|
||||||
|
- `<source>` does not exist, fails its `.sha256`, or does not unpack to exactly one top-level directory -> Fix the path or re-download the release archive, then retry
|
||||||
|
- The source carries no `VERSION`, `.wikitool-release.json` or `files` block -> Point `<source>` at a distribution export, then retry
|
||||||
|
- The source's version is older than the installed one, or a pre-release without `--pre` -> Not transient - choose another source, or pass `--pre` for a pre-release
|
||||||
|
- A `--take-release` path this run does not classify as locally changed - the one refusal a `--dry-run` also raises -> Correct it against the locally-changed list the refusal prints, then retry
|
||||||
|
- Locally changed files that neither `--keep-local` nor a `--take-release` answers for; nothing was written -> Choose one of the three answers the refusal names, re-run lines filled in: `--take-release <path>` to write the release's version over it (ends the divergence), `--keep-local` to leave them untouched (reported again on every later run until they stop diverging), or reconcile by hand and retry
|
||||||
|
|
||||||
|
**NEVER**
|
||||||
|
|
||||||
|
- Never treat any of the three answers to locally changed files as the default.
|
||||||
|
|
||||||
**NOTES**
|
**NOTES**
|
||||||
|
|
||||||
Never downloads anything: `<source>` is an already-fetched export directory or `.tar.gz` release archive (verified against a sibling `.sha256` if one is present; WARNs, does not block, if it is absent), which must unpack to exactly one top-level directory - the shape `.gitea/workflows/release.yml` packs. The write set is exactly the *new* `.wikitool-release.json`'s `files` block, minus what an export re-seeds from a blank template every time (`kb/log.md`, `raw/.gitkeep` - `chemenu.ownership.is_export_stub`) or seeds once and the instance owns from then on (`.wikitool-kb.json`, `CHANGES.md` - `chemenu.ownership.is_upgrade_preserved`), plus the stamp itself, always rewritten. Every candidate path is classified against the *local* `.wikitool-release.json`'s recorded digest for it: unchanged is overwritten silently, absent from the old stamp is created, and locally modified or locally deleted is **never** silently overwritten - the run aborts with the full list, and its text names the three answers with the command line already filled in, so that no reader takes any of them for the default. `--keep-local` proceeds and leaves every one of them untouched; `--take-release <path>` (repeatable) writes the release's version over the named path, discarding the local change, and re-creates it if it was locally deleted. The two are decided per path and compose on one call: without `--keep-local`, a locally changed path that no `--take-release` names still aborts the run. A `--take-release` path that this run does not report as locally changed is refused, in a `--dry-run` as well as a writing run - it is a mistake in the argument rather than a state of the tree, and a path that silently did nothing would report a successful upgrade while keeping the change it was asked to discard. After a `--keep-local` run the new stamp is still written whole, so it records the release's digest for files that were deliberately *not* written: the stamp is the baseline for the next comparison, not a literal inventory of what is on disk. That is what keeps a skipped file diverging - and therefore reported - on every later run, rather than quietly reading as current once it has been skipped once. A path taken with `--take-release` is the opposite case and the reason the flag exists: it was written, so it matches the digest the stamp records and stops being reported at all. A path in the old stamp but not the new one is reported as no longer part of the release and left alone unless `--prune` is passed, which removes it only if it is still unchanged since installation. Reports the migration chain the new machinery would owe (`chemenu.kb_state.chain` over the *new* tree's `instructions/migrations/`, read via a `directory` argument to `load_migrations`) but never runs any of it - there is no `migrate run`. Refuses before touching the source at all when: `VERSION` or `.wikitool-release.json` (with a `files` block) is missing locally, `.wikitool-kb.json` is missing, a migration is already outstanding against the *installed* machinery, or the working tree is dirty (not being a git repository at all is a WARN, not a refusal). Refuses after reading the source when: it carries no `VERSION`/`.wikitool-release.json`/`files` block, its version is older than or equal to the installed one (equal is a no-op success), it is a pre-release (`-beta.N`) without `--pre`, or `--take-release` names a path this run does not classify as locally changed. Reports, but does not block on, a crossed compatibility boundary. Never touches git - no commit, no push (invariant 5). The closing report carries no step list of its own: everything after the swap is one order, written in `instructions/upgrade-instance.md`, which the report names and which resumes at `instructions sync`. What a human decides *before* the swap - which release, whether to take it, where the tarball comes from - is `INSTALL.md` § "Version und Updates"
|
- Never downloads anything: `<source>` is an already-fetched export directory or `.tar.gz` release archive. An archive is verified against a sibling `.sha256` if one is present (a missing one is a WARN, not a block) and must unpack to exactly one top-level directory - the shape `.gitea/workflows/release.yml` packs.
|
||||||
|
- The write set is exactly the *new* `.wikitool-release.json`'s `files` block, minus what an export re-seeds from a blank template every time (`kb/log.md`, `raw/.gitkeep`) or seeds once and the instance owns from then on (`.wikitool-kb.json`, `CHANGES.md`), plus the stamp itself, always rewritten.
|
||||||
|
- Every candidate path is classified against the *local* `.wikitool-release.json`'s recorded digest: unchanged is overwritten silently, absent from the old stamp is created, and locally modified or locally deleted is **never** silently overwritten.
|
||||||
|
- Locally changed files abort the run with the full list; the abort text names the three answers with the command line filled in, and none of them is the default.
|
||||||
|
- `--keep-local` proceeds and leaves every locally changed file untouched. The new stamp is still written whole, recording the release's digest for files that were not written, so a skipped file keeps diverging and is reported again on every later run.
|
||||||
|
- `--take-release <path>` (repeatable) writes the release's version over the named path, discarding the local change, and re-creates it if it was locally deleted. The path then matches the stamp and stops being reported.
|
||||||
|
- The two are decided per path and compose on one call: without `--keep-local`, a locally changed path that no `--take-release` names still aborts the run.
|
||||||
|
- A path in the old stamp but not the new one is reported as no longer part of the release and left alone, unless `--prune` is passed, which removes it only if it is still unchanged since installation.
|
||||||
|
- Reports the migration chain the new machinery would owe, but never runs any of it - there is no `migrate run`.
|
||||||
|
- Reports, but does not block on, a crossed compatibility boundary.
|
||||||
|
- The local preconditions - `VERSION`, the release stamp, `.wikitool-kb.json`, no outstanding migration, a clean working tree - are checked before the source is read. Not being a git repository at all is a WARN, not a refusal.
|
||||||
|
- Never touches git - no commit, no push.
|
||||||
|
- `--dry-run` classifies and reports without writing; a pre-release (`-beta.N`) source needs `--pre`.
|
||||||
|
- An interrupted write is not resumed automatically: compare the tree against the printed classification and finish or revert by hand.
|
||||||
|
- The closing report names `instructions/upgrade-instance.md`, which carries the order for everything after the swap and resumes at `instructions sync`.
|
||||||
|
|
||||||
|
**SEE ALSO**
|
||||||
|
|
||||||
|
- `wikitool version check` - finds out whether an update exists
|
||||||
|
- `instructions/upgrade-instance.md` - the order after the swap
|
||||||
|
- `INSTALL.md` § "Version und Updates" - which release, whether to take it, where the tarball comes from
|
||||||
|
- `wikitool upstream merge` - the update path for a checkout with shared git history
|
||||||
|
- `wikitool migrate status` - the migrations the report names
|
||||||
|
|
||||||
#### `version show`
|
#### `version show`
|
||||||
|
|
||||||
@@ -1627,6 +1704,11 @@ Print this instance's stack version and where it came from.
|
|||||||
- budget: exempt
|
- budget: exempt
|
||||||
- network: no
|
- network: no
|
||||||
|
|
||||||
|
**EXAMPLES**
|
||||||
|
|
||||||
|
- `tools/wikitool version show`
|
||||||
|
- `tools/wikitool version show --json`
|
||||||
|
|
||||||
**EXIT STATUS**
|
**EXIT STATUS**
|
||||||
|
|
||||||
- 0 success
|
- 0 success
|
||||||
@@ -1638,7 +1720,15 @@ Print this instance's stack version and where it came from.
|
|||||||
|
|
||||||
**NOTES**
|
**NOTES**
|
||||||
|
|
||||||
Development tree, or a distribution with its export date and origin. Bare `wikitool version` is an alias for this. Read-only, offline, and **exempt from the Iteration Budget Gate**
|
- Prints `VERSION` and where this tree came from: "development tree" without a release stamp, otherwise the export date, source commit and repository from `.wikitool-release.json`, plus the release page when the stamp records one.
|
||||||
|
- `--json` prints the version, its compatibility key, the stamp and the update URL.
|
||||||
|
- Bare `wikitool version` is an alias for this.
|
||||||
|
- Read-only and offline; exempt from the Iteration Budget Gate.
|
||||||
|
|
||||||
|
**SEE ALSO**
|
||||||
|
|
||||||
|
- `wikitool version check` - asks whether a newer stack exists
|
||||||
|
- `wikitool version notes` - prints a version's release notes
|
||||||
|
|
||||||
#### `version check`
|
#### `version check`
|
||||||
|
|
||||||
@@ -1656,18 +1746,40 @@ Ask the origin's release feed whether a newer stack exists.
|
|||||||
- budget: exempt
|
- budget: exempt
|
||||||
- network: yes
|
- network: yes
|
||||||
|
|
||||||
|
**EXAMPLES**
|
||||||
|
|
||||||
|
- `tools/wikitool version check`
|
||||||
|
- `tools/wikitool version check --json`
|
||||||
|
|
||||||
**EXIT STATUS**
|
**EXIT STATUS**
|
||||||
|
|
||||||
- 0 success
|
- 0 success
|
||||||
- 1 The feed could not be reached, answered non-JSON, or carried no `tag_name`. **Never** answers "up to date" for a question it could not ask
|
- 1 `VERSION` is missing or unparseable
|
||||||
|
- 1 The feed could not be reached, answered non-JSON, or carried no `tag_name`
|
||||||
|
|
||||||
**ON FAILURE**
|
**ON FAILURE**
|
||||||
|
|
||||||
- The feed could not be reached, answered non-JSON, or carried no `tag_name`. **Never** answers "up to date" for a question it could not ask -> A network failure is transient - retry once, then report it. HTTP 401/403 names `$WIKITOOL_UPDATE_TOKEN`; 404 means no release exists yet or the URL points at the wrong repo
|
- `VERSION` is missing or unparseable -> Fix `VERSION` and retry
|
||||||
|
- The feed could not be reached, answered non-JSON, or carried no `tag_name` -> A network failure is transient - retry once, then report it. HTTP 401/403 names `$WIKITOOL_UPDATE_TOKEN`; 404 means no release exists yet or the URL points at the wrong repo
|
||||||
|
|
||||||
|
**NEVER**
|
||||||
|
|
||||||
|
- Never report "up to date" for a check that failed - an unreachable feed is not that answer.
|
||||||
|
|
||||||
**NOTES**
|
**NOTES**
|
||||||
|
|
||||||
Ask the origin's release feed whether a newer stack exists, and whether the step crosses a compatibility boundary (`state: current|update|migration|ahead`). One of the **two** commands in `wikitool` that make a network call, and the only one whose whole job it is - `version notes` is the other, and only on a distributed instance. Never reached implicitly from another command, needs no key, times out, and reports an unreachable feed as an error rather than as "up to date". The feed is `$WIKITOOL_UPDATE_URL`, else the release stamp's, else the built-in origin; `$WIKITOOL_UPDATE_TOKEN` is only needed if that feed is not readable anonymously. Read-only and exempt from the budget gate
|
- Asks the release feed for its latest release and compares it with `VERSION`: `state` is `current`, `update`, `migration` (the step crosses a compatibility boundary) or `ahead`.
|
||||||
|
- One of the **two** commands in `wikitool` that make a network call, and the only one whose whole job it is - `version notes` is the other, and only on a distributed instance.
|
||||||
|
- Never reached implicitly from another command, needs no key, and times out after `--timeout` seconds (default 10).
|
||||||
|
- The feed is `--url`, else `$WIKITOOL_UPDATE_URL`, else the release stamp's, else the built-in origin. `$WIKITOOL_UPDATE_TOKEN` is only needed if that feed is not readable anonymously.
|
||||||
|
- For `update` or `migration` it prints that applying the release is a separate, manual step (`INSTALL.md` § "Eine Instanz aktualisieren").
|
||||||
|
- Read-only; exempt from the Iteration Budget Gate.
|
||||||
|
|
||||||
|
**SEE ALSO**
|
||||||
|
|
||||||
|
- `wikitool dist upgrade` - applies the update this reports
|
||||||
|
- `wikitool version notes` - prints the release notes
|
||||||
|
- `INSTALL.md` § "Version und Updates" - what the operator decides before an update
|
||||||
|
|
||||||
#### `version notes`
|
#### `version notes`
|
||||||
|
|
||||||
@@ -1685,18 +1797,41 @@ Print one version's release notes.
|
|||||||
- budget: exempt
|
- budget: exempt
|
||||||
- network: yes
|
- network: yes
|
||||||
|
|
||||||
|
**EXAMPLES**
|
||||||
|
|
||||||
|
- `tools/wikitool version notes`
|
||||||
|
- `tools/wikitool version notes --version 7.0.0`
|
||||||
|
- `tools/wikitool version notes --offline`
|
||||||
|
|
||||||
**EXIT STATUS**
|
**EXIT STATUS**
|
||||||
|
|
||||||
- 0 success
|
- 0 success
|
||||||
- 1 An unparseable `--version`, an unreadable `VERSION` when `--version` is omitted, or a missing `CHANGES.md`. No entry for the requested version is an error only where the feed cannot answer either: in a tree with no release stamp (a dev checkout - write the entry, or `version bump`), with `--offline`, or when the feed could not be reached or returned a release with an empty `body`. Every one of those failures names the stamp's `release_url` where it has one, so a run that cannot read the notes is still told where they are
|
- 1 `--version` is unparseable, `VERSION` is unreadable when `--version` is omitted, or `CHANGES.md` is missing
|
||||||
|
- 1 No entry for the requested version in a tree with no release stamp (a dev checkout)
|
||||||
|
- 1 No entry for the requested version, and `--offline` was passed
|
||||||
|
- 1 No entry for the requested version, and the feed could not be reached or returned a release with an empty `body`
|
||||||
|
|
||||||
**ON FAILURE**
|
**ON FAILURE**
|
||||||
|
|
||||||
- An unparseable `--version`, an unreadable `VERSION` when `--version` is omitted, or a missing `CHANGES.md`. No entry for the requested version is an error only where the feed cannot answer either: in a tree with no release stamp (a dev checkout - write the entry, or `version bump`), with `--offline`, or when the feed could not be reached or returned a release with an empty `body`. Every one of those failures names the stamp's `release_url` where it has one, so a run that cannot read the notes is still told where they are -> Fix the named argument or file, then retry. A feed failure is transient - retry once, then read the release page the error names. Safe to retry
|
- `--version` is unparseable, `VERSION` is unreadable when `--version` is omitted, or `CHANGES.md` is missing -> Fix the named argument or file, then retry
|
||||||
|
- No entry for the requested version in a tree with no release stamp (a dev checkout) -> Write the entry, or run `version bump`
|
||||||
|
- No entry for the requested version, and `--offline` was passed -> Read the release page the error names
|
||||||
|
- No entry for the requested version, and the feed could not be reached or returned a release with an empty `body` -> A feed failure is transient - retry once, then read the release page the error names
|
||||||
|
|
||||||
**NOTES**
|
**NOTES**
|
||||||
|
|
||||||
Default: this tree's `VERSION`. The `CHANGES.md` entry where there is one, and where there is not, the feed's latest release notes. The fallback exists because an instance's `CHANGES.md` is a stub `dist upgrade` never overwrites (`chemenu.ownership.is_upgrade_preserved`), so the local file can never carry the entry - not today and not after any future release, which made the command permanently unanswerable exactly where the release notes are most needed. It is reached **only with a release stamp present**, i.e. only from a `dist export` tree: a dev checkout keeps the plain error, which is what keeps the origin repo and CI offline. **stdout carries nothing but the notes**; the line naming the feed being asked, and the one naming the release that answered, go to stderr - `release.yml` redirects stdout into the file it posts as the release body. Only the feed's *latest* release can be asked for (`update_url` is the one URL a stamp records, and composing a by-tag URL out of it would be guessing at an API shape), so a returned version other than the one asked for is named on stderr and printed anyway - the expected shape before an upgrade, where `VERSION` still names the release being left. `--offline` refuses the call and fails with the stamp's `release_url` instead. Read-only and exempt from the budget gate
|
- Prints the `CHANGES.md` entry for `--version` (default: this tree's `VERSION`).
|
||||||
|
- With no such entry, in a tree with a release stamp (a `dist export` tree) and without `--offline`, it prints the release feed's latest release notes instead. A tree without a stamp (a dev checkout) never asks the feed.
|
||||||
|
- Only the feed's latest release can be asked for. When that is a different version than requested, stderr names it and the notes are printed anyway - the expected case before an upgrade, where `VERSION` still names the release being left.
|
||||||
|
- stdout carries nothing but the notes; the line naming the feed being asked and the one naming the release that answered go to stderr. `release.yml` redirects stdout into the file it posts as the release body.
|
||||||
|
- `--offline` never asks the feed and fails with the stamp's `release_url` instead.
|
||||||
|
- Every failure names the stamp's `release_url` where it has one, so a run that cannot read the notes is still told where they are.
|
||||||
|
- Read-only, safe to retry, and exempt from the Iteration Budget Gate.
|
||||||
|
|
||||||
|
**SEE ALSO**
|
||||||
|
|
||||||
|
- `wikitool version check` - asks the same feed whether a newer stack exists
|
||||||
|
- `wikitool version bump` - writes the entry this prints
|
||||||
|
|
||||||
#### `version bump`
|
#### `version bump`
|
||||||
|
|
||||||
@@ -1714,18 +1849,54 @@ Raise or continue the one running candidate between two releases.
|
|||||||
- budget: counted
|
- budget: counted
|
||||||
- network: no
|
- network: no
|
||||||
|
|
||||||
|
**EXAMPLES**
|
||||||
|
|
||||||
|
- `tools/wikitool version bump --patch --title "Fix the lint report path" --impact low`
|
||||||
|
- `tools/wikitool version bump --minor --title "New command: wikitool review" --dry-run`
|
||||||
|
- `tools/wikitool version bump --major --title "Rename --gate-file to --remotes-file" --breaking "--gate-file is gone; scripts must pass --remotes-file" --no-migration "no content changes"`
|
||||||
|
|
||||||
**EXIT STATUS**
|
**EXIT STATUS**
|
||||||
|
|
||||||
- 0 success
|
- 0 success
|
||||||
- 1 More or fewer than one of `--major/--minor/--patch`, an empty `--title`, an unknown `--impact`, a missing `VERSION`/`CHANGES.md`, `VERSION` and the changelog's newest entry naming different versions, an escalation to a boundary crossing without `--breaking` or with neither a migration document nor `--no-migration`, `--breaking`/`--no-migration` on a bump that crosses nothing, or `--migration-required` combined with `--no-migration`, on a bump with no running candidate, with no `--no-migration` line to retract, or without a migration document already targeting the new base
|
- 1 Not exactly one of `--major`/`--minor`/`--patch`, an empty `--title`, or an unknown `--impact`
|
||||||
|
- 1 `VERSION` or `CHANGES.md` is missing, or `VERSION` and the changelog's newest entry name different versions
|
||||||
|
- 1 An escalation to a boundary crossing without `--breaking`, or with neither a migration document targeting the new base nor `--no-migration`
|
||||||
|
- 1 `--breaking` or `--no-migration` on a bump that crosses nothing
|
||||||
|
- 1 `--migration-required` combined with `--no-migration`, on a bump with no running candidate, with no `--no-migration` line to retract, or without a migration document already targeting the new base
|
||||||
|
|
||||||
**ON FAILURE**
|
**ON FAILURE**
|
||||||
|
|
||||||
- More or fewer than one of `--major/--minor/--patch`, an empty `--title`, an unknown `--impact`, a missing `VERSION`/`CHANGES.md`, `VERSION` and the changelog's newest entry naming different versions, an escalation to a boundary crossing without `--breaking` or with neither a migration document nor `--no-migration`, `--breaking`/`--no-migration` on a bump that crosses nothing, or `--migration-required` combined with `--no-migration`, on a bump with no running candidate, with no `--no-migration` line to retract, or without a migration document already targeting the new base -> **Not idempotent**: a second run escalates or continues the candidate again. If the outcome is uncertain, read `VERSION` and the top of `CHANGES.md` before retrying
|
- Not exactly one of `--major`/`--minor`/`--patch`, an empty `--title`, or an unknown `--impact` -> Nothing was written - fix the argument and retry once
|
||||||
|
- `VERSION` or `CHANGES.md` is missing, or `VERSION` and the changelog's newest entry name different versions -> Nothing was written - fix whichever is wrong, then retry
|
||||||
|
- An escalation to a boundary crossing without `--breaking`, or with neither a migration document targeting the new base nor `--no-migration` -> Nothing was written - add what the error asks for, or, if nothing actually breaks, choose a smaller part
|
||||||
|
- `--breaking` or `--no-migration` on a bump that crosses nothing -> Nothing was written - drop the flag and retry
|
||||||
|
- `--migration-required` combined with `--no-migration`, on a bump with no running candidate, with no `--no-migration` line to retract, or without a migration document already targeting the new base -> Nothing was written - write the migration document or fix the combination, then retry
|
||||||
|
|
||||||
|
**NEVER**
|
||||||
|
|
||||||
|
- Never re-run after an uncertain outcome without first reading `VERSION` and the top of `CHANGES.md` - a second run escalates or continues the candidate again.
|
||||||
|
- Never hand-edit `VERSION` or the machine-written parts of the entry (heading, bump list, breaking and migration lines); `--migration-required` is the only way to take the migration line back.
|
||||||
|
|
||||||
**NOTES**
|
**NOTES**
|
||||||
|
|
||||||
`VERSION` gets a `-beta.N` suffix, never a second fresh number per bump. `--major/--minor/--patch` is **max-wins escalation** against the last release (patch < minor < major): a `--patch` on a MINOR candidate only advances `N`, and escalation never steps back down. Opens the matching `CHANGES.md` entry on the first bump of a candidate (heading, date, author, and a machine-managed `<!-- wikitool:bumps -->` list of every `--title` collected so far, graded by `--impact`, default `medium`) and updates that same entry in place on every later bump of the same candidate - one entry per candidate, not one per bump. The list renders grouped under `**High/Medium/Low impact**` headings (empty groups omitted), except when every bump so far is `medium`, where it stays the flat, ungrouped list the region always had - `version regrade` corrects a grade after the fact. Refuses more or fewer than one part, an empty title, an unknown `--impact`, and a `VERSION`/newest-changelog-entry mismatch. Compatibility follows the **leftmost non-zero component** of the candidate's base, which for this stack (at `1.0.0` and up) means MAJOR: PATCH is a fix, MINOR a compatible capability, MAJOR a version that is **not a drop-in replacement** - any hand-work on update, or a downgrade that no longer works. Whether content must be migrated is a second, independent question. The bump that first escalates a candidate past the boundary requires `--breaking "<what stops working>"` and, on top of it, a migration document targeting the candidate's base or `--no-migration "<reason>"`; both are anchored just above the bump list, persist over later bumps of the same candidate without being repeated, and are refused on a bump that crosses nothing at all. The two then behave differently on a *second* crossing, because they answer different questions: a further `--breaking` **joins** the ones already recorded (one reason per crossing - rendered flat on the marker line while there is only one, as bullets under a bare marker from the second onward, and repeating a reason verbatim is a no-op), while a further `--no-migration` **replaces** the single line that says whether content has to change. A candidate crossing the boundary twice is the normal shape of a long-running one, and each crossing is a separate thing an operator has to act on; whether content migrates stays one yes/no about the candidate as a whole. There is deliberately no retraction path for a single accumulated `--breaking` reason - `--migration-required` retracts the migration line, and nothing retracts a breaking one. A later bump of the same candidate that finds out `--no-migration` was wrong after all retracts that line with `--migration-required` instead of restating `--no-migration` - refused without a migration document already targeting the new base, and without an existing `--no-migration` line to retract. Which part a change earns stays a judgment call: the command enforces that a crossing documents itself, never that the part was chosen correctly
|
- `VERSION` gets a `-beta.N` suffix: one running candidate between two releases, never a fresh number per bump.
|
||||||
|
- `--major`/`--minor`/`--patch` is **max-wins escalation** against the last release (patch < minor < major): a `--patch` on a MINOR candidate only advances `N`, and escalation never steps back down.
|
||||||
|
- The first bump of a candidate opens its `CHANGES.md` entry - heading, date, author, and a machine-managed `<!-- wikitool:bumps -->` list of every `--title` collected so far, graded by `--impact` (default `medium`). Every later bump of the same candidate updates that entry in place: one entry per candidate, not one per bump.
|
||||||
|
- The bump list renders grouped under `**High/Medium/Low impact**` headings, empty groups omitted - except while every bump is `medium`, where it stays one flat list. `version regrade` corrects a grade after the fact.
|
||||||
|
- Writes the heading, the bump list and the breaking/migration lines; the entry's prose is left to the author.
|
||||||
|
- Compatibility follows the **leftmost non-zero component** of the candidate's base, which for this stack (at `1.0.0` and up) means MAJOR: PATCH is a fix, MINOR a compatible capability, MAJOR a version that is **not a drop-in replacement** - any hand-work on update, or a downgrade that no longer works. Whether content must be migrated is a second, independent question.
|
||||||
|
- The bump that first escalates a candidate past the boundary requires `--breaking "<what stops working>"` and, on top of it, a migration document targeting the candidate's base or `--no-migration "<reason>"`. Both are anchored just above the bump list, persist over later bumps of the same candidate without being repeated, and are refused on a bump that crosses nothing at all.
|
||||||
|
- On a later crossing of the same candidate, a further `--breaking` **joins** the reasons already recorded (flat on the marker line while there is one, bullets under a bare marker from the second on; repeating a reason verbatim is a no-op), while a further `--no-migration` **replaces** the single migration line.
|
||||||
|
- `--migration-required` retracts the running candidate's `--no-migration` line; it needs a migration document already targeting the new base. Nothing retracts a recorded `--breaking` reason.
|
||||||
|
- Enforces that a crossing documents itself, never that the part was chosen correctly.
|
||||||
|
- Not idempotent: every successful run escalates or continues the candidate again.
|
||||||
|
- `--dry-run` reports the step without writing.
|
||||||
|
|
||||||
|
**SEE ALSO**
|
||||||
|
|
||||||
|
- `wikitool version regrade` - corrects an `--impact` grade
|
||||||
|
- `wikitool version release` - fixes the candidate into a release
|
||||||
|
- `instructions/migrate-corpus.md` - how a migration document is written
|
||||||
|
|
||||||
#### `version regrade`
|
#### `version regrade`
|
||||||
|
|
||||||
@@ -1743,18 +1914,38 @@ List the running candidate's bump titles with their impact grade, or change one
|
|||||||
- budget: exempt_without_args
|
- budget: exempt_without_args
|
||||||
- network: no
|
- network: no
|
||||||
|
|
||||||
|
**EXAMPLES**
|
||||||
|
|
||||||
|
- `tools/wikitool version regrade`
|
||||||
|
- `tools/wikitool version regrade 3 7 --impact high`
|
||||||
|
|
||||||
**EXIT STATUS**
|
**EXIT STATUS**
|
||||||
|
|
||||||
- 0 success
|
- 0 success
|
||||||
- 1 A missing `VERSION`/`CHANGES.md`, `VERSION` and the changelog's newest entry naming different versions, a topmost entry with no bump list, an index outside the rendered list's range, indices given without `--impact`, or an unknown `--impact`
|
- 1 `VERSION` or `CHANGES.md` is missing, `VERSION` and the changelog's newest entry name different versions, or the topmost entry has no bump list
|
||||||
|
- 1 An index outside the rendered list's range, indices given without `--impact`, or an unknown `--impact`
|
||||||
|
|
||||||
**ON FAILURE**
|
**ON FAILURE**
|
||||||
|
|
||||||
- A missing `VERSION`/`CHANGES.md`, `VERSION` and the changelog's newest entry naming different versions, a topmost entry with no bump list, an index outside the rendered list's range, indices given without `--impact`, or an unknown `--impact` -> The bare listing never writes anything. A write is **not idempotent** against a changed list: re-running the same indices after a first success regrades whatever is at those positions *now*, which may no longer be the same bumps - list again before retrying
|
- `VERSION` or `CHANGES.md` is missing, `VERSION` and the changelog's newest entry name different versions, or the topmost entry has no bump list -> Nothing was written - fix whichever is wrong, then retry
|
||||||
|
- An index outside the rendered list's range, indices given without `--impact`, or an unknown `--impact` -> Nothing was written - list again with the bare command, then retry once with corrected arguments
|
||||||
|
|
||||||
|
**NEVER**
|
||||||
|
|
||||||
|
- Never re-run the same indices after a write without listing again first - the positions may have moved.
|
||||||
|
|
||||||
**NOTES**
|
**NOTES**
|
||||||
|
|
||||||
1-based rendered position (no arguments - the correction path for a `--impact` judgement made at bump time), or change one or more of them in a single call: `version regrade 3 7 --impact high` grades both against a single read of today's list, not position 3 first and then position 7 against whatever that produced. Touches only the topmost entry's bump list - never `VERSION`, never any other part of `CHANGES.md`. The bare listing is read-only and exempt from the Iteration Budget Gate, like `version notes`; a call with indices writes `CHANGES.md` and is counted like `version bump`. Refuses an index outside the rendered list's range, an unknown `--impact`, indices given without `--impact`, a missing `VERSION`/`CHANGES.md`, a `VERSION`/newest-changelog-entry mismatch, or a topmost entry with no bump list at all
|
- Without arguments: lists the running candidate's bump titles with their grade, numbered by 1-based position in the rendered list (High before Medium before Low, chronological within a grade). Read-only and exempt from the Iteration Budget Gate.
|
||||||
|
- With indices and `--impact`: sets the grade of every named position in one call, all resolved against a single read of the current list - `version regrade 3 7 --impact high` grades what is at 3 and 7 now, not 7 after 3 has moved. Writes `CHANGES.md` and is counted by the Iteration Budget Gate.
|
||||||
|
- The correction path for an `--impact` grade judged at bump time.
|
||||||
|
- Touches only the topmost entry's bump list - never `VERSION`, never any other part of `CHANGES.md`.
|
||||||
|
- A write is not idempotent against a changed list: after a first success, the same indices may name different bumps.
|
||||||
|
|
||||||
|
**SEE ALSO**
|
||||||
|
|
||||||
|
- `wikitool version bump` - sets the grade in the first place
|
||||||
|
- `wikitool version release` - refuses without a summary above this list
|
||||||
|
|
||||||
#### `version release`
|
#### `version release`
|
||||||
|
|
||||||
@@ -1772,18 +1963,42 @@ Fix the running candidate: strip `VERSION`'s `-beta.N` suffix and close its `CHA
|
|||||||
- budget: counted
|
- budget: counted
|
||||||
- network: no
|
- network: no
|
||||||
|
|
||||||
|
**EXAMPLES**
|
||||||
|
|
||||||
|
- `tools/wikitool version release --dry-run`
|
||||||
|
- `tools/wikitool version release --title "Command records rewritten for agents"`
|
||||||
|
|
||||||
**EXIT STATUS**
|
**EXIT STATUS**
|
||||||
|
|
||||||
- 0 success
|
- 0 success
|
||||||
- 1 A missing `VERSION`/`CHANGES.md`, `VERSION` already a release (no running candidate), `VERSION` and the changelog's newest entry naming different versions, or (from two bumps on) an entry with no summary paragraph above the changesets
|
- 1 `VERSION` is already a release - there is no running candidate to fix
|
||||||
|
- 1 `VERSION` or `CHANGES.md` is missing, or `VERSION` and the changelog's newest entry name different versions
|
||||||
|
- 1 Two or more bumps and no summary paragraph above the changesets
|
||||||
|
|
||||||
**ON FAILURE**
|
**ON FAILURE**
|
||||||
|
|
||||||
- A missing `VERSION`/`CHANGES.md`, `VERSION` already a release (no running candidate), `VERSION` and the changelog's newest entry naming different versions, or (from two bumps on) an entry with no summary paragraph above the changesets -> **Not idempotent**: a second run fails outright once the suffix is gone. If the outcome is uncertain, read `VERSION` before retrying - a release-shaped `VERSION` means it already ran
|
- `VERSION` is already a release - there is no running candidate to fix -> After an uncertain run this means it already ran; otherwise there is nothing to release
|
||||||
|
- `VERSION` or `CHANGES.md` is missing, or `VERSION` and the changelog's newest entry name different versions -> Fix whichever is wrong, then retry
|
||||||
|
- Two or more bumps and no summary paragraph above the changesets -> Write a short summary paragraph right below the bump list, then retry
|
||||||
|
|
||||||
|
**NEVER**
|
||||||
|
|
||||||
|
- Never retry after an uncertain outcome without reading `VERSION` first - a release-shaped `VERSION` means it already ran.
|
||||||
|
|
||||||
**NOTES**
|
**NOTES**
|
||||||
|
|
||||||
Ends the pre-release phase `version bump` started. Without `--title` the heading keeps whichever bump last set it; with it, the heading's title is replaced - the normal case for a candidate that collected several bump titles, since the entry wants a summarising heading rather than the most recent one. Leaves the entry's machine-managed bump-title list untouched, as the record of what happened. Refuses when the candidate collected two or more bumps and the entry still carries no summary paragraph (at least 200 non-whitespace characters) between the bump list and the first `### <bump title>` changeset heading; a candidate with exactly one bump is exempt, since there its own changeset already is the summary. `--dry-run` runs this check too and reports the same refusal. Commits nothing and pushes nothing (invariant 5) - the following `publish` moves `VERSION` onto `main`, which `release.yml` reacts to. Refuses when `VERSION` is already a release (no running candidate to fix), or when the changelog's newest entry does not match `VERSION`
|
- Strips `VERSION`'s `-beta.N` suffix - the candidate's base becomes the release - and closes the candidate's `CHANGES.md` entry.
|
||||||
|
- Without `--title` the heading keeps whichever bump last set it; `--title` replaces it - the normal case for a candidate that collected several bumps, whose entry wants a summarising heading rather than the most recent one.
|
||||||
|
- Leaves the entry's machine-managed bump list untouched, as the record of what happened.
|
||||||
|
- From two bumps on, requires a summary paragraph (at least 200 non-whitespace characters) between the bump list and the first `### <bump title>` changeset heading. A candidate with exactly one bump is exempt, since there its own changeset already is the summary. `--dry-run` runs this check too.
|
||||||
|
- Commits nothing and pushes nothing. The following `publish` moves `VERSION` onto `main`, which `release.yml` reacts to.
|
||||||
|
- Not idempotent: a second run fails once the suffix is gone.
|
||||||
|
|
||||||
|
**SEE ALSO**
|
||||||
|
|
||||||
|
- `wikitool version bump` - opens and continues the candidate
|
||||||
|
- `wikitool version regrade` - shows the bump list the summary sits under
|
||||||
|
- `wikitool publish` - moves the released `VERSION` onto `main`
|
||||||
|
|
||||||
### Content migrations
|
### Content migrations
|
||||||
|
|
||||||
|
|||||||
+180
-102
@@ -467,12 +467,14 @@ def build_plan(origin: Optional[Origin] = None) -> dict[str, PlannedFile]:
|
|||||||
if source.is_file():
|
if source.is_file():
|
||||||
plan[relative] = _read_planned_file(source, relative)
|
plan[relative] = _read_planned_file(source, relative)
|
||||||
|
|
||||||
# `raw/` and `incoming/` are both flat now (Gitea #67 removes type
|
# `raw/` and `incoming/` are both flat (Gitea #67 removed type
|
||||||
# subdirectories from the addressing scheme entirely - a file's location
|
# subdirectories from the addressing scheme entirely - a file's location
|
||||||
# under `raw/` is a date shard computed by `raw accept`, never a hand-picked
|
# under `raw/` is a date shard computed by `raw accept`, never a hand-picked
|
||||||
# type). `.gitignore` (also exported, see ROOT_FILES) excludes `incoming/`
|
# type), so an export creates no subdirectories under either root. The
|
||||||
# again once the instance is a git repo, which is why bootstrap.md
|
# exported `.gitignore` excludes what lands in `incoming/` but not its
|
||||||
# re-creates it for a plain clone that never had this export step at all.
|
# `.gitkeep` (`/incoming/*` plus `!/incoming/.gitkeep`, Gitea #88), so the
|
||||||
|
# anchor is tracked and a plain clone of the instance has the directory
|
||||||
|
# without any bootstrap step re-creating it.
|
||||||
plan["raw/.gitkeep"] = PlannedFile("")
|
plan["raw/.gitkeep"] = PlannedFile("")
|
||||||
plan["incoming/.gitkeep"] = PlannedFile("")
|
plan["incoming/.gitkeep"] = PlannedFile("")
|
||||||
|
|
||||||
@@ -583,36 +585,63 @@ def _write_plan(target: Path, plan: dict[str, PlannedFile]) -> None:
|
|||||||
atomic="Yes - nothing is written until every file is planned",
|
atomic="Yes - nothing is written until every file is planned",
|
||||||
budget=cli_contract.Budget.COUNTED,
|
budget=cli_contract.Budget.COUNTED,
|
||||||
),
|
),
|
||||||
notes="Write a contentless, distributable copy of this repo's machinery into an empty "
|
notes=(
|
||||||
"`<target>` directory: `AGENTS.md`/`README.md`/`EVALS.md` with any "
|
"Writes a contentless, distributable copy of this repo's machinery into an empty or "
|
||||||
"`dist:strip-start`...`dist:strip-end` marker region removed, `instructions/` "
|
"new `<target>` directory.",
|
||||||
"(minus `instructions/dev/`), `types/` (the `root: kb` page type-specs and their schemas "
|
"Ships `AGENTS.md`/`README.md`/`EVALS.md` with any `dist:strip-start`...`dist:strip-end` "
|
||||||
"re-keyed as `.template`, the stack's own verbatim), `docs/` verbatim, `tools/` (no "
|
"marker region removed, `instructions/` (minus `instructions/dev/`), `types/` (the "
|
||||||
"venv/caches), the `.github/hooks/`+`.vibe/` session-tracing config plus "
|
"`root: kb` page type-specs and their schemas re-keyed as `.template`, the stack's own "
|
||||||
"`.claude/settings.json`, `kb/CONTRACT.md` (no pages, no areas), the two flat anchors "
|
"verbatim), `docs/` verbatim, `tools/` (no venv/caches), the "
|
||||||
"`raw/.gitkeep` and `incoming/.gitkeep` (both roots are flat now that a file's location "
|
"`.github/hooks/`+`.vibe/` session-tracing config plus `.claude/settings.json`, "
|
||||||
"under `raw/` is a date shard rather than a hand-picked type, so a fresh export no longer "
|
"`kb/CONTRACT.md` (no pages, no areas) and `VERSION`.",
|
||||||
"creates any type subdirectories under either root; `incoming/.gitkeep` is trackable and "
|
"Ships `raw/` and `incoming/` as flat roots, each with a `.gitkeep` and no "
|
||||||
"survives becoming a git repository, so a plain clone gets the directory without any "
|
"subdirectories. `incoming/.gitkeep` is trackable and survives becoming a git "
|
||||||
"bootstrap step re-creating it), `VERSION`, `USER.md.template`/`SOUL.md.template` plus "
|
"repository, so a plain clone gets the directory without any bootstrap step.",
|
||||||
"`kb/CONVENTIONS.md.template` and each collection's contract re-keyed as "
|
"Ships templates, never the filled files: `USER.md.template`/`SOUL.md.template`, "
|
||||||
"`kb/<name>/COLLECTION.md.template` (the templates ship; the filled "
|
"`kb/CONVENTIONS.md.template`, and each collection's contract re-keyed as "
|
||||||
"`USER.md`/`SOUL.md`/`kb/CONVENTIONS.md`/`kb/<name>/COLLECTION.md`/`types/<page-type>.md` "
|
"`kb/<name>/COLLECTION.md.template`. The filled "
|
||||||
"never do - all of them bind their instance and none are the stack's to decide, and "
|
"`USER.md`/`SOUL.md`/`kb/CONVENTIONS.md`/`kb/<name>/COLLECTION.md`/"
|
||||||
"`find_leaks` refuses a plan carrying one), and a generated `.wikitool-release.json` stamp "
|
"`types/<page-type>.md` bind their instance; `find_leaks` refuses a plan carrying one.",
|
||||||
"(version, export date, origin, and a sha256 per exported file - the base a later upgrade "
|
"Writes a generated `.wikitool-release.json` stamp: version, export date, origin, and "
|
||||||
"would compare against). The four origin options only fill stamp fields: `export` never "
|
"a sha256 per exported file - the base a later upgrade compares against.",
|
||||||
"calls git and cannot discover them. Refuses a non-empty target, and a tree with no "
|
"The four origin options only fill stamp fields: `export` never calls git and cannot "
|
||||||
"`VERSION`. See `instructions/setup-instance.md`. One-way: there is no command that "
|
"discover them.",
|
||||||
"reconstructs a distributed instance into a dev instance - work on the stack in the origin "
|
"One-way: no command reconstructs a distributed instance into a dev instance - work on "
|
||||||
"repo (or a new dev instance exported from it) instead",
|
"the stack in the origin repo, or in a new dev instance exported from it.",
|
||||||
failures=(cli_contract.Failure(
|
"`--dry-run` lists every file it would write, and writes nothing.",
|
||||||
label="",
|
),
|
||||||
cause="Target exists and is not empty, is not a directory, or the tree has no "
|
failures=(
|
||||||
"readable `VERSION`",
|
cli_contract.Failure(
|
||||||
reaction="Point `<target>` at an empty (or new) directory and retry. Never merge into a "
|
cause="`<target>` exists and is not empty, or is not a directory",
|
||||||
"non-empty one by hand",
|
reaction="Point `<target>` at an empty (or new) directory and retry",
|
||||||
),),
|
),
|
||||||
|
cli_contract.Failure(
|
||||||
|
cause="The tree has no readable `VERSION`",
|
||||||
|
reaction="Fix `VERSION`, then retry",
|
||||||
|
),
|
||||||
|
cli_contract.Failure(
|
||||||
|
cause="A licence file is missing from the source tree",
|
||||||
|
reaction="Restore it, then retry",
|
||||||
|
),
|
||||||
|
cli_contract.Failure(
|
||||||
|
cause="The plan would carry this instance's own data (a filled personalization, "
|
||||||
|
"conventions or page type-spec file)",
|
||||||
|
reaction="Report it: it is an allowlist bug in `dist export`, not something to "
|
||||||
|
"work around by deleting files from the target",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
examples=(
|
||||||
|
"tools/wikitool dist export ../my-wiki --dry-run",
|
||||||
|
"tools/wikitool dist export ../my-wiki",
|
||||||
|
),
|
||||||
|
never=(
|
||||||
|
"Never merge an export into a non-empty directory by hand.",
|
||||||
|
),
|
||||||
|
see_also=(
|
||||||
|
"`instructions/setup-instance.md` - what comes after the export",
|
||||||
|
"`wikitool dist upgrade` - applies a later export to an existing instance",
|
||||||
|
"`wikitool version show` - reads the stamp this writes",
|
||||||
|
),
|
||||||
))
|
))
|
||||||
@app.command("export")
|
@app.command("export")
|
||||||
def export_command(
|
def export_command(
|
||||||
@@ -961,76 +990,119 @@ def _report_plan(
|
|||||||
properties=cli_contract.Properties(
|
properties=cli_contract.Properties(
|
||||||
effect=cli_contract.Effect.WRITE,
|
effect=cli_contract.Effect.WRITE,
|
||||||
idempotent=cli_contract.Idempotent.NO,
|
idempotent=cli_contract.Idempotent.NO,
|
||||||
atomic="**Yes for the refusal cases above - nothing is written.** Once writing starts "
|
atomic="Yes for every refusal - nothing is written. Once writing starts it is a plain "
|
||||||
"it is a plain sequential file copy with no partial-state cleanup: an interruption "
|
"sequential file copy with no partial-state cleanup: an interruption mid-copy "
|
||||||
"mid-copy (killed process, disk full) can leave the tree part-old, part-new",
|
"(killed process, disk full) can leave the tree part-old, part-new",
|
||||||
budget=cli_contract.Budget.COUNTED,
|
budget=cli_contract.Budget.COUNTED,
|
||||||
),
|
),
|
||||||
notes="Never downloads anything: `<source>` is an already-fetched export directory or "
|
notes=(
|
||||||
"`.tar.gz` release archive (verified against a sibling `.sha256` if one is present; WARNs, "
|
"Never downloads anything: `<source>` is an already-fetched export directory or "
|
||||||
"does not block, if it is absent), which must unpack to exactly one top-level directory - "
|
"`.tar.gz` release archive. An archive is verified against a sibling `.sha256` if one "
|
||||||
"the shape `.gitea/workflows/release.yml` packs. The write set is exactly the *new* "
|
"is present (a missing one is a WARN, not a block) and must unpack to exactly one "
|
||||||
"`.wikitool-release.json`'s `files` block, minus what an export re-seeds from a blank "
|
"top-level directory - the shape `.gitea/workflows/release.yml` packs.",
|
||||||
"template every time (`kb/log.md`, `raw/.gitkeep` - `chemenu.ownership.is_export_stub`) or "
|
"The write set is exactly the *new* `.wikitool-release.json`'s `files` block, minus "
|
||||||
"seeds once and the instance owns from then on (`.wikitool-kb.json`, `CHANGES.md` - "
|
"what an export re-seeds from a blank template every time (`kb/log.md`, "
|
||||||
"`chemenu.ownership.is_upgrade_preserved`), plus the stamp itself, always rewritten. Every "
|
"`raw/.gitkeep`) or seeds once and the instance owns from then on "
|
||||||
"candidate path is classified against the *local* `.wikitool-release.json`'s recorded "
|
"(`.wikitool-kb.json`, `CHANGES.md`), plus the stamp itself, always rewritten.",
|
||||||
"digest for it: unchanged is overwritten silently, absent from the old stamp is created, "
|
"Every candidate path is classified against the *local* `.wikitool-release.json`'s "
|
||||||
"and locally modified or locally deleted is **never** silently overwritten - the run aborts "
|
"recorded digest: unchanged is overwritten silently, absent from the old stamp is "
|
||||||
"with the full list, and its text names the three answers with the command line already "
|
"created, and locally modified or locally deleted is **never** silently overwritten.",
|
||||||
"filled in, so that no reader takes any of them for the default. `--keep-local` proceeds "
|
"Locally changed files abort the run with the full list; the abort text names the "
|
||||||
"and leaves every one of them untouched; `--take-release <path>` (repeatable) writes the "
|
"three answers with the command line filled in, and none of them is the default.",
|
||||||
"release's version over the named path, discarding the local change, and re-creates it if "
|
"`--keep-local` proceeds and leaves every locally changed file untouched. The new "
|
||||||
"it was locally deleted. The two are decided per path and compose on one call: without "
|
"stamp is still written whole, recording the release's digest for files that were "
|
||||||
"`--keep-local`, a locally changed path that no `--take-release` names still aborts the "
|
"not written, so a skipped file keeps diverging and is reported again on every later "
|
||||||
"run. A `--take-release` path that this run does not report as locally changed is refused, "
|
"run.",
|
||||||
"in a `--dry-run` as well as a writing run - it is a mistake in the argument rather than a "
|
"`--take-release <path>` (repeatable) writes the release's version over the named "
|
||||||
"state of the tree, and a path that silently did nothing would report a successful upgrade "
|
"path, discarding the local change, and re-creates it if it was locally deleted. The "
|
||||||
"while keeping the change it was asked to discard. After a `--keep-local` run the new stamp "
|
"path then matches the stamp and stops being reported.",
|
||||||
"is still written whole, so it records the release's digest for files that were "
|
"The two are decided per path and compose on one call: without `--keep-local`, a "
|
||||||
"deliberately *not* written: the stamp is the baseline for the next comparison, not a "
|
"locally changed path that no `--take-release` names still aborts the run.",
|
||||||
"literal inventory of what is on disk. That is what keeps a skipped file diverging - and "
|
"A path in the old stamp but not the new one is reported as no longer part of the "
|
||||||
"therefore reported - on every later run, rather than quietly reading as current once it "
|
"release and left alone, unless `--prune` is passed, which removes it only if it is "
|
||||||
"has been skipped once. A path taken with `--take-release` is the opposite case and the "
|
"still unchanged since installation.",
|
||||||
"reason the flag exists: it was written, so it matches the digest the stamp records and "
|
"Reports the migration chain the new machinery would owe, but never runs any of it - "
|
||||||
"stops being reported at all. A path in the old stamp but not the new one is reported as no "
|
"there is no `migrate run`.",
|
||||||
"longer part of the release and left alone unless `--prune` is passed, which removes it "
|
"Reports, but does not block on, a crossed compatibility boundary.",
|
||||||
"only if it is still unchanged since installation. Reports the migration chain the new "
|
"The local preconditions - `VERSION`, the release stamp, `.wikitool-kb.json`, no "
|
||||||
"machinery would owe (`chemenu.kb_state.chain` over the *new* tree's "
|
"outstanding migration, a clean working tree - are checked before the source is read. "
|
||||||
"`instructions/migrations/`, read via a `directory` argument to `load_migrations`) but "
|
"Not being a git repository at all is a WARN, not a refusal.",
|
||||||
"never runs any of it - there is no `migrate run`. Refuses before touching the source at "
|
"Never touches git - no commit, no push.",
|
||||||
"all when: `VERSION` or `.wikitool-release.json` (with a `files` block) is missing locally, "
|
"`--dry-run` classifies and reports without writing; a pre-release (`-beta.N`) source "
|
||||||
"`.wikitool-kb.json` is missing, a migration is already outstanding against the *installed* "
|
"needs `--pre`.",
|
||||||
"machinery, or the working tree is dirty (not being a git repository at all is a WARN, not "
|
"An interrupted write is not resumed automatically: compare the tree against the "
|
||||||
"a refusal). Refuses after reading the source when: it carries no "
|
"printed classification and finish or revert by hand.",
|
||||||
"`VERSION`/`.wikitool-release.json`/`files` block, its version is older than or equal to "
|
"The closing report names `instructions/upgrade-instance.md`, which carries the order "
|
||||||
"the installed one (equal is a no-op success), it is a pre-release (`-beta.N`) without "
|
"for everything after the swap and resumes at `instructions sync`.",
|
||||||
"`--pre`, or `--take-release` names a path this run does not classify as locally changed. "
|
),
|
||||||
"Reports, but does not block on, a crossed compatibility boundary. Never touches git - no "
|
failures=(
|
||||||
"commit, no push (invariant 5). The closing report carries no step list of its own: "
|
cli_contract.Failure(
|
||||||
"everything after the swap is one order, written in `instructions/upgrade-instance.md`, "
|
cause="The source's version equals the installed one - a no-op success",
|
||||||
"which the report names and which resumes at `instructions sync`. What a human decides "
|
reaction="",
|
||||||
"*before* the swap - which release, whether to take it, where the tarball comes from - is "
|
code=0,
|
||||||
"`INSTALL.md` § \"Version und Updates\"",
|
),
|
||||||
failures=(cli_contract.Failure(
|
cli_contract.Failure(
|
||||||
label="",
|
cause="Local `VERSION` missing, or no local `.wikitool-release.json` with a `files` "
|
||||||
cause="Missing local `VERSION`/`.wikitool-release.json`(`files`)/`.wikitool-kb.json`, "
|
"block",
|
||||||
"a migration already outstanding against the installed machinery, a dirty working "
|
reaction="Not transient - fix the named precondition and retry. A checkout with "
|
||||||
"tree, a source with no `VERSION`/stamp/`files` block, a source version that is older "
|
"shared git history takes stack updates with `wikitool upstream merge` instead",
|
||||||
"than, equal to, or (without `--pre`) a pre-release relative to the installed one, a "
|
),
|
||||||
"`--take-release` path that is not classified as locally changed (the one refusal a "
|
cli_contract.Failure(
|
||||||
"`--dry-run` also raises), or one or more locally changed files that neither "
|
cause="`.wikitool-kb.json` is missing",
|
||||||
"`--keep-local` nor a `--take-release` answers for",
|
reaction="Run `wikitool migrate baseline <version>`, then retry",
|
||||||
reaction="For every refusal above: fix the named precondition and retry - none of them are "
|
),
|
||||||
"transient. For a rejected `--take-release` path: correct it against the "
|
cli_contract.Failure(
|
||||||
"locally-changed list the refusal prints. For locally changed files, the refusal names "
|
cause="A migration is already outstanding against the *installed* machinery",
|
||||||
"all three answers with the re-run line filled in - `--take-release <path>` to write "
|
reaction="Finish it first - `wikitool migrate status` names it - then retry",
|
||||||
"the release's version over it (which ends the divergence), `--keep-local` to leave "
|
),
|
||||||
"them untouched (repeatable, and it reports the same files again on every subsequent "
|
cli_contract.Failure(
|
||||||
"run until they stop diverging), or reconcile by hand and retry. An interrupted write "
|
cause="The working tree is dirty",
|
||||||
"is not resumed automatically; compare the tree against the printed classification and "
|
reaction="Commit or stash first, then retry",
|
||||||
"finish or revert by hand",
|
),
|
||||||
),),
|
cli_contract.Failure(
|
||||||
|
cause="`<source>` does not exist, fails its `.sha256`, or does not unpack to exactly "
|
||||||
|
"one top-level directory",
|
||||||
|
reaction="Fix the path or re-download the release archive, then retry",
|
||||||
|
),
|
||||||
|
cli_contract.Failure(
|
||||||
|
cause="The source carries no `VERSION`, `.wikitool-release.json` or `files` block",
|
||||||
|
reaction="Point `<source>` at a distribution export, then retry",
|
||||||
|
),
|
||||||
|
cli_contract.Failure(
|
||||||
|
cause="The source's version is older than the installed one, or a pre-release "
|
||||||
|
"without `--pre`",
|
||||||
|
reaction="Not transient - choose another source, or pass `--pre` for a pre-release",
|
||||||
|
),
|
||||||
|
cli_contract.Failure(
|
||||||
|
cause="A `--take-release` path this run does not classify as locally changed - the "
|
||||||
|
"one refusal a `--dry-run` also raises",
|
||||||
|
reaction="Correct it against the locally-changed list the refusal prints, then retry",
|
||||||
|
),
|
||||||
|
cli_contract.Failure(
|
||||||
|
cause="Locally changed files that neither `--keep-local` nor a `--take-release` "
|
||||||
|
"answers for; nothing was written",
|
||||||
|
reaction="Choose one of the three answers the refusal names, re-run lines filled in: "
|
||||||
|
"`--take-release <path>` to write the release's version over it (ends the "
|
||||||
|
"divergence), `--keep-local` to leave them untouched (reported again on every "
|
||||||
|
"later run until they stop diverging), or reconcile by hand and retry",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
examples=(
|
||||||
|
"tools/wikitool dist upgrade ../chemenu-7.1.0.tar.gz --dry-run",
|
||||||
|
"tools/wikitool dist upgrade ../chemenu-7.1.0.tar.gz",
|
||||||
|
"tools/wikitool dist upgrade ../chemenu-7.1.0.tar.gz --take-release tools/README.md",
|
||||||
|
),
|
||||||
|
never=(
|
||||||
|
"Never treat any of the three answers to locally changed files as the default.",
|
||||||
|
),
|
||||||
|
see_also=(
|
||||||
|
"`wikitool version check` - finds out whether an update exists",
|
||||||
|
"`instructions/upgrade-instance.md` - the order after the swap",
|
||||||
|
"`INSTALL.md` § \"Version und Updates\" - which release, whether to take it, where "
|
||||||
|
"the tarball comes from",
|
||||||
|
"`wikitool upstream merge` - the update path for a checkout with shared git history",
|
||||||
|
"`wikitool migrate status` - the migrations the report names",
|
||||||
|
),
|
||||||
))
|
))
|
||||||
@app.command("upgrade")
|
@app.command("upgrade")
|
||||||
def upgrade_command(
|
def upgrade_command(
|
||||||
@@ -1226,6 +1298,12 @@ def run_upgrade(
|
|||||||
dst = config.ROOT / relative
|
dst = config.ROOT / relative
|
||||||
dst.parent.mkdir(parents=True, exist_ok=True)
|
dst.parent.mkdir(parents=True, exist_ok=True)
|
||||||
shutil.copy2(src, dst)
|
shutil.copy2(src, dst)
|
||||||
|
# The new stamp is written whole even after `--keep-local` skipped files: it is the
|
||||||
|
# baseline for the next comparison, not a literal inventory of what is on disk. That
|
||||||
|
# is what keeps a skipped file diverging - and therefore reported - on every later
|
||||||
|
# run, rather than quietly reading as current once it has been skipped once. A
|
||||||
|
# `--take-release` path is the opposite case: it was written, so it matches the
|
||||||
|
# digest recorded here and stops being reported at all.
|
||||||
shutil.copy2(stamp_path, config.ROOT / version_mod.RELEASE_STAMP_FILENAME)
|
shutil.copy2(stamp_path, config.ROOT / version_mod.RELEASE_STAMP_FILENAME)
|
||||||
|
|
||||||
pruned: list[str] = []
|
pruned: list[str] = []
|
||||||
|
|||||||
@@ -89,14 +89,26 @@ def _describe_origin(stamp: Optional[dict]) -> str:
|
|||||||
atomic="Read-only",
|
atomic="Read-only",
|
||||||
budget=cli_contract.Budget.EXEMPT,
|
budget=cli_contract.Budget.EXEMPT,
|
||||||
),
|
),
|
||||||
notes="Development tree, or a distribution with its export date and origin. Bare "
|
notes=(
|
||||||
"`wikitool version` is an alias for this. Read-only, offline, and **exempt from the "
|
"Prints `VERSION` and where this tree came from: \"development tree\" without a "
|
||||||
"Iteration Budget Gate**",
|
"release stamp, otherwise the export date, source commit and repository from "
|
||||||
|
"`.wikitool-release.json`, plus the release page when the stamp records one.",
|
||||||
|
"`--json` prints the version, its compatibility key, the stamp and the update URL.",
|
||||||
|
"Bare `wikitool version` is an alias for this.",
|
||||||
|
"Read-only and offline; exempt from the Iteration Budget Gate.",
|
||||||
|
),
|
||||||
failures=(cli_contract.Failure(
|
failures=(cli_contract.Failure(
|
||||||
label="",
|
|
||||||
cause="`VERSION` is missing or unparseable",
|
cause="`VERSION` is missing or unparseable",
|
||||||
reaction="Fix `VERSION` and retry",
|
reaction="Fix `VERSION` and retry",
|
||||||
),),
|
),),
|
||||||
|
examples=(
|
||||||
|
"tools/wikitool version show",
|
||||||
|
"tools/wikitool version show --json",
|
||||||
|
),
|
||||||
|
see_also=(
|
||||||
|
"`wikitool version check` - asks whether a newer stack exists",
|
||||||
|
"`wikitool version notes` - prints a version's release notes",
|
||||||
|
),
|
||||||
))
|
))
|
||||||
@app.command("show")
|
@app.command("show")
|
||||||
def show_command(
|
def show_command(
|
||||||
@@ -141,22 +153,47 @@ def show_command(
|
|||||||
budget=cli_contract.Budget.EXEMPT,
|
budget=cli_contract.Budget.EXEMPT,
|
||||||
network=cli_contract.Network.YES,
|
network=cli_contract.Network.YES,
|
||||||
),
|
),
|
||||||
notes="Ask the origin's release feed whether a newer stack exists, and whether the step "
|
notes=(
|
||||||
"crosses a compatibility boundary (`state: current|update|migration|ahead`). One of the "
|
"Asks the release feed for its latest release and compares it with `VERSION`: `state` "
|
||||||
"**two** commands in `wikitool` that make a network call, and the only one whose whole job "
|
"is `current`, `update`, `migration` (the step crosses a compatibility boundary) or "
|
||||||
"it is - `version notes` is the other, and only on a distributed instance. Never reached "
|
"`ahead`.",
|
||||||
"implicitly from another command, needs no key, times out, and reports an unreachable feed "
|
"One of the **two** commands in `wikitool` that make a network call, and the only one "
|
||||||
"as an error rather than as \"up to date\". The feed is `$WIKITOOL_UPDATE_URL`, else the "
|
"whose whole job it is - `version notes` is the other, and only on a distributed "
|
||||||
"release stamp's, else the built-in origin; `$WIKITOOL_UPDATE_TOKEN` is only needed if that "
|
"instance.",
|
||||||
"feed is not readable anonymously. Read-only and exempt from the budget gate",
|
"Never reached implicitly from another command, needs no key, and times out after "
|
||||||
failures=(cli_contract.Failure(
|
"`--timeout` seconds (default 10).",
|
||||||
label="",
|
"The feed is `--url`, else `$WIKITOOL_UPDATE_URL`, else the release stamp's, else the "
|
||||||
cause="The feed could not be reached, answered non-JSON, or carried no `tag_name`. "
|
"built-in origin. `$WIKITOOL_UPDATE_TOKEN` is only needed if that feed is not readable "
|
||||||
"**Never** answers \"up to date\" for a question it could not ask",
|
"anonymously.",
|
||||||
reaction="A network failure is transient - retry once, then report it. HTTP 401/403 names "
|
"For `update` or `migration` it prints that applying the release is a separate, manual "
|
||||||
"`$WIKITOOL_UPDATE_TOKEN`; 404 means no release exists yet or the URL points at the "
|
"step (`INSTALL.md` § \"Eine Instanz aktualisieren\").",
|
||||||
"wrong repo",
|
"Read-only; exempt from the Iteration Budget Gate.",
|
||||||
),),
|
),
|
||||||
|
failures=(
|
||||||
|
cli_contract.Failure(
|
||||||
|
cause="`VERSION` is missing or unparseable",
|
||||||
|
reaction="Fix `VERSION` and retry",
|
||||||
|
),
|
||||||
|
cli_contract.Failure(
|
||||||
|
cause="The feed could not be reached, answered non-JSON, or carried no `tag_name`",
|
||||||
|
reaction="A network failure is transient - retry once, then report it. HTTP 401/403 "
|
||||||
|
"names `$WIKITOOL_UPDATE_TOKEN`; 404 means no release exists yet or the URL points "
|
||||||
|
"at the wrong repo",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
examples=(
|
||||||
|
"tools/wikitool version check",
|
||||||
|
"tools/wikitool version check --json",
|
||||||
|
),
|
||||||
|
never=(
|
||||||
|
"Never report \"up to date\" for a check that failed - an unreachable feed is not "
|
||||||
|
"that answer.",
|
||||||
|
),
|
||||||
|
see_also=(
|
||||||
|
"`wikitool dist upgrade` - applies the update this reports",
|
||||||
|
"`wikitool version notes` - prints the release notes",
|
||||||
|
"`INSTALL.md` § \"Version und Updates\" - what the operator decides before an update",
|
||||||
|
),
|
||||||
))
|
))
|
||||||
@app.command("check")
|
@app.command("check")
|
||||||
def check_command(
|
def check_command(
|
||||||
@@ -238,33 +275,53 @@ def check_command(
|
|||||||
budget=cli_contract.Budget.EXEMPT,
|
budget=cli_contract.Budget.EXEMPT,
|
||||||
network=cli_contract.Network.YES,
|
network=cli_contract.Network.YES,
|
||||||
),
|
),
|
||||||
notes="Default: this tree's `VERSION`. The `CHANGES.md` entry where there is one, and where "
|
notes=(
|
||||||
"there is not, the feed's latest release notes. The fallback exists because an instance's "
|
"Prints the `CHANGES.md` entry for `--version` (default: this tree's `VERSION`).",
|
||||||
"`CHANGES.md` is a stub `dist upgrade` never overwrites (`chemenu.ownership.is_upgrade_"
|
"With no such entry, in a tree with a release stamp (a `dist export` tree) and without "
|
||||||
"preserved`), so the local file can never carry the entry - not today and not after any "
|
"`--offline`, it prints the release feed's latest release notes instead. A tree "
|
||||||
"future release, which made the command permanently unanswerable exactly where the release "
|
"without a stamp (a dev checkout) never asks the feed.",
|
||||||
"notes are most needed. It is reached **only with a release stamp present**, i.e. only from "
|
"Only the feed's latest release can be asked for. When that is a different version "
|
||||||
"a `dist export` tree: a dev checkout keeps the plain error, which is what keeps the origin "
|
"than requested, stderr names it and the notes are printed anyway - the expected case "
|
||||||
"repo and CI offline. **stdout carries nothing but the notes**; the line naming the feed "
|
"before an upgrade, where `VERSION` still names the release being left.",
|
||||||
"being asked, and the one naming the release that answered, go to stderr - `release.yml` "
|
"stdout carries nothing but the notes; the line naming the feed being asked and the "
|
||||||
"redirects stdout into the file it posts as the release body. Only the feed's *latest* "
|
"one naming the release that answered go to stderr. `release.yml` redirects stdout "
|
||||||
"release can be asked for (`update_url` is the one URL a stamp records, and composing a "
|
"into the file it posts as the release body.",
|
||||||
"by-tag URL out of it would be guessing at an API shape), so a returned version other than "
|
"`--offline` never asks the feed and fails with the stamp's `release_url` instead.",
|
||||||
"the one asked for is named on stderr and printed anyway - the expected shape before an "
|
"Every failure names the stamp's `release_url` where it has one, so a run that cannot "
|
||||||
"upgrade, where `VERSION` still names the release being left. `--offline` refuses the call "
|
"read the notes is still told where they are.",
|
||||||
"and fails with the stamp's `release_url` instead. Read-only and exempt from the budget gate",
|
"Read-only, safe to retry, and exempt from the Iteration Budget Gate.",
|
||||||
failures=(cli_contract.Failure(
|
),
|
||||||
label="",
|
failures=(
|
||||||
cause="An unparseable `--version`, an unreadable `VERSION` when `--version` is "
|
cli_contract.Failure(
|
||||||
"omitted, or a missing `CHANGES.md`. No entry for the requested version is an error "
|
cause="`--version` is unparseable, `VERSION` is unreadable when `--version` is "
|
||||||
"only where the feed cannot answer either: in a tree with no release stamp (a dev "
|
"omitted, or `CHANGES.md` is missing",
|
||||||
"checkout - write the entry, or `version bump`), with `--offline`, or when the feed "
|
reaction="Fix the named argument or file, then retry",
|
||||||
"could not be reached or returned a release with an empty `body`. Every one of those "
|
),
|
||||||
"failures names the stamp's `release_url` where it has one, so a run that cannot read "
|
cli_contract.Failure(
|
||||||
"the notes is still told where they are",
|
cause="No entry for the requested version in a tree with no release stamp (a dev "
|
||||||
reaction="Fix the named argument or file, then retry. A feed failure is transient - retry "
|
"checkout)",
|
||||||
"once, then read the release page the error names. Safe to retry",
|
reaction="Write the entry, or run `version bump`",
|
||||||
),),
|
),
|
||||||
|
cli_contract.Failure(
|
||||||
|
cause="No entry for the requested version, and `--offline` was passed",
|
||||||
|
reaction="Read the release page the error names",
|
||||||
|
),
|
||||||
|
cli_contract.Failure(
|
||||||
|
cause="No entry for the requested version, and the feed could not be reached or "
|
||||||
|
"returned a release with an empty `body`",
|
||||||
|
reaction="A feed failure is transient - retry once, then read the release page the "
|
||||||
|
"error names",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
examples=(
|
||||||
|
"tools/wikitool version notes",
|
||||||
|
"tools/wikitool version notes --version 7.0.0",
|
||||||
|
"tools/wikitool version notes --offline",
|
||||||
|
),
|
||||||
|
see_also=(
|
||||||
|
"`wikitool version check` - asks the same feed whether a newer stack exists",
|
||||||
|
"`wikitool version bump` - writes the entry this prints",
|
||||||
|
),
|
||||||
))
|
))
|
||||||
@app.command("notes")
|
@app.command("notes")
|
||||||
def notes_command(
|
def notes_command(
|
||||||
@@ -398,55 +455,90 @@ def _no_entry_message(wanted: Version, has_stamp: bool, release_url: str) -> str
|
|||||||
atomic="No - `VERSION` then `CHANGES.md`",
|
atomic="No - `VERSION` then `CHANGES.md`",
|
||||||
budget=cli_contract.Budget.COUNTED,
|
budget=cli_contract.Budget.COUNTED,
|
||||||
),
|
),
|
||||||
notes="`VERSION` gets a `-beta.N` suffix, never a second fresh number per bump. "
|
notes=(
|
||||||
"`--major/--minor/--patch` is **max-wins escalation** against the last release "
|
"`VERSION` gets a `-beta.N` suffix: one running candidate between two releases, never "
|
||||||
|
"a fresh number per bump.",
|
||||||
|
"`--major`/`--minor`/`--patch` is **max-wins escalation** against the last release "
|
||||||
"(patch < minor < major): a `--patch` on a MINOR candidate only advances `N`, and "
|
"(patch < minor < major): a `--patch` on a MINOR candidate only advances `N`, and "
|
||||||
"escalation never steps back down. Opens the matching `CHANGES.md` entry on the first bump "
|
"escalation never steps back down.",
|
||||||
"of a candidate (heading, date, author, and a machine-managed `<!-- wikitool:bumps -->` "
|
"The first bump of a candidate opens its `CHANGES.md` entry - heading, date, author, "
|
||||||
"list of every `--title` collected so far, graded by `--impact`, default `medium`) and "
|
"and a machine-managed `<!-- wikitool:bumps -->` list of every `--title` collected so "
|
||||||
"updates that same entry in place on every later bump of the same candidate - one entry per "
|
"far, graded by `--impact` (default `medium`). Every later bump of the same candidate "
|
||||||
"candidate, not one per bump. The list renders grouped under "
|
"updates that entry in place: one entry per candidate, not one per bump.",
|
||||||
"`**High/Medium/Low impact**` headings (empty groups omitted), except when every bump so "
|
"The bump list renders grouped under `**High/Medium/Low impact**` headings, empty "
|
||||||
"far is `medium`, where it stays the flat, ungrouped list the region always had - "
|
"groups omitted - except while every bump is `medium`, where it stays one flat list. "
|
||||||
"`version regrade` corrects a grade after the fact. Refuses more or fewer than one part, an "
|
"`version regrade` corrects a grade after the fact.",
|
||||||
"empty title, an unknown `--impact`, and a `VERSION`/newest-changelog-entry mismatch. "
|
"Writes the heading, the bump list and the breaking/migration lines; the entry's prose "
|
||||||
"Compatibility follows the **leftmost non-zero component** of the candidate's base, which "
|
"is left to the author.",
|
||||||
"for this stack (at `1.0.0` and up) means MAJOR: PATCH is a fix, MINOR a compatible "
|
"Compatibility follows the **leftmost non-zero component** of the candidate's base, "
|
||||||
"capability, MAJOR a version that is **not a drop-in replacement** - any hand-work on "
|
"which for this stack (at `1.0.0` and up) means MAJOR: PATCH is a fix, MINOR a "
|
||||||
"update, or a downgrade that no longer works. Whether content must be migrated is a second, "
|
"compatible capability, MAJOR a version that is **not a drop-in replacement** - any "
|
||||||
"independent question. The bump that first escalates a candidate past the boundary requires "
|
"hand-work on update, or a downgrade that no longer works. Whether content must be "
|
||||||
"`--breaking \"<what stops working>\"` and, on top of it, a migration document targeting "
|
"migrated is a second, independent question.",
|
||||||
"the candidate's base or `--no-migration \"<reason>\"`; both are anchored just above the "
|
"The bump that first escalates a candidate past the boundary requires `--breaking "
|
||||||
"bump list, persist over later bumps of the same candidate without being repeated, and are "
|
"\"<what stops working>\"` and, on top of it, a migration document targeting the "
|
||||||
"refused on a bump that crosses nothing at all. The two then behave differently on a "
|
"candidate's base or `--no-migration \"<reason>\"`. Both are anchored just above the "
|
||||||
"*second* crossing, because they answer different questions: a further `--breaking` "
|
"bump list, persist over later bumps of the same candidate without being repeated, and "
|
||||||
"**joins** the ones already recorded (one reason per crossing - rendered flat on the marker "
|
"are refused on a bump that crosses nothing at all.",
|
||||||
"line while there is only one, as bullets under a bare marker from the second onward, and "
|
"On a later crossing of the same candidate, a further `--breaking` **joins** the "
|
||||||
"repeating a reason verbatim is a no-op), while a further `--no-migration` **replaces** the "
|
"reasons already recorded (flat on the marker line while there is one, bullets under a "
|
||||||
"single line that says whether content has to change. A candidate crossing the boundary "
|
"bare marker from the second on; repeating a reason verbatim is a no-op), while a "
|
||||||
"twice is the normal shape of a long-running one, and each crossing is a separate thing an "
|
"further `--no-migration` **replaces** the single migration line.",
|
||||||
"operator has to act on; whether content migrates stays one yes/no about the candidate as a "
|
"`--migration-required` retracts the running candidate's `--no-migration` line; it "
|
||||||
"whole. There is deliberately no retraction path for a single accumulated `--breaking` "
|
"needs a migration document already targeting the new base. Nothing retracts a "
|
||||||
"reason - `--migration-required` retracts the migration line, and nothing retracts a "
|
"recorded `--breaking` reason.",
|
||||||
"breaking one. A later bump of the same candidate that finds out `--no-migration` was wrong "
|
"Enforces that a crossing documents itself, never that the part was chosen correctly.",
|
||||||
"after all retracts that line with `--migration-required` instead of restating "
|
"Not idempotent: every successful run escalates or continues the candidate again.",
|
||||||
"`--no-migration` - refused without a migration document already targeting the new base, "
|
"`--dry-run` reports the step without writing.",
|
||||||
"and without an existing `--no-migration` line to retract. Which part a change earns stays "
|
),
|
||||||
"a judgment call: the command enforces that a crossing documents itself, never that the "
|
failures=(
|
||||||
"part was chosen correctly",
|
cli_contract.Failure(
|
||||||
failures=(cli_contract.Failure(
|
cause="Not exactly one of `--major`/`--minor`/`--patch`, an empty `--title`, or an "
|
||||||
label="",
|
"unknown `--impact`",
|
||||||
cause="More or fewer than one of `--major/--minor/--patch`, an empty `--title`, an "
|
reaction="Nothing was written - fix the argument and retry once",
|
||||||
"unknown `--impact`, a missing `VERSION`/`CHANGES.md`, `VERSION` and the changelog's "
|
),
|
||||||
"newest entry naming different versions, an escalation to a boundary crossing without "
|
cli_contract.Failure(
|
||||||
"`--breaking` or with neither a migration document nor `--no-migration`, "
|
cause="`VERSION` or `CHANGES.md` is missing, or `VERSION` and the changelog's "
|
||||||
"`--breaking`/`--no-migration` on a bump that crosses nothing, or `--migration-required` "
|
"newest entry name different versions",
|
||||||
"combined with `--no-migration`, on a bump with no running candidate, with no "
|
reaction="Nothing was written - fix whichever is wrong, then retry",
|
||||||
"`--no-migration` line to retract, or without a migration document already targeting "
|
),
|
||||||
"the new base",
|
cli_contract.Failure(
|
||||||
reaction="**Not idempotent**: a second run escalates or continues the candidate again. If "
|
cause="An escalation to a boundary crossing without `--breaking`, or with neither a "
|
||||||
"the outcome is uncertain, read `VERSION` and the top of `CHANGES.md` before retrying",
|
"migration document targeting the new base nor `--no-migration`",
|
||||||
),),
|
reaction="Nothing was written - add what the error asks for, or, if nothing actually "
|
||||||
|
"breaks, choose a smaller part",
|
||||||
|
),
|
||||||
|
cli_contract.Failure(
|
||||||
|
cause="`--breaking` or `--no-migration` on a bump that crosses nothing",
|
||||||
|
reaction="Nothing was written - drop the flag and retry",
|
||||||
|
),
|
||||||
|
cli_contract.Failure(
|
||||||
|
cause="`--migration-required` combined with `--no-migration`, on a bump with no "
|
||||||
|
"running candidate, with no `--no-migration` line to retract, or without a "
|
||||||
|
"migration document already targeting the new base",
|
||||||
|
reaction="Nothing was written - write the migration document or fix the "
|
||||||
|
"combination, then retry",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
examples=(
|
||||||
|
'tools/wikitool version bump --patch --title "Fix the lint report path" --impact low',
|
||||||
|
'tools/wikitool version bump --minor --title "New command: wikitool review" --dry-run',
|
||||||
|
'tools/wikitool version bump --major --title "Rename --gate-file to --remotes-file" '
|
||||||
|
'--breaking "--gate-file is gone; scripts must pass --remotes-file" '
|
||||||
|
'--no-migration "no content changes"',
|
||||||
|
),
|
||||||
|
never=(
|
||||||
|
"Never re-run after an uncertain outcome without first reading `VERSION` and the top "
|
||||||
|
"of `CHANGES.md` - a second run escalates or continues the candidate again.",
|
||||||
|
"Never hand-edit `VERSION` or the machine-written parts of the entry (heading, bump "
|
||||||
|
"list, breaking and migration lines); `--migration-required` is the only way to take "
|
||||||
|
"the migration line back.",
|
||||||
|
),
|
||||||
|
see_also=(
|
||||||
|
"`wikitool version regrade` - corrects an `--impact` grade",
|
||||||
|
"`wikitool version release` - fixes the candidate into a release",
|
||||||
|
"`instructions/migrate-corpus.md` - how a migration document is written",
|
||||||
|
),
|
||||||
))
|
))
|
||||||
@app.command("bump")
|
@app.command("bump")
|
||||||
def bump_command(
|
def bump_command(
|
||||||
@@ -653,28 +745,51 @@ def bump_command(
|
|||||||
atomic="No - `VERSION` then `CHANGES.md`",
|
atomic="No - `VERSION` then `CHANGES.md`",
|
||||||
budget=cli_contract.Budget.COUNTED,
|
budget=cli_contract.Budget.COUNTED,
|
||||||
),
|
),
|
||||||
notes="Ends the pre-release phase `version bump` started. Without `--title` the heading "
|
notes=(
|
||||||
"keeps whichever bump last set it; with it, the heading's title is replaced - the normal "
|
"Strips `VERSION`'s `-beta.N` suffix - the candidate's base becomes the release - and "
|
||||||
"case for a candidate that collected several bump titles, since the entry wants a "
|
"closes the candidate's `CHANGES.md` entry.",
|
||||||
"summarising heading rather than the most recent one. Leaves the entry's machine-managed "
|
"Without `--title` the heading keeps whichever bump last set it; `--title` replaces it "
|
||||||
"bump-title list untouched, as the record of what happened. Refuses when the candidate "
|
"- the normal case for a candidate that collected several bumps, whose entry wants a "
|
||||||
"collected two or more bumps and the entry still carries no summary paragraph (at least 200 "
|
"summarising heading rather than the most recent one.",
|
||||||
"non-whitespace characters) between the bump list and the first `### <bump title>` "
|
"Leaves the entry's machine-managed bump list untouched, as the record of what "
|
||||||
"changeset heading; a candidate with exactly one bump is exempt, since there its own "
|
"happened.",
|
||||||
"changeset already is the summary. `--dry-run` runs this check too and reports the same "
|
"From two bumps on, requires a summary paragraph (at least 200 non-whitespace "
|
||||||
"refusal. Commits nothing and pushes nothing (invariant 5) - the following `publish` moves "
|
"characters) between the bump list and the first `### <bump title>` changeset "
|
||||||
"`VERSION` onto `main`, which `release.yml` reacts to. Refuses when `VERSION` is already a "
|
"heading. A candidate with exactly one bump is exempt, since there its own changeset "
|
||||||
"release (no running candidate to fix), or when the changelog's newest entry does not match "
|
"already is the summary. `--dry-run` runs this check too.",
|
||||||
"`VERSION`",
|
"Commits nothing and pushes nothing. The following `publish` moves `VERSION` onto "
|
||||||
failures=(cli_contract.Failure(
|
"`main`, which `release.yml` reacts to.",
|
||||||
label="",
|
"Not idempotent: a second run fails once the suffix is gone.",
|
||||||
cause="A missing `VERSION`/`CHANGES.md`, `VERSION` already a release (no running "
|
),
|
||||||
"candidate), `VERSION` and the changelog's newest entry naming different versions, or "
|
failures=(
|
||||||
"(from two bumps on) an entry with no summary paragraph above the changesets",
|
cli_contract.Failure(
|
||||||
reaction="**Not idempotent**: a second run fails outright once the suffix is gone. If the "
|
cause="`VERSION` is already a release - there is no running candidate to fix",
|
||||||
"outcome is uncertain, read `VERSION` before retrying - a release-shaped `VERSION` "
|
reaction="After an uncertain run this means it already ran; otherwise there is "
|
||||||
"means it already ran",
|
"nothing to release",
|
||||||
),),
|
),
|
||||||
|
cli_contract.Failure(
|
||||||
|
cause="`VERSION` or `CHANGES.md` is missing, or `VERSION` and the changelog's newest "
|
||||||
|
"entry name different versions",
|
||||||
|
reaction="Fix whichever is wrong, then retry",
|
||||||
|
),
|
||||||
|
cli_contract.Failure(
|
||||||
|
cause="Two or more bumps and no summary paragraph above the changesets",
|
||||||
|
reaction="Write a short summary paragraph right below the bump list, then retry",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
examples=(
|
||||||
|
"tools/wikitool version release --dry-run",
|
||||||
|
'tools/wikitool version release --title "Command records rewritten for agents"',
|
||||||
|
),
|
||||||
|
never=(
|
||||||
|
"Never retry after an uncertain outcome without reading `VERSION` first - a "
|
||||||
|
"release-shaped `VERSION` means it already ran.",
|
||||||
|
),
|
||||||
|
see_also=(
|
||||||
|
"`wikitool version bump` - opens and continues the candidate",
|
||||||
|
"`wikitool version regrade` - shows the bump list the summary sits under",
|
||||||
|
"`wikitool publish` - moves the released `VERSION` onto `main`",
|
||||||
|
),
|
||||||
))
|
))
|
||||||
@app.command("release")
|
@app.command("release")
|
||||||
def release_command(
|
def release_command(
|
||||||
@@ -775,26 +890,45 @@ def release_command(
|
|||||||
atomic="No - `CHANGES.md` only, and only when indices are given",
|
atomic="No - `CHANGES.md` only, and only when indices are given",
|
||||||
budget=cli_contract.Budget.EXEMPT_WITHOUT_ARGS,
|
budget=cli_contract.Budget.EXEMPT_WITHOUT_ARGS,
|
||||||
),
|
),
|
||||||
notes="1-based rendered position (no arguments - the correction path for a `--impact` "
|
notes=(
|
||||||
"judgement made at bump time), or change one or more of them in a single call: "
|
"Without arguments: lists the running candidate's bump titles with their grade, "
|
||||||
"`version regrade 3 7 --impact high` grades both against a single read of today's list, not "
|
"numbered by 1-based position in the rendered list (High before Medium before Low, "
|
||||||
"position 3 first and then position 7 against whatever that produced. Touches only the "
|
"chronological within a grade). Read-only and exempt from the Iteration Budget Gate.",
|
||||||
"topmost entry's bump list - never `VERSION`, never any other part of `CHANGES.md`. The "
|
"With indices and `--impact`: sets the grade of every named position in one call, all "
|
||||||
"bare listing is read-only and exempt from the Iteration Budget Gate, like `version notes`; "
|
"resolved against a single read of the current list - `version regrade 3 7 --impact "
|
||||||
"a call with indices writes `CHANGES.md` and is counted like `version bump`. Refuses an "
|
"high` grades what is at 3 and 7 now, not 7 after 3 has moved. Writes `CHANGES.md` and "
|
||||||
"index outside the rendered list's range, an unknown `--impact`, indices given without "
|
"is counted by the Iteration Budget Gate.",
|
||||||
"`--impact`, a missing `VERSION`/`CHANGES.md`, a `VERSION`/newest-changelog-entry mismatch, "
|
"The correction path for an `--impact` grade judged at bump time.",
|
||||||
"or a topmost entry with no bump list at all",
|
"Touches only the topmost entry's bump list - never `VERSION`, never any other part of "
|
||||||
failures=(cli_contract.Failure(
|
"`CHANGES.md`.",
|
||||||
label="",
|
"A write is not idempotent against a changed list: after a first success, the same "
|
||||||
cause="A missing `VERSION`/`CHANGES.md`, `VERSION` and the changelog's newest entry "
|
"indices may name different bumps.",
|
||||||
"naming different versions, a topmost entry with no bump list, an index outside the "
|
),
|
||||||
"rendered list's range, indices given without `--impact`, or an unknown `--impact`",
|
failures=(
|
||||||
reaction="The bare listing never writes anything. A write is **not idempotent** against a "
|
cli_contract.Failure(
|
||||||
"changed list: re-running the same indices after a first success regrades whatever is "
|
cause="`VERSION` or `CHANGES.md` is missing, `VERSION` and the changelog's newest "
|
||||||
"at those positions *now*, which may no longer be the same bumps - list again before "
|
"entry name different versions, or the topmost entry has no bump list",
|
||||||
"retrying",
|
reaction="Nothing was written - fix whichever is wrong, then retry",
|
||||||
),),
|
),
|
||||||
|
cli_contract.Failure(
|
||||||
|
cause="An index outside the rendered list's range, indices given without "
|
||||||
|
"`--impact`, or an unknown `--impact`",
|
||||||
|
reaction="Nothing was written - list again with the bare command, then retry once "
|
||||||
|
"with corrected arguments",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
examples=(
|
||||||
|
"tools/wikitool version regrade",
|
||||||
|
"tools/wikitool version regrade 3 7 --impact high",
|
||||||
|
),
|
||||||
|
never=(
|
||||||
|
"Never re-run the same indices after a write without listing again first - the "
|
||||||
|
"positions may have moved.",
|
||||||
|
),
|
||||||
|
see_also=(
|
||||||
|
"`wikitool version bump` - sets the grade in the first place",
|
||||||
|
"`wikitool version release` - refuses without a summary above this list",
|
||||||
|
),
|
||||||
))
|
))
|
||||||
@app.command("regrade")
|
@app.command("regrade")
|
||||||
def regrade_command(
|
def regrade_command(
|
||||||
|
|||||||
Reference in new issue
Block a user