fix(TOOLS): frontmatter parser drops block-list tags/related — KnowledgeGraph edges silently dead#1330
Open
Tokyofloripa wants to merge 1 commit into
Conversation
…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.
There was a problem hiding this comment.
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- itemlines). - 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(); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
fix(TOOLS): frontmatter parser drops YAML block-list
tags:/related:→ KnowledgeGraph tag & related edges are silently deadTL;DR
On any real knowledge archive,
KnowledgeGraph.tsreportstag: 0andrelated: 0edges — only body[[wikilinks]]ever connect. Root cause: the hand-rolled frontmatter parser skips indented lines, so the YAML block-list form oftags:andrelated:(which is what the Knowledge skill actually writes) is never parsed. The same defect killsMemoryRetriever.ts's tag-match bonus. Two-function fix per file; weights, BFS, and CLI behavior untouched.Reproduction (v5.0.0, before)
A note whose frontmatter is the standard block-list form:
shows
Tags: []and "No direct connections found" inrelated <slug>— its weight-5relatededges and weight-1 tag edges never form.Root cause
parseFrontmatter()(KnowledgeGraph.ts~L88,MemoryRetriever.ts~L98) doesif (line.startsWith(" ") || line.startsWith("\t")) continue;. The indented- itemlines of a block list are skipped, sotags:/related:resolve to empty →node.tags = []→ zero tag co-occurrence edges, and notes rendertype: unknown.extractRelated()(KnowledgeGraph.ts) only recognizes the structured- slug:/type:form. Real notes use wikilink list items (- "[[slug]]"), so zerorelatededges 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- itemlines) into astring[], stripping-and matched quotes; inline[a, b]arrays and quoted scalars still parse; no lowercasing (callers handle it).extractRelated(): anormalizeSlug()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)
related <slug>now resolves the note's real typedrelated:links and its tags.MemoryRetriever.ts: a query whose term appears only in a note'stags:(absent from the body) now retrieves it atscore: 5.0(=TAG_MATCH_WEIGHT) — impossible before (tags parsed to[]).tags:field still showsTags: [](no over-match / contamination); top tag clusters are real facets, not parser artifacts;git diffhunks are confined toparseFrontmatter+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/findandMemoryRetriever'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.tsandMemoryRetriever.tscarry two independent hand-rolled frontmatter parsers — the divergence is what let this bug live in one place. A future cleanup could extract a single sharedparseFrontmatterso 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 toReleases/v5.0.0/.claude/PAI/TOOLS/, verified viagit apply --check).