Skip to content

fix(TOOLS): frontmatter parser drops block-list tags/related — KnowledgeGraph edges silently dead#1330

Open
Tokyofloripa wants to merge 1 commit into
danielmiessler:mainfrom
Tokyofloripa:fix/frontmatter-blocklist-parser
Open

fix(TOOLS): frontmatter parser drops block-list tags/related — KnowledgeGraph edges silently dead#1330
Tokyofloripa wants to merge 1 commit into
danielmiessler:mainfrom
Tokyofloripa:fix/frontmatter-blocklist-parser

Conversation

@Tokyofloripa
Copy link
Copy Markdown

fix(TOOLS): frontmatter parser drops YAML block-list tags:/related: → KnowledgeGraph tag & related edges are silently dead

TL;DR

On any real knowledge archive, KnowledgeGraph.ts reports tag: 0 and related: 0 edges — only body [[wikilinks]] ever connect. Root cause: the hand-rolled frontmatter parser skips indented lines, so the YAML block-list form of tags: and related: (which is what the Knowledge skill actually writes) is never parsed. The same defect kills MemoryRetriever.ts's tag-match bonus. Two-function fix per file; weights, BFS, and CLI behavior untouched.

Reproduction (v5.0.0, before)

$ bun .claude/PAI/TOOLS/KnowledgeGraph.ts stats
  Nodes: 120
  Edges: 304 (tag: 0, wikilink: 304, related: 0)   ← tag & related edge classes empty
  Isolated nodes: 16

A note whose frontmatter is the standard block-list form:

tags:
  - area/healthcare
  - role/physician
related:
  - "[[albert-einstein-hospital]]"

shows Tags: [] and "No direct connections found" in related <slug> — its weight-5 related edges and weight-1 tag edges never form.

Root cause

  1. parseFrontmatter() (KnowledgeGraph.ts ~L88, MemoryRetriever.ts ~L98) does if (line.startsWith(" ") || line.startsWith("\t")) continue;. The indented - item lines of a block list are skipped, so tags:/related: resolve to empty → node.tags = []zero tag co-occurrence edges, and notes render type: unknown.
  2. extractRelated() (KnowledgeGraph.ts) only recognizes the structured - slug: / type: form. Real notes use wikilink list items (- "[[slug]]"), so zero related edges form.

Net: of the three documented edge classes (related=5, wikilink=3, tag=1), only wikilink (body-scanned) works. The graph layer is effectively non-functional for the note format the Knowledge skill itself produces.

The fix (scope-contained — only the two parser functions)

  • parseFrontmatter(): parse YAML block-list values (key: followed by indented - item lines) into a string[], stripping - and matched quotes; inline [a, b] arrays and quoted scalars still parse; no lowercasing (callers handle it).
  • extractRelated(): a normalizeSlug() helper extracts the target from all four item forms — - "[[slug]]", - [[slug]], - "slug", - slug — unwrapping [[…]], dropping |alias, keeping the last / segment; the legacy - slug:/type: structured form still works.
  • buildGraph, BFS/traverse, edge weights (5/3/1), TAG_GROUP_CAP, and every CLI verb are unchanged.

Testing evidence (after)

$ bun .claude/PAI/TOOLS/KnowledgeGraph.ts stats
  Nodes: 120
  Edges: 10672 (tag: 9936, wikilink: 304, related: 432)   ← both classes live; wikilink unchanged
  Isolated nodes: 2
  • related <slug> now resolves the note's real typed related: links and its tags.
  • MemoryRetriever.ts: a query whose term appears only in a note's tags: (absent from the body) now retrieves it at score: 5.0 (= TAG_MATCH_WEIGHT) — impossible before (tags parsed to []).
  • Correctness checks: a note without a tags: field still shows Tags: [] (no over-match / contamination); top tag clusters are real facets, not parser artifacts; git diff hunks are confined to parseFrontmatter + extractRelated; all CLI verbs exit 0; node count unchanged (skip-files honored).

Impact

Affects every PAI user who lets the Knowledge skill write notes (the default block-list frontmatter). Before this, KnowledgeGraph traverse/related/hubs/find and MemoryRetriever's tag bonus operate on empty tag/related data; the "edges > nodes" health expectation is unreachable. After, the graph and tag-aware retrieval work as documented.

Optional follow-up (not in this PR)

KnowledgeGraph.ts and MemoryRetriever.ts carry two independent hand-rolled frontmatter parsers — the divergence is what let this bug live in one place. A future cleanup could extract a single shared parseFrontmatter so the next fix touches one implementation.

Provenance

Found while building a 2,000+ note domain knowledge cluster, where the dead tag/related edges made graph traversal useless — the parser fix was the precondition for the graph layer to function at all. Patches: KnowledgeGraph.patch, MemoryRetriever.patch (apply cleanly to Releases/v5.0.0/.claude/PAI/TOOLS/, verified via git apply --check).

…s aren't silently dead

parseFrontmatter() skipped indented lines, so the block-list form of tags:/related:
(what the Knowledge skill writes) was never parsed -> tag and related edge classes
were always empty; only body [[wikilinks]] connected. extractRelated() only handled
the structured '- slug:/type:' form, not wikilink list items. Fix both parsers in
KnowledgeGraph.ts plus the twin parseFrontmatter in MemoryRetriever.ts. Weights (5/3/1),
BFS, TAG_GROUP_CAP, and all CLI verbs unchanged. Verified on a 120-note archive:
edges 304 -> 10672 (tag 0 -> 9936, related 0 -> 432), isolated 16 -> 2.
Copilot AI review requested due to automatic review settings June 5, 2026 03:58
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR improves YAML frontmatter handling in the PAI tools, adding support for block-style lists and more robust parsing/normalization when extracting related links.

Changes:

  • Add block-list parsing for frontmatter keys with empty values (e.g., tags: followed by indented - item lines).
  • Improve related: extraction by normalizing slugs and supporting simple list items alongside structured {slug,type} entries.
  • Filter out empty entries when parsing inline list syntax ([a, b, ]).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

File Description
Releases/v5.0.0/.claude/PAI/TOOLS/MemoryRetriever.ts Adds block-list parsing state (blockKey) and filters empty inline-list items.
Releases/v5.0.0/.claude/PAI/TOOLS/KnowledgeGraph.ts Extends frontmatter parsing to handle block lists and improves related slug/type extraction with normalization.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +98 to +108
// Block-list item: an indented "- item" belongs to the preceding empty-valued key.
const listItem = line.match(/^\s+-\s+(.*)$/);
if (blockKey && listItem) {
const item = listItem[1].trim().replace(/^['"]|['"]$/g, "");
if (item.length > 0) {
if (!Array.isArray(result[blockKey])) result[blockKey] = [];
(result[blockKey] as string[]).push(item);
}
continue;
}
if (line.startsWith(" ") || line.startsWith("\t")) continue; // skip other nested lines
Comment on lines 111 to +113
const key = line.substring(0, colonIdx).trim();
let value: string | string[] = line.substring(colonIdx + 1).trim();
if (value === "") { blockKey = key; continue; } // maybe a YAML block-list parent
Comment on lines 117 to +118
result[key] = value;
blockKey = null;
Comment on lines +83 to +86
const lines = match[1].split("\n");
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line.startsWith(" ") || line.startsWith("\t")) continue;
Comment on lines +169 to +171
if (slug.includes("/")) {
slug = slug.split("/").pop()!.trim();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants