feat(search): index prose content for BM25 full-text search - #617
feat(search): index prose content for BM25 full-text search#617ShauryaaSharma wants to merge 2 commits into
Conversation
58cd6c4 to
2135922
Compare
|
Rebased onto main (post-#667) , all tests green ✅ Rebased on top of the #667 merge. One minor conflict in Verification against the #518 repro in the bug suite: The #667 markup battery ( |
|
Huge thanks for opening this PR and for the work you put into it. The maintainer shop is currently full, so this may sit for a bit before it gets a proper review. We will come back to this as soon as possible with real feedback; I wanted to make sure it did not sit unacknowledged in the meantime. |
|
No worries at all @DeusData, take your time, there's no rush on my end! Happy to wait and appreciate you acknowledging it. 🙂 |
074e59e to
2135922
Compare
Section nodes (markdown) and Module nodes (YAML/JSON) previously exposed only their heading/name to BM25, so search_graph could not match the prose body or a config description. Index that text so content is searchable. - store: add a `body` column to the nodes_fts FTS5 table; new cbm_store_fts_rebuild() drops+recreates the table (upgrading legacy 4-column databases) and backfills `body` from each node's docstring, guarded by json_valid() against malformed-JSON rows - pipeline: both FTS backfill sites now call cbm_store_fts_rebuild() - mcp: stop excluding Section/Module from BM25 results (they rank below code symbols, so existing result ordering is preserved) - internal/cbm: capture the markdown section body beneath each heading (DeusData#518) and promote top-level description/summary/purpose values onto the file's Module node (DeusData#519), reusing the existing docstring property - tests: 7 extraction cases + 3 store FTS cases Closes DeusData#518 Closes DeusData#519 Signed-off-by: ShauryaaSharma <shauryasofficial27@gmail.com>
2135922 to
411ad44
Compare
# Conflicts: # src/store/store.c
|
Hey @DeusData, just wanted to check in. I rebased and resolved the merge conflict in store.c (was just two comment blocks colliding with the new #768 schema-compat probe on main, no functional overlap), so this should be a clean merge again whenever you get a chance to take a look. Happy to make any changes if you'd like a different direction on anything. Thanks again for your work maintaining this! |
|
This has not fallen off the queue. I rechecked the current head (2bc902e): the recorded full CI set is green, and there has been no newer contributor commit requiring another response. No further action is needed from you until the maintainer diff review; thanks for keeping the branch current. |
|
Reviewed this in full — thank you, and sorry it has waited so long. The engineering is solid and it is built at the right layer; what is holding it is a sizing question plus a rebase, not a design objection. What I want to call out first, because it is the thing most contributions in this area get wrong: you did not hand-roll a BM25 implementation. You extended the SQLite FTS5 index we already vendor and already use for exactly this, added no dependency, and in the process deduplicated the two existing backfill sites into a shared Several details showed real care: the One factual correction worth having on record, in your favour: issue #518 asserts that The branch has drifted and needs a rebase. The one thing I need from you before this can land: numbers. The backfill indexes the docstring of every node, not only the Section and Module nodes that #518 and #519 need. On our reference corpus (~8.5M nodes) I estimate that at +150–400 MB of FTS index and +15–60 s per index run — and that rebuild happens on every run, including incremental background refreshes. Those are engineering estimates, not measurements, and I would rather not merge a scale change on an estimate. Could you run One thing that is on us, not you. The PR un-excludes both So: rebase, fix the stale ranking comment, and bring benchmark numbers. The direction call on Module scope is ours and I will get you an answer. One last note — a small operational nuance rather than a defect: DROP+recreate opens a brief "no such table" window where a concurrent |
|
Thank you for the sustained work on this contribution. I need to correct our earlier sequencing: asking first for a broad rebase and combined benchmark left you carrying two product decisions in one review, which was not the clearest path. Please split this into two ordered PRs:
The second PR may be stacked on the first if that is the cleanest way to preserve dependencies. Please provide focused correctness and performance evidence for each slice rather than one aggregate benchmark over both behaviors. The umbrella PR can remain as the ledger that links the two. This structure lets us evaluate the shared storage/search change separately from the public Module semantics. Thank you for your patience with the earlier mixed guidance. |
|
Three things: the direction answer I owe you, a trap in the rebase that neither of my earlier reviews caught, and a correction that works in your favour. First — confirmed, the gap is still entirely real. I re-checked against current Second — the direction answer I said I owed you, and I am sorry it took this long: yes, The reasoning is the project's stated priority: findability first, token thrift second. A node carrying a human-written description is precisely what someone is searching for when they type a phrase rather than a symbol, and excluding it means the graph knows the answer and refuses to say so. The noise objection is real but bounded — a Module node only becomes interesting if it has a description, and the So: build for both labels returnable. Third — the rebase is bigger than "re-apply the label filter to both queries", and this is the part I most want you to see before you start. Since you opened this, That is a row-level incremental insert on the delta-merge path, with a documented FTS policy block just above it at It needs to be part of the first slice, not a follow-up. Related: The correction in your favour, on the cost question. I framed the benchmark request as though this PR introduces a rebuild. It does not — the full-index path on I still want measured numbers per slice rather than estimates — mine were guesses and I labelled them as such — but you are measuring a smaller delta than my earlier comment suggested. Where that leaves the split, unchanged from 18 August: slice one is the FTS And a straight question, because seven weeks is a long time and I would rather know than keep guessing. Do you still want to carry this? It is a real capability and the work you have done is sound — the FTS5 reuse instead of a hand-rolled BM25, the |
|
Yes, I still want to carry this. I apologize for the delay. Thanks for asking directly rather than letting it drift, and thanks for the direction call on Section/Module; that was the piece I was parked on. Slice 1 is up as #1778. The split. Agreed, and I've stacked them:
This PR stays open as the ledger. The Confirmed against current main before starting, so I wasn't working from stale assumptions:
On the rebase: the old branch was ~1100 commits behind its merge base, and Two small departures from the original branch, both because your review pointed at them. The cap now reuses the existing The DROP+CREATE window is stated in the PR description rather than left to be discovered: during the one-time schema upgrade there's a brief interval where a concurrent On the numbers, one thing I need to be straight with you about. I can't run What I can measure directly is the narrowed question from your correction, since the full-index path already does delete-all plus full re-INSERT, the real delta is per-row body tokenisation and the storage the column adds. I can build a standalone harness against the vendored SQLite with FTS5 and measure, at a range of node counts and realistic docstring lengths:
That isolates the variable your estimate was about and it's reproducible, I'd commit the harness so you can run it too. What it won't give you is real-corpus wall-clock at your scale. For that I think the honest options are that you run I've deliberately not built the harness yet, if it isn't the evidence you want, I'd rather hear that than measure the wrong thing twice. Say the word and it's a short job. Per slice, as you asked, slice 1's numbers with slice 1, slice 2's with slice 2. Slice 2 goes up once #1778 has a direction. |
|
Yes to the harness — build it. That is exactly the evidence I want, and better than what I asked for. Answering the question you are blocked on first, since you sensibly asked before measuring. On the benchmark: your refusal was the right call and I am glad you made it. A 32-bit MinGW toolchain with no The harness you propose measures precisely the variable my own correction narrowed this to. Since the full-index path already does delete-all plus full re-INSERT, the real delta is per-row body tokenisation plus the column's storage — and that is portable, isolatable, and does not need the product binary at all. Index size with and without And I will take the other half. Real-corpus wall-clock on the reference machine is my job, not yours — I have the corpus and the built binary and you have neither. I will run Your read on the Giving it a dedicated Two design decisions of yours I want to endorse explicitly, because both are the kind of thing a reviewer might otherwise read as churn:
On the from-scratch rebuild: agreed, and it was your call to make. A branch ~1,100 commits behind, across three files that were all reworked around the exact regions you touched, is not a rebase — it is an archaeology exercise where the most likely outcome is silently reintroducing something that was deliberately changed. Using the old diff as a design spec and rebuilding against current Thank you for the pre-flight verification too. Independently re-confirming the five locations against current main — and conceding that your own Disclosing the DROP+CREATE window in the PR description rather than leaving it to be found is the right instinct as well. One-time and graceful is fine; undocumented is not. I am picking up #1778 now. #617 stays open as the ledger, as you proposed. Sorry again for the seven weeks. You have been the most responsive party in this exchange and you were waiting on a direction call that only I could make. |
|
Harness is committed — Two things from it worth flagging here, since they bear on the sizing question this PR has been held on:
Not quoting timings — my machine's run-to-run spread is 39–44%, large next to the delta, and the harness flags that itself rather than presenting a noisy column as authoritative. Your reference-machine run is the one that will produce meaningful wall-clock; thanks for taking that half. Ledger status: slice 1 is #1778 (open, merges clean). Slice 2 (#519) stacks on it and I'll hold until #1778 has a direction, since review could move the base. This stays open as the ledger as agreed. And no need to keep apologising for the seven weeks — you gave me a genuinely useful review and the |
What & why
search_graphBM25 only matched node names and headings, so it was blind to theprose that documentation- and config-heavy repos carry. Markdown
Sectionnodesexposed only their heading; YAML/JSON
Modulenodes only their file name — thesection body and the description value were never indexed, and
Section/Modulewere excluded from BM25 results entirely. This indexes that prose so content is
searchable.
Closes #518
Closes #519
Changes
bodycolumn to the nodes_fts FTS5 table; newcbm_store_fts_rebuild() drops+recreates it (upgrading legacy 4-column DBs) and
backfills
bodyfrom each node's docstring, guarded by json_valid().results still sort first).
description/summary/purpose value onto the Module node (META.yaml/frontmatter description values not indexed for BM25 search #519), reusing the
existing docstring property.
Testing
7 extraction cases + 3 store FTS cases added. Verified end-to-end: bodies are
extracted → indexed into nodes_fts.body → returned by BM25; json_valid() tolerates
malformed rows; legacy FTS tables upgrade on rebuild.
Notes
Backward compatible (additive column; legacy DBs upgrade on next index). No MCP
tool changes, no new deps, no new system()/popen()/network calls. #518 and #519
share the FTS
bodyinfra (#519 can't work without it), so they're together —happy to split if preferred.