forked from jason/echo
ver 1.5.0 — six-improvement pass: retention, routing, self-firing memory
1. capture-update keeps the whole body (was truncating to first line) 2. frontmatter completeness: kind-default status + kind-seeded tags at capture, fm create-or-replace, incomplete-frontmatter lint, sweep backfill (one data source: KIND_STATUS/KIND_REQUIRED_FM) 3. pre-write duplicate gate (exit 76, --merge-into/--force) 4. recall corpus + ranking: sessions/journal indexed (down-weighted), BM25 x freshness x status fusion, recall --json, index schema 2 5. session hooks: SessionStart auto-load, Stop reflection nudge 6. one-tap triage verb with processing-log audit; --json on read verbs +22 mock end-to-end tests; all suites green. Docs current (README, CHANGELOG, SKILL.md, commands, references, MAINTENANCE, eval README). Drops tracked .DS_Store (gitignored); retires README-gretchen.md (moved to gitignored dist/); adds the field report that drove item 2. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -120,6 +120,22 @@ def as_list(value: object) -> list[object]:
|
||||
return value if isinstance(value, list) else [value]
|
||||
|
||||
|
||||
def fm_field_populated(raw: str, fields: dict[str, object], field: str) -> bool:
|
||||
"""True when `field` exists AND holds a real value. parse_frontmatter is line-based,
|
||||
so a block-style list (`tags:\n - x`) parses as "" — check the raw text for items
|
||||
before calling the field empty."""
|
||||
value = fields.get(field)
|
||||
if isinstance(value, list):
|
||||
return bool(value)
|
||||
if str(value or "").strip():
|
||||
return True
|
||||
lines = raw.splitlines()
|
||||
for index, line in enumerate(lines):
|
||||
if re.match(rf"^{re.escape(field)}:\s*$", line):
|
||||
return index + 1 < len(lines) and bool(re.match(r"^\s+-\s*\S", lines[index + 1]))
|
||||
return False
|
||||
|
||||
|
||||
def route_matchers():
|
||||
routing = json.loads((SCRIPT_DIR / "routing.json").read_text(encoding="utf-8"))
|
||||
routes = [(item["id"], re.compile(item["pattern"])) for item in routing.get("routes", [])]
|
||||
@@ -171,6 +187,15 @@ def main() -> int:
|
||||
missing = [k for k in REQUIRED_FM if not str(fm.get(k, "")).strip()]
|
||||
if fm and missing:
|
||||
flag("missing-frontmatter", f"{path}: missing {', '.join(missing)}")
|
||||
# Kind-scoped completeness: entity notes must classify (status + tags). The
|
||||
# per-kind requirement map lives in echo_index (KIND_REQUIRED_FM) so capture's
|
||||
# defaults, this check, and sweep's backfill all read the same source of truth.
|
||||
kind = idx_mod.kind_for_path(path)
|
||||
if fm and kind:
|
||||
for field in idx_mod.KIND_REQUIRED_FM.get(kind, ()):
|
||||
if not fm_field_populated(raw, fm, field):
|
||||
what = f"missing {field}" if field not in fm else f"empty {field}"
|
||||
flag("incomplete-frontmatter", f"{path}: {what}")
|
||||
created, updated = parse_date(fm.get("created")), parse_date(fm.get("updated"))
|
||||
if created and updated and updated < created:
|
||||
flag("date-order", f"{path}: updated {updated} is before created {created}")
|
||||
@@ -307,6 +332,7 @@ def main() -> int:
|
||||
"unknown-path": "Path matches no route in routing.json",
|
||||
"retired-path": "Write to a retired/dead path",
|
||||
"missing-frontmatter": "Missing required frontmatter field",
|
||||
"incomplete-frontmatter": "Incomplete entity frontmatter (status/tags) — sweep.py --apply backfills",
|
||||
"date-order": "updated earlier than created",
|
||||
"future-date": "updated date is in the future",
|
||||
"source-notes-wikilink": "Wikilink in source_notes (must be plain paths)",
|
||||
|
||||
Reference in New Issue
Block a user