diff --git a/AGENTS.md b/AGENTS.md index 1650631..b2a1006 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -291,7 +291,10 @@ Every `tools/wikitool` call has exactly four outcomes: 1. **Success (exit 0).** Continue. 2. **Validation error (exit 1 with an `ERROR` line).** Not transient - re-running unchanged - fails identically. Read the message, fix the cause, retry **once** with corrected input. + fails identically. The `ERROR` line on stdout is followed by the command's ON FAILURE + reaction(s) on stderr - the same text `wikitool -h` prints, without a second call - + or a bare `see: wikitool -h` pointer where the record has none yet. Read the + message, fix the cause, retry **once** with corrected input. 3. **User clearance required (exit 42).** Not an error and not yours to resolve: show the command's output to the user verbatim and stop. See [Gates](#gates). 4. **Unexpected error (timeout, crash, interrupted process).** Do not guess whether it diff --git a/CHANGES.md b/CHANGES.md index 063eef8..1834e98 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -59,7 +59,7 @@ concern - readable here, never shipped as something to parse. --- -## 7.1.0-beta.21 - 2026-09-26 - Command records: NOTES is always a tuple of bullets; every record's examples are tested +## 7.1.0-beta.22 - 2026-09-26 - fail() prints the command's ON FAILURE lines on stderr **Author:** Torben Nehmer @@ -70,6 +70,7 @@ concern - readable here, never shipped as something to parse. **Medium impact** - CalDAV task-tracker provider (Nextcloud Tasks, iOS Reminders); review reports unknown-value findings instead of skipping them - dist export no longer cuts the dist export record out of the shipped tools/CONTRACT.md +- fail() prints the command's ON FAILURE lines on stderr **Low impact** - version bump no longer points at version release in its output @@ -342,6 +343,21 @@ over the real registry hold what the rewrite established: every command has at l example, and every command with a gate shows how its clearance is passed back in (`--confirm`, `--confirm-rebase` or `--resume`). +### fail() prints the command's ON FAILURE lines on stderr + +`_util.fail()` used to print only its `ERROR` line; the reaction a caller needs the moment a +command declines lived one lookup away, in `wikitool -h`'s ON FAILURE section - exactly +the lookup AGENTS.md's own tool error contract already warned is the one most likely to be +skipped in the heat of a failure. `fail()` now prints that section's exit-1 causes (each with a +reaction) right after the `ERROR` line, on stderr and as plain text rather than through Rich - a +reaction can carry a literal `[--flag]`, which Rich would otherwise read as markup. A record +with no exit-1 cause of its own falls back to a bare `see: wikitool -h` pointer; +`docs contract` is the one real command that hits it today. Nothing about stdout changes: a +command's output on success, or up to and including its `ERROR` line on failure, is +byte-identical to before. `cli.py`'s and `_util.py`'s lookup of the running command's +`cli_contract` path is now one shared function, `cli_contract.path_of`, in place of a private +copy that used to live only in `cli.py`. + --- ## 7.0.0 - 2026-09-22 - Task-Tracker-Anbindung: Vorhaben als Seitenart, Verpflichtungsschicht, Weekly Review als Read-Time-Join diff --git a/VERSION b/VERSION index 1af214d..f367546 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.1.0-beta.21 +7.1.0-beta.22 diff --git a/tools/CONTRACT.md b/tools/CONTRACT.md index 68ea468..95de8fe 100644 --- a/tools/CONTRACT.md +++ b/tools/CONTRACT.md @@ -74,6 +74,11 @@ tools/wikitool -h way, for a human or an agent. Bare `tools/wikitool -h` prints the index below plus a pointer back to this form. +A call that ends through `_util.fail()` (exit 1) prints its `ERROR` line to stdout as before, then +its record's ON FAILURE reaction(s) to stderr, in the same ` -> ` form `-h` prints +- so the reaction is in front of the caller without a second `-h` call. A record with no exit-1 +cause of its own falls back to a bare `see: wikitool -h` pointer. + ## Commands diff --git a/tools/chemenu/cli.py b/tools/chemenu/cli.py index 5fc48b9..3ce4fb8 100644 --- a/tools/chemenu/cli.py +++ b/tools/chemenu/cli.py @@ -189,21 +189,6 @@ app.command("sync")(git_publish.sync_command) app.command("doctor")(doctor.doctor_command) -def _contract_path(ctx) -> str: - """The dotted `cli_contract` path for `ctx`'s command (`"xref add"`, - `"new"`), built by walking up the Click context chain and collecting each - level's own `info_name` - never from `ctx.command_path`, which is - prefixed with whatever this process's argv[0] happened to be (`wikitool`, - `cli.py`, `-c` under a `python -c` snippet, ...) and would make path - resolution depend on how the CLI was invoked.""" - parts: list[str] = [] - node = ctx - while node.parent is not None: - parts.append(node.info_name) - node = node.parent - return " ".join(reversed(parts)) - - def _render_options_text(command, ctx) -> str: """Click's own Arguments/Options sections, plain-formatted, for splicing into a `cli_contract` record's OPTIONS section. A fixed width (not the @@ -264,7 +249,7 @@ try: if ctx.parent is None: formatter.write(_render_root_help()) return - record = cli_contract.get(_contract_path(ctx)) + record = cli_contract.get(cli_contract.path_of(ctx)) if record is None: _original_format_help(self, ctx, formatter) return diff --git a/tools/chemenu/cli_contract.py b/tools/chemenu/cli_contract.py index c9c2361..248a91d 100644 --- a/tools/chemenu/cli_contract.py +++ b/tools/chemenu/cli_contract.py @@ -77,6 +77,12 @@ class Failure: that is not one (an unreachable remote reported and skipped). An empty `reaction` renders the EXIT STATUS line only. + A `code: 1` entry's `reaction` is not only read from `-h`: `_util.fail()` + prints it on stderr, right after the `ERROR` line, the moment the command + actually fails (`render_failure_hint`). Write it to stand on its own at + that moment too, not only next to `cause` in a document someone is + reading end to end. + `label` names the usage form a cause belongs to (`"new project"`) and is rendered as a `