18ae28f918
Chemenu kompiliert Rohnotizen zu einem verlinkten, quellengebundenen Wiki: raw/ -> types/ + tools/ -> kb/ -> reports/. Was mechanisch ist, macht tools/wikitool; was Urteil braucht, macht ein Agent unter Contracts, deren Grenzen in Code durchgesetzt sind statt im Prompt. Dieser Commit ist der Startpunkt der oeffentlichen Historie. Die vorherige Entwicklung fand in einer privaten Instanz statt und ist nicht Teil dieses Repositorys; ihre Erzaehlung steht vollstaendig in CHANGES.md, das mit 44 Eintraegen von 0.1.0 bis 2.1.0 erhalten geblieben ist. Der mitgelieferte Korpus ist ein Testbett und eine Demo: 170 Seiten ueber den Stack selbst - Gates, Lint, Versionierung, Suche, das Wiki-Muster. Er dokumentiert das Werkzeug mit den eigenen Mitteln des Werkzeugs. Lizenz: AGPL-3.0 fuer den Stack (tools/, types/), CC-BY-4.0 fuer die Inhalte. Die Grenze zwischen beiden ist der Dateiplan, den dist export berechnet - siehe NOTICE.
85 lines
3.2 KiB
Python
85 lines
3.2 KiB
Python
"""Append correctly-formatted entries to wiki/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 wiki/log.md.")
|
|
|
|
VALID_OPS = ["ingest", "query", "lint", "create", "update", "delete", "rename"]
|
|
|
|
# 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 wiki/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)}.")
|