# description
A single entity in the graph. Typically a file on disk (a markdown skill, an agent, a TOML sub-agent definition, a plain-markdown note), but MAY also be a **virtual / derived** entity that lives only in memory and is reconstructed from one or more source files on every scan (e.g. an MCP server node derived from `settings.json` / `mcp.json` / `config.toml`). Virtual nodes carry `virtual: true` and use a synthetic `path` scheme (`mcp://<name>`, etc.). The `kind` is whatever the classifying Provider declares, open by design; the **built-in Claude Provider** emits `skill` / `agent` / `command` / `markdown` today, but external Providers (Cursor, Obsidian, …) MAY emit their own. Format-named kinds (`markdown`, future `toml`, future `json`) are reserved for the generic fallback only, when a file matches a specific role (agent / command / skill) that classification prevails over format naming.

# properties.path.description
Relative path from the scope root. Canonical node identifier in v0. Survives frontmatter edits; breaks on file moves (rare enough). FUTURE: a sibling `id` field (UUID stored in frontmatter) lands with write-back to decouple identity from location; `path` will remain as display metadata at that point. Stability: stable.

# properties.kind.description
Category assigned by the Provider. Open-by-design, any non-empty string an enabled Provider declares is valid (built-in Claude Provider catalog: `skill` / `agent` / `command` / `markdown`; external Providers MAY declare their own). The pattern restricts kind names to ASCII letters, digits, underscore, and hyphen, starting with a letter, up to 64 chars. The restriction is a security boundary: the UI uses the kind name as a fragment of CSS custom-property identifiers (`--sm-kind-<name>`) injected into a `<style>` tag, so values that would break out of the declaration context (semicolons, braces, whitespace) MUST be rejected at the kernel boundary. Per-kind frontmatter schemas live with the Provider that emits the kind. Stability: stable.

# properties.provider.description
Identifier of the Provider extension that classified this node (e.g. `claude`).

# properties.bodyHash.description
sha256 of the body content (post-frontmatter), hex-encoded lowercase. Used for rename heuristic (high confidence) and stale detection.

# properties.frontmatterHash.description
sha256 of the raw frontmatter block, hex-encoded lowercase. Used for rename heuristic (medium confidence).

# properties.bytes.description
Size breakdown in bytes: frontmatter, body, total.

# properties.tokens.description
Size breakdown in tokens (computed with the configured encoding, `cl100k_base` or `o200k_base`): frontmatter, body, total. Optional; MAY be absent if tokenization was disabled.

# properties.modifiedAtMs.description
File modification time (`mtime`) in Unix milliseconds, captured at scan time from the on-disk `lstat` that already guards the read. Optional and nullable: virtual / derived nodes (`virtual: true`) have no backing file and omit it. Surfaced via `/api/nodes` and `/api/scan` so the UI can show and sort a 'last modified' column; sorting uses this raw value, the display format is a UI concern. NOT content: it never participates in `bodyHash` / `frontmatterHash`.

# properties.linksOutCount.description
Outgoing links from this node to other graph nodes.

# properties.linksInCount.description
Incoming links from other graph nodes to this one.

# properties.externalRefsCount.description
http/https URLs in the body after normalization and exact-match dedup.

# properties.externalRefs.description
Distinct external URLs (http/https) the body references, in extractor-order (first-seen wins, dedup is by normalised URL). The denormalised `externalRefsCount` rides alongside and MUST equal `externalRefs.length` when both are present. Surfaced via `/api/nodes` so the inspector can list every external URL without a second round-trip.

# properties.externalRefs.items.properties.url.description
Normalised URL (lowercased host, fragment stripped).

# properties.externalRefs.items.properties.line.description
1-indexed line of the occurrence in the source body, when known.

# properties.externalRefs.items.properties.originalTrigger.description
Author substring (almost always equals `url`).

# properties.sidecar.description
Step 9.6.2, co-located `.sm` sidecar overlay. Carries presence flag, drift status (null when no sidecar), and the parsed `annotations:` block (null when absent or empty). The kernel re-derives `status` on every scan from the live hashes; clients should treat it as authoritative for the snapshot but never persist it across scans.

