tools: command records, Workshop runs and session budget group - examples, prohibitions (#142)
CI / verify (push) Successful in 1m19s
Release / release (push) Successful in 36s

Files changed:
- CHANGES.md
- VERSION
- tools/CONTRACT.md
- tools/chemenu/commands/run_budget.py
- tools/chemenu/commands/work_cmd.py
This commit is contained in:
torben committed 2026-09-26 08:57:03 +02:00
1 parent 964978a9c6
commit b9c22f783f
5 files changed
+185 -36

No files matched your search

+9 -1
View File
@@ -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 **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, Finding and checking group: one line per cause, examples, prohibitions
- Command records, Provenance group: examples, exit lines per cause - Command records, Provenance group: examples, exit lines per cause
- Command records, Raw material and uploads group: one line per cause, examples, prohibitions - Command records, Raw material and uploads group: one line per cause, examples, prohibitions
- Command records, Workshop runs and session budget group: 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
@@ -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 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. 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 ## 7.0.0 - 2026-09-22 - Task-Tracker-Anbindung: Vorhaben als Seitenart, Verpflichtungsschicht, Weekly Review als Read-Time-Join
+1 -1
View File
@@ -1 +1 @@
7.1.0-beta.14 7.1.0-beta.15
+76 -8
View File
@@ -1734,18 +1734,42 @@ Scaffold `work/<runkey>/` for one workshop run.
- budget: counted - budget: counted
- network: no - 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** **EXIT STATUS**
- 0 success - 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** **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** **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/<runkey>/` 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 (`<runkey>-<date>`) 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` #### `work close`
@@ -1763,18 +1787,37 @@ Delete a finished workshop.
- budget: counted - budget: counted
- network: no - 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** **EXIT STATUS**
- 0 success - 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** **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** **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/<run-key>/` 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` #### `budget status`
@@ -1792,13 +1835,23 @@ Show the current session's `wikitool` call count and recent command history.
- budget: exempt - budget: exempt
- network: no - network: no
**EXAMPLES**
- `tools/wikitool budget status`
**EXIT STATUS** **EXIT STATUS**
- 0 success - 0 success
**NOTES** **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` #### `budget reset`
@@ -1816,6 +1869,10 @@ Clear the current session's (or every session's) iteration budget state.
- budget: counted - budget: counted
- network: no - network: no
**EXAMPLES**
- `tools/wikitool budget reset --yes # only after the user approved it`
**EXIT STATUS** **EXIT STATUS**
- 0 success - 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` - `--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** **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 ### Types, instructions and docs
+27 -5
View File
@@ -333,9 +333,18 @@ def refund() -> None:
atomic="Read-only", atomic="Read-only",
budget=cli_contract.Budget.EXEMPT, budget=cli_contract.Budget.EXEMPT,
), ),
notes="Recent command history is never counted against the budget. Never fails. Safe to " notes=(
"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.",
),
failures=(), 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(): def status_command():
"""Show the current session's call count and recent command history.""" """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`)", atomic="Read/rewrite of one JSON file (or its deletion, with `--all`)",
budget=cli_contract.Budget.COUNTED, budget=cli_contract.Budget.COUNTED,
), ),
notes="Requires `--yes`: clearing the counter is itself a way around the gate, so it needs " notes=(
"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.",
),
failures=(cli_contract.Failure( failures=(cli_contract.Failure(
label="",
cause="`--yes` not passed", cause="`--yes` not passed",
reaction="Get the user's approval, then re-run with `--yes`", 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( def reset_command(
all_sessions: bool = typer.Option( all_sessions: bool = typer.Option(
+72 -21
View File
@@ -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", atomic="Yes - one directory with two files",
budget=cli_contract.Budget.COUNTED, budget=cli_contract.Budget.COUNTED,
), ),
notes="Refuses a collision instead of suffixing it, and writes the required `README.md` + " notes=(
"`plan.md`. `--input` derives the run key from the path below `raw/` (an ingest); `--key` " "Creates `work/<runkey>/` with the required `README.md` and `plan.md`.",
"names it outright for a run with no raw input - a migration or a sweep across `kb/` - and " "`--input` derives the run key from the path below `raw/` (an ingest); `--key` names it "
"may not start with `ingest-`, which stays reserved for derived keys. Exactly one of the " "outright for a run with no raw input - a migration or a sweep across `kb/` - and may "
"two. `--again` opens a dated second pass over a tree that has itself changed. See " "not start with `ingest-`, which stays reserved for derived keys. Exactly one of the "
"`work/CONTRACT.md`", "two.",
failures=(cli_contract.Failure( "Refuses a collision instead of suffixing it.",
label="", "`--again` opens a dated second pass (`<runkey>-<date>`) over a tree that has itself "
cause="Neither or both of `--input`/`--key` given, `--input` outside `raw/`, a " "changed.",
"`--key` that is empty or starts with `ingest-`, or the workshop already exists", "`--dry-run` reports the run key and files without writing.",
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", 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( def new_command(
input_path: Optional[str] = typer.Option( input_path: Optional[str] = typer.Option(
@@ -247,14 +276,36 @@ def new_command(
atomic="No - a recursive delete", atomic="No - a recursive delete",
budget=cli_contract.Budget.COUNTED, budget=cli_contract.Budget.COUNTED,
), ),
notes="Lists what would be lost and requires `--yes`, because nothing in it is recoverable " notes=(
"from the rest of the repo - the durable conclusions must already be in `kb/`", "Deletes `work/<run-key>/` recursively.",
failures=(cli_contract.Failure( "Without `--yes` it lists what would be lost and refuses: nothing in a workshop is "
label="", "recoverable from the rest of the repo, so the durable conclusions must already be in "
cause="Unknown run key, or `--yes` was not passed", "`kb/`.",
reaction="For \"not confirmed\": check the listed files are no longer needed, confirm the " "`--dry-run` lists the files without deleting.",
"conclusions are in `kb/`, then re-run with `--yes`", "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( def close_command(
run_key: str = typer.Option(..., "--run-key", help="The workshop directory name"), run_key: str = typer.Option(..., "--run-key", help="The workshop directory name"),