From b9c22f783f8e201eab87a6a5caf88721dcecdaac Mon Sep 17 00:00:00 2001 From: Torben Nehmer Date: Sat, 26 Sep 2026 08:57:03 +0200 Subject: [PATCH] tools: command records, Workshop runs and session budget group - examples, prohibitions (#142) Files changed: - CHANGES.md - VERSION - tools/CONTRACT.md - tools/chemenu/commands/run_budget.py - tools/chemenu/commands/work_cmd.py --- CHANGES.md | 10 ++- VERSION | 2 +- tools/CONTRACT.md | 84 ++++++++++++++++++++++--- tools/chemenu/commands/run_budget.py | 32 ++++++++-- tools/chemenu/commands/work_cmd.py | 93 +++++++++++++++++++++------- 5 files changed, 185 insertions(+), 36 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index e488fbd..9f6fd83 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -59,7 +59,7 @@ concern - readable here, never shipped as something to parse. --- -## 7.1.0-beta.14 - 2026-09-26 - Command records, Raw material and uploads group: one line per cause, examples, prohibitions +## 7.1.0-beta.15 - 2026-09-26 - Command records, Workshop runs and session budget group: examples, prohibitions **Author:** Torben Nehmer @@ -83,6 +83,7 @@ concern - readable here, never shipped as something to parse. - Command records, Finding and checking group: one line per cause, examples, prohibitions - Command records, Provenance group: examples, exit lines per cause - Command records, Raw material and uploads group: one line per cause, examples, prohibitions +- Command records, Workshop runs and session budget group: examples, prohibitions ### CalDAV task-tracker provider (Nextcloud Tasks, iOS Reminders); review reports unknown-value findings instead of skipping them @@ -279,6 +280,13 @@ reaction and in NEVER, what `raw/CONTRACT.md` already asks of an agent: show the wait, since only the user can tell a new edition from a second source. `upload accept` states its gate's shape itself instead of pointing at the Mass-Update Gate's. +### Command records, Workshop runs and session budget group: examples, prohibitions + +`work new`, `work close`, `budget status` and `budget reset` rewritten the same way; text only. +`budget reset` now carries AGENTS.md invariant 6's rule as NEVER - it is never run on an agent's +own initiative to get past a budget refusal - and `work close` states the caller's side of its +`--yes`: the run's conclusions are in `kb/` first. + --- ## 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 29f89c6..9485aca 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.1.0-beta.14 +7.1.0-beta.15 diff --git a/tools/CONTRACT.md b/tools/CONTRACT.md index b435153..9c43183 100644 --- a/tools/CONTRACT.md +++ b/tools/CONTRACT.md @@ -1734,18 +1734,42 @@ Scaffold `work//` for one workshop run. - budget: counted - network: no +**EXAMPLES** + +- `tools/wikitool work new --input raw/2026/09/handbuch` +- `tools/wikitool work new --key migrate-7.0.0` +- `tools/wikitool work new --input raw/2026/09/handbuch --again` + **EXIT STATUS** - 0 success -- 1 Neither or both of `--input`/`--key` given, `--input` outside `raw/`, a `--key` that is empty or starts with `ingest-`, or the workshop already exists +- 1 Neither or both of `--input`/`--key` given, or a `--key` that is empty or starts with `ingest-` +- 1 `--input` is outside `raw/`, does not exist, or is `raw/` itself (an empty run key) +- 1 The workshop already exists **ON FAILURE** -- Neither or both of `--input`/`--key` given, `--input` outside `raw/`, a `--key` that is empty or starts with `ingest-`, or the workshop already exists -> A collision is not transient: resume the existing run instead, or pass `--again` if the tree itself changed. Never create a numbered variant by hand +- Neither or both of `--input`/`--key` given, or a `--key` that is empty or starts with `ingest-` -> Fix the argument and retry once +- `--input` is outside `raw/`, does not exist, or is `raw/` itself (an empty run key) -> Point `--input` at material under `raw/`, or use `--key` for a run with no raw input +- The workshop already exists -> A collision is not transient: resume the existing run instead, or pass `--again` if the tree itself changed + +**NEVER** + +- Never create a numbered variant of a run key by hand. **NOTES** -Refuses a collision instead of suffixing it, and writes the required `README.md` + `plan.md`. `--input` derives the run key from the path below `raw/` (an ingest); `--key` names it outright for a run with no raw input - a migration or a sweep across `kb/` - and may not start with `ingest-`, which stays reserved for derived keys. Exactly one of the two. `--again` opens a dated second pass over a tree that has itself changed. See `work/CONTRACT.md` +- Creates `work//` with the required `README.md` and `plan.md`. +- `--input` derives the run key from the path below `raw/` (an ingest); `--key` names it outright for a run with no raw input - a migration or a sweep across `kb/` - and may not start with `ingest-`, which stays reserved for derived keys. Exactly one of the two. +- Refuses a collision instead of suffixing it. +- `--again` opens a dated second pass (`-`) over a tree that has itself changed. +- `--dry-run` reports the run key and files without writing. + +**SEE ALSO** + +- `work/CONTRACT.md` - run keys, required files, how a run closes +- `wikitool work close` - deletes the run when it is done +- `instructions/ingest-large-tree.md` - the ingest that opens a run #### `work close` @@ -1763,18 +1787,37 @@ Delete a finished workshop. - budget: counted - network: no +**EXAMPLES** + +- `tools/wikitool work close --run-key ingest-2026-09-handbuch --dry-run` +- `tools/wikitool work close --run-key ingest-2026-09-handbuch --yes` + **EXIT STATUS** - 0 success -- 1 Unknown run key, or `--yes` was not passed +- 1 Unknown run key +- 1 `--yes` was not passed - the output lists what would be lost **ON FAILURE** -- Unknown run key, or `--yes` was not passed -> For "not confirmed": check the listed files are no longer needed, confirm the conclusions are in `kb/`, then re-run with `--yes` +- Unknown run key -> Check `ls work/` for the open runs, then retry once +- `--yes` was not passed - the output lists what would be lost -> Check the listed files are no longer needed, confirm the conclusions are in `kb/`, then re-run with `--yes` + +**NEVER** + +- Never pass `--yes` before the run's conclusions are in `kb/`. **NOTES** -Lists what would be lost and requires `--yes`, because nothing in it is recoverable from the rest of the repo - the durable conclusions must already be in `kb/` +- Deletes `work//` recursively. +- Without `--yes` it lists what would be lost and refuses: nothing in a workshop is recoverable from the rest of the repo, so the durable conclusions must already be in `kb/`. +- `--dry-run` lists the files without deleting. +- Not idempotent: once deleted, the run key is unknown. + +**SEE ALSO** + +- `work/CONTRACT.md` - how a run closes +- `wikitool work new` - opens a run #### `budget status` @@ -1792,13 +1835,23 @@ Show the current session's `wikitool` call count and recent command history. - budget: exempt - network: no +**EXAMPLES** + +- `tools/wikitool budget status` + **EXIT STATUS** - 0 success **NOTES** -Recent command history is never counted against the budget. Never fails. Safe to retry freely. +- Shows the current session's `wikitool` call count and its recent command history. +- Never counted against the budget; never fails; safe to retry freely. + +**SEE ALSO** + +- `instructions/session-setup.md` - scoping the budget to a task +- `instructions/gates.md` - what to do when the Iteration Budget Gate refuses #### `budget reset` @@ -1816,6 +1869,10 @@ Clear the current session's (or every session's) iteration budget state. - budget: counted - network: no +**EXAMPLES** + +- `tools/wikitool budget reset --yes # only after the user approved it` + **EXIT STATUS** - 0 success @@ -1825,9 +1882,20 @@ Clear the current session's (or every session's) iteration budget state. - `--yes` not passed -> Get the user's approval, then re-run with `--yes` +**NEVER** + +- Never run it on your own initiative to get past a budget or loop-breaker refusal. + **NOTES** -Requires `--yes`: clearing the counter is itself a way around the gate, so it needs the same explicit human approval +- Clears the current session's iteration budget state; `--all` deletes every session's. +- Requires `--yes`: clearing the counter is itself a way around the Iteration Budget Gate, so it needs the same explicit human approval. +- Counted against the budget like any other call. + +**SEE ALSO** + +- `wikitool budget status` - the current count +- `instructions/gates.md` - why `budget reset` is not the escape hatch ### Types, instructions and docs diff --git a/tools/chemenu/commands/run_budget.py b/tools/chemenu/commands/run_budget.py index bca47e1..783d5c2 100644 --- a/tools/chemenu/commands/run_budget.py +++ b/tools/chemenu/commands/run_budget.py @@ -333,9 +333,18 @@ def refund() -> None: atomic="Read-only", budget=cli_contract.Budget.EXEMPT, ), - notes="Recent command history is never counted against the budget. Never fails. Safe to " - "retry freely.", + notes=( + "Shows the current session's `wikitool` call count and its recent command history.", + "Never counted against the budget; never fails; safe to retry freely.", + ), failures=(), + examples=( + "tools/wikitool budget status", + ), + see_also=( + "`instructions/session-setup.md` - scoping the budget to a task", + "`instructions/gates.md` - what to do when the Iteration Budget Gate refuses", + ), )) def status_command(): """Show the current session's call count and recent command history.""" @@ -371,13 +380,26 @@ def reset_message() -> str: atomic="Read/rewrite of one JSON file (or its deletion, with `--all`)", budget=cli_contract.Budget.COUNTED, ), - notes="Requires `--yes`: clearing the counter is itself a way around the gate, so it needs " - "the same explicit human approval", + notes=( + "Clears the current session's iteration budget state; `--all` deletes every session's.", + "Requires `--yes`: clearing the counter is itself a way around the Iteration Budget " + "Gate, so it needs the same explicit human approval.", + "Counted against the budget like any other call.", + ), failures=(cli_contract.Failure( - label="", cause="`--yes` not passed", reaction="Get the user's approval, then re-run with `--yes`", ),), + examples=( + "tools/wikitool budget reset --yes # only after the user approved it", + ), + never=( + "Never run it on your own initiative to get past a budget or loop-breaker refusal.", + ), + see_also=( + "`wikitool budget status` - the current count", + "`instructions/gates.md` - why `budget reset` is not the escape hatch", + ), )) def reset_command( all_sessions: bool = typer.Option( diff --git a/tools/chemenu/commands/work_cmd.py b/tools/chemenu/commands/work_cmd.py index 2481781..6c9fc0f 100644 --- a/tools/chemenu/commands/work_cmd.py +++ b/tools/chemenu/commands/work_cmd.py @@ -142,19 +142,48 @@ Cut the tree into units. One unit does one job and becomes one source page. A un atomic="Yes - one directory with two files", budget=cli_contract.Budget.COUNTED, ), - notes="Refuses a collision instead of suffixing it, and writes the required `README.md` + " - "`plan.md`. `--input` derives the run key from the path below `raw/` (an ingest); `--key` " - "names it outright for a run with no raw input - a migration or a sweep across `kb/` - and " - "may not start with `ingest-`, which stays reserved for derived keys. Exactly one of the " - "two. `--again` opens a dated second pass over a tree that has itself changed. See " - "`work/CONTRACT.md`", - failures=(cli_contract.Failure( - label="", - cause="Neither or both of `--input`/`--key` given, `--input` outside `raw/`, a " - "`--key` that is empty or starts with `ingest-`, or the workshop already exists", - reaction="A collision is not transient: resume the existing run instead, or pass " - "`--again` if the tree itself changed. Never create a numbered variant by hand", - ),), + notes=( + "Creates `work//` with the required `README.md` and `plan.md`.", + "`--input` derives the run key from the path below `raw/` (an ingest); `--key` names it " + "outright for a run with no raw input - a migration or a sweep across `kb/` - and may " + "not start with `ingest-`, which stays reserved for derived keys. Exactly one of the " + "two.", + "Refuses a collision instead of suffixing it.", + "`--again` opens a dated second pass (`-`) over a tree that has itself " + "changed.", + "`--dry-run` reports the run key and files without writing.", + ), + failures=( + cli_contract.Failure( + cause="Neither or both of `--input`/`--key` given, or a `--key` that is empty or " + "starts with `ingest-`", + reaction="Fix the argument and retry once", + ), + cli_contract.Failure( + cause="`--input` is outside `raw/`, does not exist, or is `raw/` itself (an empty " + "run key)", + reaction="Point `--input` at material under `raw/`, or use `--key` for a run with no " + "raw input", + ), + cli_contract.Failure( + cause="The workshop already exists", + reaction="A collision is not transient: resume the existing run instead, or pass " + "`--again` if the tree itself changed", + ), + ), + examples=( + "tools/wikitool work new --input raw/2026/09/handbuch", + "tools/wikitool work new --key migrate-7.0.0", + "tools/wikitool work new --input raw/2026/09/handbuch --again", + ), + never=( + "Never create a numbered variant of a run key by hand.", + ), + see_also=( + "`work/CONTRACT.md` - run keys, required files, how a run closes", + "`wikitool work close` - deletes the run when it is done", + "`instructions/ingest-large-tree.md` - the ingest that opens a run", + ), )) def new_command( input_path: Optional[str] = typer.Option( @@ -247,14 +276,36 @@ def new_command( atomic="No - a recursive delete", budget=cli_contract.Budget.COUNTED, ), - notes="Lists what would be lost and requires `--yes`, because nothing in it is recoverable " - "from the rest of the repo - the durable conclusions must already be in `kb/`", - failures=(cli_contract.Failure( - label="", - cause="Unknown run key, or `--yes` was not passed", - reaction="For \"not confirmed\": check the listed files are no longer needed, confirm the " - "conclusions are in `kb/`, then re-run with `--yes`", - ),), + notes=( + "Deletes `work//` recursively.", + "Without `--yes` it lists what would be lost and refuses: nothing in a workshop is " + "recoverable from the rest of the repo, so the durable conclusions must already be in " + "`kb/`.", + "`--dry-run` lists the files without deleting.", + "Not idempotent: once deleted, the run key is unknown.", + ), + failures=( + cli_contract.Failure( + cause="Unknown run key", + reaction="Check `ls work/` for the open runs, then retry once", + ), + cli_contract.Failure( + cause="`--yes` was not passed - the output lists what would be lost", + reaction="Check the listed files are no longer needed, confirm the conclusions are in " + "`kb/`, then re-run with `--yes`", + ), + ), + examples=( + "tools/wikitool work close --run-key ingest-2026-09-handbuch --dry-run", + "tools/wikitool work close --run-key ingest-2026-09-handbuch --yes", + ), + never=( + "Never pass `--yes` before the run's conclusions are in `kb/`.", + ), + see_also=( + "`work/CONTRACT.md` - how a run closes", + "`wikitool work new` - opens a run", + ), )) def close_command( run_key: str = typer.Option(..., "--run-key", help="The workshop directory name"),