init: llm-wiki-haness 하네스 설계
This commit is contained in:
@@ -0,0 +1,233 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
import hashlib
|
||||
import json
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
|
||||
RUNTIME = Path(__file__).resolve().parents[1] / "runtime"
|
||||
sys.path.insert(0, str(RUNTIME))
|
||||
import moc_indexer # noqa: E402
|
||||
|
||||
|
||||
class MocIndexerTest(unittest.TestCase):
|
||||
def _cutover(self, root: Path, paths: list[Path]) -> dict[Path, Path]:
|
||||
mapping: dict[Path, Path] = {}
|
||||
entries = []
|
||||
for legacy in paths:
|
||||
relative = legacy.relative_to(root).as_posix()
|
||||
category = legacy.parent.name
|
||||
canonical_rel = f"vault/10-projects/proj/{category}/{legacy.name}"
|
||||
canonical = root / canonical_rel
|
||||
canonical.parent.mkdir(parents=True, exist_ok=True)
|
||||
content = legacy.read_bytes()
|
||||
canonical.write_bytes(content)
|
||||
legacy.write_text(f"---\ncanonical_path: {canonical_rel}\n---\n", encoding="utf-8")
|
||||
entries.append({
|
||||
"legacy_path": relative,
|
||||
"canonical_path": canonical_rel,
|
||||
"sha256": hashlib.sha256(content).hexdigest(),
|
||||
})
|
||||
mapping[legacy] = canonical
|
||||
manifest = root / "harness/source/vault-layout.json"
|
||||
manifest.parent.mkdir(parents=True)
|
||||
manifest.write_text(json.dumps({
|
||||
"schema_version": "vault-layout/v1",
|
||||
"mode": "canonical",
|
||||
"migration_manifest": {"schema_version": "vault-migration/v1", "entries": entries},
|
||||
}), encoding="utf-8")
|
||||
return mapping
|
||||
|
||||
def test_canonical_edges_sort_children_and_preserve_manual_cluster(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
project = root / "raw/project-notes/proj.md"
|
||||
project.parent.mkdir(parents=True)
|
||||
project.write_text("# P\n## Cluster / 묶음\n- [[raw/errors/manual]]\n", encoding="utf-8")
|
||||
branch_dir = root / "raw/branch-notes"
|
||||
branch_dir.mkdir(parents=True)
|
||||
for slug in ("feature-zeta-child-contract-runtime", "feature-alpha-child-contract-runtime"):
|
||||
(branch_dir / f"{slug}.md").write_text(
|
||||
f"---\nid: {slug}\nproject: proj\nparent_branch:\ncontract_packet: 1\n---\n# B\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
updates, stats = moc_indexer.build_updates(root)
|
||||
self.assertEqual(stats["structured_children"], 2)
|
||||
project.write_text(updates[project], encoding="utf-8")
|
||||
text = project.read_text(encoding="utf-8")
|
||||
self.assertLess(text.index("feature-alpha"), text.index("feature-zeta"))
|
||||
self.assertIn("[[raw/errors/manual]]", text)
|
||||
self.assertEqual(moc_indexer.build_updates(root)[0], {})
|
||||
|
||||
def test_relation_config_generates_multiple_reverse_views_and_multiline_daily_edges(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
project = root / "raw/project-notes/proj.md"
|
||||
project.parent.mkdir(parents=True)
|
||||
project.write_text("# Project\n## Cluster / 묶음\nproject manual\n", encoding="utf-8")
|
||||
branch = root / "raw/branch-notes/feature-sample-parent-contract.md"
|
||||
branch.parent.mkdir(parents=True)
|
||||
branch.write_text(
|
||||
"---\nproject: proj\nparent_branch:\ncontract_packet: 1\n---\n# Branch\n## Cluster / 묶음\nbranch manual\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
official = root / "raw/official-docs/source.md"
|
||||
official.parent.mkdir(parents=True)
|
||||
official.write_text("---\nrelated_branches: [feature-sample-parent-contract]\n---\n# Source\n", encoding="utf-8")
|
||||
error = root / "raw/errors/error.md"
|
||||
error.parent.mkdir(parents=True)
|
||||
error.write_text("---\nrelated_branches: [feature-sample-parent-contract]\n---\n# Error\n", encoding="utf-8")
|
||||
daily = root / "raw/daily-notes/2026-07-20.md"
|
||||
daily.parent.mkdir(parents=True)
|
||||
daily.write_text("---\nbranches: [\n feature-sample-parent-contract\n]\n---\n# Daily\n", encoding="utf-8")
|
||||
|
||||
updates, stats = moc_indexer.build_updates(root)
|
||||
for path, text in updates.items():
|
||||
path.write_text(text, encoding="utf-8")
|
||||
project_text = project.read_text(encoding="utf-8")
|
||||
branch_text = branch.read_text(encoding="utf-8")
|
||||
self.assertIn("<!-- GENERATED: branches:start -->", project_text)
|
||||
self.assertIn("[[raw/branch-notes/feature-sample-parent-contract]]", project_text)
|
||||
self.assertIn("<!-- GENERATED: sources:start -->", branch_text)
|
||||
self.assertIn("[[raw/official-docs/source]]", branch_text)
|
||||
self.assertIn("<!-- GENERATED: errors:start -->", branch_text)
|
||||
self.assertIn("[[raw/errors/error]]", branch_text)
|
||||
self.assertIn("<!-- GENERATED: daily-notes:start -->", branch_text)
|
||||
self.assertIn("[[raw/daily-notes/2026-07-20]]", branch_text)
|
||||
self.assertIn("project manual", project_text)
|
||||
self.assertIn("branch manual", branch_text)
|
||||
self.assertEqual(stats["canonical_edges"], 4)
|
||||
self.assertEqual(moc_indexer.build_updates(root)[0], {})
|
||||
|
||||
def test_canonical_mode_indexes_manifest_paths_and_excludes_stubs(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
project = root / "raw/project-notes/proj.md"
|
||||
project.parent.mkdir(parents=True)
|
||||
project.write_text("# P\n## Cluster / 묶음\n", encoding="utf-8")
|
||||
branch = root / "raw/branch-notes/feature-sample-contract.md"
|
||||
branch.parent.mkdir(parents=True)
|
||||
branch.write_text(
|
||||
"---\nproject: proj\nparent_branch:\ncontract_packet: 1\n---\n# B\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
mapping = self._cutover(root, [project, branch])
|
||||
updates, stats = moc_indexer.build_updates(root)
|
||||
canonical_project = mapping[project]
|
||||
self.assertEqual(set(updates), {canonical_project})
|
||||
self.assertIn(
|
||||
"[[raw/branch-notes/feature-sample-contract]]",
|
||||
updates[canonical_project],
|
||||
)
|
||||
self.assertEqual(stats["mode"], "canonical")
|
||||
self.assertEqual(stats["namespace"], "canonical")
|
||||
canonical_project.write_text(updates[canonical_project], encoding="utf-8")
|
||||
self.assertEqual(moc_indexer.build_updates(root)[0], {})
|
||||
|
||||
def test_invalid_or_duplicate_generated_markers_fail_closed(self) -> None:
|
||||
with self.assertRaises(moc_indexer.MocError) as raised:
|
||||
moc_indexer._replace_or_insert_marker(
|
||||
"<!-- GENERATED: sources:start -->\n<!-- GENERATED: sources:start -->\n<!-- GENERATED: sources:end -->\n",
|
||||
set(),
|
||||
"sources",
|
||||
"hub.md",
|
||||
)
|
||||
self.assertEqual(raised.exception.code, "INVALID_GENERATED_BLOCK")
|
||||
|
||||
def test_all_named_raw_relations_support_project_and_multi_parent_views(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
project = root / "raw/project-notes/proj.md"
|
||||
project.parent.mkdir(parents=True)
|
||||
project.write_text("# Project\n## Cluster / 묶음\nmanual project\n", encoding="utf-8")
|
||||
branches = []
|
||||
for slug in ("feature-parent-one", "feature-parent-two"):
|
||||
branch = root / f"raw/branch-notes/{slug}.md"
|
||||
branch.parent.mkdir(parents=True, exist_ok=True)
|
||||
branch.write_text("# Branch\n## Cluster / 묶음\nmanual branch\n", encoding="utf-8")
|
||||
branches.append(branch)
|
||||
|
||||
fixtures = [
|
||||
("raw/official-docs/source.md", "related_branches: [feature-parent-one, feature-parent-two]\nrelated_projects: [proj]"),
|
||||
("raw/interviews/question.md", "related_branches: [feature-parent-one]\nrelated_projects: [proj]"),
|
||||
("raw/lectures/course.md", "related_branches: [feature-parent-one]\nrelated_projects: [proj]"),
|
||||
("raw/job-postings/job.md", "related_branches: [feature-parent-one]\nrelated_projects: [proj]"),
|
||||
("raw/blog-topics/topic.md", "related_branches: [feature-parent-one]\nrelated_projects: [proj]"),
|
||||
]
|
||||
for relative, fields in fixtures:
|
||||
path = root / relative
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(f"---\n{fields}\n---\n# Child\n", encoding="utf-8")
|
||||
|
||||
updates, stats = moc_indexer.build_updates(root)
|
||||
for path, text in updates.items():
|
||||
path.write_text(text, encoding="utf-8")
|
||||
|
||||
project_text = project.read_text(encoding="utf-8")
|
||||
first_text = branches[0].read_text(encoding="utf-8")
|
||||
second_text = branches[1].read_text(encoding="utf-8")
|
||||
for marker in ("sources", "interviews", "lectures", "job-postings", "blog-topics"):
|
||||
self.assertIn(f"<!-- GENERATED: {marker}:start -->", project_text)
|
||||
self.assertIn(f"<!-- GENERATED: {marker}:start -->", first_text)
|
||||
self.assertIn("[[raw/official-docs/source]]", second_text)
|
||||
self.assertIn("manual project", project_text)
|
||||
self.assertGreaterEqual(stats["canonical_edges"], 11)
|
||||
self.assertEqual(moc_indexer.build_updates(root)[0], {})
|
||||
|
||||
def test_deleted_last_child_clears_generated_view_and_preserves_manual_content(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
branch = root / "raw/branch-notes/feature-parent.md"
|
||||
branch.parent.mkdir(parents=True)
|
||||
branch.write_text("# Branch\n## Cluster / 묶음\nmanual\n", encoding="utf-8")
|
||||
source = root / "raw/official-docs/source.md"
|
||||
source.parent.mkdir(parents=True)
|
||||
source.write_text("---\nrelated_branches: [feature-parent]\n---\n# Source\n", encoding="utf-8")
|
||||
for path, text in moc_indexer.build_updates(root)[0].items():
|
||||
path.write_text(text, encoding="utf-8")
|
||||
source.unlink()
|
||||
|
||||
updates, _stats = moc_indexer.build_updates(root)
|
||||
rendered = updates[branch]
|
||||
self.assertIn("<!-- GENERATED: sources:start -->\n<!-- GENERATED: sources:end -->", rendered)
|
||||
self.assertNotIn("raw/official-docs/source", rendered)
|
||||
self.assertIn("manual", rendered)
|
||||
|
||||
def test_canonical_derived_relations_support_nested_and_multiple_parents(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
concept = root / "wiki/concepts/concept.md"
|
||||
project = root / "wiki/projects/proj/design.md"
|
||||
concept.parent.mkdir(parents=True)
|
||||
project.parent.mkdir(parents=True)
|
||||
concept.write_text("# Concept\nmanual concept\n", encoding="utf-8")
|
||||
project.write_text("# Project\nmanual project\n", encoding="utf-8")
|
||||
children = [
|
||||
("wiki/interview/interview.md", "derived-interviews"),
|
||||
("wiki/portfolio/portfolio.md", "derived-portfolios"),
|
||||
("wiki/blog/blog.md", "derived-blogs"),
|
||||
]
|
||||
for relative, _marker in children:
|
||||
child = root / relative
|
||||
child.parent.mkdir(parents=True, exist_ok=True)
|
||||
child.write_text(
|
||||
"---\ncanonical_sources: [wiki/concepts/concept, wiki/projects/proj/design]\n---\n# Derived\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
updates, stats = moc_indexer.build_updates(root)
|
||||
concept_text = updates[concept]
|
||||
project_text = updates[project]
|
||||
for relative, marker in children:
|
||||
link = Path(relative).with_suffix("").as_posix()
|
||||
self.assertIn(f"<!-- GENERATED: {marker}:start -->", concept_text)
|
||||
self.assertIn(f"[[{link}]]", concept_text)
|
||||
self.assertIn(f"[[{link}]]", project_text)
|
||||
self.assertEqual(stats["canonical_edges"], 6)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user