feat(markdown): resolve code-span mentions into references edges - #3562
AstroMined wants to merge 1 commit into
Conversation
The markdown extractor's docstring promised a heading --references--> node edge when a backtick Name matches a known pattern, but nothing implemented it, so an AST build produced no document-to-code edge at all. extract_markdown now collects inline code spans outside fences and frontmatter as raw_calls tagged language "markdown", owned by the heading they sit under (the page before the first heading). A new markdown_mentions language resolver runs after the id-remap passes and turns them into references edges: the explicit path::Symbol[::member] form resolves through the cited file's symbols and is EXTRACTED 1.0; a bare or dotted name resolves only when exactly one callable code node carries that label and is INFERRED 0.95. Ambiguous names, builtins, missing files and non-callable matches emit nothing. The shared cross-file call pass skips markdown raw_calls so prose never emits calls edges. Tests cover span classification, raw_call ownership, end-to-end resolution, ambiguity, and cache round-tripping.
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 3 advisory finding(s) below merit a look before merge.
Formal verification. No changes could be formally verified in this run.
Graphify review — findings
Adds a Markdown code-span mention pass that turns backtick-quoted symbol references (Widget, render(), pkg.Widget, src/mod.py::Widget::render) in headings and body prose into references edges from the citing heading (or the page, above the first heading) to the code node that defines the symbol. Path-qualified mentions resolve against the cited file, walking the :: chain through contains/method edges and emitting an EXTRACTED edge; bare mentions resolve only on a unique case-sensitive match among callable/type nodes, emitting an INFERRED (0.95) edge, and ambiguous names produce nothing. Mentions ride the raw_calls channel tagged language: "markdown" so the id-remap passes rewrite them, and the shared cross-file call pass skips them so a citation never becomes a calls edge; spans reading as files (setup.py), commands, or prose are filtered out during extraction.
Worth a look
- Dotted bare mention with non-code extension misclassified as symbol —
graphify/extractors/markdown.py:275· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Segment-aligned suffix match returns first of a single-element list but suffix strip is wrong for './' prefixes —
graphify/markdown_resolution.py· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Relative Markdown paths with '..' can resolve to an unrelated suffix —
graphify/markdown_resolution.py:79· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 1978 functions depend on the 288 functions this change touches.
Health — this change adds coupling hotspots:
- new:
extract()— 591 callers, 44 callees - new:
_rebuild_code()— 116 callers, 51 callees - new:
extract_js()— 85 callers, 4 callees - new:
extract_xaml()— 19 callers, 17 callees - new:
dispatch_command()— 2 callers, 124 callees - new:
_get_extractor()— 26 callers, 6 callees - new:
extract_markdown()— 17 callers, 9 callees - new:
run_pipeline()— 8 callers, 13 callees - …and 31 more — each is listed as a finding
Verification — 1978 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 1803 function(s) in the blast radius were not formally verified this run
Test selection
Test selection
276 of 276 test file(s) selected (100%) via static blast radius.
Escalated to a full run for safety — the selection is not trustworthy on its own (see below). CI should run the whole suite.
tests/test_affected_cli.py— full-run-safetytests/test_affected_member_seed.py— full-run-safetytests/test_agents_platform.py— full-run-safetytests/test_analyze.py— full-run-safetytests/test_anthropic_custom_endpoint.py— full-run-safetytests/test_antigravity_install.py— full-run-safetytests/test_apm_fallback_version.py— full-run-safetytests/test_architecture_doc.py— full-run-safetytests/test_astro_extraction.py— impact, full-run-safetytests/test_astro_import_ids.py— impact, full-run-safetytests/test_atomic_canvas_export.py— full-run-safetytests/test_atomic_version_stamp.py— full-run-safetytests/test_atomic_writes.py— full-run-safetytests/test_backend_env_isolation.py— full-run-safetytests/test_backend_extras.py— full-run-safetytests/test_benchmark.py— full-run-safetytests/test_benchmark_raw_graph.py— full-run-safetytests/test_build.py— impact, full-run-safetytests/test_build_merge_dedup_scope.py— full-run-safetytests/test_build_merge_hyperedges_and_prune.py— full-run-safetytests/test_build_merge_shrink_guard.py— full-run-safetytests/test_builtin_global_type_refs.py— impact, full-run-safetytests/test_cache.py— full-run-safetytests/test_callflow_html.py— full-run-safetytests/test_cargo_introspect.py— full-run-safetytests/test_carried_hyperedge_remap.py— full-run-safetytests/test_case_sensitive_resolution.py— impact, full-run-safetytests/test_charmap_encoding.py— full-run-safetytests/test_chunking.py— full-run-safetytests/test_cjs_module_extension.py— impact, full-run-safetytests/test_claude_cli_backend.py— full-run-safetytests/test_claude_md.py— full-run-safetytests/test_cli_broken_pipe.py— full-run-safetytests/test_cli_export.py— full-run-safetytests/test_cli_help.py— full-run-safetytests/test_cluster.py— full-run-safetytests/test_codebuddy.py— full-run-safetytests/test_community_hub_labels.py— full-run-safetytests/test_community_labels_skill.py— full-run-safetytests/test_confidence.py— full-run-safetytests/test_corrupt_graph_json.py— full-run-safetytests/test_cpp_nested_and_cli.py— impact, full-run-safetytests/test_cpp_objc_cross_file_calls.py— impact, full-run-safetytests/test_cpp_preprocess.py— full-run-safetytests/test_cross_extension_reexport_self_cycle.py— impact, full-run-safetytests/test_cross_language_call_resolution.py— impact, full-run-safetytests/test_cross_repo_member_calls.py— impact, full-run-safetytests/test_cross_repo_shared_types.py— full-run-safetytests/test_csharp_call_site_generic_args.py— impact, full-run-safetytests/test_csharp_enum_members.py— impact, full-run-safety- … and 226 more
changed code file(s) with no mapped test (
graphify/markdown_resolution.py) — a coverage gap or a missing link — running the full suite rather than only the selected tests
Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.
Formal verification
Could not verify: Could not verify extract.
The verifier did not have enough to check extract, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `cache_root` is annotated `Path | None` — outside the synthesizable primitive/collection set
Could not verify: Could not verify extract\_markdown.
The verifier did not have enough to check extract\_markdown, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
· 2 grounded finding(s) anchored inline below; 37 more finding(s) on lines outside this diff (see the check run).
| return None, [m.group(1)] | ||
|
|
||
|
|
||
| def extract_markdown(path: Path) -> dict: |
There was a problem hiding this comment.
extract_markdown()
fans out to 9 callees (efferent coupling); 17 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| return calls | ||
|
|
||
|
|
||
| def resolve_markdown_mentions( |
There was a problem hiding this comment.
resolve_markdown_mentions()
fans out to 6 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
Fixes #3561.
The markdown extractor's docstring promised a heading --references--> node edge for backtick names
but nothing implemented it, so an AST build produced no document-to-code edge.
extract_markdownnow reports inline code spans (outside fences and frontmatter) asraw_callstagged
language: "markdown", owned by the enclosing heading or the page node. A newgraphify/markdown_resolution.pyregisters amarkdown_mentionslanguage resolver that turns theminto
referencesedges:path::Symbol[::member]resolves through the cited file's symbols(EXTRACTED 1.0); a bare or dotted name resolves only to a unique callable code node (INFERRED 0.95).
Ambiguous, builtin, missing-file and non-callable mentions emit nothing. The shared cross-file call
pass skips markdown raw_calls so prose never produces
callsedges. The stale docstring line isreplaced with the actual behaviour.
Tests:
tests/test_markdown_code_spans.pycovers span classification, raw_call ownership and linepinning, end-to-end resolution (INFERRED, EXTRACTED, doc-relative path, ambiguous, missing file, no
calls from document nodes, no dangling edges), and cache round-tripping.
uv run pytestanduv run --frozen ruff check .are green apart from fourtest_backup_*cases intest_export.pyand four
test_pathless_postgres_*cases intest_extract_cli.py, which fail identically on cleanv8.