Skip to content
Back to skills

Frontmatter Guard

ASecurity

Injects the canonical vault frontmatter schema snippet into agent prompts before any vault-write task, preventing malformed YAML frontmatter in Obsidian notes. <example>Context: wave-executor is about to dispatch a vault-mirror agent that writes learning notes under ~/Projects/vault/40-learnings/. user: "dispatch vault-write agent" assistant: "Injecting frontmatter-guard snippet into agent prompt (vault scope detected). Required fields: id, type, created, updated. Enum type: note|daily|projec...

  • 53 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added May 28, 2026
ai-agents

Works with

  • cli

Security analysis

A100/100

Scanned October 6, 2026

npx -y skills add Kanevry/session-orchestrator --skill frontmatter-guard --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Frontmatter Guard?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Frontmatter Guard
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/kanevry-frontmatter-guard/badge)](https://www.skillsdirectory.com/skills/kanevry-frontmatter-guard)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: frontmatter-guard
description: >
  Injects the canonical vault frontmatter schema snippet into agent prompts before any vault-write task,
  preventing malformed YAML frontmatter in Obsidian notes. <example>Context: wave-executor is about to
  dispatch a vault-mirror agent that writes learning notes under ~/Projects/vault/40-learnings/. user:
  "dispatch vault-write agent" assistant: "Injecting frontmatter-guard snippet into agent prompt (vault
  scope detected). Required fields: id, type, created, updated. Enum type:
  note|daily|project|person|reference|idea|learning|session." <commentary>The wave-executor pre-dispatch
  hook calls detectVaultTaskScope() — the fileScope contains /Projects/vault/40-learnings/ so the guard
  triggers and the snippet is prepended to the agent system prompt.</commentary></example>
user-invocable: false
model: inherit
---

# Frontmatter-Guard Skill

> Project-instruction file resolution: `CLAUDE.md` and `AGENTS.md` (Codex CLI) are transparent aliases — see [skills/_shared/instruction-file-resolution.md](../_shared/instruction-file-resolution.md). Wherever this skill mentions `CLAUDE.md`, the alias rule applies.

## Purpose

vault-mirror and other vault-write agents frequently produce notes with missing or malformed YAML frontmatter — incorrect `type` enum values, missing `id`, wrong date formats. The resulting notes fail the vault-sync validator at session-end, causing hard-gate failures that interrupt the close flow.

This skill solves the problem at the source: it injects the canonical vault frontmatter schema as a Markdown snippet into agent prompts *before* any vault-write task is dispatched. Agents that receive the snippet produce conformant frontmatter on the first attempt, eliminating the validator feedback loop.

## When to Use

Trigger this skill whenever the task being dispatched has vault-write scope. Use the `detectVaultTaskScope()` heuristic from `scripts/lib/frontmatter-guard.mjs` to decide programmatically:

```js
import { readVaultSchema, generateFrontmatterSnippet, detectVaultTaskScope } from './scripts/lib/frontmatter-guard.mjs';

const isVaultTask = detectVaultTaskScope(taskDescription, fileScope);
if (isVaultTask) {
  const schema = readVaultSchema();
  if (schema) {
    const snippet = generateFrontmatterSnippet(schema);
    // prepend snippet to agent system prompt
  }
}
```

The heuristic fires when any of the following is true:

- Any file in `fileScope` contains `/Projects/vault/` in its path.
- Any file in `fileScope` is under a known vault subdirectory: `40-learnings/`, `50-sessions/`, `03-daily/`, `01-projects/`.
- `taskDescription` mentions `vault` or `vault-mirror` AND contains a write-intent keyword (`write`, `create`, `generate`, `emit`, `mirror`, `update`, `add`, `insert`).

`readVaultSchema()` returns `null` when the schema source is missing — the caller must handle this gracefully (skip injection rather than throw).

## How to Invoke

### Programmatic (wave-executor pre-dispatch — deferred to W3-C2)

The canonical call site is the wave-executor pre-dispatch hook. Before spawning an agent whose `fileScope` matches the heuristic, the coordinator calls:

```js
import {
  readVaultSchema,
  computeSchemaHash,
  generateFrontmatterSnippet,
  detectVaultTaskScope,
} from '../../scripts/lib/frontmatter-guard.mjs';

function buildAgentPrompt(basePrompt, taskDescription, fileScope) {
  if (!detectVaultTaskScope(taskDescription, fileScope)) {
    return basePrompt;
  }
  const schema = readVaultSchema(); // null if schema source missing
  if (!schema) return basePrompt;   // graceful fallback

  const snippet = generateFrontmatterSnippet(schema);
  const hash = computeSchemaHash(schema.schemaText);
  return `${snippet}\n\n<!-- frontmatter-guard:${hash} -->\n\n${basePrompt}`;
}
```

