Files
chemenu/tools/chemenu/commands/review_cmd.py
T
torben e07d1ca42a
CI / verify (push) Successful in 50s
Release / release (push) Successful in 36s
SP-Zugriffsweg explizit (access: api/snapshot, #133) und follow_up_at-Korrektur (dueWithTime/dueDay, #135)
Files changed:
- CHANGES.md
- INSTALL.md
- VERSION
- tools/CONTRACT.md
- tools/chemenu/commands/doctor.py
- tools/chemenu/commands/new_page.py
- tools/chemenu/commands/review_cmd.py
- tools/chemenu/review.py
- tools/chemenu/tasks/__init__.py
- tools/chemenu/tasks/config.py
- tools/chemenu/tasks/protocol.py
- tools/chemenu/tasks/superproductivity.py
- tools/chemenu/tests/test_doctor.py
- tools/chemenu/tests/test_new_page.py
- tools/chemenu/tests/test_review.py
- tools/chemenu/tests/test_superproductivity.py
- tools/chemenu/tests/test_tasks_config.py
2026-09-20 20:52:05 +02:00

96 lines
3.6 KiB
Python

"""`wikitool review` - the terminal adapter over `chemenu.review` (Gitea #125).
The checks, the join and the partial-report rule live in `chemenu.review`,
which imports no CLI machinery. This module owns only what a terminal needs:
the `--json` flag, the two render forms, and the exit code.
"""
from __future__ import annotations
import json
import typer
from chemenu import config
from chemenu.commands._util import fail
from chemenu.errors import ValidationError
from chemenu.review import ALL_CHECKS, ReviewReport, run_review
__all__ = ["render_report", "report_to_dict", "review_command"]
def render_report(report: ReviewReport) -> str:
"""The `--json`-free rendering. One line per finding, `[check] project:
message`, so a hit can be told apart from the summary line without a
schema - the same shape `search`'s table takes for the same reason."""
lines: list[str] = []
if report.source is not None:
lines.append(f"Source: {report.source.kind} ({report.source.detail})")
if report.checks_skipped:
lines.append("INCOMPLETE - the following check(s) did not run:")
for check, reason in report.checks_skipped:
lines.append(f" - {check}: {reason}")
lines.append(
f"Partial result: {report.kb_project_count} kb/ project page(s) found; no "
"tracker cross-check for the check(s) above."
)
lines.append("")
if not report.findings:
lines.append("No findings.")
else:
for finding in report.findings:
lines.append(f"[{finding.check}] {finding.project}: {finding.message}")
lines.append("")
lines.append(f"{len(report.findings)} finding(s), {len(report.checks_run)}/{len(ALL_CHECKS)} check(s) ran.")
return "\n".join(lines)
def report_to_dict(report: ReviewReport) -> dict:
"""The `--json` form. Carries the same three things the text form does -
findings, which checks ran, which were skipped and why - so a caller never
has to parse prose to tell a partial report from a complete one."""
return {
"findings": [
{"check": finding.check, "project": finding.project, "message": finding.message}
for finding in report.findings
],
"checks_run": list(report.checks_run),
"checks_skipped": [
{"check": check, "reason": reason} for check, reason in report.checks_skipped
],
"kb_project_count": report.kb_project_count,
"complete": report.complete,
"source": (
{"kind": report.source.kind, "detail": report.source.detail}
if report.source is not None
else None
),
}
def review_command(
json_out: bool = typer.Option(False, "--json", help="Print the findings as JSON."),
):
"""Run the weekly GTD review: join the task tracker against kb/gtd/ pages
over the project name and report the five staleness/mismatch checks
(#119 D10/D26). Read-only - stores nothing, not even a reports/ file
(#119 D3), and is exempt from the Iteration Budget Gate like `search`."""
try:
report = run_review(config.ROOT)
except ValidationError as exc:
fail(str(exc))
return
if json_out:
typer.echo(json.dumps(report_to_dict(report), indent=2))
else:
typer.echo(render_report(report))
if not report.complete:
# Printed above already - this is deliberately not fail(), which
# would swallow the report just rendered behind a single ERROR line.
# See chemenu.review.ReviewReport.complete: an incomplete report must
# never exit 0 the way a quiet week does.
raise typer.Exit(code=1)