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>
4.3 KiB
ECHO plugin — improvements & fixes (frontmatter completeness)
Context
While doing routine vault edits I found that partially-populated entity notes accumulate silently. An audit of resources/people|companies|references found 18 of 71 notes missing status and/or with empty tags (tags: [] or no key). Neither echo doctor nor vault_lint.py (/echo-health) catches this. I hand-fixed the vault, but the tooling should prevent the drift and make it easy to remediate. Three changes, root-cause first.
Root cause — capture writes incomplete canonical frontmatter
New entity notes created via capture land with tags: [] and no status key (verified on freshly-captured reference and company notes). Every such note is then "incomplete" by convention (complete siblings like resources/references/obsidian-local-rest-api.md carry status: active + real tags). So the drift is created at write time.
Fix (in capture / _build_note, echo_ops.py):
- Stamp a kind-appropriate default
status(e.g.activefor person/company/reference/project) instead of omitting it. - Replace the empty
tags: []scaffold with either (a) an auto-seeded baseline tag (the note'skind, e.g.- company) so the key is never empty, or (b) a required--tagsargument for entity kinds, or (c) aneeds-tagsmarker tag that the completeness check (below) flags. Pick one; my preference is (a)+(b): seed- <kind>and accept--tags a,b,cto enrich at capture time. - Acceptance: a note created by
capture "X" --kind companypasses the completeness check below with zero follow-up edits.
Fix 2 — fm cannot create a missing key (only replace)
echo.py: cmd_fm() is cmd_patch(path, "replace", "frontmatter", field, ...). If the field is absent, the Obsidian REST API returns 400 invalid-target ("The patch you provided could not be applied"). This means echo fm note.md status active fails on exactly the notes that need it (status missing), forcing agents into GET-modify-PUT, which is error-prone (I clobbered tags on 2 notes with a naive rewrite before catching it).
Fix: make fm create-or-replace. On invalid-target, fall back to inserting the key into the frontmatter block (after type:, preserving all other lines) and re-PUT — or add an explicit fm --create / set op. Must be surgical: never rewrite unrelated frontmatter or body.
- Acceptance:
echo fm note.md status activesucceeds whether or notstatusalready exists, changing nothing else in the file.
Fix 3 — vault_lint.py: add an incomplete-frontmatter check
Today REQUIRED_FM = ("type", "created") only, and status is validated solely for projects (folder↔status match). Nothing requires status/tags on entity notes.
Fix: add a check that, for entity kinds (person, company, reference, and any others that should be classified — area, skill), flags:
- missing or empty
status - missing
tagskey, ortags: [], or atags:key with no list items
Emit as a new check id incomplete-frontmatter with a message like "{path}: missing status" / "{path}: empty tags". Keep it kind-scoped so episodic/journal/session notes (which legitimately omit these) are not flagged. Consider a per-kind required-field map rather than the single global REQUIRED_FM tuple, so the rule is data-driven.
- Acceptance: running
vault_lint.pyon a vault with atags: []company note reports oneincomplete-frontmatterviolation; a fully-populated note reports none.
Nice-to-have
- A
--fix/ remediation mode (or asweepsub-pass) that backfillsstatus: <default>and a- <kind>baseline tag on flagged notes, so cleanup is one command instead of a hand-rolled script. - Note the zsh gotcha for any shipped helper loops: unquoted scalar
for x in $vardoes NOT word-split in zsh — use arrays orwhile read.
Evidence / where I looked
echo.pycmd_fm(~L405), arg parser (~L673),cmd_patch.vault_lint.pyREQUIRED_FM(L42), per-note loop (~L161–173), check-label registry (~L301+),walk()/list_dir()enumeration.echo_ops.py_build_note(~L103) — has astatus_vparam already, so capture is positioned to stamp status.- Entity index
_agent/index/entities.jsonstorespath/kind/title/aliases/last_seen— nottags/status(so a completeness check must read notes, asvault_lintalready does).