Find files in raw/ that have no corresponding wiki entries. When ``kb_dir`` is provided, each raw file is first resolved through the hash registry (``kb_dir/.openkb/hashes.json``): registered files are checked against their ``doc_name`` artifacts, which can differ from the raw stem
(
raw: Path,
wiki: Path,
*,
kb_dir: Path | None = None,
)
| 346 | |
| 347 | |
| 348 | def find_missing_entries( |
| 349 | raw: Path, |
| 350 | wiki: Path, |
| 351 | *, |
| 352 | kb_dir: Path | None = None, |
| 353 | ) -> list[str]: |
| 354 | """Find files in raw/ that have no corresponding wiki entries. |
| 355 | |
| 356 | When ``kb_dir`` is provided, each raw file is first resolved through |
| 357 | the hash registry (``kb_dir/.openkb/hashes.json``): registered files |
| 358 | are checked against their ``doc_name`` artifacts, which can differ |
| 359 | from the raw stem (watch-mode/URL ingests keep the original filename |
| 360 | in raw/ — e.g. ``2509.11420.pdf`` → ``2509-11420.md`` — and collision |
| 361 | suffixes rename artifacts too). Files with no registry entry fall back |
| 362 | to the stem heuristic: "present" means a sources/ or summaries/ page |
| 363 | with the same stem. |
| 364 | |
| 365 | Args: |
| 366 | raw: Path to the raw documents directory. |
| 367 | wiki: Path to the wiki root directory. |
| 368 | kb_dir: Root of the knowledge base. When None, only the legacy |
| 369 | stem heuristic is used. |
| 370 | |
| 371 | Returns: |
| 372 | List of filenames in raw/ with no wiki entry. |
| 373 | """ |
| 374 | sources_dir = wiki / "sources" |
| 375 | summaries_dir = wiki / "summaries" |
| 376 | |
| 377 | sources_stems = {p.stem for p in sources_dir.glob("*.md")} if sources_dir.exists() else set() |
| 378 | summary_stems = ( |
| 379 | {p.stem for p in summaries_dir.glob("*.md")} if summaries_dir.exists() else set() |
| 380 | ) |
| 381 | known_stems = sources_stems | summary_stems |
| 382 | |
| 383 | registry = None |
| 384 | if kb_dir is not None: |
| 385 | registry_file = kb_dir / ".openkb" / "hashes.json" |
| 386 | if registry_file.exists(): |
| 387 | # Deferred import: converter pulls in pymupdf/markitdown, |
| 388 | # which lint doesn't otherwise need at import time. |
| 389 | from openkb.converter import _registry_path |
| 390 | from openkb.state import HashRegistry |
| 391 | |
| 392 | registry = HashRegistry(registry_file) |
| 393 | |
| 394 | missing: list[str] = [] |
| 395 | if raw.exists(): |
| 396 | for f in raw.iterdir(): |
| 397 | if not f.is_file(): |
| 398 | continue |
| 399 | if registry is not None: |
| 400 | meta = registry.get_by_path(_registry_path(f, kb_dir)) |
| 401 | if meta is not None: |
| 402 | # Registered file — the registry's doc_name is the |
| 403 | # single source of truth for artifact names. |
| 404 | doc_name = meta.get("doc_name") or Path(meta.get("name", f.name)).stem |
| 405 | present = ( |