Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
305a955
WIP: caller/callee in markdown
muayyad-alsadi Oct 16, 2026
9ee11bf
make CodeRabbit's happy by fixing cache
muayyad-alsadi Oct 17, 2026
deff7df
mdslug [text](path#slug), update CHANGELOG.md
muayyad-alsadi Oct 18, 2026
194a84e
update unit test in test/mdsectioncheck
muayyad-alsadi Oct 18, 2026
9464c61
handle missing heading in markdown link.
muayyad-alsadi Oct 18, 2026
4d8dd11
Mark a markdown link with via="link" when no call shares that edge.
muayyad-alsadi Oct 18, 2026
403bee4
Mark agent rows with via="link", and pin the link when the crawl root…
muayyad-alsadi Oct 18, 2026
dae894b
fix sed on macOS behavior
muayyad-alsadi Oct 18, 2026
b6bdcc8
Mark a direct doc caller with via="link" on --safe-delete and --impact.
muayyad-alsadi Oct 18, 2026
31e0fc4
Mark in-file anchors, wikilinks, and root-relative links with via="li…
muayyad-alsadi Oct 18, 2026
6f3ffe4
Stamp anchor="missing" when a markdown slug names no heading. Columna…
muayyad-alsadi Oct 18, 2026
d915362
Fold call, name, and link into one byte per edge. Path, connect, and …
muayyad-alsadi Oct 18, 2026
4438031
Read a markdown section link from a language fact, so the shared Lang…
muayyad-alsadi Oct 19, 2026
30b4fe0
Merge main (939dbb69) into feature_markdown_graph
barefootski Oct 19, 2026
3a3f0a6
chore(versions): kParserVer 206 after the merge with main; re-pin qsc…
barefootski Oct 19, 2026
2254f8e
Keep the farthest section end inside the section index, so quality-de…
muayyad-alsadi Oct 19, 2026
18565fd
Merge origin/main (train 31, 90f6fae6) into the markdown section link…
barefootski Oct 19, 2026
cddf0f0
Mark anchor="missing" only when the slug is provably absent
barefootski Oct 19, 2026
5f3f83d
Re-pin qschemetrip after the merge with train 31
barefootski Oct 19, 2026
e1a9d7f
Spell the out-of-model slug test without cloning another ASCII scan
barefootski Oct 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .ripwire_quality_acks
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ ack api-surface 61a7f958d4bf7e92 4 cid=8329d88eed614818 now=4 was=3 facet=contra
ack api-surface 61e5df9e1e40ff70 5 cid=e2155dd6082b880a E2 (terminality round A, lane E): +1 defaulted out-param: the receipt's ONE next= is read off the fold it renders (callers 2, incompatible 0)
ack api-surface 6302e2e27e23bcde 6 cid=7202bbc7db7cc1da C1 F-06/F-07/F-10 (the listing-paging round): three listing verbs learn to disclose and page their row listings, and every gating row is that one change. api-surface 14 = ONE trailing DEFAULTED parameter each (an int pageOffset, an McpPageArgs window, a SituPageArgs, or the next= invocation a header now carries) on the emitters that must be TOLD their window — writeFlags/writeGate, writeCappedRows/writeCappedList/writeFlip/writeFlipHeader/writeFlipLights, computeFlip, nearestGateNames (which gains its cap and its TOTAL, the disclosure itself), situShowingNote/writeSituation, and the three MCP twins flagsText/flipText/situationDiffJson; additive by construction, every pre-existing call site compiles unchanged, and the alternative — a second capped emitter per verb — is the drift this repo removes rather than adds, because two emitters that disagree about a window can drop the row that IS the answer. verbosity 3 = kDocDriftLegend +20 lines and writeDocDriftPage +10 are the in-band vocabulary a reader needs to read shown_failed=/failed_capped=/failed_total= where they meet it (the rationale and the next= scan were already hoisted OUT of the body into their own function and comment, which took the complexity row to zero and the LOC row from +48 to +10); dispatchMcpLine +12 is two pagedResult wrappers on a pre-existing 1376-line dispatcher this lane adds to rather than creates. complexity 1 = the same dispatcher, +9 on a base of 518. duplication 1 = flagsText | flipText at 110 tokens, down from 131 after the shared mcpRowCap fold; the residual is getIndex + compute + captureXml, the shape EVERY index-backed MCP twin in this file has, and merging two verbs that return different results behind one entry point would be worse code than the clone. short-horizon-churn 19 = this lane's own footprint across cli.h/docdrift.h/darkflags.h/flipimpact.h/situ.h/mcp*, plus cli.h symbols three other lanes touched the same day; none foreign, none thrash.
ack api-surface 6483016e50837e70 3 cid=4c53bf4b5b7d4a0c degrade disclosure: each read seam and the ref pool now take the DISCLOSE sink whose field the document reads
ack api-surface 6511634f0768c591 5 cid=0f75a00dd52bc21d now=5 was=3 facet=contract-change p=src/graphlegend.h:921 impactRollupLegend carries the importer cut and the type rollup clauses. That signature is the one upstream already ships. Local main still has the three argument form.
ack api-surface 654551bf984cc299 5 cid=913136f787574436 P9 the folded edit receipt: runEditVerb gains the postCheck opt-out parameter (defaulted true, so every existing call site is source-compatible) and the line-range + post-check splice; editplan's ensureStage and receipt each gain the root parameter they need to spell one identity (M12's root-relative rule, applied to the sibling it missed) and to run the per-op post-check. The +15 LOC in runEditVerb is the copy-out-before-the-index-rebuild discipline the fold requires — every reference into ing dangles once getIndex re-ingests, and that is stated in the code.
ack api-surface 68301548c0560f5d 3 cid=07b87ac0d0ae631a interface-dispatch lane (2026-09-16, test/narrowcheck.sh arms 26-36): Narrower::rule2RecvVarType takes the two inputs class identity needs — the ClassIdentity index built once in buildGraph and the chaUp base-name graph C++ lookup walks for inherited nested types (1 -> 3 parameters). Single call site (graph.h buildGraph Rule 2), updated in the same commit. Every other quality-delta row this lane produced was fixed in code rather than acked: the identity logic moved out of Narrower into IdentityNarrower (Narrower verbosity 409 -> 800 undone), nine over-complex functions split, and five clone rows removed by reusing extent::inSet with kHeadRuleLangs/kExtentClassKinds, symbolsByFileInIdOrder, and a flat include index.
ack api-surface 69cd4f735374521d 5 cid=27ae3ff0233a5863 now=5 was=4 facet=contract-change p=src/editcheck.h:755 train 30 fcall-forward nit N1 (d62c00e0): ONE trailing span parameter, the graph's keptForwards, so this call-site reader applies the one model.h forwardCounts rule (--uses' own) instead of counting a forward buildGraph dropped; every caller passes g.keptForwards in the same commit; red-first in test/fcallforwardcheck.sh S1-S3.
Expand Down
25 changes: 18 additions & 7 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ not published here — see `docs/EVALS.md` for the instruments behind the headli