The `<!-- frontmatter-guard:<hash> -->` HTML comment records which schema version was injected. It is invisible in rendered Markdown and allows forensic tracing of schema drift.

### Manual (coordinator-direct)

When reviewing a vault-write agent's output, call `generateFrontmatterSnippet()` and paste the result into the task description manually if the auto-dispatch hook is not yet wired.

## Output Contract

`generateFrontmatterSnippet(schema)` returns a deterministic Markdown string with the following sections, in order:

1. **H2 heading** — `## Vault Frontmatter Schema (REQUIRED for files under ~/Projects/vault/)`
2. **Required fields** — inline code list: `id`, `type`, `created`, `updated`
3. **Enums** — `type` (8 values) and optional `status` (7 values) as pipe-separated backtick lists
4. **Field formats** — `id` regex, `tags` nesting convention, date format
5. **Examples** — one fenced YAML block per canonical type: `reference`, `session`, `learning`, `daily`, `project`

The output is stable across calls for the same schema version. Regenerate only when `computeSchemaHash()` returns a different value than the previously injected hash.

## Schema Source Path

The canonical schema source is `packages/zod-schemas/src/vault-frontmatter.ts` inside a **projects-baseline** checkout. That checkout is **optional and private** — see [`docs/baseline.md`](../../docs/baseline.md) — so the path is RESOLVED, never hardcoded. `resolveSchemaSourcePath()` resolves it in two tiers and returns `null` when nothing resolves. When the EXPLICIT tier is set it is used **alone** — probing past a wrong explicit value would silently read a different baseline than the one named:

| Tier | Candidate | Set by |
|---|---|---|
| **explicit** (exclusive) | `<baseline-path>/packages/zod-schemas/src/vault-frontmatter.ts` | `SO_BASELINE_PATH` env, else `owner.yaml` `paths.baseline-path` (host-local, never committed) — via `resolveHostPath('baseline-path', …)` |
| convention 1 | `<repoRoot>/../projects-baseline/packages/…` | sibling-checkout convention, the same one `scripts/sync-vault-schema.mjs` uses |
| convention 2 | `~/Projects/projects-baseline/packages/…` | legacy default this module shipped with |

Before this was resolved, convention 2 was the ONLY path and it was hardcoded: on a host whose checkout lives anywhere else, `readVaultSchema()` returned `null` and `generateFrontmatterSnippet()` then died with `Cannot destructure property 'typeEnum' of 'schema' as it is undefined`.

`readVaultSchema()` reads the resolved file on every call unless the in-memory mtime cache is current. It returns `null` (no throw) when no candidate resolves or the file is unreadable.

**Degraded mode.** With `null`, `generateFrontmatterSnippet()` does not throw: it falls back to an in-module enum/field set mirroring `skills/vault-sync/validator.mjs` (this repo's own in-tree copy of the schema, and what `vault-sync` actually validates against) and writes ONE stderr WARN per process. `computeSchemaHash()` returns `null` in that state — never the SHA-256 of the empty string, which would look like a real measurement and compare equal across every baseline-less host.

The parsed output includes:

| Field | Type | Description |
|---|---|---|
| `typeEnum` | `string[]` | Values of `vaultNoteTypeSchema` z.enum |
| `statusEnum` | `string[]` | Values of `vaultNoteStatusSchema` z.enum |
| `requiredFields` | `string[]` | Always `['id', 'type', 'created', 'updated']` |
| `idRegex` | `string` | Pattern from `slugRegex` in source |
| `tagsRegex` | `string` | Pattern from `tagPathRegex` in source |
| `schemaText` | `string` | Raw source text (used by `computeSchemaHash`) |

## Update Workflow

When the canonical schema source changes (new `type` enum value, new required field, regex adjustment):

1. The mtime cache in `frontmatter-guard.mjs` auto-invalidates — no manual step needed.
2. `readVaultSchema()` re-parses the source on the next call and returns updated values.
3. `generateFrontmatterSnippet()` produces an updated snippet automatically.
4. `computeSchemaHash()` returns a new 8-char hash, which differs from previously-injected hashes in agent prompts — this is the signal that the injected snippet is stale.
5. If the schema adds a new required field or changes enum values in a breaking way, update the five examples in `generateFrontmatterSnippet()` inside `frontmatter-guard.mjs` to reflect the new valid shapes.

Schema drift between the canonical source and vault-sync's vendored inline schema must be resolved in the same commit — see `skills/vault-sync/SKILL.md` § Schema source.

## See Also

- `scripts/lib/frontmatter-guard.mjs` — implementation
- `skills/vault-sync/SKILL.md` — downstream validator (session-end hard gate)
- `skills/vault-mirror/SKILL.md` — primary vault-write consumer
- `skills/wave-executor/SKILL.md` — future call site (W3-C2 wiring)
- `skills/_shared/config-reading.md` — Session Config field reference

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…