chore: track claude skills, tools, templates, reference code and research-wiki

- Add all claude skills (brainstorming, commit, debugging, TDD, etc.)
- Add claude hooks (pre-commit-guard, post-edit-quality)
- Add research templates (experiment plan, research brief, etc.)
- Add claude tools (arxiv/semantic_scholar/openalex fetch, wiki, exa)
- Add TRM4 reference implementation as algorithm fidelity baseline
- Add research-wiki content (plans, index, graph, query_pack)
- Update .gitignore to exclude .graphify_version runtime state
This commit is contained in:
2026-07-06 20:59:03 -04:00
parent 0616d16956
commit 6bdb802f01
98 changed files with 19321 additions and 0 deletions
@@ -0,0 +1,193 @@
"""
Embedding 持久化单元测试
=======================
测试 TreeIndex 的 embedding 序列化/反序列化功能。
"""
import json
import os
import tempfile
from pathlib import Path
import numpy as np
import pytest
from video_tree_trm.tree_index import (
L1Node,
L2Node,
L3Node,
IndexMeta,
TreeIndex,
_embed_to_str,
_embed_from_str,
)
class TestEmbeddingSerialization:
"""测试 embedding 序列化辅助函数。"""
def test_embed_to_str_and_back(self):
"""测试 base64 序列化/反序列化往返正确。"""
arr = np.random.randn(768).astype(np.float32)
s = _embed_to_str(arr)
recovered = _embed_from_str(s)
assert recovered is not None
assert recovered.dtype == np.float32
assert recovered.shape == (768,)
np.testing.assert_array_almost_equal(arr, recovered, decimal=6)
def test_embed_to_str_handles_none(self):
"""测试 None 输入返回 None。"""
assert _embed_to_str(None) is None
assert _embed_from_str(None) is None
assert _embed_from_str("") is None
class TestL3NodeEmbedding:
"""测试 L3Node embedding 序列化。"""
def test_l3_to_dict_without_embedding(self):
"""测试不带 embedding 序列化。"""
node = L3Node(
id="l3_0",
description="测试描述",
timestamp=1.0,
frame_path="frame.jpg",
raw_content="原始内容",
)
d = {
"id": node.id,
"description": node.description,
"timestamp": node.timestamp,
"frame_path": node.frame_path,
"raw_content": node.raw_content,
}
# 无 embedding 字段
assert "embedding" not in d
def test_l3_embedding_roundtrip(self):
"""测试 L3 embedding 序列化往返。"""
embed = np.random.randn(768).astype(np.float32)
s = _embed_to_str(embed)
recovered = _embed_from_str(s)
np.testing.assert_array_almost_equal(embed, recovered, decimal=6)
class TestTreeIndexEmbedding:
"""测试 TreeIndex 完整序列化/反序列化。"""
def test_save_load_without_embedding(self):
"""测试不带 embedding 保存/加载(向后兼容)。"""
with tempfile.TemporaryDirectory() as tmpdir:
path = os.path.join(tmpdir, "test.json")
# 创建简单树
l3 = L3Node(id="l3_0", description="L3 描述")
l2 = L2Node(id="l2_0", description="L2 描述", children=[l3])
l1 = L1Node(id="l1_0", summary="L1 摘要", children=[l2])
meta = IndexMeta(source_path="test.mp4", modality="video")
tree = TreeIndex(metadata=meta, roots=[l1])
# 保存(不含 embedding
tree.save_json(path, include_embedding=False)
# 加载
loaded = TreeIndex.load_json(path)
assert len(loaded.roots) == 1
assert loaded.roots[0].summary == "L1 摘要"
assert not loaded.is_embedded # 无 embedding
def test_save_load_with_embedding(self):
"""测试带 embedding 保存/加载。"""
with tempfile.TemporaryDirectory() as tmpdir:
path = os.path.join(tmpdir, "test_embed.json")
# 创建带 embedding 的树
embed_l1 = np.random.randn(768).astype(np.float32)
embed_l2 = np.random.randn(768).astype(np.float32)
embed_l3 = np.random.randn(768).astype(np.float32)
l3 = L3Node(id="l3_0", description="L3 描述", embedding=embed_l3)
l2 = L2Node(id="l2_0", description="L2 描述", embedding=embed_l2, children=[l3])
l1 = L1Node(id="l1_0", summary="L1 摘要", embedding=embed_l1, children=[l2])
meta = IndexMeta(
source_path="test.mp4",
modality="video",
embed_model="test-model",
embed_dim=768,
)
tree = TreeIndex(metadata=meta, roots=[l1])
# 保存(含 embedding
tree.save_json(path, include_embedding=True)
# 验证 JSON 中有 embedding 字段
with open(path, encoding="utf-8") as f:
data = json.load(f)
assert "embedding" in data["roots"][0]
assert data["metadata"]["embed_model"] == "test-model"
# 加载
loaded = TreeIndex.load_json(path)
assert loaded.is_embedded
np.testing.assert_array_almost_equal(
loaded.roots[0].embedding, embed_l1, decimal=6
)
np.testing.assert_array_almost_equal(
loaded.roots[0].children[0].embedding, embed_l2, decimal=6
)
np.testing.assert_array_almost_equal(
loaded.roots[0].children[0].children[0].embedding, embed_l3, decimal=6
)
def test_load_old_format_compatible(self):
"""测试加载旧格式(无 embedding 字段)JSON 兼容。"""
with tempfile.TemporaryDirectory() as tmpdir:
path = os.path.join(tmpdir, "old_format.json")
# 手动创建旧格式 JSON(无 embedding 字段)
old_data = {
"metadata": {
"source_path": "test.mp4",
"modality": "video",
"created_at": "2024-01-01T00:00:00",
},
"roots": [
{
"id": "l1_0",
"summary": "L1 摘要",
"time_range": None,
"children": [
{
"id": "l2_0",
"description": "L2 描述",
"time_range": None,
"children": [
{
"id": "l3_0",
"description": "L3 描述",
"timestamp": 1.0,
"frame_path": None,
"raw_content": None,
}
],
}
],
}
],
}
with open(path, "w", encoding="utf-8") as f:
json.dump(old_data, f)
# 加载
loaded = TreeIndex.load_json(path)
assert len(loaded.roots) == 1
assert not loaded.is_embedded # 无 embedding,向后兼容
if __name__ == "__main__":
pytest.main([__file__, "-v"])