## [Unreleased]

### Added — markdown links bind the exact heading (#409 item 1, #414)

A cross-file `[text](path/other.md#slug)` now edges to that file's heading — `--callees` / `--callers` on a doc section
list the sections it links to instead of one file-level edge. The extractor caches the link as written; `buildGraph` joins
the path against the linking file, so `../b/notes.md` never lands on a same-named file in the linker's own directory and a
link to a heading named like one of the linking file's own headings does not bind to the latter. Two headings of the same
slug give one edge (the first). A missing heading falls back to the target file's edge; percent-encoded slugs and GitHub's
`-1` duplicate suffix are not modelled and also fall back. A markdown link that no call shares is marked `via="link"` on
`--callers`, `--callees`, `--path`, `--connect`, and on a map or body callee row. `count=` and `cross_kind` stay the same. A crawl rooted at `.` stores `./docs/a.md`. The link index normalizes that path, so a root of `.` pins the same heading as an absolute root. `find_symbol` and `find_referencing_symbols` mark the link row, and the answer adds `via_link_note` when a row has it. `--json` callers and callees add that note too. `--safe-delete` and `--impact` mark a direct doc caller `via="link"`. `callers=` and `reaches=` still count it. An in-file anchor, a wikilink, and a root-relative link are `via="link"` too. The mark follows the reference, so an unpinned link does not clear a pinned one on the same pair. A link whose file is in the tree and whose slug names no heading carries `anchor="missing"`, unless the slug cannot be judged (non-ASCII, percent-encoded, a `-1` duplicate suffix of a heading that exists, a file that is one heading line): that link keeps the file edge with no mark. Columnar `<via>` is `2` for a direct markdown link and `1` for a name-only edge. Gate: `test/mdsectioncheck.sh`.

