Skip to content

perf(markdown): skip link nodes for targets no file can supply - #612

Merged
bompus merged 3 commits into
fork/consolidatedfrom
probe/md-paths
Nov 1, 2026
Merged

bompus merged 3 commits into
fork/consolidatedfrom
probe/md-paths

Conversation

@bompus

@bompus bompus commented Nov 1, 2026 •

Copy link
Copy Markdown
Owner

Why

A Markdown link or path mention whose target basename no indexed file has still minted an import node and a contains edge, even though the resolver's file-path arm could not match it. The first draft removed the reference too; review showed that loses a Django route link, a target added later by sync, and a link target past the digest budget. The shipped change removes only the decorative node and keeps every reference.

Change

  • A full index skips the import node for a target whose basename no indexed file has. The reference is always kept, so the resolver's route, module and table arms still match it, and codegraph sync can resolve a target added later from the parked failed reference (Incremental sync misses cross-file relations after a new export is added colbymchenry/codegraph#1240).
  • Incremental sync keeps every node (indexFile passes no context), so a file added later resolves even if the Markdown owner is unchanged.
  • A skipped target stays in a separate links: part of the file digest (up to 64 targets / 800 characters), so a link past the 12-item display digest is still searchable.
  • The known basenames and the full-index flag ride to each parse worker once per spawn on the grammar handshake.
  • Tests: __tests__/markdown-path-refs.test.ts covers references kept, nodes skipped on a full index, nodes kept on sync, route/basename/symbol/anchor/command retention, a dotted relative route, and a 13th link in links:. __tests__/integration/full-pipeline.test.ts starts with a full index and adds the missing target through sync, then checks the edge.

Results

Supabase, full index, base 47839cc4 vs this branch:

base probe
nodes 152,274 141,880
edges 328,137 317,743
Markdown import nodes 14,038 3,644
failed Markdown refs 11,368 11,368

Resolved Markdown imports/references/calls edge multisets are byte-identical (SHA-256) on supabase (3,058), n8n (2,231), pretix (1) and discourse (624); the only removed rows are the skipped import nodes and their contains edges. n8n -419 nodes, discourse -376, pretix -1; failed Markdown reference counts unchanged on all four.

Index time: supabase median 16.176 s to 15.953 s (Hyperfine 2.0.0, 8 runs per build across two ABBA rounds, 0.223 s / 1.4%, interquartile ranges 16.170-16.235 vs 15.867-15.982 not overlapping).

Checks

Re-index Markdown-heavy projects after upgrading.

A Markdown link or path mention with a relative path created an import node
and a reference even when no indexed file had that basename, so the
reference could never resolve. On supabase those dead targets were 10,120
of 14,038 Markdown import nodes and 10,321 of 11,368 failed Markdown refs.

The extractor now drops a slash-shaped target whose basename no indexed file
has. Bare names stay: a root-absolute link (`/pricing`), a root-level mention
(`Next.js`) or a parent reference can resolve to a route, module or table
node by name, which the resolver reaches without a file node.

The known basenames come from the scan plus existing DB rows, ride to each
parse worker once per spawn on the grammar handshake, and also serve the
in-process fallback. Single-file re-index paths keep their old permissive
behaviour.

Resolved Markdown edges are byte-identical on supabase, n8n, pretix and
discourse. Supabase: 152,274 -> 142,154 nodes and 328,137 -> 318,017 edges
(dead import nodes and their contains edges), failed Markdown refs 11,368 ->
1,047. Full index median 16.126 s -> 15.920 s (Hyperfine 2.0.0, 8 runs per
build across two ABBA rounds, interquartile ranges not overlapping). The
payroll-go golden dump loses only the four dead README refs.

Full suite 958 files / 11,350 passed.
@bompus bompus added the author:deepseek Pull request written by a deepseek model label Nov 1, 2026

@bompus-ai-review bompus-ai-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bompus AI Review (GPT-6.1 Terra, max reasoning): 3 finding(s).

Comment thread src/extraction/markdown-extractor.ts Resolved
Comment thread src/extraction/markdown-extractor.ts Resolved
Comment thread src/extraction/markdown-extractor.ts Resolved
Review fixes for the dead-target pruning:

