24cd221b21
Files changed: - CHANGES.md - VERSION - kb/entities/projects/Chemenu.md - kb/log.md - tools/chemenu/commands/_util.py - tools/chemenu/commands/cite_cmd.py - tools/chemenu/commands/git_publish.py - tools/chemenu/commands/log_append.py - tools/chemenu/commands/page_ops.py - tools/chemenu/commands/provenance_cmd.py - tools/chemenu/commands/raw_cmd.py - tools/chemenu/commands/run_budget.py - tools/chemenu/commands/touch.py - tools/chemenu/commands/xref.py - tools/chemenu/frontmatter_io.py - tools/chemenu/lint_core.py - tools/chemenu/tests/test_log_append.py - tools/chemenu/tests/test_source_hygiene.py - tools/chemenu/tests/test_touch.py - tools/chemenu/tests/test_type_resolver.py - tools/chemenu/type_resolver.py - tools/wikitool - types/type-spec.md - types/type-spec.schema.yaml
85 lines
3.2 KiB
Python
85 lines
3.2 KiB
Python
"""Append correctly-formatted entries to kb/log.md."""
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
import typer
|
|
|
|
from chemenu import config
|
|
from chemenu.commands._util import fail, rel_path, success, today_iso
|
|
|
|
app = typer.Typer(help="Manage kb/log.md.")
|
|
|
|
VALID_OPS = ["ingest", "query", "lint", "create", "update", "delete", "rename", "move"]
|
|
|
|
# Matches the "## [YYYY-MM-DD] op | title" heading `format_log_entry` writes,
|
|
# in file order (oldest first, since entries are appended).
|
|
LOG_ENTRY_RE = re.compile(r"^## \[(\d{4}-\d{2}-\d{2})\] (\S+) \| (.+)$", re.MULTILINE)
|
|
|
|
|
|
def format_log_entry(op: str, title: str, body: str = "", today: Optional[str] = None) -> str:
|
|
today = today or today_iso()
|
|
entry = f"## [{today}] {op} | {title}\n"
|
|
if body.strip():
|
|
entry += f"\n{body.strip()}\n"
|
|
entry += "\n---\n"
|
|
return entry
|
|
|
|
|
|
def parse_log_entries(text: str) -> list[tuple[str, str, str]]:
|
|
"""Return every logged (date, op, title) entry in file order."""
|
|
return [(m.group(1), m.group(2), m.group(3)) for m in LOG_ENTRY_RE.finditer(text)]
|
|
|
|
|
|
def ingests_since_last_lint(entries: list[tuple[str, str, str]]) -> int:
|
|
"""Count `ingest` entries logged after the most recent `lint` entry (or
|
|
since the start of the log, if it has never been linted).
|
|
|
|
This is the deterministic count behind the Maintenance Schedule's "every
|
|
10 sources" full-lint cadence: nothing else in the system tracks it, so
|
|
without this the claim was prose with no enforcement - an agent (or user)
|
|
had to remember to count."""
|
|
count = 0
|
|
for _date, op, _title in entries:
|
|
if op == "lint":
|
|
count = 0
|
|
elif op == "ingest":
|
|
count += 1
|
|
return count
|
|
|
|
|
|
@app.command("append")
|
|
def log_append(
|
|
op: str = typer.Option(..., "--op", help="|".join(VALID_OPS)),
|
|
title: str = typer.Option(..., "--title", help="Brief description, e.g. a source path"),
|
|
body: str = typer.Option("", "--body", help="Optional multi-line details"),
|
|
body_file: Optional[Path] = typer.Option(None, "--body-file", help="Read the body from a file instead of --body"),
|
|
):
|
|
if op not in VALID_OPS:
|
|
fail(f"--op must be one of {VALID_OPS}")
|
|
text = body
|
|
if body_file:
|
|
text = body_file.read_text(encoding="utf-8")
|
|
entry = format_log_entry(op, title, text)
|
|
with config.LOG_FILE.open("a", encoding="utf-8") as f:
|
|
f.write("\n" + entry)
|
|
success(f"Appended log entry to {rel_path(config.LOG_FILE)}")
|
|
|
|
|
|
@app.command("status")
|
|
def log_status():
|
|
"""Report how many `ingest` operations have been logged since the last
|
|
`lint` - the deterministic trigger for the Maintenance Schedule's "every
|
|
10 sources" full-lint cadence. Read-only."""
|
|
if not config.LOG_FILE.exists():
|
|
success("No kb/log.md yet; nothing logged.")
|
|
return
|
|
entries = parse_log_entries(config.LOG_FILE.read_text(encoding="utf-8"))
|
|
count = ingests_since_last_lint(entries)
|
|
typer.echo(f"Ingests since last lint: {count}")
|
|
if count >= 10:
|
|
typer.echo("Threshold reached (>=10) - run the wiki-lint skill (`tools/wikitool lint ...`) next.")
|
|
success(f"{len(entries)} total log entries in {rel_path(config.LOG_FILE)}.")
|