### Changed — `--for`'s first eight rows name up to eight distinct files before any file takes a second row (head round-robin)

A routed (subtoken+body) `--for` answer's head was a list of the highest-scoring rows, and one file often took most of it: on
Expand Down Expand Up @@ -271,7 +281,7 @@ it now names nothing.) Go's
written `*os.File` no longer resolves to an in-tree type that happens to be called `File`. A Java/Kotlin class-name
receiver binds an in-tree class only where the caller's file sees it — its package, a single-type import naming it, an
on-demand import of its package (`import ext.Util; Util.open()`, `import ext.*; Util.open()`, `Util::open` no longer bind
an in-tree `Util` as proven). `kParserVer` and `kCacheVersion` move (198 and 36 in this release, see the versions note).
an in-tree `Util` as proven). `kParserVer` and `kCacheVersion` move (206 and 36 in this release, see the versions note).
### Fixed — `f.call( … )` and `f.apply( … )` on a proven function are calls of `f`

`handleRequest.call( request.server, null, request, reply )` used to be recorded only as a member call named `call`, so
Expand All @@ -281,7 +291,7 @@ the file declares as a function, or imports from a relative module, now stands f
where the resolver proves a function or method: an imported object, const or class (`rpc.call()` on a required
module object, `Klass.call( this )`) and a name bound by name alone add nothing. `f.bind( … )` is a value use of `f`,
never a call. Floors, shared with the bare call `f()`: a function-local `var f` / `let f` declared after the site does not
shadow it, and a re-assignment `f = …` is not followed. `kParserVer` and `kCacheVersion` move (198 and 36 in this release, see the versions note).
shadow it, and a re-assignment `f = …` is not followed. `kParserVer` and `kCacheVersion` move (206 and 36 in this release, see the versions note).
### Fixed — a method a name-template lookup can name no longer reads `risk="none-found"`

