mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 19:47:12 +01:00
Indexes the arch-wiki-docs dump that is already on disk and exposes it
to the knowledge-rag MCP server, so Arch questions can be asked by
symptom ("why doesn't my systemd user unit see DISPLAY under i3")
rather than by page title, with no network round-trip at query time.
The one Arch-specific MCP server in the wild fetches pages over the
network and bundles pacman operations that duplicate the local yay-mcp.
This does neither: it only searches, against a local corpus.
install.sh installs its own dependencies, wires the MCP config and
registers a pacman hook so the index follows package updates. The
generated index is machine-specific and regenerable, so it is
gitignored rather than committed.
_reindex_entry runs under the knowledge-rag interpreter, where
mcp_server is importable and this repo's packages are not. Resolving
that import with importlib.import_module states the deferral outright
instead of a function-level import statement that reads as an oversight,
and the backend failure path now re-raises as SystemExit, so the
exit-code translation is explicit rather than a swallowed exception.
Both replace lint suppressions; the package carries none.
pylint runs in its own venv, so beautifulsoup4 and markdownify are
declared there too — being in meta/requirements.txt does not make them
resolvable to the hook.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YQLwdquDCNmWtvgi3Lk72J
143 lines
4.2 KiB
Python
143 lines
4.2 KiB
Python
"""Convert an offline ArchWiki HTML page into RAG-friendly Markdown.
|
|
|
|
Pure functions over strings: nothing here touches the filesystem, so the whole
|
|
conversion is testable from an HTML fixture.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
from bs4 import BeautifulSoup, Tag
|
|
from markdownify import markdownify
|
|
|
|
from python_pkg.archwiki_rag.constants import (
|
|
CONTENT_ID,
|
|
HASHED_STEM,
|
|
LOCAL_HREF,
|
|
STRIP_SELECTORS,
|
|
WIKI_BASE_URL,
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
from pathlib import Path
|
|
|
|
|
|
def is_wiki_page(path: Path) -> bool:
|
|
"""Report whether a file in the dump is a real, indexable wiki page.
|
|
|
|
Parameters:
|
|
path (Path): Candidate file from the offline HTML tree.
|
|
|
|
Returns:
|
|
bool: True for ordinary pages, False for the md5-named stray non-English
|
|
pages the dump generator leaves in the English tree.
|
|
"""
|
|
return path.suffix == ".html" and not HASHED_STEM.match(path.stem)
|
|
|
|
|
|
def page_name(path: Path, source: Path) -> str:
|
|
"""Derive the wiki page name from a file's position in the dump.
|
|
|
|
Subpages are stored in nested directories -- ``en/Systemd/User.html`` is
|
|
the page ``Systemd/User`` -- so the name is the path relative to the tree
|
|
root, not merely the filename stem.
|
|
|
|
Parameters:
|
|
path (Path): File such as ``.../en/Systemd/User.html``.
|
|
source (Path): Root of the offline HTML tree.
|
|
|
|
Returns:
|
|
str: The page name, e.g. ``Systemd/User``.
|
|
"""
|
|
return path.relative_to(source).with_suffix("").as_posix()
|
|
|
|
|
|
def wiki_url(page: str) -> str:
|
|
"""Build the upstream URL for a page name.
|
|
|
|
Parameters:
|
|
page (str): Page name such as ``Systemd/User``.
|
|
|
|
Returns:
|
|
str: Canonical https URL on wiki.archlinux.org.
|
|
"""
|
|
return f"{WIKI_BASE_URL}{page}"
|
|
|
|
|
|
def extract_title(soup: BeautifulSoup, fallback: str) -> str:
|
|
"""Read the human-readable page title.
|
|
|
|
Prefers the rendered ``<h1>`` heading, which carries the wiki's own
|
|
capitalisation (``systemd``, not ``Systemd``).
|
|
|
|
Parameters:
|
|
soup (BeautifulSoup): Parsed page.
|
|
fallback (str): Value to use when the page has no ``<h1>``.
|
|
|
|
Returns:
|
|
str: Page title.
|
|
"""
|
|
heading = soup.find("h1")
|
|
if heading is None:
|
|
return fallback
|
|
text = heading.get_text(strip=True)
|
|
return text or fallback
|
|
|
|
|
|
def rewrite_links(content: Tag) -> None:
|
|
"""Rewrite relative page links to absolute wiki.archlinux.org URLs, in place.
|
|
|
|
The dump cross-links pages as ``../en/Foo.html``, which is meaningless once
|
|
a chunk is quoted back to the user. Absolute URLs make retrieved passages
|
|
citable and let the reader follow references without the local tree.
|
|
|
|
Parameters:
|
|
content (Tag): Article body to mutate.
|
|
"""
|
|
for anchor in content.find_all("a", href=True):
|
|
match = LOCAL_HREF.match(str(anchor["href"]))
|
|
if match is None:
|
|
continue
|
|
anchor["href"] = wiki_url(match["page"]) + (match["frag"] or "")
|
|
|
|
|
|
def strip_chrome(content: Tag) -> None:
|
|
"""Remove navigation and editing furniture from the article body, in place.
|
|
|
|
Parameters:
|
|
content (Tag): Article body to mutate.
|
|
"""
|
|
for selector in STRIP_SELECTORS:
|
|
for element in content.select(selector):
|
|
element.decompose()
|
|
|
|
|
|
def html_to_markdown(html: str, page: str) -> str | None:
|
|
"""Render one wiki page as Markdown with a citable front-matter header.
|
|
|
|
Parameters:
|
|
html (str): Raw HTML of an offline wiki page.
|
|
page (str): Page name used for the source URL and title fallback.
|
|
|
|
Returns:
|
|
str | None: Markdown document, or None when the page has no article body
|
|
(malformed or placeholder files in the dump).
|
|
"""
|
|
soup = BeautifulSoup(html, "html.parser")
|
|
content = soup.find(id=CONTENT_ID)
|
|
if content is None:
|
|
return None
|
|
|
|
title = extract_title(soup, page)
|
|
strip_chrome(content)
|
|
rewrite_links(content)
|
|
|
|
body = markdownify(str(content), heading_style="ATX").strip()
|
|
|
|
# A single H1 plus a source line: the title gives the heading-aware chunker
|
|
# a document-level anchor, and the URL rides along into every chunk's text
|
|
# so retrieved passages can be attributed without a metadata lookup.
|
|
header = f"# {title}\n\nSource: {wiki_url(page)}\n"
|
|
return f"{header}\n{body}\n"
|