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
|
**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, Git group: NOTES as bullets, one exit line per cause, examples and prohibitions
|
||||||
- Command records, Catalog and log group: bullets, examples, prohibitions
|
- Command records, Catalog and log group: bullets, examples, prohibitions
|
||||||
- Command records, Distribution and versioning group: one line per cause, 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 -->
|
<!-- /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
|
||||||
@@ -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
|
now carries the reasoning for writing the stamp whole after `--keep-local`, which used to live
|
||||||
in the record.
|
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
|
## 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**
|
**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 <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]` - Scaffold `kb/entities/<subdir>/<Name>.md`
|
- `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> ...` - Scaffold `kb/concepts/<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]` - Scaffold `kb/sources/Source - <Name>.md` (prefix added automatically) with a `raw_files:` list (rejects paths that don't exist)
|
- `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` - Scaffold `kb/comparisons/X vs Y.md`
|
- `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]` - 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 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**
|
**PROPERTIES**
|
||||||
|
|
||||||
@@ -165,21 +165,59 @@ Scaffold a new wiki page of any type.
|
|||||||
- network: no
|
- network: no
|
||||||
- gates: human-intervention-required (`new project` only)
|
- 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**
|
**EXIT STATUS**
|
||||||
|
|
||||||
- 0 success
|
- 0 success
|
||||||
- 1 new <type>: Duplicate page title, unknown type, invalid `--set` value, or a `raw_files` path that doesn't exist
|
- 1 A page with this title already exists, the type is unknown, or a `--set` value is invalid
|
||||||
- 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"`)
|
- 1 A `raw_files` path does not exist
|
||||||
- 42 needs clearance - human-intervention-required (`new project` only) (see AGENTS.md § Gates)
|
- 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**
|
**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
|
- 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
|
||||||
- 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 `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**
|
**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`
|
#### `task new`
|
||||||
|
|
||||||
@@ -197,18 +235,52 @@ Create one open item in the configured task tracker - never a kb/ page.
|
|||||||
- budget: counted
|
- budget: counted
|
||||||
- network: yes
|
- 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**
|
**EXIT STATUS**
|
||||||
|
|
||||||
- 0 success
|
- 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**
|
**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**
|
**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`
|
#### `task list`
|
||||||
|
|
||||||
@@ -226,18 +298,32 @@ List a project's open items - id, title, and whether each carries the WAITING st
|
|||||||
- budget: counted
|
- budget: counted
|
||||||
- network: yes
|
- network: yes
|
||||||
|
|
||||||
|
**EXAMPLES**
|
||||||
|
|
||||||
|
- `tools/wikitool task list --project "Homelab migration"`
|
||||||
|
|
||||||
**EXIT STATUS**
|
**EXIT STATUS**
|
||||||
|
|
||||||
- 0 success
|
- 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**
|
**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**
|
**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`
|
#### `task close`
|
||||||
|
|
||||||
@@ -255,18 +341,39 @@ Mark one tracker item done - never delete it.
|
|||||||
- budget: counted
|
- budget: counted
|
||||||
- network: yes
|
- network: yes
|
||||||
|
|
||||||
|
**EXAMPLES**
|
||||||
|
|
||||||
|
- `tools/wikitool task close --id <item-id>`
|
||||||
|
|
||||||
**EXIT STATUS**
|
**EXIT STATUS**
|
||||||
|
|
||||||
- 0 success
|
- 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**
|
**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**
|
**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`
|
#### `touch`
|
||||||
|
|
||||||
@@ -284,18 +391,46 @@ Bump a page's `modified:` date and optionally rewrite its other frontmatter fiel
|
|||||||
- budget: counted
|
- budget: counted
|
||||||
- network: no
|
- 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**
|
**EXIT STATUS**
|
||||||
|
|
||||||
- 0 success
|
- 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**
|
**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**
|
**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`
|
#### `rename`
|
||||||
|
|
||||||
@@ -313,18 +448,42 @@ Rename a page, or repoint references that name a page that never existed.
|
|||||||
- budget: counted
|
- budget: counted
|
||||||
- network: no
|
- network: no
|
||||||
|
|
||||||
|
**EXAMPLES**
|
||||||
|
|
||||||
|
- `tools/wikitool rename --from "act_runner" --to "Act Runner" --dry-run`
|
||||||
|
- `tools/wikitool rename --from "Docker Engine" --to "Docker"`
|
||||||
|
|
||||||
**EXIT STATUS**
|
**EXIT STATUS**
|
||||||
|
|
||||||
- 0 success
|
- 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**
|
**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**
|
**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`
|
#### `rm`
|
||||||
|
|
||||||
@@ -342,18 +501,41 @@ Delete a page and mechanically de-link it from the rest of the wiki.
|
|||||||
- budget: counted
|
- budget: counted
|
||||||
- network: no
|
- 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**
|
**EXIT STATUS**
|
||||||
|
|
||||||
- 0 success
|
- 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**
|
**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**
|
**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`
|
#### `move`
|
||||||
|
|
||||||
@@ -372,18 +554,46 @@ Move a page (or every misplaced page) to the directory its type-spec computes.
|
|||||||
- budget: counted
|
- budget: counted
|
||||||
- network: no
|
- network: no
|
||||||
|
|
||||||
|
**EXAMPLES**
|
||||||
|
|
||||||
|
- `tools/wikitool move --page "Docker"`
|
||||||
|
- `tools/wikitool move --reconcile --dry-run`
|
||||||
|
- `tools/wikitool move --reconcile`
|
||||||
|
|
||||||
**EXIT STATUS**
|
**EXIT STATUS**
|
||||||
|
|
||||||
- 0 success
|
- 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**
|
**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**
|
**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
|
### Links and citations
|
||||||
|
|
||||||
|
|||||||
@@ -331,57 +331,32 @@ def _ensure_tracker_project(page_title: str, *, resume: bool) -> Optional[str]:
|
|||||||
synopsis=(
|
synopsis=(
|
||||||
cli_contract.Variant(
|
cli_contract.Variant(
|
||||||
usage='new <type-name> --name "<Name>" [--type <path>] [--set field=value ...]',
|
usage='new <type-name> --name "<Name>" [--type <path>] [--set field=value ...]',
|
||||||
notes="Scaffold a page of any type. The type-spec drives fields, directory "
|
notes="Any type; its type-spec decides fields, directory, title prefix and template.",
|
||||||
"(`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`.",
|
|
||||||
),
|
),
|
||||||
cli_contract.Variant(
|
cli_contract.Variant(
|
||||||
usage='new entity --name "<Name>" --set entity_type=<t> [--set tags=a,b] '
|
usage='new entity --name "<Name>" --set entity_type=<t> [--set tags=a,b] '
|
||||||
"[--set related=X,Y] [--set sources=\"Source - Z\"] "
|
"[--set related=X,Y] [--set sources=\"Source - Z\"] "
|
||||||
"[--set provenance=sourced|general|mixed]",
|
"[--set provenance=sourced|general|mixed]",
|
||||||
notes="Scaffold `kb/entities/<subdir>/<Name>.md`",
|
notes="Writes `kb/entities/<subdir>/<Name>.md`",
|
||||||
),
|
),
|
||||||
cli_contract.Variant(
|
cli_contract.Variant(
|
||||||
usage='new concept --name "<Name>" --set concept_type=<t> ...',
|
usage='new concept --name "<Name>" --set concept_type=<t> ...',
|
||||||
notes="Scaffold `kb/concepts/<Name>.md`",
|
notes="Writes `kb/concepts/<Name>.md`",
|
||||||
),
|
),
|
||||||
cli_contract.Variant(
|
cli_contract.Variant(
|
||||||
usage='new source --name "<Name>" --set raw_files=raw/notes/x.md,raw/notes/y.md '
|
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]",
|
"[--set source_url=<URL>] [--set entities=A,B] [--set concepts=C,D]",
|
||||||
notes="Scaffold `kb/sources/Source - <Name>.md` (prefix added automatically) with a "
|
notes="Writes `kb/sources/Source - <Name>.md` (prefix added automatically) with a "
|
||||||
"`raw_files:` list (rejects paths that don't exist)",
|
"`raw_files:` list; rejects paths that don't exist",
|
||||||
),
|
),
|
||||||
cli_contract.Variant(
|
cli_contract.Variant(
|
||||||
usage='new comparison --name "X vs Y" --set entities=X,Y',
|
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(
|
cli_contract.Variant(
|
||||||
usage='new project --name "<Name>" --set responsibility=<bereich> [--resume]',
|
usage='new project --name "<Name>" --set responsibility=<bereich> [--resume]',
|
||||||
notes="Scaffold `kb/gtd/<bereich>/<Name>.md` **and**, if `.wikitool-tasks.json` "
|
notes="Writes `kb/gtd/<bereich>/<Name>.md` and, with a task tracker configured, a "
|
||||||
"configures a task tracker, a same-named tracker project - one name, one identity. "
|
"same-named tracker 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 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",
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
properties=cli_contract.Properties(
|
properties=cli_contract.Properties(
|
||||||
@@ -395,36 +370,109 @@ def _ensure_tracker_project(page_title: str, *, resume: bool) -> Optional[str]:
|
|||||||
budget=cli_contract.Budget.COUNTED,
|
budget=cli_contract.Budget.COUNTED,
|
||||||
gates=("human-intervention-required (`new project` only)",),
|
gates=("human-intervention-required (`new project` only)",),
|
||||||
),
|
),
|
||||||
notes="`new`/`xref`/`log append` only produce structurally-correct frontmatter and body "
|
notes=(
|
||||||
"skeletons/edits - the prose (Description, Summary, judgment calls about relationships) is "
|
"The type-spec drives everything: fields, directory (`base_dir`/`layout`), title "
|
||||||
"still written by the LLM afterwards.",
|
"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=(
|
failures=(
|
||||||
cli_contract.Failure(
|
cli_contract.Failure(
|
||||||
label="new <type>",
|
cause="A page with this title already exists, the type is unknown, or a `--set` "
|
||||||
cause="Duplicate page title, unknown type, invalid `--set` value, or a "
|
"value is invalid",
|
||||||
"`raw_files` path that doesn't exist",
|
reaction="Not transient - fix the argument and retry once",
|
||||||
reaction="Not transient; fix the argument and retry once. Never hand-craft the page "
|
),
|
||||||
"instead",
|
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(
|
cli_contract.Failure(
|
||||||
label="new project",
|
label="new project",
|
||||||
cause="Everything `new <type>` covers, **plus**: the name is already taken in the "
|
cause="The name is already taken in the tracker (case-insensitively; for `caldav` "
|
||||||
"tracker (case-insensitively - for `caldav` this is checked against every list in "
|
"against every list in the account)",
|
||||||
"the account, not only the ones counted as projects), `--resume` was passed for a "
|
reaction="Not transient - choose another name. If an earlier run of this exact "
|
||||||
"type other than `project`, or the configured provider's access path has no write "
|
"command asked a human to create the project and they did, re-run with `--resume`",
|
||||||
"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",
|
|
||||||
),
|
),
|
||||||
|
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(
|
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",
|
atomic="No - one write per referencing page, then the file move",
|
||||||
budget=cli_contract.Budget.COUNTED,
|
budget=cli_contract.Budget.COUNTED,
|
||||||
),
|
),
|
||||||
notes="Rename a page and repoint every reference to it: body `[[wikilinks]]` (aliases and "
|
notes=(
|
||||||
"anchors preserved), a `[^cite-id]` whose id was derived from the old title (refreshed to "
|
"Renames a page and repoints every reference to it: body `[[wikilinks]]` (aliases and "
|
||||||
"match the new one, both in its Footnotes definition and every reference to it), the page's "
|
"anchors preserved), a `[^cite-id]` whose id was derived from the old title (refreshed "
|
||||||
"own H1, and every page-ref frontmatter array declared by the type's `page_ref_fields:`. If "
|
"to match the new one, both in its Footnotes definition and every reference to it), the "
|
||||||
"`--from` is *not* a page but is referenced, it instead repoints those references onto the "
|
"page's own H1, and every page-ref frontmatter array declared by the type's "
|
||||||
"existing `--to` page and moves nothing - the fix for a reference spelled `act_runner` when "
|
"`page_ref_fields:`.",
|
||||||
"the page is `Act Runner`",
|
"If `--from` is *not* a page but is referenced, it instead repoints those references "
|
||||||
failures=(cli_contract.Failure(
|
"onto the existing `--to` page and moves nothing - the fix for a reference spelled "
|
||||||
label="",
|
"`act_runner` when the page is `Act Runner`.",
|
||||||
cause="Neither `--from` nor `--to` is a page, target title already taken, or "
|
"Each page's rewrite is idempotent, so a re-run as-is is safe. If a write fails "
|
||||||
"`--from` equals `--to`",
|
"midway, nothing is renamed on disk and the error lists what was updated.",
|
||||||
reaction="Safe to retry once as-is; each page's rewrite is idempotent. Use `--dry-run` "
|
"`--dry-run` lists every page it would change; run it first to see the blast radius.",
|
||||||
"first to see the blast radius. Never fix up references by hand instead",
|
),
|
||||||
),),
|
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(
|
def rename_command(
|
||||||
old: str = typer.Option(..., "--from", help="Current page title, exactly as it appears"),
|
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",
|
atomic="No - one write per referencing page, then the delete",
|
||||||
budget=cli_contract.Budget.COUNTED,
|
budget=cli_contract.Budget.COUNTED,
|
||||||
),
|
),
|
||||||
notes="Refuses without `--yes` while other pages still reference it. Strips ref-array "
|
notes=(
|
||||||
"entries and bare `- [[Title]]` / `- **label:** [[Title]]` bullets; leaves prose and inline "
|
"Deletes a page and de-links it from the rest of the wiki: strips page-ref array "
|
||||||
"citations in place and reports them",
|
"entries and bare `- [[Title]]` / `- **label:** [[Title]]` bullets pointing at it.",
|
||||||
failures=(cli_contract.Failure(
|
"Leaves prose references and inline citations in place and reports them afterwards; "
|
||||||
label="",
|
"those are an editorial fix, not a reason to retry.",
|
||||||
cause="Page not found, **or** other pages still reference it and `--yes` was not "
|
"Refuses without `--yes` while other pages still reference it, listing them.",
|
||||||
"passed",
|
"If a de-link write fails midway, the page is not deleted, so nothing is orphaned and "
|
||||||
reaction="For \"still referenced\": show the user the inbound list, get approval, then "
|
"a re-run is safe.",
|
||||||
"re-run with `--yes`. Prose references it reports afterwards are an editorial fix, not "
|
"`--dry-run` lists what would change, without writing.",
|
||||||
"a retry",
|
"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(
|
def rm_command(
|
||||||
page_title: str = typer.Option(..., "--page", help="Exact title of the page to delete"),
|
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",
|
"each idempotent",
|
||||||
budget=cli_contract.Budget.COUNTED,
|
budget=cli_contract.Budget.COUNTED,
|
||||||
),
|
),
|
||||||
notes="Move a page to the directory its type-spec computes for its current frontmatter "
|
notes=(
|
||||||
"(`base_dir` + `layout` - the same rule `new` places a page by, via "
|
"Moves a page to the directory its type-spec computes for its current frontmatter "
|
||||||
"`TypeResolver.compute_target_dir`), never a hand-chosen destination - there is no "
|
"(`base_dir` + `layout`, the same rule `new` places a page by) - never to a hand-chosen "
|
||||||
"`--to <dir>`. `--reconcile` applies it corpus-wide: every misplaced page moves in one "
|
"destination; there is no `--to <dir>`.",
|
||||||
"call, and a second run reports nothing left to do (`lint`'s `Misplaced Pages` finding is "
|
"`--reconcile` applies it corpus-wide: every misplaced page moves in one call, and a "
|
||||||
"the advisory that this fixes, and its `Nested Pages` finding the hard one - see `lint`). "
|
"second run reports nothing left to do. It fixes `lint`'s `Misplaced Pages` (advisory) "
|
||||||
"Neither mode touches a body or a frontmatter field, and the page's title (its only "
|
"and `Nested Pages` (hard) findings.",
|
||||||
"identity in the wiki) never changes - only the file moves. A directory a move empties is "
|
"Neither mode touches a body or a frontmatter field, and the page's title - its only "
|
||||||
"removed along with it, so a page that was nested below its area leaves no leftover "
|
"identity in the wiki - never changes; only the file moves.",
|
||||||
"directory behind. A destination already occupied (a pre-existing duplicate-stem collision) "
|
"A directory a move empties is removed with it, so a page nested below its area leaves "
|
||||||
"is refused rather than silently skipped",
|
"no leftover directory behind.",
|
||||||
failures=(cli_contract.Failure(
|
"A page already at its computed location is reported and left alone, and `--reconcile` "
|
||||||
label="",
|
"only re-moves what is still misplaced, so a re-run is safe.",
|
||||||
cause="Neither or both of `--page`/`--reconcile` given, the named page not found, it "
|
"`--dry-run` (with `--reconcile`) lists the moves; run it first to see the blast "
|
||||||
"has no `type:` to compute a placement from, or the destination already exists",
|
"radius.",
|
||||||
reaction="Safe to retry once as-is; a page already at its computed location is reported "
|
"Run `wikitool index rebuild` afterwards.",
|
||||||
"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",
|
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(
|
def move_command(
|
||||||
page_title: Optional[str] = typer.Option(
|
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
|
the way `new project`'s own tracker-then-page order does within a single
|
||||||
command.
|
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`
|
This module owns only the CLI shape - parsing, the `--project`/`--inbox`
|
||||||
exclusivity (#132 D4), the `--follow-up-at` date, and `task list`/`task
|
exclusivity (#132 D4), the `--follow-up-at` date, and `task list`/`task
|
||||||
close`'s rendering. The writes themselves are
|
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,
|
budget=cli_contract.Budget.COUNTED,
|
||||||
network=cli_contract.Network.YES,
|
network=cli_contract.Network.YES,
|
||||||
),
|
),
|
||||||
notes="The second creation command alongside `new project`, and the last one their split "
|
notes=(
|
||||||
"needed - see `docs/knowledge-and-commitment.md`. Exactly one of `--project` (an existing "
|
"Creates one open item in the configured task tracker; never touches `kb/`.",
|
||||||
"tracker project, matched case-insensitively - never created and never searched or "
|
"Exactly one of `--project` or `--inbox` is required; an omitted `--project` refuses "
|
||||||
"guessed) or `--inbox` (the tracker's own inbox, a deliberate exit with a cost: an item "
|
"rather than silently falling into the inbox.",
|
||||||
"filed there never appears in `review`, since every one of its checks is reached through a "
|
"`--project` names an existing tracker project, matched case-insensitively - never "
|
||||||
"project name and the inbox has none) is required; an omitted `--project` refuses rather "
|
"created, and never searched or guessed.",
|
||||||
"than silently falling into the inbox. `--waiting` sets the WAITING status the review's own "
|
"`--inbox` files into the tracker's own inbox. An item filed there never appears in "
|
||||||
"waiting-overdue check reads; `--follow-up-at` is refused without `--waiting`, since it is "
|
"`review`, since every one of its checks reaches items through a project name.",
|
||||||
"never a due date on its own. `--notes` carries a freetext backref (e.g. to the kb/ source "
|
"`--waiting` sets the WAITING status `review`'s waiting-overdue check reads. "
|
||||||
"page this item came from), stored verbatim, never parsed - the same posture a `WAITING` "
|
"`--follow-up-at` is refused without `--waiting` - it is never a due date on its own.",
|
||||||
"item's own title already has for the person named in it. No `.wikitool-tasks.json` fails "
|
"`--notes` carries a freetext backref (e.g. to the `kb/` source page the item came "
|
||||||
"immediately with the same \"no tracker configured\" message as `review`. A provider whose "
|
"from), stored verbatim, never parsed.",
|
||||||
"configured access path has no write path (Super Productivity's `access: \"snapshot\"`) "
|
"No `.wikitool-tasks.json` fails immediately with a \"no tracker configured\" message.",
|
||||||
"refuses **entirely**, exit **1**, naming the `access: \"api\"` instance to use instead - "
|
"A provider whose configured access path has no write path (Super Productivity's "
|
||||||
"same posture as `new project`. Unlike `new project`, **never exits 42**: every provider "
|
"`access: \"snapshot\"`) refuses entirely with exit 1, naming the `access: \"api\"` "
|
||||||
"offering a write path at all has a real item-creation call (Super Productivity's "
|
"instance to use instead.",
|
||||||
"`POST /tasks`, where `POST /projects` does not exist) - a named `--project` that does not "
|
"**Never exits 42.** A `--project` matching no tracker project, or `--waiting` against "
|
||||||
"match any tracker project, or `--waiting` against a provider that cannot represent it "
|
"a provider that cannot represent it right now (Super Productivity: the `waiting` tag "
|
||||||
"right now (Super Productivity: the `waiting` tag does not exist yet, and tags cannot be "
|
"does not exist yet, and its API cannot create tags), are ordinary exit-1 refusals "
|
||||||
"created via its API), are ordinary exit-1 refusals instead, creating nothing",
|
"that create nothing.",
|
||||||
failures=(cli_contract.Failure(
|
"Not idempotent: every successful run creates another item.",
|
||||||
label="",
|
),
|
||||||
cause="No `.wikitool-tasks.json`, neither or both of `--project`/`--inbox` given, a "
|
failures=(
|
||||||
"`--follow-up-at` without `--waiting` or not `YYYY-MM-DD`, a `--project` name matching "
|
cli_contract.Failure(
|
||||||
"no tracker project, `--waiting` against a provider with no way to represent it right "
|
cause="No `.wikitool-tasks.json` - no tracker configured",
|
||||||
"now (Super Productivity: the `waiting` tag does not exist), or a read-only access path "
|
reaction="Not transient - configure a tracker first",
|
||||||
"(Super Productivity's `access: \"snapshot\"`)",
|
),
|
||||||
reaction="Not transient; fix the argument, create the missing tracker project or tag "
|
cli_contract.Failure(
|
||||||
"first, or point at an `access: \"api\"` instance, then retry once. **Never exit 42** - "
|
cause="Neither or both of `--project`/`--inbox`, or a `--follow-up-at` without "
|
||||||
"unlike `new project`, every provider offering a write path at all has a real "
|
"`--waiting` or not `YYYY-MM-DD`",
|
||||||
"item-creation call, so there is no human-clearance step to wait on here",
|
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(
|
def task_new_command(
|
||||||
title: str = typer.Option(..., "--title", help="The item's title. Stored verbatim, never parsed."),
|
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,
|
budget=cli_contract.Budget.COUNTED,
|
||||||
network=cli_contract.Network.YES,
|
network=cli_contract.Network.YES,
|
||||||
),
|
),
|
||||||
notes="Read-only; the id source `task close` and the review's own "
|
notes=(
|
||||||
"`waiting_overdue`/`someday_stale` findings need, without first running `wikitool review`. "
|
"Lists one tracker project's open items: id, title, and whether each carries the "
|
||||||
"Works on either access mode a provider offers, unlike the write commands below. No "
|
"WAITING status.",
|
||||||
"`.wikitool-tasks.json` fails with the same \"no tracker configured\" message as "
|
"The id source `task close` and `review`'s `waiting_overdue`/`someday_stale` findings "
|
||||||
"`review`/`task new`; a `--project` matching no tracker project prints \"No open items\", "
|
"need, without running `review` first.",
|
||||||
"since `TaskReader.open_items` does not distinguish \"empty\" from \"unknown\" "
|
"Works on every access path a provider offers, read-only ones included.",
|
||||||
"(`chemenu.tasks.protocol.TaskReader.open_items`'s own docstring)",
|
"A `--project` matching no tracker project prints \"No open items\": the tracker read "
|
||||||
failures=(cli_contract.Failure(
|
"does not distinguish an empty project from an unknown one.",
|
||||||
label="",
|
"Read-only.",
|
||||||
cause="No `.wikitool-tasks.json`",
|
),
|
||||||
reaction="Not transient; configure a tracker first, then retry once. A `--project` "
|
failures=(
|
||||||
"matching no tracker project is not an error here - see its Commands row",
|
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(
|
def task_list_command(
|
||||||
project: str = typer.Option(
|
project: str = typer.Option(
|
||||||
@@ -251,23 +306,41 @@ def task_list_command(
|
|||||||
budget=cli_contract.Budget.COUNTED,
|
budget=cli_contract.Budget.COUNTED,
|
||||||
network=cli_contract.Network.YES,
|
network=cli_contract.Network.YES,
|
||||||
),
|
),
|
||||||
notes="`<item-id>` is the provider's own id, from `task list` or a `review` finding, never "
|
notes=(
|
||||||
"a title - the tracker-side identity is opaque, unlike the project name that is `kb/`'s and "
|
"Marks one tracker item done. It never deletes or moves an item - the only closing "
|
||||||
"the tracker's only shared coupling. The only closing write this stack makes: no \"move a "
|
"write this stack makes.",
|
||||||
"reminder\", no \"remove an item\". No `.wikitool-tasks.json` fails with the same \"no "
|
"`--id` is the provider's own item id, from `task list` or a `review` finding - never a "
|
||||||
"tracker configured\" message as `task new`. A provider whose configured access path has no "
|
"title.",
|
||||||
"write path (Super Productivity's `access: \"snapshot\"`) refuses **entirely**, exit **1**, "
|
"No `.wikitool-tasks.json` fails with a \"no tracker configured\" message.",
|
||||||
"naming the `access: \"api\"` instance to use instead - same posture as `task new`. Never "
|
"A provider whose configured access path has no write path (Super Productivity's "
|
||||||
"exits 42, same reasoning as `task new`: every provider offering a write path has a real "
|
"`access: \"snapshot\"`) refuses entirely with exit 1, naming the `access: \"api\"` "
|
||||||
"per-item write call",
|
"instance to use instead.",
|
||||||
failures=(cli_contract.Failure(
|
"**Never exits 42.**",
|
||||||
label="",
|
),
|
||||||
cause="No `.wikitool-tasks.json`, an `--id` matching no tracker item right now, or a "
|
failures=(
|
||||||
"read-only access path (Super Productivity's `access: \"snapshot\"`)",
|
cli_contract.Failure(
|
||||||
reaction="Not transient; fix the id (re-run `task list` or `review` to get a current one) "
|
cause="No `.wikitool-tasks.json` - no tracker configured",
|
||||||
"or point at an `access: \"api\"` instance, then retry once. **Never exit 42**, same "
|
reaction="Not transient - configure a tracker first",
|
||||||
"reasoning as `task new`",
|
),
|
||||||
),),
|
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(
|
def task_close_command(
|
||||||
item_id: str = typer.Option(
|
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",
|
atomic="Yes - single file write, and every refusal happens before it",
|
||||||
budget=cli_contract.Budget.COUNTED,
|
budget=cli_contract.Budget.COUNTED,
|
||||||
),
|
),
|
||||||
notes="Update a page's own frontmatter: bump `modified:` and optionally rewrite any field "
|
notes=(
|
||||||
"its type declares. `--summary`/`--provenance` are shorthands; `--set` reaches every other "
|
"Bumps `modified:` to today and optionally rewrites any other field the page's type "
|
||||||
"field and **replaces** its value, while `--add`/`--remove` change single elements of an "
|
"declares.",
|
||||||
"array field (removing an absent element succeeds and says so). Repeating `--set` for one "
|
"`--summary`/`--provenance` are shorthands; `--set` reaches every other field and "
|
||||||
"array field appends *within the call*, and `\\,` is a literal comma - same rules as "
|
"**replaces** its value, while `--add`/`--remove` change single elements of an array "
|
||||||
"`new --set`. Refused with the command that owns them instead: `type:` (page-lifecycle), "
|
"field (removing an absent element succeeds and says so).",
|
||||||
"and the page-ref arrays `related:`/`sources:`/`entities:`/`concepts:` (`xref`). Everything "
|
"Repeating `--set` for one array field appends *within the call*, and `\\,` is a "
|
||||||
"else the schema declares is settable, and an unknown field lists what the page actually "
|
"literal comma.",
|
||||||
"has. Schema-validates the fields it writes, and `raw_files:` entries must exist on disk. A "
|
"Refused, naming the command that owns them instead: `type:` (the page-lifecycle "
|
||||||
"source declares `date:` instead of `modified:`, and that is the *publication* date of the "
|
"procedure) and the page-ref arrays `related:`/`sources:`/`entities:`/`concepts:` "
|
||||||
"raw material - it is never bumped to today, and changes only when `--date` names a value "
|
"(`xref`). Everything else the schema declares is settable, and an unknown field is "
|
||||||
"explicitly.",
|
"refused with the list of fields the page actually has.",
|
||||||
failures=(cli_contract.Failure(
|
"Schema-validates the fields it writes; `raw_files:` entries must exist on disk.",
|
||||||
label="",
|
"A source page declares `date:` instead of `modified:`: the *publication* date of the "
|
||||||
cause="Page not found; an invalid value for a field it writes; a field owned by "
|
"raw material. It is never bumped to today and changes only when `--date` names a value "
|
||||||
"another command (`type:`, a page-ref array) or absent from the type's schema; "
|
"explicitly.",
|
||||||
"`--add`/`--remove` on a non-array field; a `raw_files:` path that doesn't exist",
|
"`--no-date` changes only the given fields and leaves the date alone; `--dry-run` "
|
||||||
reaction="Fix the argument and retry once. Safe to re-run as-is: `--set` and `--add` are "
|
"previews the new frontmatter without writing.",
|
||||||
"idempotent, and `--remove` of an already-absent element succeeds while reporting it",
|
"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(
|
def touch_command(
|
||||||
page_title: str = typer.Option(..., "--page", help="Exact page title, e.g. 'Docker Cheatsheet'"),
|
page_title: str = typer.Option(..., "--page", help="Exact page title, e.g. 'Docker Cheatsheet'"),
|
||||||
|
|||||||
Reference in new issue
Block a user