`getattr(obj, f"watch_{name}")`, `hasattr`, `cls.__dict__.get(f"on_{n}")` and JS/TS `this['compile' + node.typename]`
Expand All @@ -295,7 +305,7 @@ each template's candidates (`<pd cands= shown=>`, first 8, `next=` for the rest)
`--safe-delete`, when nothing else was found, it still makes `risk="hedged"` and names its site as a `<pdw>` row. A template with no literal side at all (`codes[sc + '']`, `f"{m}"`) carries no
name and records nothing, like a bare `obj[k]`. Matched
by name, never a proven call: `callers=`, `impact_reaches=`, `count=` and the `--dead-code` list are unchanged. The
`--json` and `--format=columnar` roots carry both counts but not the rows; the MCP transport carries neither yet. Cache format and parser version move (the reference role set grew; 36 and 198 in this release, see the versions
`--json` and `--format=columnar` roots carry both counts but not the rows; the MCP transport carries neither yet. Cache format and parser version move (the reference role set grew; 36 and 206 in this release, see the versions
note). Gate: `test/safedeletecheck.sh` arms 9–18.
### Changed — `--for` tail rank rows name their file's top symbols

Expand Down Expand Up @@ -323,7 +333,7 @@ scope only, a Rust `const` / `static` at module, `impl`, trait or `extern` level
level; a call in a function-local initializer belongs to the enclosing function. Package-level grouped Go `var ( a = …;
b = … )` declarations, which were missed (grouped `const` was already read), are now one symbol per name. On fzf, gotestsum and nfpm (Go), 574
function-local symbols leave the map, no package-level one is lost, and 87 grouped names are added. `kParserVer` moves
(198 in this release, see the versions note). Gate: `test/localdeclcheck.sh`.
(206 in this release, see the versions note). Gate: `test/localdeclcheck.sh`.

### Changed — `--path` hop rows name a member by its owner (`Owner.name`)

Expand Down Expand Up @@ -587,15 +597,16 @@ server, when the cgroup's charge is the larger reading: restarting the server do

### Changed — the versions this release moves, stated once

`kParserVer` 167 → 198: the Clojure grammar (#349) took 171 on `main` and slice 4a (#400) 173 (its full-use file tag 174); the value-reference walk through
`kParserVer` 167 → 206: the Clojure grammar (#349) took 171 on `main` and slice 4a (#400) 173 (its full-use file tag 174); the value-reference walk through
`??`/`||`/`&&`/`or`/`and` carried 173 on its branch, the Ruby/Python subscript references and Ruby's main-object mixins
174 (file tag 175), and the Elixir quote live holes (#380) 145 on theirs; they landed together at 176 (file tag 177). The
minimum-arity field (`paramsMin`) carried 178 (file tag 179) on its branch and the Go receiver scope (#403) 182 (file tag 183);
those landed at 184 (file tag 185). Call-result typing carried 180 (file tag 181), the `f.call` / `f.apply` forwarders 188
(189), the function-local Go / Rust / GDScript declarations 190 (191) and the name-template references 192 (193) on their
branches; those landed at 196 (file tag 197). The qualified-annotation and `x op= v` rebinding change carried 194 (195) on
its branch. This release is 198, above every number a branch build has used, so no cache such a build wrote is read as this
release's; its full-use file tag is 199.
its branch and landed with train 31 at 198 (199); branches not in this release carried 202 (203) and 204 (205). The markdown section
links (#409) carried 185 (186) on their branch and were renumbered when it merged. This release is 206, above every number a branch
build has used, so no cache such a build wrote is read as this release's; its full-use file tag is 207.
`kCacheVersion` 31 → 36: a reference record's role byte gained two values, the subscript role (9) and the inert role (10,
numbered 9 on #380's branch, which predates the subscript role), and the binding kind byte one (Ruby's main-object mixin) (32);
a definition record gained `paramsMin`, the fewest arguments a call can pass (33 on its branch, 34 in the train that landed
Expand Down
22 changes: 18 additions & 4 deletions src/columnar.h
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,9 @@ inline void emitColumnarDepthColumn( std::FILE* out, const std::vector<NodeId>&
rw::emitTo( out, "{}", column );
}

// FE-B: the optional dense `<via>` column — 1 where the row's edge is name-only (via="name"), one value per row in row
// order. Present only when some row carries it, so an answer with no hedged row pays 0 bytes; columnarViaField is its
// fields= suffix.
// The optional dense `<via>` column — 1 where the row's edge is name-only (via="name"), 2 where it is a direct markdown
// link (via="link"), one value per row in row order. Present only when some row carries it, so an answer with no such
// row pays 0 bytes; columnarViaField is its fields= suffix.
inline bool columnarViaPresent( const std::vector<char>* viaName ) noexcept
{
return viaName != nullptr && std::any_of( viaName->begin(), viaName->end(), []( char c ) { return c != 0; } );
Expand All @@ -179,6 +179,20 @@ inline std::string_view columnarViaField( const std::vector<char>* viaName ) noe
{
return columnarViaPresent( viaName ) ? ",via" : "";
}
inline void emitColumnarViaColumn( std::FILE* out, const std::vector<char>& via, std::size_t rowCount )
{
rw::emitTo( out, "<via>" );
for( std::size_t i = 0; i < rowCount; ++i )
{
if( i )
{
std::fputc( ',', out );
}
const unsigned char v = ( i < via.size() ) ? static_cast<unsigned char>( via[ i ] ) : 0;
std::fputc( v <= 9 ? char( '0' + v ) : '0', out );
}
rw::emitTo( out, "</via>" );
}

// CALLSITE-AT (2026-10-09): the optional `<sites_at>` column — each row's call sites (editcheck.h rowCallSites), its tokens joined
// by a space, XML-escaped with a value comma written &#44; (kColumnarLegend's rule), an empty value for a row with none —
Expand Down Expand Up @@ -291,7 +305,7 @@ inline void emitColumnarOptionalColumns( std::FILE* out, const IngestResult& ing
}
if( columnarViaPresent( viaName ) )
{
emitColumnarFlagColumn( out, "via", rows.size(), [ & ]( std::size_t i ) { return i < viaName->size() && ( *viaName )[ i ] != 0; } );
emitColumnarViaColumn( out, *viaName, rows.size() );
}
if( extras.atTokens != nullptr && extras.atTotals != nullptr )
{
Expand Down
10 changes: 9 additions & 1 deletion src/compactlegend.h
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,8 @@ inline constexpr std::string_view kCompactProsePrefixes[] =
"<!-- b over_ceiling=", // …and a past-the-budget one's (serialize.h kOverCeilingBodyLegend)
"<!-- via=\"name\"", // FE-B: graphlegend.h's via="name" comments (viaNameLegendComment, kMapViaNameLegend); the
// element-qualified via rows (<s>/<c>/<e>, the <via> column) and x= restate them
"<!-- via=\"link\"", // a markdown link edge (viaLinkLegendComment, kMapViaLinkLegend); the via=link rows restate it
"<!-- anchor=\"missing\"", // a link whose slug names no heading (anchorMissLegendComment, kMapAnchorMissLegend)
"<!-- src_cut: ", // --pack-top-n's cut reading (serialize.h kPackSourceCutLegend); the two src rows below
};

Expand Down Expand Up @@ -584,7 +586,11 @@ inline constexpr CompactCompletenessTerm kCompactCompletenessTerms[] =
// and VALUE-qualified (<s>/<c>/<e> whose via= lists name), so impact's <f via="import"> importer rows never pull it in; the
// columnar form names it in fields= like the tested column.
{ "via", "via=name: the target was matched by name alone (receiver unproven); every by-name candidate in reach is listed; NOT a claim the edge is false", true, "s", MapHeaderRead::No, "name" },
{ "via", "via=link: this edge is a markdown link, not a call", true, "s", MapHeaderRead::No, "link" },
{ "anchor", "anchor=missing: the markdown link named a heading this file does not have, so the edge is the file", true, "s", MapHeaderRead::No, "missing" },
{ "via", "<c via=\"name\">: that callee matched by name alone (receiver unproven); it does NOT mean the edge is false", true, "c", MapHeaderRead::No, "name" },
{ "via", "<c via=\"link\">: that callee is a markdown link, not a call", true, "c", MapHeaderRead::No, "link" },
{ "anchor", "<c anchor=missing>: that markdown link named a heading this file does not have, so the edge is the file", true, "c", MapHeaderRead::No, "missing" },
// CALLSITE-AT (2026-10-09): a callers row's call sites (editcheck.h rowCallSites); keyed to the callers schema, so the
// map's own <s> rows never read it. The columnar form names the same list as its sites_at column.
{ "sites_at", "<s sites_at=>: its calls of the leaf name, any receiver, as file:line (p= is its def line); not proof each binds", true, "s", MapHeaderRead::No, {}, "callers" },
Expand All @@ -594,6 +600,8 @@ inline constexpr CompactCompletenessTerm kCompactCompletenessTerms[] =
// FE-B: a map <c> row merging N byte-identical via=name rows (serialize.h writeMapCalleeRows) carries x=N
{ "x", "<c x=N>: N same-named via=name rows merged; callees=FILE:SYM lists all", true, "c" },
{ "via", "<e via=name>: that edge matched by name alone (receiver unproven); NOT a claim the edge is false", true, "e", MapHeaderRead::No, "name" },
{ "via", "<e via=link>: that edge is a markdown link, not a call", true, "e", MapHeaderRead::No, "link" },
{ "anchor", "<e anchor=missing>: that markdown link named a heading this file does not have, so the edge is the file", true, "e", MapHeaderRead::No, "missing" },
// attrib-multidef (defattrib.h; graphlegend.h kDefTableLegend): the definition table of a multi-group selector, its rows'
// ties, the cut table's count, and the columnar split column. Present-only, element-scoped. A row's prov="split" (Q1) reads
// through the shared prov= row above, which already fires on any prov= in the payload.
Expand All @@ -614,7 +622,7 @@ inline constexpr CompactCompletenessTerm kCompactCompletenessTerms[] =
{ "cand", "dc cand=: the candidate defs as file:line (legend full only); cand_more= how many more", true, "dc" },
{ "fields", "<of_def> column: each row's def ix=K groups (K~s split label, K~n by name, K~t primary template), space-separated", true, "cols", MapHeaderRead::No, "of_def" },
{ "fields", "<split> column: 1 where the row's edge to of= carries the resolver's split label (may be another arm's)", true, "cols", MapHeaderRead::No, "split" },
{ "fields", "<via> column: 1 = via=name, the row's edge matched by name alone (receiver unproven); NOT a claim the edge is false", true, "cols", MapHeaderRead::No, "via" },
{ "fields", "<via> column: 1 = via=name, the row's edge matched by name alone (receiver unproven); 2 = via=link, a markdown link; NOT a claim the edge is false", true, "cols", MapHeaderRead::No, "via" },
// DEPTH-LABELLED --impact (0.6.5): graph.h transitiveCallersDepth's hop per row. The <s> row prints d= run-length
// (graph.h depthRunAttrXml: the window's first row and each depth change), the columnar form a dense <depth> column, and
// the root by_depth= (graphlegend.h byDepthAttrXml) partitions reaches= by depth; all three are absent at reaches="0".
Expand Down
Loading
Loading