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
96 lines
3.6 KiB
Python
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)
|