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

+27 -5
View File
@@ -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(
+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",
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/<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.",
),
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/<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.",
),
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"),