tools: command records, Pages group - one line per cause, examples, prohibitions (#142)
Files changed: - CHANGES.md - VERSION - tools/CONTRACT.md - tools/chemenu/commands/new_page.py - tools/chemenu/commands/page_ops.py - tools/chemenu/commands/task_cmd.py - tools/chemenu/commands/touch.py
This commit is contained in:
1 parent
9d6ca6b193
commit
d0a740acc9
7 files changed
+685
-218
No files matched your search
+16
-1
@@ -59,7 +59,7 @@ concern - readable here, never shipped as something to parse.
|
||||
|
||||
---
|
||||
|
||||
## 7.1.0-beta.9 - 2026-09-26 - Command records, Distribution and versioning group: one line per cause, examples, prohibitions
|
||||
## 7.1.0-beta.10 - 2026-09-26 - Command records, Pages group: one line per cause, examples, prohibitions
|
||||
|
||||
**Author:** Torben Nehmer
|
||||
|
||||
@@ -78,6 +78,7 @@ concern - readable here, never shipped as something to parse.
|
||||
- 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, Distribution and versioning group: one line per cause, examples, prohibitions
|
||||
- Command records, Pages group: one line per cause, examples, prohibitions
|
||||
<!-- /wikitool:bumps -->
|
||||
|
||||
### CalDAV task-tracker provider (Nextcloud Tasks, iOS Reminders); review reports unknown-value findings instead of skipping them
|
||||
@@ -224,6 +225,20 @@ which stopped being true when `!/incoming/.gitkeep` was added, and `dist upgrade
|
||||
now carries the reasoning for writing the stamp whole after `--keep-local`, which used to live
|
||||
in the record.
|
||||
|
||||
### Command records, Pages group: one line per cause, examples, prohibitions
|
||||
|
||||
`new`, `task new`/`list`/`close`, `touch`, `rename`, `rm` and `move` rewritten the same way;
|
||||
text only. `new`'s long per-variant notes moved into NOTES bullets, leaving each variant one line
|
||||
that says where the page lands, and `new project`'s tracker cases - name taken, read-only access
|
||||
path, a provider that cannot create projects (exit 42, cleared with `--resume`) - each got their
|
||||
own exit line and reaction. Every "same posture as `new project`" and "same reasoning as
|
||||
`task new`" in the three `task` records is replaced by the fact it pointed at. The records also
|
||||
name failures the code already had and the old text left out: `new`'s missing capture field and
|
||||
its page-write failure after the tracker project was confirmed, and the partial-write failures of
|
||||
`rename`, `rm` and `move --reconcile`. The reasoning behind `task new`/`task close` never exiting
|
||||
42, and behind `task close` taking an id rather than a title, moved into `task_cmd.py`'s module
|
||||
docstring.
|
||||
|
||||
---
|
||||
|
||||
## 7.0.0 - 2026-09-22 - Task-Tracker-Anbindung: Vorhaben als Seitenart, Verpflichtungsschicht, Weekly Review als Read-Time-Join
|
||||
|
||||
+243
-33
@@ -149,12 +149,12 @@ Scaffold a new wiki page of any type.
|
||||
|
||||
**SYNOPSIS**
|
||||
|
||||
- `wikitool new <type-name> --name "<Name>" [--type <path>] [--set field=value ...]` - Scaffold a page of any type. The type-spec drives fields, directory (`base_dir`/`layout`), title prefix, and template - a schema `default:` is materialized only for a field the schema also lists in `required:` (an optional field's default is a reader-side assumption, not a scaffold-time value) - `--set` is repeatable, and comma-separated values fill array fields. An element that itself contains a comma is written `\,`, or passed as its own repeated `--set` for that field - repeating an array field appends. See `types list`/`types describe`.
|
||||
- `wikitool new entity --name "<Name>" --set entity_type=<t> [--set tags=a,b] [--set related=X,Y] [--set sources="Source - Z"] [--set provenance=sourced|general|mixed]` - Scaffold `kb/entities/<subdir>/<Name>.md`
|
||||
- `wikitool new concept --name "<Name>" --set concept_type=<t> ...` - Scaffold `kb/concepts/<Name>.md`
|
||||
- `wikitool new source --name "<Name>" --set raw_files=raw/notes/x.md,raw/notes/y.md [--set source_url=<URL>] [--set entities=A,B] [--set concepts=C,D]` - Scaffold `kb/sources/Source - <Name>.md` (prefix added automatically) with a `raw_files:` list (rejects paths that don't exist)
|
||||
- `wikitool new comparison --name "X vs Y" --set entities=X,Y` - Scaffold `kb/comparisons/X vs Y.md`
|
||||
- `wikitool new project --name "<Name>" --set responsibility=<bereich> [--resume]` - Scaffold `kb/gtd/<bereich>/<Name>.md` **and**, if `.wikitool-tasks.json` configures a task tracker, a same-named tracker project - one name, one identity. Tracker before page: the tracker side is settled first, so a failure past that point leaves a tracker project with no page - a state `review`'s check 3 already reports - never a page with no tracker project. No tracker configured is a legitimate, explicitly announced state (page only). A name already taken (case-insensitively) in `kb/` or the tracker is refused outright, naming where it was found, and creates nothing. A provider whose *configured access path* has no write path (Super Productivity's `access: "snapshot"` - the tracker is read-only from there by construction) refuses **entirely**, exit **1**, naming the `access: "api"` instance to use instead - neither the tracker project nor the page is created, and `--resume` behaves the same. A provider that could write but has no project-creation endpoint of its own (Super Productivity's `access: "api"` - `GET /projects` exists, `POST /projects` does not) raises `chemenu.errors.HumanInterventionRequired`; the command shows its instructions and exits **42** (`needs_clearance()`, same posture as the four named gates, without being a fifth one - see that class's docstring), creating nothing. `--resume` is how a later run tells the command a human has done what that message asked: it re-verifies via the read path (`find_project`) before continuing to page creation, rather than trusting the claim, and repeats the same 42 if the tracker still doesn't have it. `--resume` on any other type is refused
|
||||
- `wikitool new <type-name> --name "<Name>" [--type <path>] [--set field=value ...]` - Any type; its type-spec decides fields, directory, title prefix and template.
|
||||
- `wikitool new entity --name "<Name>" --set entity_type=<t> [--set tags=a,b] [--set related=X,Y] [--set sources="Source - Z"] [--set provenance=sourced|general|mixed]` - Writes `kb/entities/<subdir>/<Name>.md`
|
||||
- `wikitool new concept --name "<Name>" --set concept_type=<t> ...` - Writes `kb/concepts/<Name>.md`
|
||||
- `wikitool new source --name "<Name>" --set raw_files=raw/notes/x.md,raw/notes/y.md [--set source_url=<URL>] [--set entities=A,B] [--set concepts=C,D]` - Writes `kb/sources/Source - <Name>.md` (prefix added automatically) with a `raw_files:` list; rejects paths that don't exist
|
||||
- `wikitool new comparison --name "X vs Y" --set entities=X,Y` - Writes `kb/comparisons/X vs Y.md`
|
||||
- `wikitool new project --name "<Name>" --set responsibility=<bereich> [--resume]` - Writes `kb/gtd/<bereich>/<Name>.md` and, with a task tracker configured, a same-named tracker project
|
||||
|
||||
**PROPERTIES**
|
||||
|
||||
@@ -165,21 +165,59 @@ Scaffold a new wiki page of any type.
|
||||
- network: no
|
||||
- gates: human-intervention-required (`new project` only)
|
||||
|
||||
**EXAMPLES**
|
||||
|
||||
- `tools/wikitool new entity --name "Docker" --set entity_type=tool --set tags=containers`
|
||||
- `tools/wikitool new source --name "Docker Cheatsheet" --set raw_files=raw/2026/09/docker-cheatsheet.md --set fidelity=verbatim --set authority=reporting`
|
||||
- `tools/wikitool new project --name "Homelab migration" --set responsibility=infrastruktur --resume # re-run after exit 42, once the user created the tracker project`
|
||||
|
||||
**EXIT STATUS**
|
||||
|
||||
- 0 success
|
||||
- 1 new <type>: Duplicate page title, unknown type, invalid `--set` value, or a `raw_files` path that doesn't exist
|
||||
- 1 new project: Everything `new <type>` covers, **plus**: the name is already taken in the tracker (case-insensitively - for `caldav` this is checked against every list in the account, not only the ones counted as projects), `--resume` was passed for a type other than `project`, or the configured provider's access path has no write path at all (Super Productivity's `access: "snapshot"`)
|
||||
- 42 needs clearance - human-intervention-required (`new project` only) (see AGENTS.md § Gates)
|
||||
- 1 A page with this title already exists, the type is unknown, or a `--set` value is invalid
|
||||
- 1 A `raw_files` path does not exist
|
||||
- 1 A capture field the type-spec requires is missing, or set to `unknown`
|
||||
- 1 `--resume` with a type other than `project`
|
||||
- 1 new project: The name is already taken in the tracker (case-insensitively; for `caldav` against every list in the account)
|
||||
- 1 new project: The configured access path has no write path (Super Productivity's `access: "snapshot"`)
|
||||
- 1 new project: The page write failed after the tracker project was confirmed to exist
|
||||
- 42 new project: The provider cannot create the project itself (Super Productivity's `access: "api"`); the output says what a human has to create
|
||||
|
||||
**ON FAILURE**
|
||||
|
||||
- new <type>: Duplicate page title, unknown type, invalid `--set` value, or a `raw_files` path that doesn't exist -> Not transient; fix the argument and retry once. Never hand-craft the page instead
|
||||
- new project: Everything `new <type>` covers, **plus**: the name is already taken in the tracker (case-insensitively - for `caldav` this is checked against every list in the account, not only the ones counted as projects), `--resume` was passed for a type other than `project`, or the configured provider's access path has no write path at all (Super Productivity's `access: "snapshot"`) -> A collision, a bad `--set`, or a read-only access path is not transient, same as `new <type>` - the last of those points at the `access: "api"` instance instead and refuses on every `--resume` retry too, since nothing about the config changes by asking again. **Exit 42** (`NEEDS USER CLEARANCE`, not exit 1) is its own separate outcome from the ordinary exit-1 cases above, and is `superproductivity`-only: that provider *can* write but cannot create the project itself and a human must, per the printed instructions; re-run with `--resume` once that is done - it re-verifies via the read path rather than trusting the claim, and exits 42 again unchanged if the tracker still does not have it. `caldav` never produces this outcome - `MKCALENDAR` is a real collection-creation verb, so a valid, non-colliding name always creates the list itself
|
||||
- A page with this title already exists, the type is unknown, or a `--set` value is invalid -> Not transient - fix the argument and retry once
|
||||
- A `raw_files` path does not exist -> Not transient - fix the path and retry once
|
||||
- A capture field the type-spec requires is missing, or set to `unknown` -> Pass it explicitly (e.g. `--set fidelity=verbatim --set authority=reporting`), then retry once
|
||||
- `--resume` with a type other than `project` -> Drop `--resume` and retry once
|
||||
- new project: The name is already taken in the tracker (case-insensitively; for `caldav` against every list in the account) -> Not transient - choose another name. If an earlier run of this exact command asked a human to create the project and they did, re-run with `--resume`
|
||||
- new project: The configured access path has no write path (Super Productivity's `access: "snapshot"`) -> Not transient - point at the `access: "api"` instance the error names. A `--resume` retry refuses the same way, since nothing about the config changes by asking again
|
||||
- new project: The page write failed after the tracker project was confirmed to exist -> Fix the write error, then re-run with `--resume` - a plain re-run is refused as a tracker collision
|
||||
- new project: The provider cannot create the project itself (Super Productivity's `access: "api"`); the output says what a human has to create -> Show the user the command's full output verbatim and stop. Once they have created the project, re-run the same command with `--resume`; it re-verifies and exits 42 again, unchanged, if the tracker still does not have it
|
||||
|
||||
**NEVER**
|
||||
|
||||
- Never hand-craft the page, or its frontmatter, instead.
|
||||
|
||||
**NOTES**
|
||||
|
||||
`new`/`xref`/`log append` only produce structurally-correct frontmatter and body skeletons/edits - the prose (Description, Summary, judgment calls about relationships) is still written by the LLM afterwards.
|
||||
- The type-spec drives everything: fields, directory (`base_dir`/`layout`), title prefix, and template. `types list`/`types describe` show what a type requires.
|
||||
- A schema `default:` is materialized only for a field the schema also lists in `required:`.
|
||||
- `--set` is repeatable, and comma-separated values fill array fields. An element that itself contains a comma is written `\,`, or passed as its own repeated `--set` for that field - repeating an array field appends.
|
||||
- A capture field the type-spec requires (a source's `fidelity`/`authority`) must be passed with `--set`; `new` never guesses it and refuses `unknown` for it.
|
||||
- Produces structurally correct frontmatter and a body skeleton only - the prose (Description, Summary, judgment calls about relationships) is written afterwards.
|
||||
- `new project`: with `.wikitool-tasks.json` configuring a task tracker, also makes sure a same-named tracker project exists - one name, one identity. No tracker configured is a legitimate, explicitly announced state: page only.
|
||||
- `new project`: tracker before page. The tracker side is settled first, so a failure past that point leaves a tracker project with no page - a state `review`'s check 3 reports - never a page with no tracker project.
|
||||
- `new project`: a name already taken, case-insensitively, in `kb/` or the tracker is refused outright, naming where it was found, and creates nothing. For `caldav` the tracker check covers every list in the account, not only the ones counted as projects.
|
||||
- `new project`: a provider whose configured access path has no write path (Super Productivity's `access: "snapshot"`) refuses entirely with exit 1, naming the `access: "api"` instance to use instead - neither the tracker project nor the page is created, and `--resume` behaves the same.
|
||||
- `new project`: a provider that could write but has no project-creation call of its own (Super Productivity's `access: "api"` - `GET /projects` exists, `POST /projects` does not) prints instructions for a human and exits 42, creating nothing. That is not one of the named gates, but the same exit code and the same handling. `caldav` never does this: `MKCALENDAR` creates the list, so a valid, non-colliding name always creates it.
|
||||
- `--resume` is how a later run tells the command a human has done what that message asked: it re-verifies through the tracker's read path before continuing to page creation, rather than trusting the claim, and exits 42 again if the tracker still does not have the project. `--resume` on any other type is refused.
|
||||
|
||||
**SEE ALSO**
|
||||
|
||||
- `wikitool types describe <type>` - what a type requires and where it lands
|
||||
- `wikitool touch` - changes a page's own frontmatter afterwards
|
||||
- `wikitool task new` - a tracker item without a page
|
||||
- `docs/knowledge-and-commitment.md` - why a project is a page and a tracker project
|
||||
|
||||
#### `task new`
|
||||
|
||||
@@ -197,18 +235,52 @@ Create one open item in the configured task tracker - never a kb/ page.
|
||||
- budget: counted
|
||||
- network: yes
|
||||
|
||||
**EXAMPLES**
|
||||
|
||||
- `tools/wikitool task new --title "Renew the TLS certificate" --project "Homelab migration"`
|
||||
- `tools/wikitool task new --title "Quote from the electrician" --project "Homelab migration" --waiting --follow-up-at 2026-10-05`
|
||||
- `tools/wikitool task new --title "Read the qmd README" --inbox --notes "from [[Source - qmd - GitHub Repository]]"`
|
||||
|
||||
**EXIT STATUS**
|
||||
|
||||
- 0 success
|
||||
- 1 No `.wikitool-tasks.json`, neither or both of `--project`/`--inbox` given, a `--follow-up-at` without `--waiting` or not `YYYY-MM-DD`, a `--project` name matching no tracker project, `--waiting` against a provider with no way to represent it right now (Super Productivity: the `waiting` tag does not exist), or a read-only access path (Super Productivity's `access: "snapshot"`)
|
||||
- 1 No `.wikitool-tasks.json` - no tracker configured
|
||||
- 1 Neither or both of `--project`/`--inbox`, or a `--follow-up-at` without `--waiting` or not `YYYY-MM-DD`
|
||||
- 1 A `--project` name matching no tracker project
|
||||
- 1 `--waiting` against a provider with no way to represent it right now (Super Productivity: the `waiting` tag does not exist)
|
||||
- 1 A read-only access path (Super Productivity's `access: "snapshot"`)
|
||||
|
||||
**ON FAILURE**
|
||||
|
||||
- No `.wikitool-tasks.json`, neither or both of `--project`/`--inbox` given, a `--follow-up-at` without `--waiting` or not `YYYY-MM-DD`, a `--project` name matching no tracker project, `--waiting` against a provider with no way to represent it right now (Super Productivity: the `waiting` tag does not exist), or a read-only access path (Super Productivity's `access: "snapshot"`) -> Not transient; fix the argument, create the missing tracker project or tag first, or point at an `access: "api"` instance, then retry once. **Never exit 42** - unlike `new project`, every provider offering a write path at all has a real item-creation call, so there is no human-clearance step to wait on here
|
||||
- No `.wikitool-tasks.json` - no tracker configured -> Not transient - configure a tracker first
|
||||
- Neither or both of `--project`/`--inbox`, or a `--follow-up-at` without `--waiting` or not `YYYY-MM-DD` -> Fix the argument and retry once
|
||||
- A `--project` name matching no tracker project -> Create the tracker project first, or fix the name, then retry once
|
||||
- `--waiting` against a provider with no way to represent it right now (Super Productivity: the `waiting` tag does not exist) -> Create the tag in the tracker first, then retry once
|
||||
- A read-only access path (Super Productivity's `access: "snapshot"`) -> Point at the `access: "api"` instance the error names, then retry once
|
||||
|
||||
**NEVER**
|
||||
|
||||
- Never fall back to `--inbox`, or to another project, when `--project` does not match.
|
||||
|
||||
**NOTES**
|
||||
|
||||
The second creation command alongside `new project`, and the last one their split needed - see `docs/knowledge-and-commitment.md`. Exactly one of `--project` (an existing tracker project, matched case-insensitively - never created and never searched or guessed) or `--inbox` (the tracker's own inbox, a deliberate exit with a cost: an item filed there never appears in `review`, since every one of its checks is reached through a project name and the inbox has none) is required; an omitted `--project` refuses rather than silently falling into the inbox. `--waiting` sets the WAITING status the review's own waiting-overdue check reads; `--follow-up-at` is refused without `--waiting`, since it is never a due date on its own. `--notes` carries a freetext backref (e.g. to the kb/ source page this item came from), stored verbatim, never parsed - the same posture a `WAITING` item's own title already has for the person named in it. No `.wikitool-tasks.json` fails immediately with the same "no tracker configured" message as `review`. A provider whose configured access path has no write path (Super Productivity's `access: "snapshot"`) refuses **entirely**, exit **1**, naming the `access: "api"` instance to use instead - same posture as `new project`. Unlike `new project`, **never exits 42**: every provider offering a write path at all has a real item-creation call (Super Productivity's `POST /tasks`, where `POST /projects` does not exist) - a named `--project` that does not match any tracker project, or `--waiting` against a provider that cannot represent it right now (Super Productivity: the `waiting` tag does not exist yet, and tags cannot be created via its API), are ordinary exit-1 refusals instead, creating nothing
|
||||
- Creates one open item in the configured task tracker; never touches `kb/`.
|
||||
- Exactly one of `--project` or `--inbox` is required; an omitted `--project` refuses rather than silently falling into the inbox.
|
||||
- `--project` names an existing tracker project, matched case-insensitively - never created, and never searched or guessed.
|
||||
- `--inbox` files into the tracker's own inbox. An item filed there never appears in `review`, since every one of its checks reaches items through a project name.
|
||||
- `--waiting` sets the WAITING status `review`'s waiting-overdue check reads. `--follow-up-at` is refused without `--waiting` - it is never a due date on its own.
|
||||
- `--notes` carries a freetext backref (e.g. to the `kb/` source page the item came from), stored verbatim, never parsed.
|
||||
- No `.wikitool-tasks.json` fails immediately with a "no tracker configured" message.
|
||||
- A provider whose configured access path has no write path (Super Productivity's `access: "snapshot"`) refuses entirely with exit 1, naming the `access: "api"` instance to use instead.
|
||||
- **Never exits 42.** A `--project` matching no tracker project, or `--waiting` against a provider that cannot represent it right now (Super Productivity: the `waiting` tag does not exist yet, and its API cannot create tags), are ordinary exit-1 refusals that create nothing.
|
||||
- Not idempotent: every successful run creates another item.
|
||||
|
||||
**SEE ALSO**
|
||||
|
||||
- `wikitool task list` - a project's open items and their ids
|
||||
- `wikitool task close` - marks an item done
|
||||
- `wikitool new project` - a project page and its tracker project
|
||||
- `docs/knowledge-and-commitment.md` - why commitments live in the tracker, not in `kb/`
|
||||
|
||||
#### `task list`
|
||||
|
||||
@@ -226,18 +298,32 @@ List a project's open items - id, title, and whether each carries the WAITING st
|
||||
- budget: counted
|
||||
- network: yes
|
||||
|
||||
**EXAMPLES**
|
||||
|
||||
- `tools/wikitool task list --project "Homelab migration"`
|
||||
|
||||
**EXIT STATUS**
|
||||
|
||||
- 0 success
|
||||
- 1 No `.wikitool-tasks.json`
|
||||
- 0 A `--project` matching no tracker project - prints "No open items", not an error
|
||||
- 1 No `.wikitool-tasks.json` - no tracker configured
|
||||
|
||||
**ON FAILURE**
|
||||
|
||||
- No `.wikitool-tasks.json` -> Not transient; configure a tracker first, then retry once. A `--project` matching no tracker project is not an error here - see its Commands row
|
||||
- No `.wikitool-tasks.json` - no tracker configured -> Not transient - configure a tracker first, then retry once
|
||||
|
||||
**NOTES**
|
||||
|
||||
Read-only; the id source `task close` and the review's own `waiting_overdue`/`someday_stale` findings need, without first running `wikitool review`. Works on either access mode a provider offers, unlike the write commands below. No `.wikitool-tasks.json` fails with the same "no tracker configured" message as `review`/`task new`; a `--project` matching no tracker project prints "No open items", since `TaskReader.open_items` does not distinguish "empty" from "unknown" (`chemenu.tasks.protocol.TaskReader.open_items`'s own docstring)
|
||||
- Lists one tracker project's open items: id, title, and whether each carries the WAITING status.
|
||||
- The id source `task close` and `review`'s `waiting_overdue`/`someday_stale` findings need, without running `review` first.
|
||||
- Works on every access path a provider offers, read-only ones included.
|
||||
- A `--project` matching no tracker project prints "No open items": the tracker read does not distinguish an empty project from an unknown one.
|
||||
- Read-only.
|
||||
|
||||
**SEE ALSO**
|
||||
|
||||
- `wikitool task close` - closes an item by the id listed here
|
||||
- `wikitool review` - the findings that name items
|
||||
|
||||
#### `task close`
|
||||
|
||||
@@ -255,18 +341,39 @@ Mark one tracker item done - never delete it.
|
||||
- budget: counted
|
||||
- network: yes
|
||||
|
||||
**EXAMPLES**
|
||||
|
||||
- `tools/wikitool task close --id <item-id>`
|
||||
|
||||
**EXIT STATUS**
|
||||
|
||||
- 0 success
|
||||
- 1 No `.wikitool-tasks.json`, an `--id` matching no tracker item right now, or a read-only access path (Super Productivity's `access: "snapshot"`)
|
||||
- 1 No `.wikitool-tasks.json` - no tracker configured
|
||||
- 1 An `--id` matching no tracker item right now
|
||||
- 1 A read-only access path (Super Productivity's `access: "snapshot"`)
|
||||
|
||||
**ON FAILURE**
|
||||
|
||||
- No `.wikitool-tasks.json`, an `--id` matching no tracker item right now, or a read-only access path (Super Productivity's `access: "snapshot"`) -> Not transient; fix the id (re-run `task list` or `review` to get a current one) or point at an `access: "api"` instance, then retry once. **Never exit 42**, same reasoning as `task new`
|
||||
- No `.wikitool-tasks.json` - no tracker configured -> Not transient - configure a tracker first
|
||||
- An `--id` matching no tracker item right now -> Get a current id from `task list` or `review`, then retry once
|
||||
- A read-only access path (Super Productivity's `access: "snapshot"`) -> Point at the `access: "api"` instance the error names, then retry once
|
||||
|
||||
**NEVER**
|
||||
|
||||
- Never pass a title as `--id`.
|
||||
|
||||
**NOTES**
|
||||
|
||||
`<item-id>` is the provider's own id, from `task list` or a `review` finding, never a title - the tracker-side identity is opaque, unlike the project name that is `kb/`'s and the tracker's only shared coupling. The only closing write this stack makes: no "move a reminder", no "remove an item". No `.wikitool-tasks.json` fails with the same "no tracker configured" message as `task new`. A provider whose configured access path has no write path (Super Productivity's `access: "snapshot"`) refuses **entirely**, exit **1**, naming the `access: "api"` instance to use instead - same posture as `task new`. Never exits 42, same reasoning as `task new`: every provider offering a write path has a real per-item write call
|
||||
- Marks one tracker item done. It never deletes or moves an item - the only closing write this stack makes.
|
||||
- `--id` is the provider's own item id, from `task list` or a `review` finding - never a title.
|
||||
- No `.wikitool-tasks.json` fails with a "no tracker configured" message.
|
||||
- A provider whose configured access path has no write path (Super Productivity's `access: "snapshot"`) refuses entirely with exit 1, naming the `access: "api"` instance to use instead.
|
||||
- **Never exits 42.**
|
||||
|
||||
**SEE ALSO**
|
||||
|
||||
- `wikitool task list` - where the id comes from
|
||||
- `wikitool review` - findings that carry item ids
|
||||
|
||||
#### `touch`
|
||||
|
||||
@@ -284,18 +391,46 @@ Bump a page's `modified:` date and optionally rewrite its other frontmatter fiel
|
||||
- budget: counted
|
||||
- network: no
|
||||
|
||||
**EXAMPLES**
|
||||
|
||||
- `tools/wikitool touch --page "Docker" --summary "Container runtime and image format"`
|
||||
- `tools/wikitool touch --page "Docker" --add tags=containers --no-date`
|
||||
- `tools/wikitool touch --page "Source - Docker Cheatsheet" --date 2026-09-01 --dry-run`
|
||||
|
||||
**EXIT STATUS**
|
||||
|
||||
- 0 success
|
||||
- 1 Page not found; an invalid value for a field it writes; a field owned by another command (`type:`, a page-ref array) or absent from the type's schema; `--add`/`--remove` on a non-array field; a `raw_files:` path that doesn't exist
|
||||
- 1 Page not found
|
||||
- 1 An invalid value for a field it writes, or a `raw_files:` path that doesn't exist
|
||||
- 1 A field owned by another command (`type:`, a page-ref array) or absent from the type's schema
|
||||
- 1 `--add`/`--remove` on a non-array field
|
||||
|
||||
**ON FAILURE**
|
||||
|
||||
- Page not found; an invalid value for a field it writes; a field owned by another command (`type:`, a page-ref array) or absent from the type's schema; `--add`/`--remove` on a non-array field; a `raw_files:` path that doesn't exist -> Fix the argument and retry once. Safe to re-run as-is: `--set` and `--add` are idempotent, and `--remove` of an already-absent element succeeds while reporting it
|
||||
- Page not found -> Fix the title and retry once
|
||||
- An invalid value for a field it writes, or a `raw_files:` path that doesn't exist -> Fix the argument and retry once
|
||||
- A field owned by another command (`type:`, a page-ref array) or absent from the type's schema -> Use the command the error names, or a field from the list it prints
|
||||
- `--add`/`--remove` on a non-array field -> Use `--set` for a scalar field, then retry once
|
||||
|
||||
**NEVER**
|
||||
|
||||
- Never edit `type:` or a page-ref array (`related:`/`sources:`/`entities:`/`concepts:`) by hand - `type:` changes go through the page-lifecycle procedure, page-ref arrays through `xref`.
|
||||
|
||||
**NOTES**
|
||||
|
||||
Update a page's own frontmatter: bump `modified:` and optionally rewrite any field its type declares. `--summary`/`--provenance` are shorthands; `--set` reaches every other field and **replaces** its value, while `--add`/`--remove` change single elements of an array field (removing an absent element succeeds and says so). Repeating `--set` for one array field appends *within the call*, and `\,` is a literal comma - same rules as `new --set`. Refused with the command that owns them instead: `type:` (page-lifecycle), and the page-ref arrays `related:`/`sources:`/`entities:`/`concepts:` (`xref`). Everything else the schema declares is settable, and an unknown field lists what the page actually has. Schema-validates the fields it writes, and `raw_files:` entries must exist on disk. A source declares `date:` instead of `modified:`, and that is the *publication* date of the raw material - it is never bumped to today, and changes only when `--date` names a value explicitly.
|
||||
- Bumps `modified:` to today and optionally rewrites any other field the page's type declares.
|
||||
- `--summary`/`--provenance` are shorthands; `--set` reaches every other field and **replaces** its value, while `--add`/`--remove` change single elements of an array field (removing an absent element succeeds and says so).
|
||||
- Repeating `--set` for one array field appends *within the call*, and `\,` is a literal comma.
|
||||
- Refused, naming the command that owns them instead: `type:` (the page-lifecycle procedure) and the page-ref arrays `related:`/`sources:`/`entities:`/`concepts:` (`xref`). Everything else the schema declares is settable, and an unknown field is refused with the list of fields the page actually has.
|
||||
- Schema-validates the fields it writes; `raw_files:` entries must exist on disk.
|
||||
- A source page declares `date:` instead of `modified:`: the *publication* date of the raw material. It is never bumped to today and changes only when `--date` names a value explicitly.
|
||||
- `--no-date` changes only the given fields and leaves the date alone; `--dry-run` previews the new frontmatter without writing.
|
||||
- Safe to re-run as-is: `--set` and `--add` are idempotent, and `--remove` of an already-absent element succeeds while reporting it.
|
||||
|
||||
**SEE ALSO**
|
||||
|
||||
- `wikitool xref add` / `wikitool xref remove` - the page-ref arrays
|
||||
- `instructions/page-lifecycle.md` - changing a page's `type:`
|
||||
|
||||
#### `rename`
|
||||
|
||||
@@ -313,18 +448,42 @@ Rename a page, or repoint references that name a page that never existed.
|
||||
- budget: counted
|
||||
- network: no
|
||||
|
||||
**EXAMPLES**
|
||||
|
||||
- `tools/wikitool rename --from "act_runner" --to "Act Runner" --dry-run`
|
||||
- `tools/wikitool rename --from "Docker Engine" --to "Docker"`
|
||||
|
||||
**EXIT STATUS**
|
||||
|
||||
- 0 success
|
||||
- 1 Neither `--from` nor `--to` is a page, target title already taken, or `--from` equals `--to`
|
||||
- 1 `--from` equals `--to`
|
||||
- 1 Neither `--from` nor `--to` is a page
|
||||
- 1 The `--to` title is already taken
|
||||
- 1 A page write failed partway; nothing was renamed on disk
|
||||
|
||||
**ON FAILURE**
|
||||
|
||||
- Neither `--from` nor `--to` is a page, target title already taken, or `--from` equals `--to` -> Safe to retry once as-is; each page's rewrite is idempotent. Use `--dry-run` first to see the blast radius. Never fix up references by hand instead
|
||||
- `--from` equals `--to` -> Fix the arguments and retry once
|
||||
- Neither `--from` nor `--to` is a page -> Create the page first with `wikitool new`, or drop the reference with `wikitool xref remove`
|
||||
- The `--to` title is already taken -> Choose another title and retry once
|
||||
- A page write failed partway; nothing was renamed on disk -> Check `git status`, resolve the write failure (permissions/disk), then re-run the full command - safe, since each page's rewrite is idempotent
|
||||
|
||||
**NEVER**
|
||||
|
||||
- Never fix up references by hand instead.
|
||||
|
||||
**NOTES**
|
||||
|
||||
Rename a page and repoint every reference to it: body `[[wikilinks]]` (aliases and anchors preserved), a `[^cite-id]` whose id was derived from the old title (refreshed to match the new one, both in its Footnotes definition and every reference to it), the page's own H1, and every page-ref frontmatter array declared by the type's `page_ref_fields:`. If `--from` is *not* a page but is referenced, it instead repoints those references onto the existing `--to` page and moves nothing - the fix for a reference spelled `act_runner` when the page is `Act Runner`
|
||||
- Renames a page and repoints every reference to it: body `[[wikilinks]]` (aliases and anchors preserved), a `[^cite-id]` whose id was derived from the old title (refreshed to match the new one, both in its Footnotes definition and every reference to it), the page's own H1, and every page-ref frontmatter array declared by the type's `page_ref_fields:`.
|
||||
- If `--from` is *not* a page but is referenced, it instead repoints those references onto the existing `--to` page and moves nothing - the fix for a reference spelled `act_runner` when the page is `Act Runner`.
|
||||
- Each page's rewrite is idempotent, so a re-run as-is is safe. If a write fails midway, nothing is renamed on disk and the error lists what was updated.
|
||||
- `--dry-run` lists every page it would change; run it first to see the blast radius.
|
||||
|
||||
**SEE ALSO**
|
||||
|
||||
- `instructions/page-lifecycle.md` - renaming, moving and deleting a page
|
||||
- `wikitool move` - changes a page's directory, not its title
|
||||
- `wikitool rm` - deletes a page
|
||||
|
||||
#### `rm`
|
||||
|
||||
@@ -342,18 +501,41 @@ Delete a page and mechanically de-link it from the rest of the wiki.
|
||||
- budget: counted
|
||||
- network: no
|
||||
|
||||
**EXAMPLES**
|
||||
|
||||
- `tools/wikitool rm --page "Old Draft" --dry-run`
|
||||
- `tools/wikitool rm --page "Old Draft" --yes # after the user approved the inbound list`
|
||||
|
||||
**EXIT STATUS**
|
||||
|
||||
- 0 success
|
||||
- 1 Page not found, **or** other pages still reference it and `--yes` was not passed
|
||||
- 1 Page not found
|
||||
- 1 Other pages still reference it and `--yes` was not passed
|
||||
- 1 A de-link write failed partway; the page was not deleted
|
||||
|
||||
**ON FAILURE**
|
||||
|
||||
- Page not found, **or** other pages still reference it and `--yes` was not passed -> For "still referenced": show the user the inbound list, get approval, then re-run with `--yes`. Prose references it reports afterwards are an editorial fix, not a retry
|
||||
- Page not found -> Fix the title and retry once
|
||||
- Other pages still reference it and `--yes` was not passed -> Show the user the inbound list, get approval, then re-run with `--yes`
|
||||
- A de-link write failed partway; the page was not deleted -> Check `git status`, resolve the write failure, then re-run
|
||||
|
||||
**NEVER**
|
||||
|
||||
- Never pass `--yes` before the user has seen the inbound list and approved it.
|
||||
|
||||
**NOTES**
|
||||
|
||||
Refuses without `--yes` while other pages still reference it. Strips ref-array entries and bare `- [[Title]]` / `- **label:** [[Title]]` bullets; leaves prose and inline citations in place and reports them
|
||||
- Deletes a page and de-links it from the rest of the wiki: strips page-ref array entries and bare `- [[Title]]` / `- **label:** [[Title]]` bullets pointing at it.
|
||||
- Leaves prose references and inline citations in place and reports them afterwards; those are an editorial fix, not a reason to retry.
|
||||
- Refuses without `--yes` while other pages still reference it, listing them.
|
||||
- If a de-link write fails midway, the page is not deleted, so nothing is orphaned and a re-run is safe.
|
||||
- `--dry-run` lists what would change, without writing.
|
||||
- Not idempotent: once the page is gone, a second run finds nothing to delete.
|
||||
|
||||
**SEE ALSO**
|
||||
|
||||
- `instructions/page-lifecycle.md` - when a page is deleted rather than renamed
|
||||
- `wikitool rename` - repoints references instead of removing them
|
||||
|
||||
#### `move`
|
||||
|
||||
@@ -372,18 +554,46 @@ Move a page (or every misplaced page) to the directory its type-spec computes.
|
||||
- budget: counted
|
||||
- network: no
|
||||
|
||||
**EXAMPLES**
|
||||
|
||||
- `tools/wikitool move --page "Docker"`
|
||||
- `tools/wikitool move --reconcile --dry-run`
|
||||
- `tools/wikitool move --reconcile`
|
||||
|
||||
**EXIT STATUS**
|
||||
|
||||
- 0 success
|
||||
- 1 Neither or both of `--page`/`--reconcile` given, the named page not found, it has no `type:` to compute a placement from, or the destination already exists
|
||||
- 1 Neither or both of `--page`/`--reconcile` given
|
||||
- 1 The named page is not found, or has no `type:` to compute a placement from
|
||||
- 1 The destination already exists (a pre-existing duplicate-stem collision) - refused rather than silently skipped
|
||||
- 1 `--reconcile` failed partway
|
||||
|
||||
**ON FAILURE**
|
||||
|
||||
- Neither or both of `--page`/`--reconcile` given, the named page not found, it has no `type:` to compute a placement from, or the destination already exists -> Safe to retry once as-is; a page already at its computed location is reported and left alone, and `--reconcile` only re-moves what is still misplaced. Use `--dry-run` first to see the blast radius. Never choose a directory by hand instead
|
||||
- Neither or both of `--page`/`--reconcile` given -> Fix the arguments and retry once
|
||||
- The named page is not found, or has no `type:` to compute a placement from -> Fix the title, or give the page its `type:`, then retry once
|
||||
- The destination already exists (a pre-existing duplicate-stem collision) - refused rather than silently skipped -> Resolve the collision, then retry
|
||||
- `--reconcile` failed partway -> Safe to retry as-is - `--reconcile` only re-moves what is still misplaced
|
||||
|
||||
**NEVER**
|
||||
|
||||
- Never choose a directory by hand instead.
|
||||
|
||||
**NOTES**
|
||||
|
||||
Move a page to the directory its type-spec computes for its current frontmatter (`base_dir` + `layout` - the same rule `new` places a page by, via `TypeResolver.compute_target_dir`), never a hand-chosen destination - there is no `--to <dir>`. `--reconcile` applies it corpus-wide: every misplaced page moves in one call, and a second run reports nothing left to do (`lint`'s `Misplaced Pages` finding is the advisory that this fixes, and its `Nested Pages` finding the hard one - see `lint`). Neither mode touches a body or a frontmatter field, and the page's title (its only identity in the wiki) never changes - only the file moves. A directory a move empties is removed along with it, so a page that was nested below its area leaves no leftover directory behind. A destination already occupied (a pre-existing duplicate-stem collision) is refused rather than silently skipped
|
||||
- Moves a page to the directory its type-spec computes for its current frontmatter (`base_dir` + `layout`, the same rule `new` places a page by) - never to a hand-chosen destination; there is no `--to <dir>`.
|
||||
- `--reconcile` applies it corpus-wide: every misplaced page moves in one call, and a second run reports nothing left to do. It fixes `lint`'s `Misplaced Pages` (advisory) and `Nested Pages` (hard) findings.
|
||||
- Neither mode touches a body or a frontmatter field, and the page's title - its only identity in the wiki - never changes; only the file moves.
|
||||
- A directory a move empties is removed with it, so a page nested below its area leaves no leftover directory behind.
|
||||
- A page already at its computed location is reported and left alone, and `--reconcile` only re-moves what is still misplaced, so a re-run is safe.
|
||||
- `--dry-run` (with `--reconcile`) lists the moves; run it first to see the blast radius.
|
||||
- Run `wikitool index rebuild` afterwards.
|
||||
|
||||
**SEE ALSO**
|
||||
|
||||
- `wikitool lint` - reports Misplaced and Nested Pages
|
||||
- `wikitool index rebuild` - run after moving
|
||||
- `instructions/page-lifecycle.md` - moving, renaming and deleting a page
|
||||
|
||||
### Links and citations
|
||||
|
||||
|
||||
@@ -331,57 +331,32 @@ def _ensure_tracker_project(page_title: str, *, resume: bool) -> Optional[str]:
|
||||
synopsis=(
|
||||
cli_contract.Variant(
|
||||
usage='new <type-name> --name "<Name>" [--type <path>] [--set field=value ...]',
|
||||
notes="Scaffold a page of any type. The type-spec drives fields, directory "
|
||||
"(`base_dir`/`layout`), title prefix, and template - a schema `default:` is "
|
||||
"materialized only for a field the schema also lists in `required:` (an optional "
|
||||
"field's default is a reader-side assumption, not a scaffold-time value) - `--set` "
|
||||
"is repeatable, and comma-separated values fill array fields. An element that itself "
|
||||
"contains a comma is written `\\,`, or passed as its own repeated `--set` for that "
|
||||
"field - repeating an array field appends. See `types list`/`types describe`.",
|
||||
notes="Any type; its type-spec decides fields, directory, title prefix and template.",
|
||||
),
|
||||
cli_contract.Variant(
|
||||
usage='new entity --name "<Name>" --set entity_type=<t> [--set tags=a,b] '
|
||||
"[--set related=X,Y] [--set sources=\"Source - Z\"] "
|
||||
"[--set provenance=sourced|general|mixed]",
|
||||
notes="Scaffold `kb/entities/<subdir>/<Name>.md`",
|
||||
notes="Writes `kb/entities/<subdir>/<Name>.md`",
|
||||
),
|
||||
cli_contract.Variant(
|
||||
usage='new concept --name "<Name>" --set concept_type=<t> ...',
|
||||
notes="Scaffold `kb/concepts/<Name>.md`",
|
||||
notes="Writes `kb/concepts/<Name>.md`",
|
||||
),
|
||||
cli_contract.Variant(
|
||||
usage='new source --name "<Name>" --set raw_files=raw/notes/x.md,raw/notes/y.md '
|
||||
"[--set source_url=<URL>] [--set entities=A,B] [--set concepts=C,D]",
|
||||
notes="Scaffold `kb/sources/Source - <Name>.md` (prefix added automatically) with a "
|
||||
"`raw_files:` list (rejects paths that don't exist)",
|
||||
notes="Writes `kb/sources/Source - <Name>.md` (prefix added automatically) with a "
|
||||
"`raw_files:` list; rejects paths that don't exist",
|
||||
),
|
||||
cli_contract.Variant(
|
||||
usage='new comparison --name "X vs Y" --set entities=X,Y',
|
||||
notes="Scaffold `kb/comparisons/X vs Y.md`",
|
||||
notes="Writes `kb/comparisons/X vs Y.md`",
|
||||
),
|
||||
cli_contract.Variant(
|
||||
usage='new project --name "<Name>" --set responsibility=<bereich> [--resume]',
|
||||
notes="Scaffold `kb/gtd/<bereich>/<Name>.md` **and**, if `.wikitool-tasks.json` "
|
||||
"configures a task tracker, a same-named tracker project - one name, one identity. "
|
||||
"Tracker before page: the tracker side is settled first, so a failure past that "
|
||||
"point leaves a tracker project with no page - a state `review`'s check 3 already "
|
||||
"reports - never a page with no tracker project. No tracker configured is a "
|
||||
"legitimate, explicitly announced state (page only). A name already taken "
|
||||
"(case-insensitively) in `kb/` or the tracker is refused outright, naming where it "
|
||||
"was found, and creates nothing. A provider whose *configured access path* has no "
|
||||
"write path (Super Productivity's `access: \"snapshot\"` - the tracker is read-only "
|
||||
"from there by construction) refuses **entirely**, exit **1**, naming the "
|
||||
"`access: \"api\"` instance to use instead - neither the tracker project nor the "
|
||||
"page is created, and `--resume` behaves the same. A provider that could write but "
|
||||
"has no project-creation endpoint of its own (Super Productivity's `access: \"api\"` "
|
||||
"- `GET /projects` exists, `POST /projects` does not) raises "
|
||||
"`chemenu.errors.HumanInterventionRequired`; the command shows its instructions and "
|
||||
"exits **42** (`needs_clearance()`, same posture as the four named gates, without "
|
||||
"being a fifth one - see that class's docstring), creating nothing. `--resume` is "
|
||||
"how a later run tells the command a human has done what that message asked: it "
|
||||
"re-verifies via the read path (`find_project`) before continuing to page creation, "
|
||||
"rather than trusting the claim, and repeats the same 42 if the tracker still "
|
||||
"doesn't have it. `--resume` on any other type is refused",
|
||||
notes="Writes `kb/gtd/<bereich>/<Name>.md` and, with a task tracker configured, a "
|
||||
"same-named tracker project",
|
||||
),
|
||||
),
|
||||
properties=cli_contract.Properties(
|
||||
@@ -395,36 +370,109 @@ def _ensure_tracker_project(page_title: str, *, resume: bool) -> Optional[str]:
|
||||
budget=cli_contract.Budget.COUNTED,
|
||||
gates=("human-intervention-required (`new project` only)",),
|
||||
),
|
||||
notes="`new`/`xref`/`log append` only produce structurally-correct frontmatter and body "
|
||||
"skeletons/edits - the prose (Description, Summary, judgment calls about relationships) is "
|
||||
"still written by the LLM afterwards.",
|
||||
notes=(
|
||||
"The type-spec drives everything: fields, directory (`base_dir`/`layout`), title "
|
||||
"prefix, and template. `types list`/`types describe` show what a type requires.",
|
||||
"A schema `default:` is materialized only for a field the schema also lists in "
|
||||
"`required:`.",
|
||||
"`--set` is repeatable, and comma-separated values fill array fields. An element that "
|
||||
"itself contains a comma is written `\\,`, or passed as its own repeated `--set` for "
|
||||
"that field - repeating an array field appends.",
|
||||
"A capture field the type-spec requires (a source's `fidelity`/`authority`) must be "
|
||||
"passed with `--set`; `new` never guesses it and refuses `unknown` for it.",
|
||||
"Produces structurally correct frontmatter and a body skeleton only - the prose "
|
||||
"(Description, Summary, judgment calls about relationships) is written afterwards.",
|
||||
"`new project`: with `.wikitool-tasks.json` configuring a task tracker, also makes sure "
|
||||
"a same-named tracker project exists - one name, one identity. No tracker configured is "
|
||||
"a legitimate, explicitly announced state: page only.",
|
||||
"`new project`: tracker before page. The tracker side is settled first, so a failure "
|
||||
"past that point leaves a tracker project with no page - a state `review`'s check 3 "
|
||||
"reports - never a page with no tracker project.",
|
||||
"`new project`: a name already taken, case-insensitively, in `kb/` or the tracker is "
|
||||
"refused outright, naming where it was found, and creates nothing. For `caldav` the "
|
||||
"tracker check covers every list in the account, not only the ones counted as "
|
||||
"projects.",
|
||||
"`new project`: a provider whose configured access path has no write path (Super "
|
||||
"Productivity's `access: \"snapshot\"`) refuses entirely with exit 1, naming the "
|
||||
"`access: \"api\"` instance to use instead - neither the tracker project nor the page "
|
||||
"is created, and `--resume` behaves the same.",
|
||||
"`new project`: a provider that could write but has no project-creation call of its own "
|
||||
"(Super Productivity's `access: \"api\"` - `GET /projects` exists, `POST /projects` "
|
||||
"does not) prints instructions for a human and exits 42, creating nothing. That is not "
|
||||
"one of the named gates, but the same exit code and the same handling. `caldav` never "
|
||||
"does this: `MKCALENDAR` creates the list, so a valid, non-colliding name always "
|
||||
"creates it.",
|
||||
"`--resume` is how a later run tells the command a human has done what that message "
|
||||
"asked: it re-verifies through the tracker's read path before continuing to page "
|
||||
"creation, rather than trusting the claim, and exits 42 again if the tracker still does "
|
||||
"not have the project. `--resume` on any other type is refused.",
|
||||
),
|
||||
failures=(
|
||||
cli_contract.Failure(
|
||||
label="new <type>",
|
||||
cause="Duplicate page title, unknown type, invalid `--set` value, or a "
|
||||
"`raw_files` path that doesn't exist",
|
||||
reaction="Not transient; fix the argument and retry once. Never hand-craft the page "
|
||||
"instead",
|
||||
cause="A page with this title already exists, the type is unknown, or a `--set` "
|
||||
"value is invalid",
|
||||
reaction="Not transient - fix the argument and retry once",
|
||||
),
|
||||
cli_contract.Failure(
|
||||
cause="A `raw_files` path does not exist",
|
||||
reaction="Not transient - fix the path and retry once",
|
||||
),
|
||||
cli_contract.Failure(
|
||||
cause="A capture field the type-spec requires is missing, or set to `unknown`",
|
||||
reaction="Pass it explicitly (e.g. `--set fidelity=verbatim --set "
|
||||
"authority=reporting`), then retry once",
|
||||
),
|
||||
cli_contract.Failure(
|
||||
cause="`--resume` with a type other than `project`",
|
||||
reaction="Drop `--resume` and retry once",
|
||||
),
|
||||
cli_contract.Failure(
|
||||
label="new project",
|
||||
cause="Everything `new <type>` covers, **plus**: the name is already taken in the "
|
||||
"tracker (case-insensitively - for `caldav` this is checked against every list in "
|
||||
"the account, not only the ones counted as projects), `--resume` was passed for a "
|
||||
"type other than `project`, or the configured provider's access path has no write "
|
||||
"path at all (Super Productivity's `access: \"snapshot\"`)",
|
||||
reaction="A collision, a bad `--set`, or a read-only access path is not transient, "
|
||||
"same as `new <type>` - the last of those points at the `access: \"api\"` instance "
|
||||
"instead and refuses on every `--resume` retry too, since nothing about the config "
|
||||
"changes by asking again. **Exit 42** (`NEEDS USER CLEARANCE`, not exit 1) is its "
|
||||
"own separate outcome from the ordinary exit-1 cases above, and is "
|
||||
"`superproductivity`-only: that provider *can* write but cannot create the project "
|
||||
"itself and a human must, per the printed instructions; re-run with `--resume` once "
|
||||
"that is done - it re-verifies via the read path rather than trusting the claim, and "
|
||||
"exits 42 again unchanged if the tracker still does not have it. `caldav` never "
|
||||
"produces this outcome - `MKCALENDAR` is a real collection-creation verb, so a "
|
||||
"valid, non-colliding name always creates the list itself",
|
||||
cause="The name is already taken in the tracker (case-insensitively; for `caldav` "
|
||||
"against every list in the account)",
|
||||
reaction="Not transient - choose another name. If an earlier run of this exact "
|
||||
"command asked a human to create the project and they did, re-run with `--resume`",
|
||||
),
|
||||
cli_contract.Failure(
|
||||
label="new project",
|
||||
cause="The configured access path has no write path (Super Productivity's "
|
||||
"`access: \"snapshot\"`)",
|
||||
reaction="Not transient - point at the `access: \"api\"` instance the error names. "
|
||||
"A `--resume` retry refuses the same way, since nothing about the config changes by "
|
||||
"asking again",
|
||||
),
|
||||
cli_contract.Failure(
|
||||
label="new project",
|
||||
cause="The page write failed after the tracker project was confirmed to exist",
|
||||
reaction="Fix the write error, then re-run with `--resume` - a plain re-run is "
|
||||
"refused as a tracker collision",
|
||||
),
|
||||
cli_contract.Failure(
|
||||
label="new project",
|
||||
cause="The provider cannot create the project itself (Super Productivity's "
|
||||
"`access: \"api\"`); the output says what a human has to create",
|
||||
reaction="Show the user the command's full output verbatim and stop. Once they have "
|
||||
"created the project, re-run the same command with `--resume`; it re-verifies and "
|
||||
"exits 42 again, unchanged, if the tracker still does not have it",
|
||||
code=42,
|
||||
),
|
||||
),
|
||||
examples=(
|
||||
'tools/wikitool new entity --name "Docker" --set entity_type=tool --set tags=containers',
|
||||
'tools/wikitool new source --name "Docker Cheatsheet" '
|
||||
"--set raw_files=raw/2026/09/docker-cheatsheet.md --set fidelity=verbatim "
|
||||
"--set authority=reporting",
|
||||
'tools/wikitool new project --name "Homelab migration" --set responsibility=infrastruktur '
|
||||
"--resume # re-run after exit 42, once the user created the tracker project",
|
||||
),
|
||||
never=(
|
||||
"Never hand-craft the page, or its frontmatter, instead.",
|
||||
),
|
||||
see_also=(
|
||||
"`wikitool types describe <type>` - what a type requires and where it lands",
|
||||
"`wikitool touch` - changes a page's own frontmatter afterwards",
|
||||
"`wikitool task new` - a tracker item without a page",
|
||||
"`docs/knowledge-and-commitment.md` - why a project is a page and a tracker project",
|
||||
),
|
||||
))
|
||||
def new_page_command(
|
||||
|
||||
@@ -220,20 +220,51 @@ def inbound_pages(pages: dict[str, Page], title: str) -> list[str]:
|
||||
atomic="No - one write per referencing page, then the file move",
|
||||
budget=cli_contract.Budget.COUNTED,
|
||||
),
|
||||
notes="Rename a page and repoint every reference to it: body `[[wikilinks]]` (aliases and "
|
||||
"anchors preserved), a `[^cite-id]` whose id was derived from the old title (refreshed to "
|
||||
"match the new one, both in its Footnotes definition and every reference to it), the page's "
|
||||
"own H1, and every page-ref frontmatter array declared by the type's `page_ref_fields:`. If "
|
||||
"`--from` is *not* a page but is referenced, it instead repoints those references onto the "
|
||||
"existing `--to` page and moves nothing - the fix for a reference spelled `act_runner` when "
|
||||
"the page is `Act Runner`",
|
||||
failures=(cli_contract.Failure(
|
||||
label="",
|
||||
cause="Neither `--from` nor `--to` is a page, target title already taken, or "
|
||||
"`--from` equals `--to`",
|
||||
reaction="Safe to retry once as-is; each page's rewrite is idempotent. Use `--dry-run` "
|
||||
"first to see the blast radius. Never fix up references by hand instead",
|
||||
),),
|
||||
notes=(
|
||||
"Renames a page and repoints every reference to it: body `[[wikilinks]]` (aliases and "
|
||||
"anchors preserved), a `[^cite-id]` whose id was derived from the old title (refreshed "
|
||||
"to match the new one, both in its Footnotes definition and every reference to it), the "
|
||||
"page's own H1, and every page-ref frontmatter array declared by the type's "
|
||||
"`page_ref_fields:`.",
|
||||
"If `--from` is *not* a page but is referenced, it instead repoints those references "
|
||||
"onto the existing `--to` page and moves nothing - the fix for a reference spelled "
|
||||
"`act_runner` when the page is `Act Runner`.",
|
||||
"Each page's rewrite is idempotent, so a re-run as-is is safe. If a write fails "
|
||||
"midway, nothing is renamed on disk and the error lists what was updated.",
|
||||
"`--dry-run` lists every page it would change; run it first to see the blast radius.",
|
||||
),
|
||||
failures=(
|
||||
cli_contract.Failure(
|
||||
cause="`--from` equals `--to`",
|
||||
reaction="Fix the arguments and retry once",
|
||||
),
|
||||
cli_contract.Failure(
|
||||
cause="Neither `--from` nor `--to` is a page",
|
||||
reaction="Create the page first with `wikitool new`, or drop the reference with "
|
||||
"`wikitool xref remove`",
|
||||
),
|
||||
cli_contract.Failure(
|
||||
cause="The `--to` title is already taken",
|
||||
reaction="Choose another title and retry once",
|
||||
),
|
||||
cli_contract.Failure(
|
||||
cause="A page write failed partway; nothing was renamed on disk",
|
||||
reaction="Check `git status`, resolve the write failure (permissions/disk), then "
|
||||
"re-run the full command - safe, since each page's rewrite is idempotent",
|
||||
),
|
||||
),
|
||||
examples=(
|
||||
'tools/wikitool rename --from "act_runner" --to "Act Runner" --dry-run',
|
||||
'tools/wikitool rename --from "Docker Engine" --to "Docker"',
|
||||
),
|
||||
never=(
|
||||
"Never fix up references by hand instead.",
|
||||
),
|
||||
see_also=(
|
||||
"`instructions/page-lifecycle.md` - renaming, moving and deleting a page",
|
||||
"`wikitool move` - changes a page's directory, not its title",
|
||||
"`wikitool rm` - deletes a page",
|
||||
),
|
||||
))
|
||||
def rename_command(
|
||||
old: str = typer.Option(..., "--from", help="Current page title, exactly as it appears"),
|
||||
@@ -336,17 +367,42 @@ def rename_command(
|
||||
atomic="No - one write per referencing page, then the delete",
|
||||
budget=cli_contract.Budget.COUNTED,
|
||||
),
|
||||
notes="Refuses without `--yes` while other pages still reference it. Strips ref-array "
|
||||
"entries and bare `- [[Title]]` / `- **label:** [[Title]]` bullets; leaves prose and inline "
|
||||
"citations in place and reports them",
|
||||
failures=(cli_contract.Failure(
|
||||
label="",
|
||||
cause="Page not found, **or** other pages still reference it and `--yes` was not "
|
||||
"passed",
|
||||
reaction="For \"still referenced\": show the user the inbound list, get approval, then "
|
||||
"re-run with `--yes`. Prose references it reports afterwards are an editorial fix, not "
|
||||
"a retry",
|
||||
),),
|
||||
notes=(
|
||||
"Deletes a page and de-links it from the rest of the wiki: strips page-ref array "
|
||||
"entries and bare `- [[Title]]` / `- **label:** [[Title]]` bullets pointing at it.",
|
||||
"Leaves prose references and inline citations in place and reports them afterwards; "
|
||||
"those are an editorial fix, not a reason to retry.",
|
||||
"Refuses without `--yes` while other pages still reference it, listing them.",
|
||||
"If a de-link write fails midway, the page is not deleted, so nothing is orphaned and "
|
||||
"a re-run is safe.",
|
||||
"`--dry-run` lists what would change, without writing.",
|
||||
"Not idempotent: once the page is gone, a second run finds nothing to delete.",
|
||||
),
|
||||
failures=(
|
||||
cli_contract.Failure(
|
||||
cause="Page not found",
|
||||
reaction="Fix the title and retry once",
|
||||
),
|
||||
cli_contract.Failure(
|
||||
cause="Other pages still reference it and `--yes` was not passed",
|
||||
reaction="Show the user the inbound list, get approval, then re-run with `--yes`",
|
||||
),
|
||||
cli_contract.Failure(
|
||||
cause="A de-link write failed partway; the page was not deleted",
|
||||
reaction="Check `git status`, resolve the write failure, then re-run",
|
||||
),
|
||||
),
|
||||
examples=(
|
||||
'tools/wikitool rm --page "Old Draft" --dry-run',
|
||||
'tools/wikitool rm --page "Old Draft" --yes # after the user approved the inbound list',
|
||||
),
|
||||
never=(
|
||||
"Never pass `--yes` before the user has seen the inbound list and approved it.",
|
||||
),
|
||||
see_also=(
|
||||
"`instructions/page-lifecycle.md` - when a page is deleted rather than renamed",
|
||||
"`wikitool rename` - repoints references instead of removing them",
|
||||
),
|
||||
))
|
||||
def rm_command(
|
||||
page_title: str = typer.Option(..., "--page", help="Exact title of the page to delete"),
|
||||
@@ -460,25 +516,55 @@ def _rmdir_if_emptied(directory: Path) -> bool:
|
||||
"each idempotent",
|
||||
budget=cli_contract.Budget.COUNTED,
|
||||
),
|
||||
notes="Move a page to the directory its type-spec computes for its current frontmatter "
|
||||
"(`base_dir` + `layout` - the same rule `new` places a page by, via "
|
||||
"`TypeResolver.compute_target_dir`), never a hand-chosen destination - there is no "
|
||||
"`--to <dir>`. `--reconcile` applies it corpus-wide: every misplaced page moves in one "
|
||||
"call, and a second run reports nothing left to do (`lint`'s `Misplaced Pages` finding is "
|
||||
"the advisory that this fixes, and its `Nested Pages` finding the hard one - see `lint`). "
|
||||
"Neither mode touches a body or a frontmatter field, and the page's title (its only "
|
||||
"identity in the wiki) never changes - only the file moves. A directory a move empties is "
|
||||
"removed along with it, so a page that was nested below its area leaves no leftover "
|
||||
"directory behind. A destination already occupied (a pre-existing duplicate-stem collision) "
|
||||
"is refused rather than silently skipped",
|
||||
failures=(cli_contract.Failure(
|
||||
label="",
|
||||
cause="Neither or both of `--page`/`--reconcile` given, the named page not found, it "
|
||||
"has no `type:` to compute a placement from, or the destination already exists",
|
||||
reaction="Safe to retry once as-is; a page already at its computed location is reported "
|
||||
"and left alone, and `--reconcile` only re-moves what is still misplaced. Use "
|
||||
"`--dry-run` first to see the blast radius. Never choose a directory by hand instead",
|
||||
),),
|
||||
notes=(
|
||||
"Moves a page to the directory its type-spec computes for its current frontmatter "
|
||||
"(`base_dir` + `layout`, the same rule `new` places a page by) - never to a hand-chosen "
|
||||
"destination; there is no `--to <dir>`.",
|
||||
"`--reconcile` applies it corpus-wide: every misplaced page moves in one call, and a "
|
||||
"second run reports nothing left to do. It fixes `lint`'s `Misplaced Pages` (advisory) "
|
||||
"and `Nested Pages` (hard) findings.",
|
||||
"Neither mode touches a body or a frontmatter field, and the page's title - its only "
|
||||
"identity in the wiki - never changes; only the file moves.",
|
||||
"A directory a move empties is removed with it, so a page nested below its area leaves "
|
||||
"no leftover directory behind.",
|
||||
"A page already at its computed location is reported and left alone, and `--reconcile` "
|
||||
"only re-moves what is still misplaced, so a re-run is safe.",
|
||||
"`--dry-run` (with `--reconcile`) lists the moves; run it first to see the blast "
|
||||
"radius.",
|
||||
"Run `wikitool index rebuild` afterwards.",
|
||||
),
|
||||
failures=(
|
||||
cli_contract.Failure(
|
||||
cause="Neither or both of `--page`/`--reconcile` given",
|
||||
reaction="Fix the arguments and retry once",
|
||||
),
|
||||
cli_contract.Failure(
|
||||
cause="The named page is not found, or has no `type:` to compute a placement from",
|
||||
reaction="Fix the title, or give the page its `type:`, then retry once",
|
||||
),
|
||||
cli_contract.Failure(
|
||||
cause="The destination already exists (a pre-existing duplicate-stem collision) - "
|
||||
"refused rather than silently skipped",
|
||||
reaction="Resolve the collision, then retry",
|
||||
),
|
||||
cli_contract.Failure(
|
||||
cause="`--reconcile` failed partway",
|
||||
reaction="Safe to retry as-is - `--reconcile` only re-moves what is still misplaced",
|
||||
),
|
||||
),
|
||||
examples=(
|
||||
'tools/wikitool move --page "Docker"',
|
||||
"tools/wikitool move --reconcile --dry-run",
|
||||
"tools/wikitool move --reconcile",
|
||||
),
|
||||
never=(
|
||||
"Never choose a directory by hand instead.",
|
||||
),
|
||||
see_also=(
|
||||
"`wikitool lint` - reports Misplaced and Nested Pages",
|
||||
"`wikitool index rebuild` - run after moving",
|
||||
"`instructions/page-lifecycle.md` - moving, renaming and deleting a page",
|
||||
),
|
||||
))
|
||||
def move_command(
|
||||
page_title: Optional[str] = typer.Option(
|
||||
|
||||
@@ -17,6 +17,15 @@ transaction, because nothing here shares state with the page-creation path
|
||||
the way `new project`'s own tracker-then-page order does within a single
|
||||
command.
|
||||
|
||||
Neither write ever exits 42, unlike `new project`: every provider that
|
||||
offers a write path at all has a real per-item call (Super Productivity's
|
||||
`POST /tasks`, where `POST /projects` does not exist), so there is no
|
||||
human-clearance step to wait on - a missing project or tag is an ordinary
|
||||
refusal. `task close` takes the provider's own id, never a title: an item's
|
||||
tracker-side identity is opaque, while a project's *name* is the one coupling
|
||||
`kb/` and the tracker share. `--notes` is stored verbatim and never parsed -
|
||||
the same posture a WAITING item's own title has for the person named in it.
|
||||
|
||||
This module owns only the CLI shape - parsing, the `--project`/`--inbox`
|
||||
exclusivity (#132 D4), the `--follow-up-at` date, and `task list`/`task
|
||||
close`'s rendering. The writes themselves are
|
||||
@@ -66,38 +75,68 @@ def _parse_follow_up_at(text: str) -> datetime.date:
|
||||
budget=cli_contract.Budget.COUNTED,
|
||||
network=cli_contract.Network.YES,
|
||||
),
|
||||
notes="The second creation command alongside `new project`, and the last one their split "
|
||||
"needed - see `docs/knowledge-and-commitment.md`. Exactly one of `--project` (an existing "
|
||||
"tracker project, matched case-insensitively - never created and never searched or "
|
||||
"guessed) or `--inbox` (the tracker's own inbox, a deliberate exit with a cost: an item "
|
||||
"filed there never appears in `review`, since every one of its checks is reached through a "
|
||||
"project name and the inbox has none) is required; an omitted `--project` refuses rather "
|
||||
"than silently falling into the inbox. `--waiting` sets the WAITING status the review's own "
|
||||
"waiting-overdue check reads; `--follow-up-at` is refused without `--waiting`, since it is "
|
||||
"never a due date on its own. `--notes` carries a freetext backref (e.g. to the kb/ source "
|
||||
"page this item came from), stored verbatim, never parsed - the same posture a `WAITING` "
|
||||
"item's own title already has for the person named in it. No `.wikitool-tasks.json` fails "
|
||||
"immediately with the same \"no tracker configured\" message as `review`. A provider whose "
|
||||
"configured access path has no write path (Super Productivity's `access: \"snapshot\"`) "
|
||||
"refuses **entirely**, exit **1**, naming the `access: \"api\"` instance to use instead - "
|
||||
"same posture as `new project`. Unlike `new project`, **never exits 42**: every provider "
|
||||
"offering a write path at all has a real item-creation call (Super Productivity's "
|
||||
"`POST /tasks`, where `POST /projects` does not exist) - a named `--project` that does not "
|
||||
"match any tracker project, or `--waiting` against a provider that cannot represent it "
|
||||
"right now (Super Productivity: the `waiting` tag does not exist yet, and tags cannot be "
|
||||
"created via its API), are ordinary exit-1 refusals instead, creating nothing",
|
||||
failures=(cli_contract.Failure(
|
||||
label="",
|
||||
cause="No `.wikitool-tasks.json`, neither or both of `--project`/`--inbox` given, a "
|
||||
"`--follow-up-at` without `--waiting` or not `YYYY-MM-DD`, a `--project` name matching "
|
||||
"no tracker project, `--waiting` against a provider with no way to represent it right "
|
||||
"now (Super Productivity: the `waiting` tag does not exist), or a read-only access path "
|
||||
"(Super Productivity's `access: \"snapshot\"`)",
|
||||
reaction="Not transient; fix the argument, create the missing tracker project or tag "
|
||||
"first, or point at an `access: \"api\"` instance, then retry once. **Never exit 42** - "
|
||||
"unlike `new project`, every provider offering a write path at all has a real "
|
||||
"item-creation call, so there is no human-clearance step to wait on here",
|
||||
),),
|
||||
notes=(
|
||||
"Creates one open item in the configured task tracker; never touches `kb/`.",
|
||||
"Exactly one of `--project` or `--inbox` is required; an omitted `--project` refuses "
|
||||
"rather than silently falling into the inbox.",
|
||||
"`--project` names an existing tracker project, matched case-insensitively - never "
|
||||
"created, and never searched or guessed.",
|
||||
"`--inbox` files into the tracker's own inbox. An item filed there never appears in "
|
||||
"`review`, since every one of its checks reaches items through a project name.",
|
||||
"`--waiting` sets the WAITING status `review`'s waiting-overdue check reads. "
|
||||
"`--follow-up-at` is refused without `--waiting` - it is never a due date on its own.",
|
||||
"`--notes` carries a freetext backref (e.g. to the `kb/` source page the item came "
|
||||
"from), stored verbatim, never parsed.",
|
||||
"No `.wikitool-tasks.json` fails immediately with a \"no tracker configured\" message.",
|
||||
"A provider whose configured access path has no write path (Super Productivity's "
|
||||
"`access: \"snapshot\"`) refuses entirely with exit 1, naming the `access: \"api\"` "
|
||||
"instance to use instead.",
|
||||
"**Never exits 42.** A `--project` matching no tracker project, or `--waiting` against "
|
||||
"a provider that cannot represent it right now (Super Productivity: the `waiting` tag "
|
||||
"does not exist yet, and its API cannot create tags), are ordinary exit-1 refusals "
|
||||
"that create nothing.",
|
||||
"Not idempotent: every successful run creates another item.",
|
||||
),
|
||||
failures=(
|
||||
cli_contract.Failure(
|
||||
cause="No `.wikitool-tasks.json` - no tracker configured",
|
||||
reaction="Not transient - configure a tracker first",
|
||||
),
|
||||
cli_contract.Failure(
|
||||
cause="Neither or both of `--project`/`--inbox`, or a `--follow-up-at` without "
|
||||
"`--waiting` or not `YYYY-MM-DD`",
|
||||
reaction="Fix the argument and retry once",
|
||||
),
|
||||
cli_contract.Failure(
|
||||
cause="A `--project` name matching no tracker project",
|
||||
reaction="Create the tracker project first, or fix the name, then retry once",
|
||||
),
|
||||
cli_contract.Failure(
|
||||
cause="`--waiting` against a provider with no way to represent it right now (Super "
|
||||
"Productivity: the `waiting` tag does not exist)",
|
||||
reaction="Create the tag in the tracker first, then retry once",
|
||||
),
|
||||
cli_contract.Failure(
|
||||
cause="A read-only access path (Super Productivity's `access: \"snapshot\"`)",
|
||||
reaction="Point at the `access: \"api\"` instance the error names, then retry once",
|
||||
),
|
||||
),
|
||||
examples=(
|
||||
'tools/wikitool task new --title "Renew the TLS certificate" --project "Homelab migration"',
|
||||
'tools/wikitool task new --title "Quote from the electrician" --project "Homelab migration" '
|
||||
"--waiting --follow-up-at 2026-10-05",
|
||||
'tools/wikitool task new --title "Read the qmd README" --inbox '
|
||||
'--notes "from [[Source - qmd - GitHub Repository]]"',
|
||||
),
|
||||
never=(
|
||||
"Never fall back to `--inbox`, or to another project, when `--project` does not match.",
|
||||
),
|
||||
see_also=(
|
||||
"`wikitool task list` - a project's open items and their ids",
|
||||
"`wikitool task close` - marks an item done",
|
||||
"`wikitool new project` - a project page and its tracker project",
|
||||
"`docs/knowledge-and-commitment.md` - why commitments live in the tracker, not in `kb/`",
|
||||
),
|
||||
))
|
||||
def task_new_command(
|
||||
title: str = typer.Option(..., "--title", help="The item's title. Stored verbatim, never parsed."),
|
||||
@@ -192,19 +231,35 @@ def task_new_command(
|
||||
budget=cli_contract.Budget.COUNTED,
|
||||
network=cli_contract.Network.YES,
|
||||
),
|
||||
notes="Read-only; the id source `task close` and the review's own "
|
||||
"`waiting_overdue`/`someday_stale` findings need, without first running `wikitool review`. "
|
||||
"Works on either access mode a provider offers, unlike the write commands below. No "
|
||||
"`.wikitool-tasks.json` fails with the same \"no tracker configured\" message as "
|
||||
"`review`/`task new`; a `--project` matching no tracker project prints \"No open items\", "
|
||||
"since `TaskReader.open_items` does not distinguish \"empty\" from \"unknown\" "
|
||||
"(`chemenu.tasks.protocol.TaskReader.open_items`'s own docstring)",
|
||||
failures=(cli_contract.Failure(
|
||||
label="",
|
||||
cause="No `.wikitool-tasks.json`",
|
||||
reaction="Not transient; configure a tracker first, then retry once. A `--project` "
|
||||
"matching no tracker project is not an error here - see its Commands row",
|
||||
),),
|
||||
notes=(
|
||||
"Lists one tracker project's open items: id, title, and whether each carries the "
|
||||
"WAITING status.",
|
||||
"The id source `task close` and `review`'s `waiting_overdue`/`someday_stale` findings "
|
||||
"need, without running `review` first.",
|
||||
"Works on every access path a provider offers, read-only ones included.",
|
||||
"A `--project` matching no tracker project prints \"No open items\": the tracker read "
|
||||
"does not distinguish an empty project from an unknown one.",
|
||||
"Read-only.",
|
||||
),
|
||||
failures=(
|
||||
cli_contract.Failure(
|
||||
cause="A `--project` matching no tracker project - prints \"No open items\", not an "
|
||||
"error",
|
||||
reaction="",
|
||||
code=0,
|
||||
),
|
||||
cli_contract.Failure(
|
||||
cause="No `.wikitool-tasks.json` - no tracker configured",
|
||||
reaction="Not transient - configure a tracker first, then retry once",
|
||||
),
|
||||
),
|
||||
examples=(
|
||||
'tools/wikitool task list --project "Homelab migration"',
|
||||
),
|
||||
see_also=(
|
||||
"`wikitool task close` - closes an item by the id listed here",
|
||||
"`wikitool review` - the findings that name items",
|
||||
),
|
||||
))
|
||||
def task_list_command(
|
||||
project: str = typer.Option(
|
||||
@@ -251,23 +306,41 @@ def task_list_command(
|
||||
budget=cli_contract.Budget.COUNTED,
|
||||
network=cli_contract.Network.YES,
|
||||
),
|
||||
notes="`<item-id>` is the provider's own id, from `task list` or a `review` finding, never "
|
||||
"a title - the tracker-side identity is opaque, unlike the project name that is `kb/`'s and "
|
||||
"the tracker's only shared coupling. The only closing write this stack makes: no \"move a "
|
||||
"reminder\", no \"remove an item\". No `.wikitool-tasks.json` fails with the same \"no "
|
||||
"tracker configured\" message as `task new`. A provider whose configured access path has no "
|
||||
"write path (Super Productivity's `access: \"snapshot\"`) refuses **entirely**, exit **1**, "
|
||||
"naming the `access: \"api\"` instance to use instead - same posture as `task new`. Never "
|
||||
"exits 42, same reasoning as `task new`: every provider offering a write path has a real "
|
||||
"per-item write call",
|
||||
failures=(cli_contract.Failure(
|
||||
label="",
|
||||
cause="No `.wikitool-tasks.json`, an `--id` matching no tracker item right now, or a "
|
||||
"read-only access path (Super Productivity's `access: \"snapshot\"`)",
|
||||
reaction="Not transient; fix the id (re-run `task list` or `review` to get a current one) "
|
||||
"or point at an `access: \"api\"` instance, then retry once. **Never exit 42**, same "
|
||||
"reasoning as `task new`",
|
||||
),),
|
||||
notes=(
|
||||
"Marks one tracker item done. It never deletes or moves an item - the only closing "
|
||||
"write this stack makes.",
|
||||
"`--id` is the provider's own item id, from `task list` or a `review` finding - never a "
|
||||
"title.",
|
||||
"No `.wikitool-tasks.json` fails with a \"no tracker configured\" message.",
|
||||
"A provider whose configured access path has no write path (Super Productivity's "
|
||||
"`access: \"snapshot\"`) refuses entirely with exit 1, naming the `access: \"api\"` "
|
||||
"instance to use instead.",
|
||||
"**Never exits 42.**",
|
||||
),
|
||||
failures=(
|
||||
cli_contract.Failure(
|
||||
cause="No `.wikitool-tasks.json` - no tracker configured",
|
||||
reaction="Not transient - configure a tracker first",
|
||||
),
|
||||
cli_contract.Failure(
|
||||
cause="An `--id` matching no tracker item right now",
|
||||
reaction="Get a current id from `task list` or `review`, then retry once",
|
||||
),
|
||||
cli_contract.Failure(
|
||||
cause="A read-only access path (Super Productivity's `access: \"snapshot\"`)",
|
||||
reaction="Point at the `access: \"api\"` instance the error names, then retry once",
|
||||
),
|
||||
),
|
||||
examples=(
|
||||
"tools/wikitool task close --id <item-id>",
|
||||
),
|
||||
never=(
|
||||
"Never pass a title as `--id`.",
|
||||
),
|
||||
see_also=(
|
||||
"`wikitool task list` - where the id comes from",
|
||||
"`wikitool review` - findings that carry item ids",
|
||||
),
|
||||
))
|
||||
def task_close_command(
|
||||
item_id: str = typer.Option(
|
||||
|
||||
@@ -197,26 +197,61 @@ def _apply_remove(frontmatter: Dict[str, Any], field: str, value: Any) -> Option
|
||||
atomic="Yes - single file write, and every refusal happens before it",
|
||||
budget=cli_contract.Budget.COUNTED,
|
||||
),
|
||||
notes="Update a page's own frontmatter: bump `modified:` and optionally rewrite any field "
|
||||
"its type declares. `--summary`/`--provenance` are shorthands; `--set` reaches every other "
|
||||
"field and **replaces** its value, while `--add`/`--remove` change single elements of an "
|
||||
"array field (removing an absent element succeeds and says so). Repeating `--set` for one "
|
||||
"array field appends *within the call*, and `\\,` is a literal comma - same rules as "
|
||||
"`new --set`. Refused with the command that owns them instead: `type:` (page-lifecycle), "
|
||||
"and the page-ref arrays `related:`/`sources:`/`entities:`/`concepts:` (`xref`). Everything "
|
||||
"else the schema declares is settable, and an unknown field lists what the page actually "
|
||||
"has. Schema-validates the fields it writes, and `raw_files:` entries must exist on disk. A "
|
||||
"source declares `date:` instead of `modified:`, and that is the *publication* date of the "
|
||||
"raw material - it is never bumped to today, and changes only when `--date` names a value "
|
||||
"explicitly.",
|
||||
failures=(cli_contract.Failure(
|
||||
label="",
|
||||
cause="Page not found; an invalid value for a field it writes; a field owned by "
|
||||
"another command (`type:`, a page-ref array) or absent from the type's schema; "
|
||||
"`--add`/`--remove` on a non-array field; a `raw_files:` path that doesn't exist",
|
||||
reaction="Fix the argument and retry once. Safe to re-run as-is: `--set` and `--add` are "
|
||||
"idempotent, and `--remove` of an already-absent element succeeds while reporting it",
|
||||
),),
|
||||
notes=(
|
||||
"Bumps `modified:` to today and optionally rewrites any other field the page's type "
|
||||
"declares.",
|
||||
"`--summary`/`--provenance` are shorthands; `--set` reaches every other field and "
|
||||
"**replaces** its value, while `--add`/`--remove` change single elements of an array "
|
||||
"field (removing an absent element succeeds and says so).",
|
||||
"Repeating `--set` for one array field appends *within the call*, and `\\,` is a "
|
||||
"literal comma.",
|
||||
"Refused, naming the command that owns them instead: `type:` (the page-lifecycle "
|
||||
"procedure) and the page-ref arrays `related:`/`sources:`/`entities:`/`concepts:` "
|
||||
"(`xref`). Everything else the schema declares is settable, and an unknown field is "
|
||||
"refused with the list of fields the page actually has.",
|
||||
"Schema-validates the fields it writes; `raw_files:` entries must exist on disk.",
|
||||
"A source page declares `date:` instead of `modified:`: the *publication* date of the "
|
||||
"raw material. It is never bumped to today and changes only when `--date` names a value "
|
||||
"explicitly.",
|
||||
"`--no-date` changes only the given fields and leaves the date alone; `--dry-run` "
|
||||
"previews the new frontmatter without writing.",
|
||||
"Safe to re-run as-is: `--set` and `--add` are idempotent, and `--remove` of an "
|
||||
"already-absent element succeeds while reporting it.",
|
||||
),
|
||||
failures=(
|
||||
cli_contract.Failure(
|
||||
cause="Page not found",
|
||||
reaction="Fix the title and retry once",
|
||||
),
|
||||
cli_contract.Failure(
|
||||
cause="An invalid value for a field it writes, or a `raw_files:` path that doesn't "
|
||||
"exist",
|
||||
reaction="Fix the argument and retry once",
|
||||
),
|
||||
cli_contract.Failure(
|
||||
cause="A field owned by another command (`type:`, a page-ref array) or absent from "
|
||||
"the type's schema",
|
||||
reaction="Use the command the error names, or a field from the list it prints",
|
||||
),
|
||||
cli_contract.Failure(
|
||||
cause="`--add`/`--remove` on a non-array field",
|
||||
reaction="Use `--set` for a scalar field, then retry once",
|
||||
),
|
||||
),
|
||||
examples=(
|
||||
'tools/wikitool touch --page "Docker" --summary "Container runtime and image format"',
|
||||
'tools/wikitool touch --page "Docker" --add tags=containers --no-date',
|
||||
'tools/wikitool touch --page "Source - Docker Cheatsheet" --date 2026-09-01 --dry-run',
|
||||
),
|
||||
never=(
|
||||
"Never edit `type:` or a page-ref array (`related:`/`sources:`/`entities:`/"
|
||||
"`concepts:`) by hand - `type:` changes go through the page-lifecycle procedure, "
|
||||
"page-ref arrays through `xref`.",
|
||||
),
|
||||
see_also=(
|
||||
"`wikitool xref add` / `wikitool xref remove` - the page-ref arrays",
|
||||
"`instructions/page-lifecycle.md` - changing a page's `type:`",
|
||||
),
|
||||
))
|
||||
def touch_command(
|
||||
page_title: str = typer.Option(..., "--page", help="Exact page title, e.g. 'Docker Cheatsheet'"),
|
||||
|
||||
Reference in new issue
Block a user