[help wanted] HTML as a graph: sections, anchored links, and markup→code edges (sibling of #409 / #414)
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 5/5
- Estimated time
- Over a week
- Newbie friendliness
- 25/100
Research direction
Start with slice A: read the Markdown grammar setup in CMakeLists.txt, the language table in src/ingest_crawl.h, and the document handling in src/docparse.h and src/ingest_docs.h. The maintainers are taking A; slice B depends on A and the changes from #414. For a chosen slice, follow its listed files and red-first checks; done means its acceptance gate passes with the specified HTML behavior.
Written by the indexing model from the issue text.
Description
Claim a slice in this issue first. @muayyad-alsadi, after your work on #414, slice B (anchored links) is a natural next step if you'd like it. It reuses the exact (file, anchor) bind you built for markdown. Size: each slice S–M, independent after slice A. Pattern to copy: PR #414 (markdown caller/callee).
The problem, in user terms
#409 / #414 turn Markdown into a graph: headings are sections, links are edges, and a doc link is marked via="link", so it never passes for a code caller. HTML gets none of this today. A tiny site (index.html loads static/app.js, has <button onclick="saveDoc()">, links to docs/guide.html#config, and has an inline <script>) gives:
ripwire . --json files=5 symbols=12 edges=0 unindexed={"css":1}
index.html → one sec "index"; docs/guide.html → one sec "guide"
ripwire . --callers=saveDoc count="0" (the onclick in index.html is invisible)
ripwire . --grep=inlineHelper 2 hits in index.html, but it is no symbol: inline <script> is not indexed
ripwire . --recall="config file" serves the whole of guide.html, nav text included, as one unit
Why, on main:
src/docparse.h:56puts.html/.htmin the EXTRACTOR tier (DocKind::Html).extractHtml(:411) strips tags and drops<script>/<style>bodies (:421).src/ingest_docpass.h:213-214adds ONE whole-fileSection(lang=Markdown, named by the file stem). There are no headings, no anchors, and no links.- No HTML grammar is vendored (
CMakeLists.txthas the markdown block grammar, and notree-sitter-html). Inline JS is dropped, yet the file is not counted inunindexed=, so the miss is silent. - Markdown→HTML links drop as well.
emitLinkTarget(src/ingest_docs.h:316) keeps only.md/.markdowntargets, so[guide](docs/guide.html#config)makes no edge.
Proposal, ranked by value / cost
Each item lists the edge, how it binds, and how it stays honest. The rule across all of them: markup is never a code caller. Every new edge kind carries its own via= and stays out of callers=/count=/impact_reaches=, the way #414 handles links. Any scan that might miss something says so in a *_floor / unindexed= field.
- Sections + anchors (shared with markdown; cheap, high value). Vendor
tree-sitter-html(MIT, the same pin style as the other grammars).h1–h6becomeSectiondefs. Each section's span runs to the next heading at the same or a higher level, and its scope is the nearest shallower heading (the markdown rule). Everyid=(and legacy<a name=>) is an anchor target attached to its enclosing section. Text inside<nav>,<header>,<footer>and<aside>is left out of a section's body, so--recallranks the main content (the readability/trafilatura idea, made deterministic by using landmark tags only). If the page has<main>/<article>, prefer it. A file without headings stays one whole-file section, and--recallsayssection-granularonly when that is true. - Anchored doc links (copy #414 directly).
<a href="file#id">binds exactly to (file, anchor). In-filehref="#id"binds to the section that holds that id. Markdown↔HTML links work in both directions (lift the.md-only filter). Rows arevia="link". A missing anchor falls back to the file and is markedanchor="missing", and it also shows as a--doc-driftrow.http(s):,mailto:and links that leave the repo are counted (external_links=), never bound. Payoff: "what links here" =--callerson a page or section, and the nav graph =--callees. - Inline
<script>indexed as JS +<script src>/<link href>as include edges (medium). Parse each inline script's byte range with the existing JavaScript grammar, at its real offsets sop=index.html:16is right (inlineHelperbecomes a symbol and itsloadConfig()call becomes a real call edge).src/hrefbecome file→filevia="include"edges, which are dependencies, not calls.<style>and.cssstayunindexed(see "not now"). - Event-handler attributes → callback REGISTRATION sites (medium; ties into ripwire's callback/indirection work).
onclick="saveDoc()"makes the section that holds the element point tosaveDocwithvia="handler". That is the place the callback is registered, not a call. Bind only against functions the page can see: its<script src>files plus its inline scripts. Otherwise the row is hedged (bind="name").--callers=saveDocthen lists it under its ownhandlers=count, andcount=stays 0. - Literal string binds between markup and JS (medium, all exact-literal-only).
customElements.define('my-widget', MyWidget)↔<my-widget>uses.getElementById('x')/querySelector('#x')/querySelector('.c')↔ the element that hasid="x"/class="c". Rows arevia="selector"/via="element". A non-literal argument goes toselector_unresolved=N, never to a guess. Compound CSS selectors bind only on a single simple selector, and every other selector counts in the floor. - Page metadata as section text (cheap).
<title>,<meta name=description>, OpenGraphog:*and JSON-LD"@type"/"name"feed the file section's name and recall text (the extruct idea). Tables and lists (<table>,<ul>/<ol>) are served whole by--expandand never split mid-row (docling/unstructured treat them as units). <form action>/ literalfetch('/api/x')↔ server routes (higher cost; maybe its own issue). Bind only to a literal route decorator or registration (@app.route("/api/save"),app.post('/api/save', …)), withvia="route". This needs a route table per framework, so it starts with Flask and Express and disclosesroute_unbound=.
Separate issues (please don't fold them in here): template languages (Jinja/Django {% include/extends %}, Handlebars partials, ERB/EJS), because .html alone cannot tell them apart and they need their own include-graph rules; and Vue/Svelte SFCs, whose <script> is component code and belongs with the TS/JS language work.
Not doing, and why: the full DOM or the CSS cascade (specificity and inheritance are runtime semantics, and any answer would be a guess); markup created at runtime (innerHTML, JSX output, server rendering), which cannot be seen statically, so disclose it, don't infer it; fetching external URLs; and LLM entity extraction or embeddings (the GraphRAG/LightRAG approach). ripwire answers are deterministic and byte-reproducible, and an LLM-built edge cannot be gated or re-checked.
Slices for contributors (copy #414's shape: a fixture dir, a test/*check.sh gate, red-first arms)
- A. Sections + anchors (needed by B–E). The maintainers are taking this slice, so B–E have a base to build on. Comment here if you want to pair on it. Touch:
CMakeLists.txt(add_ts_grammar(html …)),src/ingest_crawl.h(kLangTablerow), a newsrc/ingest_html.hbesidesrc/ingest_docs.h, andsrc/docparse.h(move.htmlfrom the extractor tier to the grammar tier, keeping the sibling-completeness guard). Red-first:--outline=docs/guide.htmllistsGuide,Config file(sc=Guide) andAdvanced;--recall="config file"serves only theConfig filesection, and the nav text fromindex.htmlis in no section body. Near miss: a page with no headings is one section and is not calledsection-granular. - B. Anchored links (after A, plus #414 merged). Touch: the link emitter in
ingest_html.h, the (file, anchor) bind insrc/graph.hthat #414 adds, andsrc/docdrift.h. Red-first:--callers='Config file'listsindex.html::InstallationandREADME.md::Links, bothvia="link";href="guide.html#removed"givesanchor="missing"and one--doc-driftrow;--safe-delete/--impactrows carryvia="link"(the #414 review lesson). - C. Inline scripts + includes. Touch:
ingest_html.h(hand each script range to the JS parse at its offset) and the RawRef cache (src/ingest_cache.h, keep it cache-stable like #414). Red-first:--callers=loadConfigshowsinlineHelper(p=index.html:16);--callees=index.htmlshowsstatic/app.jswithvia="include"; andcount=ofloadConfigdoes not include the include edge. - D. Handlers + custom elements + selectors. Touch:
ingest_html.h(attribute scan),src/graph.h(newviakinds), and the legendssrc/compactlegend.h/src/graphlegend.h. Red-first:--callers=saveDoc→count="0" handlers="1"and avia="handler"row fromindex.html::Usage;--callers=MyWidget→ avia="element"row. Near miss:getElementById(name)givesselector_unresolved="1"and no row. - E. Metadata + table/list units. Touch:
ingest_html.hand the--expandspan rule. Red-first: a JSON-LD"name"is found by--recall, and--expandof a section that holds a table returns the whole<table>.
Acceptance (every slice)
bash test/langidentitycheck.sh BASE HEAD: non-HTML languages are SAME. HTML DIFFs are waived by--intendedrows that name the slice's gate.- No existing
callers=/count=/impact_reaches=value changes for code-only repos.--quality-deltagates 0, and the CONTRIBUTING definition of done applies.
Prior art (HTML→structure only; we copy the deterministic parts)
- docling HTML backend: h1–h6 → section headers, lists and tables as items. https://github.com/docling-project/docling (
docling/backend/html_backend.py) - unstructured
partition_html: Title/ListItem/Table elements withcategory_depthfrom the heading level. https://github.com/Unstructured-IO/unstructured - LangChain
HTMLHeaderTextSplitter(header-path metadata) and LlamaIndexHTMLNodeParser(tag-based nodes). https://github.com/langchain-ai/langchain · https://github.com/run-llama/llama_index - trafilatura / Mozilla Readability: main content vs boilerplate. https://github.com/adbar/trafilatura · https://github.com/mozilla/readability
- extruct: microdata / JSON-LD / OpenGraph. https://github.com/scrapinghub/extruct
- tree-sitter-html (grammar candidate, MIT). https://github.com/tree-sitter/tree-sitter-html
- Not taken: Microsoft GraphRAG, LightRAG and HippoRAG build graphs from LLM entity extraction, which conflicts with deterministic, gateable answers.
If nobody has claimed a slice by the time our next integration train starts, we may pick it up ourselves. Claiming one only takes a comment here.
- Dominant language
- C++
- Stars
- 2.5k
- Forks
- 158
- Avg merge
- 2d 9h
- Merged PRs (30d)
- 55
Getting set up
- No Dockerfile or Docker Compose file
- No pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from redhat-et/ripwire
-
Difficulty 2/5 Under an hour Newbie friendliness 78/100
redhat-et/ripwire#334 · 7 comments ·
Maintainers usually reply within 1 day
-
help wanted
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
redhat-et/ripwire#68 · 2 comments ·
Maintainers usually reply within 1 day
-
Difficulty 1/5 Under an hour Newbie friendliness 85/100
Maintainers usually reply within 1 day
-
Difficulty 5/5 Over a week Newbie friendliness 35/100
redhat-et/ripwire#428 · 7 comments ·
Maintainers usually reply within 1 day
-
Difficulty 5/5 Over a week Newbie friendliness 15/100
redhat-et/ripwire#409 · 10 comments ·
Maintainers usually reply within 1 day
All issues in redhat-et/ripwire
Similar issues
-
crash lld:COFF
Difficulty 2/5 1-3 hours Newbie friendliness 77/100
llvm/llvm-project#235970 · 1 comment ·
Maintainers usually reply within 1 day
-
[Bug]: iOS textarea wraps text mid-word because LynxUIBaseInput hardcodes NSLineBreakByCharWrappingOpen
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
lynx-family/lynx#10455 ·
Maintainers usually reply within 1 day
-
A-tools P-low S-blocked task
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
Maintainers usually reply within 1 day
-
cv::checkRange fails with "Unsupported matrix depth" for fp8 depthsPossibly taken @sshivensolanki claimed this today. Openbug
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Maintainers usually reply within 1 day
-
Rifle-variant weapon offsets are saved under the pistol's name [FRIK-093]Possibly taken @rollingrock claimed this today. Openbug
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
rollingrock/Fallout-4-VR-Body#182 ·