# properties.isFavorite.description
Per-node favorite flag set by the local user from the UI. Sourced from `state_node_favorites` (zone `state_`, persistent across scans). Decorated by the BFF on every `/api/nodes` response via in-memory Set lookup against the favorites table, no SQL JOIN against `scan_nodes`. Absent on emissions that don't carry per-user state (e.g. `sm export --json`); consumers that don't recognise the field MUST ignore it.

# properties.contributions.items.properties.slot.description
Slot the payload targets; the slot fixes both the renderer and the payload shape. Deliberately NOT `$ref`-ed to `view-slots.schema.json#/$defs/SlotName`: this is the kernel's core Node schema, and pointing it at the view catalog would make every consumer that compiles a ScanResult (which embeds Node) drag the UI slot catalog in too. The closed enum is enforced where it belongs, on the envelope's `contributionsRegistry` and at extension load time.

# properties.contributions.items.properties.payload.description
Shape declared by `slot`, open at this layer. The renderer narrows it.

# properties.contributions.items.properties.emittedAt.description
Unix ms at which the scan that produced this contribution emitted it.

# properties.contributions.description
BFF decoration, the node's view contributions as a LIST of rows (mirror of the kernel's `IPersistedContribution`), NOT a map: the keyed catalog is the envelope's separate `contributionsRegistry`, which carries the label / icon per slot. Delivered on the single-node envelope (`item.contributions`) and, up to `bff.maxBulkContributions`, on list envelopes. Same decoration posture as `isFavorite`: absent on emissions that carry no view state (`sm export --json`, a plain `sm scan` ScanResult), and consumers that don't recognise the field MUST ignore it.

# properties.tags.description
BFF decoration, the node's curated tags lifted from its `.sm` sidecar (`annotations.tags`) and surfaced flat so the UI renders chips without re-walking the overlay. Human curation, so the sidecar stays the source of truth (see `architecture.md` §Storage rule) and this copy is a read projection. Absent on emissions that carry no sidecar projection; consumers that don't recognise the field MUST ignore it.

# properties.body.description
BFF decoration, present ONLY on `GET /api/nodes/:pathB64?include=body`. The post-frontmatter file content as UTF-8, read from disk on demand because the kernel persists `bodyHash` only; `null` when the file is missing or unreadable. Never present without the opt-in flag, and never on a list envelope.

# properties.virtual.description
When `true`, this node is synthetic / derived: it does not correspond to a single file on disk. Reconstructed on every scan from the file(s) listed in `derivedFrom`. Use a synthetic `path` scheme (e.g. `mcp://github`) so the identifier is stable and visibly non-filesystem. Examples: MCP server nodes derived from `settings.json` / `mcp.json` / `config.toml`. When absent or `false`, the node is a normal filesystem-backed entity. Stability: experimental.

# properties.derivedFrom.description
Paths of the source files from which this node was derived. Required (and only meaningful) when `virtual: true`. Drives invalidation: if any listed source changes between scans, the virtual node's hashes change and the rename / drift machinery surfaces it. Empty / absent when the node is a regular filesystem-backed entity (the `path` itself is the source).

# $defs.sidecarOverlay.properties.present.description
True when a `<basename>.sm` file accompanies the node on disk; false otherwise.

# $defs.sidecarOverlay.properties.status.description
Drift status. NULL when no sidecar is present, or when the sidecar exists but failed to parse / validate (the row still records `present: true` so clients can distinguish absent from broken).

# $defs.sidecarOverlay.properties.annotations.description
Parsed `annotations:` block from the sidecar. NULL when no sidecar is present, when the block is absent / empty, or when the sidecar failed to parse / validate.

# $defs.sidecarOverlay.properties.root.description
Parsed YAML root of the matching `.sm` sidecar. Mirrors the shape of `sidecar.schema.json` (top-level reserved blocks `identity` / `annotations` / `settings` / `audit` plus opt-in `<plugin-id>:` namespaces). Surfaced for the UI inspector's audit panel, plugin-contributions panel, and debug panel; NULL when the sidecar is absent or failed to parse. Note the duplication with `annotations` at this overlay level, existing consumers read from `annotations`, new consumers read structured sub-fields off `root` (`root.identity.*`, `root.audit.*`, etc.). The duplication is intentional and documented; do NOT remove the top-level `annotations` field.