Use when Obsidian vault notes have incomplete or missing YAML frontmatter and need structural metadata filled in. Best for bulk enrichment of an entire vault. Trigger phrases - "add properties", "enrich metadata", "fill frontmatter", "prepare my vault", "backfill created", "enrich before sort", "missing metadata", "incomplete frontmatter".
Scanned 9/2/2026
Install to Claude Code
npx -y skills add neckarshore-skills/obsidian-vault-autopilot --skill property-enrich --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Property Enrich?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/neckarshore-skills-property-enrich)More formats (shields.io, HTML) on the badges page.
---
name: property-enrich
status: stable
description: Use when Obsidian vault notes have incomplete or missing YAML frontmatter and need structural metadata filled in. Best for bulk enrichment of an entire vault. Trigger phrases - "add properties", "enrich metadata", "fill frontmatter", "prepare my vault", "backfill created", "enrich before sort", "missing metadata", "incomplete frontmatter".
---
# Property Enrich
Fill missing structural metadata: `title`, `created`, `modified`. Additive only — never overwrites existing values, except `modified`, the sanctioned YAML-sanity repairs, the canonical reorder, and the mandated `VaultAutopilot` tag (`references/skill-log.md`). See Boundaries for the full list.
## When to Run This
**Recommended for bulk enrichment.** property-enrich fills `created`, `title`, and `modified` across your entire vault in one pass — efficient for initial setup or after a clone. Note-rename and inbox-sort auto-enrich `created` per-note during their runs (Nahbereich), so property-enrich is no longer a strict prerequisite. It remains the best choice for bulk metadata coverage and for filling `title` and `modified`, which other skills do not auto-enrich.
## Principle: Core + Nahbereich + Report
- **Core:** Fill missing metadata from content, path, filesystem
- **Nahbereich:** Create frontmatter if none exists
- **Report:** Fields added per type, source per note
## Parameters
| Parameter | Default | Description |
|-----------|---------|-------------|
| `cooldown_days` | 3 | Skip notes created within the last N days. Grace period so the user can review recent captures before automation touches them. **Date source:** YAML `created` field in frontmatter. If missing, evaluate via the Source Hierarchy (filename date > Git first-commit > filesystem birthtime), gated by the clone-cluster check (Step 3a) so clone-time birthtime never counts as a real creation date. Never use modification date. |
| `scope` | inbox | Which folder to scan. `inbox` = inbox root only (default). `inbox-tree` = inbox folder including all subfolders (opt-in for bulk-mode, e.g. initial vault setup). `vault` = entire vault excluding root. `folder:<path>` = specific subfolder. User confirms before execution. |
| `clone_cluster_skip` | true | When `true` (default), skip `created` enrichment for files whose only available date source is filesystem birthtime AND whose birthtime falls in a detected clone-cluster window. See `references/clone-cluster-detection.md`. Set to `false` to disable the gate (e.g. on known-clean vaults where you want birthtime-fallback behavior). |
## Protected Files
Never process or modify these files (see `references/vault-autopilot-note.md`):
- `_vault-autopilot.md` in vault root
- Any file starting with `_` in vault root (reserved for plugin management)
## Properties (v0.1.0)
| Property | Source | Overwrite? |
|----------|--------|-----------|
| `title` | First H1 heading, fallback: filename without `.md` | Never |
| `created` | Source Hierarchy (see below) | Never |
| `modified` | Filesystem mtime, written as `YYYY-MM-DD HH:MM` | **Always** (refreshed on every write) |
### Canonical property order
After all field writes, frontmatter is reordered into a canonical, human-readable order via `references/yaml-edits.md` recipe (g): `title` → `description` → `type` → `status` → `created` → `modified` → `aliases` → `source` → `parent` → `priority` → *(custom keys, original order)* → `tags` (last). Recipe (c) inserts safely (append-at-end); recipe (g) is the finalize pass that produces the canonical layout. The reorder moves each property as an atomic unit (key line + its list items / folded body) — it never orphans a list item.
- `modified` is written with a `HH:MM` time component (not date-only) so an enrichment run is greppable and Obsidian renders it as text, not a Date-type widget.
- **Config:** `property_order` (ordered list, default = the order above) and `modified_timestamp` (bool, default `true`) are overridable per the "opinionated defaults, configurable everything" principle.
### `created` Source Hierarchy
When `created` is missing from YAML, derive it from the highest-priority available source. Prio 1 includes a normalization preprocessing step for non-ISO formats (v0.1.3+):
| Prio | Source | How | When reliable |
|------|--------|-----|---------------|
| 1 | YAML `created` exists | If value parses as ISO 8601 → use directly. Else try German `DD.MM.YYYY[, HH:mm:ss]` normalization (per `references/german-date-normalization.md`). If normalized, use the ISO result. If unparseable, flag as Class-C "unparseable date format" finding and fall through to Prio 2. **Never overwrite the YAML value** — additive-only contract preserved (the normalized form is used internally for downstream skills like cooldown evaluation; the YAML field stays as-authored). | Always |
| 2 | Filename date pattern | Parse `YYYY-MM-DD` from filename, e.g. `2024-03-14 Meeting Notes.md` | When user names files with dates |
| 3 | Git first-commit timestamp | `git log --follow --diff-filter=A --format=%aI -- <file>` | When vault is under Git |
| 4 | Filesystem birthtime (last resort) | `stat -f %SB` (macOS) / `stat -c %W` (Linux) | Only on native (non-cloned) vaults |
**Rules:**
- Try 1 through 4 in order, use the first valid date
- Log which source was used per note in the Report (Source column)
- If no source yields a valid date, skip the note and report it as a Finding
### Clone Detection Warning
When Source 4 (filesystem birthtime) is used AND all birthtimes in the batch cluster within a 1-hour window: log a warning in the Report.
> **Warning:** All created dates derived from filesystem birthtime within a narrow window. This vault may be a clone. Consider verifying dates manually.
The warning does NOT block execution — it informs only.
### Relational Properties (v0.2.0)
> **Deferred to v0.2.0.** Properties like `aliases`, `parent`, `source`, and `priority` are not filled in the current release.
## Pre-flight
Before **every** invocation of this skill — including resumed sessions and re-triggers within the same conversation:
1. **Always (every OS):** Run [`references/clone-preflight.md`](../../references/clone-preflight.md). It detects clone-induced birthtime clusters and emits a WARN before any date-derivation runs. Cross-platform — applies on macOS, Linux, and Windows. WARN-flow only; skill execution continues.
2. **On Windows additionally:** Follow [`references/windows-preflight.md`](../../references/windows-preflight.md) end-to-end (registry check, trailing-dot folder detection, Windows-aware file-enumeration pattern). The enumeration pattern in Step 6 of that preflight applies to every subsequent file-listing call this skill makes — `Scan` (step 2 below) included. On macOS or Linux, this preflight is a no-op.
Run the checks freshly each time. Do not assume a previous turn's pass result still holds — registry state, folder topology, and birthtime clustering can change between invocations and previous results are not authoritative.
## Workflow
1. **Discover vault** — resolve `${OBSIDIAN_VAULT_PATH}`. Ask for scope. Confirm if 50+ notes.
2. **Scan** — read frontmatter, path, filesystem timestamps per note.
- **2a. Repair corrupted quoted-key variants first (Nahbereich, sanity-check).** Call `references/yaml-sanity.md` for each scanned note. Verdict-routing per `references/yaml-sanity.md` § "Per-skill policy":
- `BROKEN_KEYS_INSIDE_COLON` (shape β — F26 inside-colon): normalize via `references/yaml-edits.md` recipe (f) — handles ALL quoted-key patterns (broadened from v0.1.0/v0.1.2 hardcoded list of `"created:"`/`"modified:"`). After normalization, resolve duplicate-key collisions per recipe (f) Step 3 (identical-value collisions silent-dedup'd, divergent-value collisions ABORT — see next bullet). Re-call sanity-check (idempotent fixpoint) — verdict must now be `OK`, `OK_QUOTED`, or `OK_NO_FRONTMATTER`.
- `DUPLICATE_KEYS_IDENTICAL_VALUES` (v0.1.4 W4 — pre-existing plain duplicates with identical values): repair via recipe (f) silent dedup, then re-run sanity-check.
- `DUPLICATE_KEYS_DIVERGENT_VALUES` (v0.1.4 W4 — F7 family; recipe (f) refused to auto-resolve): **skip the file** + log Class-A finding "duplicate-key-divergent-values" (route to user / note-rename for manual resolution). Recipe (f) leaves the file unchanged on disk; user must merge values manually.
- `MULTIPLE_FRONTMATTER_BLOCKS` or `UNCLOSED_FRONTMATTER`: skip the file and log Class-A finding (route to user / note-rename).
- `OK_QUOTED` (shape α — standard quoted-key, valid YAML): proceed normally; skill regexes accept both plain and standard-quoted forms.
- `OK` / `OK_NO_FRONTMATTER`: proceed normally.
This step is mandatory BEFORE Step 3 (Compute / Source Hierarchy walk) — without it, the Hierarchy falls through to filesystem birthtime on files where YAML had a valid (but broken-keyed) date. Historical bugs: F19 LIVE-CONFIRMED in GR-2 Cell 1 (2026-04-28) — 60 of 1016 inbox-tree files affected (5.9% blast-radius). F26 cluster generalizes the inside-colon pattern across all quoted-keys. F7 (GR-3 Cell 1, 2026-05-01) generalizes duplicate-key resolution beyond first-wins-silent.
3. **Compute** — for each note missing `created`: walk the Source Hierarchy (Prio 1 through 3, with German-date normalization in Prio 1 per `references/german-date-normalization.md`, then Prio 4 gated by Step 3a). Compute `title` from H1 or filename. Read `modified` from filesystem.
- **3a. Clone-cluster gate before Prio 4 (filesystem birthtime).** Before the first invocation of Prio 4, detect the vault-wide clone-cluster window per `references/clone-cluster-detection.md` § "Cluster Window Detection". If a cluster is declared, for every note where Prio 1-3 yielded no value: invoke recipe (a) `is_birthtime_in_clone_cluster_window`. If recipe (a) returns 0 (in cluster), invoke recipe (b) `has_alternate_date_source` — note that recipe (b) walks Prio 1-3 again as a defensive re-check. If recipe (b) returns 1 (no alt source), SKIP this note's `created` enrichment: do NOT write a `created` field, log the file in the per-skill findings file as Class-C "clone-cluster birthtime, no alt source" per `references/clone-cluster-detection.md` § "Findings format". The note still gets `title` and `modified` enriched normally — only `created` is gated. If recipe (a) returns 1 (not in cluster), proceed with Prio 4 (filesystem birthtime) as before. Behavior is gated by config field `clone_cluster_skip` (default `true`); when `false`, this step is a no-op. If no cluster is declared (fewer than 10 files in any 1 h bin), this step is a no-op for every note.
4. **Preview** — summary table with sample changes including Source column. Wait for confirmation.
5. **Write** — pre-write, call `references/yaml-sanity.md` again as defense-in-depth (sanity-check is idempotent — repeated calls are no-ops if Step 2a already normalized). Add fields using line-by-line YAML edits per `references/yaml-edits.md`. Never use `str.replace`. Never use multi-line regex with `(?s)` or `.+`/`.*` against newline-spanning input. New fields are inserted as new lines immediately before the closing `---` (recipe c). List-field appends (e.g. `tags:`) follow recipe d in `references/yaml-edits.md` § "Append to a list field". Preserve all existing field values.
6. **Skill Log** — for each enriched file: add `VaultAutopilot` tag and append skill log callout row (see `references/skill-log.md`). Action format: `Added [field list] (created source: [source])`. YAML tag-list edits and skill-log callout edits MUST follow `references/yaml-edits.md` (recipes d + e).
- **6a. Finalize canonical property order (recipe g).** After all frontmatter inserts (Step 5 fields + the Step 6 `VaultAutopilot` tag), reorder the frontmatter into canonical order per `references/yaml-edits.md` recipe (g) — `title` first … `tags` last. Apply it line-by-line per the recipe, or invoke the proven reference impl `scripts/validate-recipe-g.py` (`--file <note>` prints the reordered text). The reorder moves each property as an atomic unit (no orphaned list items), is idempotent (already-canonical notes are a zero-diff no-op), and preserves birthtime by writing in place (recipe a write-back). No-op when `property_order` is configured empty.
7. **Write findings file** — for any non-trivial Findings (Class A/B/C/D as defined in `references/findings-file.md`), append a section to `<VAULT>/_vault-autopilot/findings/<YYYY-MM-DD>-property-enrich.md`. Create the folder chain if missing. Never edit prior findings — append-only ledger.
8. **Report and log** — append to `logs/run-history.md`.
## Boundaries
- Additive only, with three sanctioned exceptions: `modified`, the YAML-sanity repairs of Step 2a (`references/yaml-edits.md` recipe f), and the canonical property reorder of Step 6a (recipe g). Both of the latter rewrite existing frontmatter and are required by the workflow — see also the mandated `VaultAutopilot` tag (`references/skill-log.md`)
- Does not write `description` (property-describe), `status` or `type` (property-classify)
- Does not rewrite note body content — the mandated `Vault Autopilot` skill-log callout (`references/skill-log.md`) is the one appended exception. Does not delete, move, or rename files
- Does not fill `aliases`, `parent`, `source`, `priority` in v0.1.0
## Report Format
```
## Property Enrich Report — [Date]
### Done
| # | Note | title | created | Source | modified | Findings |
|---|------|-------|---------|--------|----------|----------|
| 1 | Budget Review.md | Budget Review | 2024-06-15 | filename | 2026-04-13 14:32 | — |
| 2 | Architecture.md | Architecture | 2025-11-20 | git | 2026-04-13 14:32 | — |
- Notes enriched: X | Already complete: X | Skipped (no valid date): X
### Clone Detection
[If triggered:] Warning — all birthtime-derived dates cluster within 1 hour. This vault may be a clone.
[If not triggered:] No clone indicators detected.
### Findings
- [Observations for other skills]
```
## Quality Check
- [ ] No existing property values were overwritten (except `modified`)
- [ ] Frontmatter finalized into canonical property order (recipe g) — `title` first, `tags` last, no orphaned list items
- [ ] `modified` written with `HH:MM` time component (`YYYY-MM-DD HH:MM`), not date-only
- [ ] `created` Source Hierarchy was followed (filename > git > birthtime)
- [ ] Source column in report shows derivation per note
- [ ] Preview shown and confirmed before writing
- [ ] No `aliases`, `parent`, `source`, or `priority` fields were written
- [ ] Tag-list edits used line-by-line procedure per `references/yaml-edits.md` (no multi-line regex, no `str.replace`)
- [ ] Sanity-check called pre-Compute (Step 2a) and pre-Write (Step 5) per `references/yaml-sanity.md`
- [ ] Quoted-key broken-key variants (shape β — inside-colon) normalized via recipe (f), not appended-below
- [ ] Duplicate-key divergent-value collisions (F7 family) ABORT recipe (f), file unchanged, Class-A finding logged — never silent-pick a winner (v0.1.4 W4)
- [ ] Duplicate-key collisions resolved (first-occurrence-wins, subsequent removed and logged as Class-D)
- [ ] German date format (`DD.MM.YYYY[, HH:mm:ss]`) recognized in Prio 1 per `references/german-date-normalization.md` — not silently dropped to Prio 2-4
- [ ] Findings file written per `references/findings-file.md` for any non-trivial findings
- [ ] Step 3a clone-cluster gate followed per `references/clone-cluster-detection.md` — files in cluster window with no alt source had `created` SKIPPED (not Prio-4-enriched), Class-C finding logged, `title`/`modified` still enriched
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!