- Full index only. indexFile() passes no context, so incremental sync keeps
  every target and a file added later still resolves from the parked
  reference (colbymchenry#1240).
- Keep root-absolute targets and extensionless paths: the resolver can match
  them to routes, modules or table rows by name, and a root-absolute link is
  not a relative project file. Only a relative, file-shaped target whose
  basename no indexed file has is dropped.
- Keep every target in the file digest. digestReferences collects the same
  list unresolvedReferences had, including skipped targets, so a link to an
  unindexed asset still makes its file findable.

Supabase: 152,274 -> 148,652 nodes and 328,137 -> 324,515 edges, 14,038 ->
10,416 Markdown import nodes, 11,368 -> 7,545 failed Markdown refs. Resolved
Markdown edges and file digests are byte-identical on supabase, n8n, pretix
and discourse. Index time shows no clear change (16.144 vs 16.032 s median,
ranges overlap).

Full suite 959 files / 11,352 passed.
@bompus bompus changed the title perf(markdown): skip path targets no indexed file can satisfy fix(markdown): skip dead relative file links on a full index Nov 1, 2026

@bompus-ai-review bompus-ai-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bompus AI Review (GPT-6.1 Terra, max reasoning): 3 finding(s).

Comment thread src/extraction/index.ts Resolved
Comment thread src/extraction/markdown-extractor.ts Resolved
Comment thread src/extraction/markdown-extractor.ts Resolved
Review round 2 changed the pruning from the reference to the decorative
import node:

- Every reference is kept. The resolver's route/module/table arms still see
  it, and sync can resolve a target added later from the parked failed
  reference. A sync integration test starts with a full index and adds the
  target afterwards.
- Only the import node and its contains edge are skipped on a full index,
  when the target's basename no indexed file has. A relative route with a
  dotted final segment (`api/users.json`) still resolves from the owner.
- Skipped targets go into a separate `links:` digest part (64 targets /
  800 chars), so a link past the 12-item display digest stays searchable;
  the test uses a 13th link.

Supabase: 152,274 -> 141,880 nodes and 328,137 -> 317,743 edges, import
nodes 14,038 -> 3,644, failed Markdown refs unchanged at 11,368. Resolved
Markdown edges are byte-identical on supabase, n8n, pretix and discourse.
Full index median 16.176 -> 15.953 s (Hyperfine 2.0.0, 8 runs per build,
two ABBA rounds, IQRs not overlapping).

Full suite 960 files / 11,353 passed.
@bompus bompus changed the title fix(markdown): skip dead relative file links on a full index perf(markdown): skip link nodes for targets no file can supply Nov 1, 2026

@bompus-ai-review bompus-ai-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bompus AI Review (GPT-6.1 Terra, max reasoning): 0 finding(s).

Review 3: only high findings and above are posted from here on.

@bompus
bompus merged commit 422b1f8 into fork/consolidated Nov 1, 2026
3 checks passed
@bompus
bompus deleted the probe/md-paths branch November 1, 2026 06:22
bompus added a commit that referenced this pull request Nov 1, 2026
A Markdown table row, list item or fenced command minted a constant or
function node plus a contains edge even though the text already lived in
the section docstring. On supabase that was 6,222 of 27,380 Markdown nodes.

Rows/items now contribute their references to the owning section, commands
keep their script calls reference from the section, and each section gets a
short commands: summary. Text the 600-character section preview drops stays
searchable in a separate text: part of the file digest; every recognized
command is kept there even after the 8-command display summary fills.
Unlabeled fences only contribute a command when the line looks like one, and
now recognize every interpreter extractScriptPath supports (node,
powershell, ...), so output, trees and JSON fragments stop becoming
commands while Node.js examples keep their script edge.

Supabase vs the merged #612 base: 141,880 -> 135,658 nodes, 317,743 ->
311,496 edges, full index median 15.920 -> 15.753 s (Hyperfine 2.0.0, 8
runs per build, two ABBA rounds, IQRs not overlapping). n8n drops 3,719
nodes and discourse 869. References that had resolved to a removed
row/command node now fail (25 supabase, 3 n8n, 1 discourse); references
from a row/command move to its section.

README, the languages reference and the MCP instructions now describe the
section-text model. Full suite 960 files / 11,359 passed.
bompus added a commit that referenced this pull request Nov 1, 2026
A Markdown table row, list item or fenced command minted a constant or
function node plus a contains edge even though the text already lived in
the section docstring. On supabase that was 6,222 of 27,380 Markdown nodes.

Rows/items now contribute their references to the owning section and
commands keep their script calls reference from the section. Text the
600-character preview drops stays searchable on the owning node's new
search_text column, indexed in nodes_fts but not displayed: late rows,
table headers, every recognized command past the 8-command summary, and
unrecognized fence lines. Token-boundary dedupe keeps a later CG-713 from
being treated as a substring of XCG-713. Unlabeled fences recognize every
interpreter extractScriptPath supports (node, powershell, ...).

Supabase vs the merged #612 base: 141,880 -> 135,658 nodes, 317,743 ->
311,496 edges; 25 references that pointed at a removed row/command node now
fail and 112 re-source to their section. Index time shows no clear change
(15.969 vs 16.047 s median, two ABBA rounds, ranges overlapping).

Schema v24 adds nodes.search_text and indexes it in nodes_fts; the golden
dumps re-baseline only for nodes that carry the field. Full suite 960 files
/ 11,359 passed.
bompus added a commit that referenced this pull request Nov 1, 2026
A Markdown table row, list item or fenced command minted a constant or
function node plus a contains edge even though the text already lived in
the section docstring. On supabase that was 6,222 of 27,380 Markdown nodes.

Rows/items now contribute their references to the owning section and
commands keep their script calls reference from the section. Text the
600-character preview drops stays searchable on the owning node's new
search_text column, indexed in nodes_fts but not displayed: late rows,
table headers, every recognized command past the 8-command summary, and
unrecognized fence lines. Token-boundary dedupe keeps a later CG-713 from
being treated as a substring of XCG-713. Unlabeled fences recognize every
interpreter extractScriptPath supports (node, powershell, ...).

Supabase vs the merged #612 base: 141,880 -> 135,658 nodes, 317,743 ->
311,496 edges; 25 references that pointed at a removed row/command node now
fail and 112 re-source to their section. Index time shows no clear change
(15.969 vs 16.047 s median, two ABBA rounds, ranges overlapping).

Schema v24 adds nodes.search_text and indexes it in nodes_fts; the golden
dumps re-baseline only for nodes that carry the field. Full suite 960 files
/ 11,359 passed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

author:deepseek Pull request written by a deepseek model

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant