diff --git a/CHANGES.md b/CHANGES.md index 06614ff..5c1c88a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -59,7 +59,7 @@ concern - readable here, never shipped as something to parse. --- -## 7.1.0-beta.11 - 2026-09-26 - Command records, Links and citations group: one line per cause, examples, prohibitions +## 7.1.0-beta.12 - 2026-09-26 - Command records, Finding and checking group: one line per cause, examples, prohibitions **Author:** Torben Nehmer @@ -80,6 +80,7 @@ concern - readable here, never shipped as something to parse. - Command records, Distribution and versioning group: one line per cause, examples, prohibitions - Command records, Pages group: one line per cause, examples, prohibitions - Command records, Links and citations group: one line per cause, examples, prohibitions +- Command records, Finding and checking group: one line per cause, examples, prohibitions ### CalDAV task-tracker provider (Nextcloud Tasks, iOS Reminders); review reports unknown-value findings instead of skipping them @@ -250,6 +251,16 @@ rest are still linked, before the run exits 1, and loses the history of the See no longer writes. The two `cite` write commands and `cite id` carry AGENTS.md invariant 1's rule on citation ids as NEVER, where an agent looking up one command finds it. +### Command records, Finding and checking group: one line per cause, examples, prohibitions + +`lint`, `search` and `review` rewritten the same way; text only. `lint`'s single sentence +listing every check is now one bullet per category, with which findings are hard, advisory or +migration-gated stated per bullet; `review`'s five checks are one bullet each. `search` splits +its four exit-1 causes, and carries AGENTS.md's "do not grep `kb/` yourself" as NEVER next to +the scope rule that justifies it. One claim is deliberately left as it stood: `lint`'s record +still describes the quote limit in blockquoted lines while the code counts quotes - which of +the two is meant is not this change's call to make. + --- ## 7.0.0 - 2026-09-22 - Task-Tracker-Anbindung: Vorhaben als Seitenart, Verpflichtungsschicht, Weekly Review als Read-Time-Join diff --git a/VERSION b/VERSION index 436a1ed..3713405 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.1.0-beta.11 +7.1.0-beta.12 diff --git a/tools/CONTRACT.md b/tools/CONTRACT.md index f9cd45c..fecd435 100644 --- a/tools/CONTRACT.md +++ b/tools/CONTRACT.md @@ -1070,6 +1070,12 @@ Run structural lint checks against kb/. - budget: counted - network: no +**EXAMPLES** + +- `tools/wikitool lint` +- `tools/wikitool lint --json` +- `tools/wikitool lint --fail-on-error` + **EXIT STATUS** - 0 success @@ -1077,11 +1083,29 @@ Run structural lint checks against kb/. **ON FAILURE** -- Only with `--fail-on-error`: hard findings exist -> Safe to retry freely, but re-run it to re-*measure*, never to re-read: the printed path holds the full report. Exit 1 means "act on the findings", not "the tool is broken" +- Only with `--fail-on-error`: hard findings exist -> Act on the findings - exit 1 here means "act on the findings", not "the tool is broken". Re-running is safe, but only to re-*measure* after a fix + +**NEVER** + +- Never re-run just to re-read the findings - the printed path holds the full report. **NOTES** -Structural + provenance checks: broken wikilinks, dangling frontmatter references, orphan pages, index drift, schema gaps, duplicate titles, title mismatches, pages nested more than one directory below their collection (hard - the generated catalog folds these into their area silently rather than merely reading it), uncovered raw files, broken `raw_files:` refs, raw files claimed by more than one source page, unmarked provenance, citation/frontmatter drift, unbalanced generated-region markers, edges whose label is missing or not authorised by the source collection's `outbound:` (both hard once `kb_version` has reached the release that introduced labelled edges - advisory below it, so a corpus mid-migration is not refused by the check measuring it), `see-also` edges whose reverse direction already carries a specific label (advisory only - redundant rather than wrong, and never migration-gated, since no version turns the redundancy into an error), a collection past the catalog's per-area shard threshold that has no areas to shard (advisory only - sharding is automatic but per *area*, so a collection nobody gave areas keeps one table however large it grows; reported with the split its subtype field would produce, and only when that split puts every resulting area at or under the threshold, so a lopsided or small collection stays silent), source pages sitting in the `unclassified` catalog slot (advisory only - `unclassified` is the visible fallback for a genuinely unclear source, not a defect), quote-limit overages (>2 blockquoted lines/page, advisory only). Prints only the sections that found something and always writes the full report to `reports/Lint Report .md` (or `--markdown`), naming the path - `--full` prints everything, `--json` prints the findings and writes nothing +- Structural and provenance checks over `kb/`: broken wikilinks, dangling frontmatter references, orphan pages, index drift, schema gaps, duplicate titles, title mismatches, uncovered raw files, broken `raw_files:` refs, raw files claimed by more than one source page, unmarked provenance, citation/frontmatter drift, and unbalanced generated-region markers. +- Pages nested more than one directory below their collection are a hard finding - the generated catalog folds these into their area silently rather than merely reading it. +- Edges whose label is missing or not authorised by the source collection's `outbound:` are both hard once `kb_version` has reached the release that introduced labelled edges, and advisory below it. +- Advisory only: `see-also` edges whose reverse direction already carries a specific label - never migration-gated. +- Advisory only: a collection past the catalog's per-area shard threshold that has no areas to shard, reported with the split its subtype field would produce, and only when that split puts every resulting area at or under the threshold. +- Advisory only: source pages sitting in the `unclassified` catalog slot. +- Advisory only: quote-limit overages (>2 blockquoted lines/page). +- Prints only the sections that found something and always writes the full report to `reports/Lint Report .md` (or `--markdown`), naming the path. `--full` prints everything; `--json` prints the findings and writes nothing. +- Exits 0 whatever it finds unless `--fail-on-error` is passed. + +**SEE ALSO** + +- `wiki-lint` skill - the procedure that runs this +- `wikitool move --reconcile` - fixes Misplaced and Nested Pages +- `wikitool log status` - whether a full lint is due #### `search` @@ -1099,18 +1123,49 @@ Find pages in `kb/` by text and/or frontmatter. - budget: exempt - network: no +**EXAMPLES** + +- `tools/wikitool search "act runner"` +- `tools/wikitool search --field entity_type=system --field '!sources'` +- `tools/wikitool search "docker" --collection entities --limit 10 --json` + **EXIT STATUS** - 0 success -- 1 `rg` is not installed or did not finish within 30 s, a malformed `--field` predicate, an unknown field name, or an unknown `--backend` +- 1 `rg` is not installed +- 1 `rg` did not finish within 30 s +- 1 A malformed `--field` predicate, or an unknown `--backend` +- 1 An unknown field name; the error lists the fields that exist **ON FAILURE** -- `rg` is not installed or did not finish within 30 s, a malformed `--field` predicate, an unknown field name, or an unknown `--backend` -> Fix the argument and retry. A timeout is a pathological pattern or an unresponsive corpus directory, not a slow answer - narrow the query or drop `--regex` rather than retrying it unchanged. An unknown field name is reported with the list of fields that do exist - it is never answered with an empty result, because that would read as "no such pages" +- `rg` is not installed -> Not transient - install `rg`, then retry +- `rg` did not finish within 30 s -> A timeout is a pathological pattern or an unresponsive corpus directory, not a slow answer - narrow the query or drop `--regex` rather than retrying it unchanged +- A malformed `--field` predicate, or an unknown `--backend` -> Fix the argument and retry +- An unknown field name; the error lists the fields that exist -> Pick a field from that list and retry + +**NEVER** + +- Never retry a timed-out query unchanged. +- Never grep `kb/` yourself instead - it can add no page this misses, only the generated files it excludes. **NOTES** -Find pages in `kb/` without reading the index. Text search runs through a pluggable backend (`rg` today); `--field` predicates are evaluated on frontmatter - `f=v`, `f~substring`, `'f>=v'`, `'f:*'` (present), `'!f'` (absent), repeatable and ANDed. With no text this is a pure structured query. One hit per line, ` | `-separated as `score | kind/subtype | title | path | summary`, so a hit can be judged without opening the page and then opened without looking it up: **title and path are never truncated** (the title is the identifier `touch`/`xref`/`cite` take), and the summary - the one lossy field, and the only one that may contain the separator - goes last, so splitting on `" | "` with `maxsplit=4` is unambiguous. Scope is pages: the backend walks `kb/` but drops anything `kb_scan.iter_kb_pages` excludes (the kb-root meta files, every `COLLECTION.md`, every generated `INDEX.md`), which is why a hand-run grep over `kb/` can add none of them but those. `--limit` defaults to 50 (`0` for no limit) and **a truncated result says so** - `50 of 182 result(s)` in the table, `total`/`truncated`/`limit` beside `count` in `--json`, where `count` stays the number of results in the payload; the same default and the same fields are what `api.search` and the MCP `search` tool carry, from one constant. A page whose frontmatter does not parse can match no positive predicate, so it is **named** rather than dropped: `--json` always carries an `unreadable` list of `{path, reason}` (usually empty), and the table form writes the same lines to stderr. `--regex` is applied by `rg` alone, whose engine is linear; the ranking boosts for title and summary are literal-containment only, so a non-literal pattern is ranked by match count. `rg` is killed after 30 s and reported as a failure. Read-only, and **exempt from the Iteration Budget Gate** +- Finds pages in `kb/` without reading the catalog. Text search runs through a pluggable backend (`rg` today). +- `--field` predicates are evaluated on frontmatter - `f=v`, `f~substring`, `'f>=v'`, `'f:*'` (present), `'!f'` (absent) - repeatable and ANDed. With no text this is a pure structured query. +- One hit per line, ` | `-separated as `score | kind/subtype | title | path | summary`. **Title and path are never truncated** - the title is the identifier `touch`/`xref`/`cite` take. The summary, the one lossy field and the only one that may contain the separator, goes last, so splitting on `" | "` with `maxsplit=4` is unambiguous. +- Scope is pages: the backend walks `kb/` but drops the kb-root meta files, every `COLLECTION.md` and every generated `INDEX.md` - a hand-run grep over `kb/` can add none of them but those. +- `--limit` defaults to 50 (`0` for no limit), and **a truncated result says so**: `50 of 182 result(s)` in the table, `total`/`truncated`/`limit` beside `count` in `--json`, where `count` stays the number of results in the payload. `api.search` and the MCP `search` tool carry the same default and the same fields. +- A page whose frontmatter does not parse can match no positive predicate, so it is **named** rather than dropped: `--json` always carries an `unreadable` list of `{path, reason}` (usually empty), and the table form writes the same lines to stderr. +- An unknown field name is reported with the list of fields that do exist - never answered with an empty result, which would read as "no such pages". +- `--regex` is applied by `rg` alone, whose engine is linear; the ranking boosts for title and summary are literal-containment only, so a non-literal pattern is ranked by match count. +- `rg` is killed after 30 s and reported as a failure. +- Read-only, and **exempt from the Iteration Budget Gate**. + +**SEE ALSO** + +- `wikitool links show` - the edges around a page once found +- `wiki-query` skill - answering a question from the wiki #### `review` @@ -1128,18 +1183,46 @@ The GTD weekly review. - budget: exempt - network: yes +**EXAMPLES** + +- `tools/wikitool review` +- `tools/wikitool review --json` + **EXIT STATUS** - 0 success -- 1 Either no `.wikitool-tasks.json` (or a malformed one) - not yours to fix by retrying unchanged, configure or repair it first - **or** the provider was reachable at config-parse time but a read call failed mid-run, in which case the full report (findings plus which checks ran) is printed first and exit 1 follows, never a silent partial success +- 1 No `.wikitool-tasks.json`, or a malformed one - a clear "no tracker configured" message +- 1 The provider was reachable at config-parse time but a read call failed mid-run; the full report (findings plus which checks ran) was printed first **ON FAILURE** -- Either no `.wikitool-tasks.json` (or a malformed one) - not yours to fix by retrying unchanged, configure or repair it first - **or** the provider was reachable at config-parse time but a read call failed mid-run, in which case the full report (findings plus which checks ran) is printed first and exit 1 follows, never a silent partial success -> The two exit-1 causes above need different responses: a config problem needs editing `.wikitool-tasks.json`; an unreachable provider (e.g. the tracker app not running) needs starting it, then a plain retry - the command re-reads everything fresh each time, so nothing here is ever stale to re-fetch +- No `.wikitool-tasks.json`, or a malformed one - a clear "no tracker configured" message -> Not fixed by retrying unchanged - configure or repair `.wikitool-tasks.json` first +- The provider was reachable at config-parse time but a read call failed mid-run; the full report (findings plus which checks ran) was printed first -> Start the unreachable provider (e.g. the tracker app), then retry plainly + +**NEVER** + +- Never present a report that exited 1 as complete. **NOTES** -Joins the configured task-tracker provider (`chemenu.tasks`) against `kb/gtd/` project pages over the case-normalized project name, at read time, storing nothing - not even a `reports/` file. Five checks: **stalled** (a tracker project with zero open items whose `kb/` page is `state: active` - `dormant`/`completed`/`abandoned` never fire, since those states mean the initiative not having a next action is expected rather than a problem), **waiting-overdue** (a `WAITING` item whose `follow_up_at` is older than `thresholds.stalled_waiting_days`), **unpaged-project** (a tracker project with no matching `kb/` page, older than `thresholds.unpaged_project_weeks`), **no-open-loop** (a `kb/` page `state: active` with no matching tracker project, or one with zero open items - the reverse direction of the unpaged-project join, so a rename on either side surfaces on both), **someday-stale** (a someday/maybe item untouched for longer than `thresholds.someday_stale_months`). A value a provider genuinely cannot supply - a `WAITING` item with no `follow_up_at` at all, a tracker project with no determinable creation date - is its own finding (`waiting_no_follow_up`/`project_age_unknown`) rather than a silent skip of waiting-overdue/unpaged-project for that item or project. Thresholds come from `.wikitool-tasks.json`, never from the schema. Text output is one `[check] project: message` line per finding, preceded by a `Source:` line naming which access path answered and, for `superproductivity`'s `access: "snapshot"`, the snapshot's age; `--json` carries the same findings plus `checks_run`/`checks_skipped`/`kb_project_count`/`complete`/`source` (`{"kind": ..., "detail": ...}` or `null`). No `.wikitool-tasks.json` fails immediately with a clear "no tracker configured" message; a provider that cannot be reached mid-run degrades only the checks that needed the failing call, and the report is never rendered as if it were complete - see its error-contract row. Read-only, and **exempt from the Iteration Budget Gate** +- Joins the configured task-tracker provider against `kb/gtd/` project pages over the case-normalized project name, at read time, storing nothing - not even a `reports/` file. +- **stalled**: a tracker project with zero open items whose `kb/` page is `state: active`; `dormant`/`completed`/`abandoned` never fire. +- **waiting-overdue**: a `WAITING` item whose `follow_up_at` is older than `thresholds.stalled_waiting_days`. +- **unpaged-project**: a tracker project with no matching `kb/` page, older than `thresholds.unpaged_project_weeks`. +- **no-open-loop**: a `kb/` page `state: active` with no matching tracker project, or one with zero open items - the reverse direction of unpaged-project, so a rename on either side surfaces on both. +- **someday-stale**: a someday/maybe item untouched for longer than `thresholds.someday_stale_months`. +- A value a provider genuinely cannot supply - a `WAITING` item with no `follow_up_at`, a tracker project with no determinable creation date - is its own finding (`waiting_no_follow_up`/`project_age_unknown`) rather than a silent skip. +- Thresholds come from `.wikitool-tasks.json`, never from the schema. +- Text output is one `[check] project: message` line per finding, preceded by a `Source:` line naming which access path answered and, for `superproductivity`'s `access: "snapshot"`, the snapshot's age. `--json` carries the same findings plus `checks_run`/`checks_skipped`/`kb_project_count`/`complete`/`source` (`{"kind": ..., "detail": ...}` or `null`). +- A provider that cannot be reached mid-run degrades only the checks that needed the failing call; the report is printed in full, then exit 1 follows - never rendered as if it were complete. +- Re-reads everything fresh on every call, so nothing is ever stale to re-fetch. +- Read-only, and **exempt from the Iteration Budget Gate**. + +**SEE ALSO** + +- `gtd-weekly-review` skill - turns the findings into decisions +- `wikitool task list` / `wikitool task close` - act on an item +- `wikitool new project` - a project page and its tracker project ### Provenance diff --git a/tools/chemenu/commands/lint.py b/tools/chemenu/commands/lint.py index 985537e..afabbc7 100644 --- a/tools/chemenu/commands/lint.py +++ b/tools/chemenu/commands/lint.py @@ -58,34 +58,47 @@ __all__ = [ atomic="Writes one report file (single atomic write) unless `--json`", budget=cli_contract.Budget.COUNTED, ), - notes="Structural + provenance checks: broken wikilinks, dangling frontmatter references, " - "orphan pages, index drift, schema gaps, duplicate titles, title mismatches, pages nested " - "more than one directory below their collection (hard - the generated catalog folds these " - "into their area silently rather than merely reading it), uncovered raw files, broken " - "`raw_files:` refs, raw files claimed by more than one source page, unmarked provenance, " - "citation/frontmatter drift, unbalanced generated-region markers, edges whose label is " - "missing or not authorised by the source collection's `outbound:` (both hard once " - "`kb_version` has reached the release that introduced labelled edges - advisory below it, " - "so a corpus mid-migration is not refused by the check measuring it), `see-also` edges " - "whose reverse direction already carries a specific label (advisory only - redundant rather " - "than wrong, and never migration-gated, since no version turns the redundancy into an " - "error), a collection past the catalog's per-area shard threshold that has no areas to " - "shard (advisory only - sharding is automatic but per *area*, so a collection nobody gave " - "areas keeps one table however large it grows; reported with the split its subtype field " - "would produce, and only when that split puts every resulting area at or under the " - "threshold, so a lopsided or small collection stays silent), source pages sitting in the " - "`unclassified` catalog slot (advisory only - `unclassified` is the visible fallback for a " - "genuinely unclear source, not a defect), quote-limit overages (>2 blockquoted lines/page, " - "advisory only). Prints only the sections that found something and always writes the full " - "report to `reports/Lint Report .md` (or `--markdown`), naming the path - `--full` " - "prints everything, `--json` prints the findings and writes nothing", + notes=( + "Structural and provenance checks over `kb/`: broken wikilinks, dangling frontmatter " + "references, orphan pages, index drift, schema gaps, duplicate titles, title " + "mismatches, uncovered raw files, broken `raw_files:` refs, raw files claimed by more " + "than one source page, unmarked provenance, citation/frontmatter drift, and unbalanced " + "generated-region markers.", + "Pages nested more than one directory below their collection are a hard finding - the " + "generated catalog folds these into their area silently rather than merely reading it.", + "Edges whose label is missing or not authorised by the source collection's `outbound:` " + "are both hard once `kb_version` has reached the release that introduced labelled " + "edges, and advisory below it.", + "Advisory only: `see-also` edges whose reverse direction already carries a specific " + "label - never migration-gated.", + "Advisory only: a collection past the catalog's per-area shard threshold that has no " + "areas to shard, reported with the split its subtype field would produce, and only " + "when that split puts every resulting area at or under the threshold.", + "Advisory only: source pages sitting in the `unclassified` catalog slot.", + "Advisory only: quote-limit overages (>2 blockquoted lines/page).", + "Prints only the sections that found something and always writes the full report to " + "`reports/Lint Report .md` (or `--markdown`), naming the path. `--full` prints " + "everything; `--json` prints the findings and writes nothing.", + "Exits 0 whatever it finds unless `--fail-on-error` is passed.", + ), failures=(cli_contract.Failure( - label="", cause="Only with `--fail-on-error`: hard findings exist", - reaction="Safe to retry freely, but re-run it to re-*measure*, never to re-read: the " - "printed path holds the full report. Exit 1 means \"act on the findings\", not \"the " - "tool is broken\"", + reaction="Act on the findings - exit 1 here means \"act on the findings\", not \"the " + "tool is broken\". Re-running is safe, but only to re-*measure* after a fix", ),), + examples=( + "tools/wikitool lint", + "tools/wikitool lint --json", + "tools/wikitool lint --fail-on-error", + ), + never=( + "Never re-run just to re-read the findings - the printed path holds the full report.", + ), + see_also=( + "`wiki-lint` skill - the procedure that runs this", + "`wikitool move --reconcile` - fixes Misplaced and Nested Pages", + "`wikitool log status` - whether a full lint is due", + ), )) def lint_command( json_out: bool = typer.Option(False, "--json", help="Print the raw findings as JSON and write no report"), diff --git a/tools/chemenu/commands/review_cmd.py b/tools/chemenu/commands/review_cmd.py index e6da08b..370a66f 100644 --- a/tools/chemenu/commands/review_cmd.py +++ b/tools/chemenu/commands/review_cmd.py @@ -86,43 +86,61 @@ def report_to_dict(report: ReviewReport) -> dict: budget=cli_contract.Budget.EXEMPT, network=cli_contract.Network.YES, ), - notes="Joins the configured task-tracker provider (`chemenu.tasks`) against `kb/gtd/` " - "project pages over the case-normalized project name, at read time, storing nothing - not " - "even a `reports/` file. Five checks: **stalled** (a tracker project with zero open items " - "whose `kb/` page is `state: active` - `dormant`/`completed`/`abandoned` never fire, since " - "those states mean the initiative not having a next action is expected rather than a " - "problem), **waiting-overdue** (a `WAITING` item whose `follow_up_at` is older than " - "`thresholds.stalled_waiting_days`), **unpaged-project** (a tracker project with no " - "matching `kb/` page, older than `thresholds.unpaged_project_weeks`), **no-open-loop** (a " - "`kb/` page `state: active` with no matching tracker project, or one with zero open items - " - "the reverse direction of the unpaged-project join, so a rename on either side surfaces on " - "both), **someday-stale** (a someday/maybe item untouched for longer than " - "`thresholds.someday_stale_months`). A value a provider genuinely cannot supply - a " - "`WAITING` item with no `follow_up_at` at all, a tracker project with no determinable " - "creation date - is its own finding (`waiting_no_follow_up`/`project_age_unknown`) rather " - "than a silent skip of waiting-overdue/unpaged-project for that item or project. Thresholds " - "come from `.wikitool-tasks.json`, never from the schema. Text output is one " - "`[check] project: message` line per finding, preceded by a `Source:` line naming which " - "access path answered and, for `superproductivity`'s `access: \"snapshot\"`, the snapshot's " - "age; `--json` carries the same findings plus " - "`checks_run`/`checks_skipped`/`kb_project_count`/`complete`/`source` " - "(`{\"kind\": ..., \"detail\": ...}` or `null`). No `.wikitool-tasks.json` fails " - "immediately with a clear \"no tracker configured\" message; a provider that cannot be " - "reached mid-run degrades only the checks that needed the failing call, and the report is " - "never rendered as if it were complete - see its error-contract row. Read-only, and " - "**exempt from the Iteration Budget Gate**", - failures=(cli_contract.Failure( - label="", - cause="Either no `.wikitool-tasks.json` (or a malformed one) - not yours to fix by " - "retrying unchanged, configure or repair it first - **or** the provider was reachable " - "at config-parse time but a read call failed mid-run, in which case the full report " - "(findings plus which checks ran) is printed first and exit 1 follows, never a silent " - "partial success", - reaction="The two exit-1 causes above need different responses: a config problem needs " - "editing `.wikitool-tasks.json`; an unreachable provider (e.g. the tracker app not " - "running) needs starting it, then a plain retry - the command re-reads everything fresh " - "each time, so nothing here is ever stale to re-fetch", - ),), + notes=( + "Joins the configured task-tracker provider against `kb/gtd/` project pages over the " + "case-normalized project name, at read time, storing nothing - not even a `reports/` " + "file.", + "**stalled**: a tracker project with zero open items whose `kb/` page is `state: " + "active`; `dormant`/`completed`/`abandoned` never fire.", + "**waiting-overdue**: a `WAITING` item whose `follow_up_at` is older than " + "`thresholds.stalled_waiting_days`.", + "**unpaged-project**: a tracker project with no matching `kb/` page, older than " + "`thresholds.unpaged_project_weeks`.", + "**no-open-loop**: a `kb/` page `state: active` with no matching tracker project, or " + "one with zero open items - the reverse direction of unpaged-project, so a rename on " + "either side surfaces on both.", + "**someday-stale**: a someday/maybe item untouched for longer than " + "`thresholds.someday_stale_months`.", + "A value a provider genuinely cannot supply - a `WAITING` item with no `follow_up_at`, a " + "tracker project with no determinable creation date - is its own finding " + "(`waiting_no_follow_up`/`project_age_unknown`) rather than a silent skip.", + "Thresholds come from `.wikitool-tasks.json`, never from the schema.", + "Text output is one `[check] project: message` line per finding, preceded by a " + "`Source:` line naming which access path answered and, for `superproductivity`'s " + "`access: \"snapshot\"`, the snapshot's age. `--json` carries the same findings plus " + "`checks_run`/`checks_skipped`/`kb_project_count`/`complete`/`source` " + "(`{\"kind\": ..., \"detail\": ...}` or `null`).", + "A provider that cannot be reached mid-run degrades only the checks that needed the " + "failing call; the report is printed in full, then exit 1 follows - never rendered as " + "if it were complete.", + "Re-reads everything fresh on every call, so nothing is ever stale to re-fetch.", + "Read-only, and **exempt from the Iteration Budget Gate**.", + ), + failures=( + cli_contract.Failure( + cause="No `.wikitool-tasks.json`, or a malformed one - a clear \"no tracker " + "configured\" message", + reaction="Not fixed by retrying unchanged - configure or repair " + "`.wikitool-tasks.json` first", + ), + cli_contract.Failure( + cause="The provider was reachable at config-parse time but a read call failed " + "mid-run; the full report (findings plus which checks ran) was printed first", + reaction="Start the unreachable provider (e.g. the tracker app), then retry plainly", + ), + ), + examples=( + "tools/wikitool review", + "tools/wikitool review --json", + ), + never=( + "Never present a report that exited 1 as complete.", + ), + see_also=( + "`gtd-weekly-review` skill - turns the findings into decisions", + "`wikitool task list` / `wikitool task close` - act on an item", + "`wikitool new project` - a project page and its tracker project", + ), )) def review_command( json_out: bool = typer.Option(False, "--json", help="Print the findings as JSON."), diff --git a/tools/chemenu/commands/search.py b/tools/chemenu/commands/search.py index 54ff215..a1f889a 100644 --- a/tools/chemenu/commands/search.py +++ b/tools/chemenu/commands/search.py @@ -136,38 +136,69 @@ def render_table(result: SearchResult, show_matches: bool) -> str: atomic="Read-only", budget=cli_contract.Budget.EXEMPT, ), - notes="Find pages in `kb/` without reading the index. Text search runs through a pluggable " - "backend (`rg` today); `--field` predicates are evaluated on frontmatter - `f=v`, " - "`f~substring`, `'f>=v'`, `'f:*'` (present), `'!f'` (absent), repeatable and ANDed. With no " - "text this is a pure structured query. One hit per line, ` | `-separated as `score | " - "kind/subtype | title | path | summary`, so a hit can be judged without opening the page and " - "then opened without looking it up: **title and path are never truncated** (the title is " - "the identifier `touch`/`xref`/`cite` take), and the summary - the one lossy field, and the " - "only one that may contain the separator - goes last, so splitting on `\" | \"` with " - "`maxsplit=4` is unambiguous. Scope is pages: the backend walks `kb/` but drops anything " - "`kb_scan.iter_kb_pages` excludes (the kb-root meta files, every `COLLECTION.md`, every " - "generated `INDEX.md`), which is why a hand-run grep over `kb/` can add none of them but " - "those. `--limit` defaults to 50 (`0` for no limit) and **a truncated result says so** - " - "`50 of 182 result(s)` in the table, `total`/`truncated`/`limit` beside `count` in `--json`, " - "where `count` stays the number of results in the payload; the same default and the same " - "fields are what `api.search` and the MCP `search` tool carry, from one constant. A page " - "whose frontmatter does not parse can match no positive predicate, so it is **named** " - "rather than dropped: `--json` always carries an `unreadable` list of `{path, reason}` " - "(usually empty), and the table form writes the same lines to stderr. `--regex` is applied " - "by `rg` alone, whose engine is linear; the ranking boosts for title and summary are " - "literal-containment only, so a non-literal pattern is ranked by match count. `rg` is " - "killed after 30 s and reported as a failure. Read-only, and **exempt from the Iteration " - "Budget Gate**", - failures=(cli_contract.Failure( - label="", - cause="`rg` is not installed or did not finish within 30 s, a malformed `--field` " - "predicate, an unknown field name, or an unknown `--backend`", - reaction="Fix the argument and retry. A timeout is a pathological pattern or an " - "unresponsive corpus directory, not a slow answer - narrow the query or drop `--regex` " - "rather than retrying it unchanged. An unknown field name is reported with the list of " - "fields that do exist - it is never answered with an empty result, because that would " - "read as \"no such pages\"", - ),), + notes=( + "Finds pages in `kb/` without reading the catalog. Text search runs through a " + "pluggable backend (`rg` today).", + "`--field` predicates are evaluated on frontmatter - `f=v`, `f~substring`, `'f>=v'`, " + "`'f:*'` (present), `'!f'` (absent) - repeatable and ANDed. With no text this is a pure " + "structured query.", + "One hit per line, ` | `-separated as `score | kind/subtype | title | path | summary`. " + "**Title and path are never truncated** - the title is the identifier " + "`touch`/`xref`/`cite` take. The summary, the one lossy field and the only one that may " + "contain the separator, goes last, so splitting on `\" | \"` with `maxsplit=4` is " + "unambiguous.", + "Scope is pages: the backend walks `kb/` but drops the kb-root meta files, every " + "`COLLECTION.md` and every generated `INDEX.md` - a hand-run grep over `kb/` can add " + "none of them but those.", + "`--limit` defaults to 50 (`0` for no limit), and **a truncated result says so**: " + "`50 of 182 result(s)` in the table, `total`/`truncated`/`limit` beside `count` in " + "`--json`, where `count` stays the number of results in the payload. `api.search` and " + "the MCP `search` tool carry the same default and the same fields.", + "A page whose frontmatter does not parse can match no positive predicate, so it is " + "**named** rather than dropped: `--json` always carries an `unreadable` list of " + "`{path, reason}` (usually empty), and the table form writes the same lines to stderr.", + "An unknown field name is reported with the list of fields that do exist - never " + "answered with an empty result, which would read as \"no such pages\".", + "`--regex` is applied by `rg` alone, whose engine is linear; the ranking boosts for " + "title and summary are literal-containment only, so a non-literal pattern is ranked by " + "match count.", + "`rg` is killed after 30 s and reported as a failure.", + "Read-only, and **exempt from the Iteration Budget Gate**.", + ), + failures=( + cli_contract.Failure( + cause="`rg` is not installed", + reaction="Not transient - install `rg`, then retry", + ), + cli_contract.Failure( + cause="`rg` did not finish within 30 s", + reaction="A timeout is a pathological pattern or an unresponsive corpus directory, " + "not a slow answer - narrow the query or drop `--regex` rather than retrying it " + "unchanged", + ), + cli_contract.Failure( + cause="A malformed `--field` predicate, or an unknown `--backend`", + reaction="Fix the argument and retry", + ), + cli_contract.Failure( + cause="An unknown field name; the error lists the fields that exist", + reaction="Pick a field from that list and retry", + ), + ), + examples=( + 'tools/wikitool search "act runner"', + "tools/wikitool search --field entity_type=system --field '!sources'", + 'tools/wikitool search "docker" --collection entities --limit 10 --json', + ), + never=( + "Never retry a timed-out query unchanged.", + "Never grep `kb/` yourself instead - it can add no page this misses, only the " + "generated files it excludes.", + ), + see_also=( + "`wikitool links show` - the edges around a page once found", + "`wiki-query` skill - answering a question from the wiki", + ), )) def search_command( text: str = typer.Argument(