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:
Jason Stedwell
2026-07-03 12:57:52 -05:00
parent e76add6192
commit be3fd36ebf
31 changed files with 1209 additions and 174 deletions
@@ -8,10 +8,14 @@ backfill what older vaults don't have yet:
2. **(re)build the entity index** (`_agent/index/entities.json`) from the notes
already in the vault (people, companies, projects, concepts, references,
meetings, areas, decisions, semantic/episodic memory, skills),
3. **symmetrize cross-links** — for every `## Related` link A -> B, ensure the
3. **backfill incomplete entity frontmatter** — fill a missing/empty `status:` with
the kind default and seed a missing/empty `tags:` with the note's kind, per the
KIND_REQUIRED_FM/KIND_STATUS maps in echo_index (what `/echo-health` flags as
`incomplete-frontmatter`),
4. **symmetrize cross-links** — for every `## Related` link A -> B, ensure the
reciprocal B -> A exists (it only adds the missing direction; it never invents
new links from body text), and
4. stamp the marker `schema_version` to the current schema (4).
5. stamp the marker `schema_version` to the current schema (4).
Dry-run by default; pass --apply to write. READ-ONLY without --apply.
Cross-platform: pure Python via echo.py.
@@ -43,6 +47,30 @@ SKIP_BASENAMES = {"README.md"}
kind_for = idx_mod.kind_for_path
def fm_block(text: str) -> str:
if not text.startswith("---"):
return ""
end = text.find("\n---", 3)
return text[:end] if end != -1 else text
def fm_populated(text: str, field: str) -> bool:
"""True when the frontmatter carries a real value for `field` (flow list `[x]`,
block list items, or a non-empty scalar). Mirrors vault_lint.fm_field_populated."""
fm = fm_block(text)
m = re.search(rf"(?m)^{re.escape(field)}:[ \t]*(.*)$", fm)
if not m:
return False
val = m.group(1).strip()
if val == "[]":
return False
if val:
return True
rest = fm[m.end():].lstrip("\n")
first = rest.splitlines()[0] if rest else ""
return bool(re.match(r"^\s+-\s*\S", first))
def get(path: str) -> str | None:
status, body = echo.request("GET", echo.vault_url(path))
if status == 404:
@@ -139,9 +167,36 @@ def main(argv: list[str] | None = None) -> int:
print(f"sweep: {tag} recall index — {rix.n_docs} docs (BM25)")
else:
n_idx = sum(1 for p in all_files if echo_recall._indexable(p))
print(f"sweep: {tag} recall index — would rebuild BM25 over {n_idx} entity notes")
print(f"sweep: {tag} recall index — would rebuild BM25 over {n_idx} corpus notes "
f"(entities + sessions/journal)")
# ---- 3. symmetrize existing Related links --------------------------------
# ---- 3. backfill incomplete entity frontmatter ----------------------------
# What /echo-health flags as incomplete-frontmatter, filled mechanically: a missing
# or empty status gets the kind default; missing/empty tags get the kind as a
# baseline tag. cmd_fm is create-or-replace, so absent keys are inserted surgically.
fixes: list[tuple[str, str, str]] = []
for path in all_files:
kind = kind_for(path)
if kind is None:
continue
text = texts.get(path) or ""
if not text.startswith("---"):
continue # no frontmatter block at all — lint flags it; not a mechanical fill
for field in idx_mod.KIND_REQUIRED_FM.get(kind, ()):
if not fm_populated(text, field):
value = (idx_mod.KIND_STATUS.get(kind, "active") if field == "status"
else json.dumps([kind]))
fixes.append((path, field, value))
print(f"sweep: {tag} frontmatter backfill — {len(fixes)} field(s) to fill")
for path, field, value in fixes[:40]:
print(f" {path}: {field} -> {value}")
if len(fixes) > 40:
print(f" ... and {len(fixes) - 40} more")
if apply:
for path, field, value in fixes:
echo.cmd_fm(path, field, value)
# ---- 4. symmetrize existing Related links --------------------------------
fileset = set(all_files)
basemap: dict[str, str] = {}
for p in all_files:
@@ -176,7 +231,7 @@ def main(argv: list[str] | None = None) -> int:
for tp, sp in missing:
links.add_one(tp, sp)
# ---- 4. stamp schema ------------------------------------------------------
# ---- 5. stamp schema ------------------------------------------------------
marker = get("_agent/echo-vault.md") or ""
cur = next((int(m) for m in re.findall(r"(?m)^schema_version:\s*(\d+)", marker)), 0)
if cur < CURRENT_SCHEMA: