mirror of
https://github.com/Egonex-AI/Understand-Anything.git
synced 2026-10-07 16:48:09 +08:00
Two narrow Karpathy-parser path-resolution gaps degraded valid wikis: Index.md/Log.md were only detected in lowercase — detect_format, parse_wiki's index/log selection, and merge-knowledge-graph's project-name detection now resolve known infra filenames case-insensitively within their directory (exact lowercase still wins when both casings exist; no recursive fuzzy matching). Root index files that link with the article-root prefix included ([[wiki/concepts/Index]] when wiki/ is the detected article root) never matched article ids relative to that root, so category membership missed and every node landed in the Other layer. Category lookup and wikilink resolution now try such targets both as written and with the root prefix stripped; links outside the article root stay unresolved. Fixes #342
199 lines
7.8 KiB
Python
199 lines
7.8 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
test_parse_knowledge_base.py — Tests for title-case infra file detection and
|
|
article-root-prefixed wiki links in the Karpathy wiki parser (issue #342).
|
|
|
|
Run from the repo root:
|
|
python -m unittest tests.skill.knowledge.test_parse_knowledge_base -v
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import importlib.util
|
|
import shutil
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
|
|
# ── Module loaders ────────────────────────────────────────────────────────
|
|
# The scripts have hyphens in their names, so we cannot `import` them
|
|
# directly. Load them via importlib so we can call module-level helpers.
|
|
|
|
_HERE = Path(__file__).resolve().parent
|
|
_REPO_ROOT = _HERE.parent.parent.parent
|
|
_SKILL_DIR = _REPO_ROOT / "understand-anything-plugin" / "skills" / "understand-knowledge"
|
|
|
|
|
|
def _load(script: str, alias: str) -> Any:
|
|
spec = importlib.util.spec_from_file_location(alias, _SKILL_DIR / script)
|
|
if spec is None or spec.loader is None:
|
|
raise RuntimeError(f"Could not load {script}")
|
|
module = importlib.util.module_from_spec(spec)
|
|
sys.modules[alias] = module
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
pkb = _load("parse-knowledge-base.py", "parse_knowledge_base")
|
|
mkg = _load("merge-knowledge-graph.py", "merge_knowledge_graph")
|
|
|
|
|
|
def _fs_is_case_sensitive(tmp: Path) -> bool:
|
|
probe = tmp / "CaseProbe.md"
|
|
probe.write_text("x", encoding="utf-8")
|
|
try:
|
|
return not (tmp / "caseprobe.md").is_file()
|
|
finally:
|
|
probe.unlink()
|
|
|
|
|
|
class TestFindMarkdownCaseInsensitive(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
self.tmp = Path(tempfile.mkdtemp(prefix="ua-pkb-"))
|
|
|
|
def tearDown(self) -> None:
|
|
shutil.rmtree(self.tmp, ignore_errors=True)
|
|
|
|
def test_title_case_file_is_found(self) -> None:
|
|
(self.tmp / "Index.md").write_text("# Wiki", encoding="utf-8")
|
|
found = pkb.find_markdown_case_insensitive(self.tmp, "index.md")
|
|
self.assertTrue(found.is_file())
|
|
self.assertEqual(found.name.lower(), "index.md")
|
|
|
|
def test_exact_lowercase_wins_when_both_exist(self) -> None:
|
|
if not _fs_is_case_sensitive(self.tmp):
|
|
self.skipTest("filesystem is case-insensitive; both casings collide")
|
|
(self.tmp / "Index.md").write_text("# Title", encoding="utf-8")
|
|
(self.tmp / "index.md").write_text("# lower", encoding="utf-8")
|
|
found = pkb.find_markdown_case_insensitive(self.tmp, "index.md")
|
|
self.assertEqual(found.name, "index.md")
|
|
|
|
def test_missing_parent_returns_candidate(self) -> None:
|
|
found = pkb.find_markdown_case_insensitive(self.tmp / "nope", "index.md")
|
|
self.assertFalse(found.is_file())
|
|
|
|
def test_merge_script_helper_matches(self) -> None:
|
|
(self.tmp / "Index.md").write_text("# Wiki", encoding="utf-8")
|
|
found = mkg._find_markdown_case_insensitive(self.tmp, "index.md")
|
|
self.assertTrue(found.is_file())
|
|
|
|
|
|
class TestTitleCaseWiki(unittest.TestCase):
|
|
"""A wiki using Index.md / Log.md must parse the same as index.md / log.md."""
|
|
|
|
def setUp(self) -> None:
|
|
self.tmp = Path(tempfile.mkdtemp(prefix="ua-pkb-wiki-"))
|
|
wiki = self.tmp / "wiki"
|
|
(wiki / "concepts").mkdir(parents=True)
|
|
(wiki / "projects").mkdir(parents=True)
|
|
(wiki / "Index.md").write_text(
|
|
"# Hermes\n\n## Concepts\n\n- [[wiki/concepts/Index]]\n\n"
|
|
"## Projects\n\n- [[wiki/projects/Personal Wiki Second Brain]]\n",
|
|
encoding="utf-8",
|
|
)
|
|
(wiki / "Log.md").write_text(
|
|
"# Log\n\n## [2026-05-01] CREATE | Seeded wiki\n", encoding="utf-8"
|
|
)
|
|
(wiki / "concepts" / "Index.md").write_text(
|
|
"# Concepts Index\n\nOverview of concepts.\n", encoding="utf-8"
|
|
)
|
|
(wiki / "projects" / "Personal Wiki Second Brain.md").write_text(
|
|
"# Personal Wiki Second Brain\n\nPilot project.\n", encoding="utf-8"
|
|
)
|
|
|
|
def tearDown(self) -> None:
|
|
shutil.rmtree(self.tmp, ignore_errors=True)
|
|
|
|
def test_detect_format_sees_title_case_infra(self) -> None:
|
|
signals = pkb.detect_format(self.tmp)
|
|
self.assertTrue(signals["has_index"])
|
|
self.assertTrue(signals["has_log"])
|
|
self.assertTrue(signals["detected"])
|
|
|
|
def test_title_case_root_infra_is_not_an_article(self) -> None:
|
|
manifest = pkb.parse_wiki(self.tmp)
|
|
article_ids = {n["id"] for n in manifest["nodes"] if n["type"] == "article"}
|
|
self.assertNotIn("article:Index", article_ids)
|
|
self.assertNotIn("article:Log", article_ids)
|
|
# Nested Index.md IS content
|
|
self.assertIn("article:concepts/Index", article_ids)
|
|
|
|
def test_root_prefixed_category_links_resolve(self) -> None:
|
|
"""[[wiki/concepts/Index]] must map to article:concepts/Index when
|
|
wiki/ is the detected article root (the #342 pilot saw every node
|
|
land in "Other" because this lookup missed)."""
|
|
manifest = pkb.parse_wiki(self.tmp)
|
|
cat_edges = {
|
|
(e["source"], e["target"])
|
|
for e in manifest["edges"]
|
|
if e["type"] == "categorized_under"
|
|
}
|
|
self.assertIn(("article:concepts/Index", "topic:concepts"), cat_edges)
|
|
self.assertIn(
|
|
("article:projects/Personal Wiki Second Brain", "topic:projects"),
|
|
cat_edges,
|
|
)
|
|
by_id = {n["id"]: n for n in manifest["nodes"]}
|
|
self.assertEqual(
|
|
by_id["article:concepts/Index"]["knowledgeMeta"].get("category"),
|
|
"Concepts",
|
|
)
|
|
|
|
def test_links_outside_article_root_stay_unresolved(self) -> None:
|
|
"""maps/*, SCHEMA etc. must not be forced into article layers."""
|
|
wiki = self.tmp / "wiki"
|
|
(wiki / "concepts" / "Index.md").write_text(
|
|
"# Concepts Index\n\nSee [[maps/overview]] and [[SCHEMA]].\n",
|
|
encoding="utf-8",
|
|
)
|
|
manifest = pkb.parse_wiki(self.tmp)
|
|
related = {
|
|
e["target"] for e in manifest["edges"] if e["source"] == "article:concepts/Index"
|
|
}
|
|
self.assertNotIn("article:maps/overview", related)
|
|
self.assertTrue(any("maps/overview" in w for w in manifest["warnings"]))
|
|
|
|
|
|
class TestLowercaseWikiStillWorks(unittest.TestCase):
|
|
"""Regression guard: the original all-lowercase layout keeps parsing."""
|
|
|
|
def setUp(self) -> None:
|
|
self.tmp = Path(tempfile.mkdtemp(prefix="ua-pkb-lower-"))
|
|
(self.tmp / "concepts").mkdir(parents=True)
|
|
(self.tmp / "index.md").write_text(
|
|
"# Wiki\n\n## Concepts\n\n- [[concepts/attention]]\n", encoding="utf-8"
|
|
)
|
|
(self.tmp / "log.md").write_text("# Log\n", encoding="utf-8")
|
|
(self.tmp / "concepts" / "attention.md").write_text(
|
|
"# Attention\n\nAll you need.\n", encoding="utf-8"
|
|
)
|
|
(self.tmp / "concepts" / "transformer.md").write_text(
|
|
"# Transformer\n\nSee [[attention]].\n", encoding="utf-8"
|
|
)
|
|
|
|
def tearDown(self) -> None:
|
|
shutil.rmtree(self.tmp, ignore_errors=True)
|
|
|
|
def test_lowercase_layout_parses_with_categories_and_edges(self) -> None:
|
|
signals = pkb.detect_format(self.tmp)
|
|
self.assertTrue(signals["detected"])
|
|
manifest = pkb.parse_wiki(self.tmp)
|
|
by_id = {n["id"]: n for n in manifest["nodes"]}
|
|
self.assertIn("article:concepts/attention", by_id)
|
|
self.assertEqual(
|
|
by_id["article:concepts/attention"]["knowledgeMeta"].get("category"),
|
|
"Concepts",
|
|
)
|
|
edge_keys = {(e["source"], e["target"], e["type"]) for e in manifest["edges"]}
|
|
self.assertIn(
|
|
("article:concepts/transformer", "article:concepts/attention", "related"),
|
|
edge_keys,
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|