Files

39 lines
4.3 KiB
Markdown
Raw Permalink Normal View History

# 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. `active` for person/company/reference/project) instead of omitting it.
- Replace the empty `tags: []` scaffold with either (a) an auto-seeded baseline tag (the note's `kind`, e.g. `- company`) so the key is never empty, or (b) a required `--tags` argument for entity kinds, or (c) a `needs-tags` marker tag that the completeness check (below) flags. Pick one; my preference is (a)+(b): seed `- <kind>` and accept `--tags a,b,c` to enrich at capture time.
- Acceptance: a note created by `capture "X" --kind company` passes 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 active` succeeds whether or not `status` already 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 `tags` key, or `tags: []`, or a `tags:` 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.py` on a vault with a `tags: []` company note reports one `incomplete-frontmatter` violation; a fully-populated note reports none.
## Nice-to-have
- A `--fix` / remediation mode (or a `sweep` sub-pass) that backfills `status: <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 $var` does NOT word-split in zsh — use arrays or `while read`.
## Evidence / where I looked
- `echo.py` `cmd_fm` (~L405), arg parser (~L673), `cmd_patch`.
- `vault_lint.py` `REQUIRED_FM` (L42), per-note loop (~L161173), check-label registry (~L301+), `walk()`/`list_dir()` enumeration.
- `echo_ops.py` `_build_note` (~L103) — has a `status_v` param already, so capture is positioned to stamp status.
- Entity index `_agent/index/entities.json` stores `path/kind/title/aliases/last_seen` — not `tags`/`status` (so a completeness check must read notes, as `vault_lint` already does).