tests/test_the_folder_is_not_the_category.py
Rama main · réplica de lectura del repositorio original.
"""資料夾是位址,不是分類。
1,754 筆書籤裡有 650 筆的 frontmatter `category` 跟所在目錄不一致(最大宗是 188
筆放在 02-seo-geo 但自稱 03-video-prompts)。這個不一致本身沒有破壞任何東西——
召回完全不讀分類(沒有任何過濾或加權用它,嵌入只有內容,路徑只是鍵),wiki 路由、
lint、健檢讀的全都是 frontmatter。
所以這份測試不要求兩邊一致。它要求的是**只有一邊算分類**:
frontmatter 的 category 是分類
目錄名是檔案放在哪,沒有別的意思
兩個「修好」它的方向都比不修更糟,所以不能讓它們悄悄發生:
搬檔案對齊 frontmatter → relative_path 變了,而那是向量索引與 knowledge_usage
的鍵;267 筆使用統計會變孤兒,退場量測歸零。
改 frontmatter 對齊目錄 → 650 張卡被重新路由到別的 wiki topic,等於讓 legacy
目錄名蓋掉分類器的判斷。
2026-09-12 我自己被這個前綴騙過一次:看到 record_id `02-seo-geo/2032...` 就推論
那張卡「被歸錯類所以被 SEO 問題撈出來」。它其實是日文 AI 生圖 prompt 合集,
frontmatter 寫 04-ai-tools-agents,而它被撈出來是內容嵌入弱相關。報告印前綴卻不
印真正的分類,就是在邀請這個推論——所以下面也釘住報告要印分類。
"""
from __future__ import annotations
import re
import sys
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
SCRIPTS = ROOT / "scripts"
sys.path.insert(0, str(SCRIPTS))
# 從路徑拿片段的寫法。這些本身沒問題(很多地方合法地用它們找檔案),
# 問題是把結果當成分類。
PATH_SEGMENT = re.compile(
r"parent\.name|os\.path\.dirname|\.parts\[|\bdirname\b|"
r"split\(['\"]/['\"]\)")
class OnlyFrontmatterDecidesTheCategory(unittest.TestCase):
def test_no_script_derives_a_category_from_a_path(self):
"""同一行同時出現「category」和「從路徑取片段」就是在把位址當分類。"""
offenders = []
for path in sorted(SCRIPTS.glob("*.py")):
for lineno, line in enumerate(
path.read_text(encoding="utf-8", errors="replace").splitlines(), 1):
code = line.split("#", 1)[0]
if "category" in code.lower() and PATH_SEGMENT.search(code):
offenders.append(f"{path.name}:{lineno}: {line.strip()}")
self.assertEqual(
offenders, [],
"資料夾名不是分類——分類只從 frontmatter 來。若真的需要位址,"
"把變數叫 folder,別叫 category:\n" + "\n".join(offenders))
def test_the_search_index_reads_the_frontmatter(self):
"""search_index.json 的 category 必須來自 frontmatter。
它是 wiki 路由、lint、健檢唯一的分類來源(sync_cards_to_wiki 讀的是
search_index 的 category 欄位)。改成讀目錄會一次改掉 650 張卡的路由。
"""
builder = (SCRIPTS / "build_search_index.sh").read_text(
encoding="utf-8", errors="replace")
self.assertRegex(builder, r"\^category:",
"分類要從 frontmatter 的 category: 欄位解析")
def test_recall_does_not_filter_or_boost_by_category(self):
"""召回不讀分類。讀了就會讓 650 筆不一致變成真正的 bug。"""
for name in ("xkb_memory_service.py", "xkb_score.py", "xkb_relevance.py",
"recall_router.py"):
text = (SCRIPTS / name).read_text(encoding="utf-8", errors="replace")
for lineno, line in enumerate(text.splitlines(), 1):
code = line.split("#", 1)[0]
if "category" not in code.lower():
continue
with self.subTest(where=f"{name}:{lineno}"):
# 當成欄位原樣搬運可以;拿來比較、過濾、加權不行。
self.assertNotRegex(
code, r"category.*(==|!=|\bin\b|>=|<=)|"
r"(==|!=|\bin\b).*category",
"召回不該依分類過濾或加權")
class ReportsMustNotPassTheFolderOffAsTheCategory(unittest.TestCase):
def test_the_evict_report_shows_the_real_category(self):
"""報告印 record_id 前綴卻不印真正的分類,就是在邀請錯誤推論。"""
import xkb_evict_report as rep
self.assertTrue(hasattr(rep, "describe"))
source = (SCRIPTS / "xkb_evict_report.py").read_text(
encoding="utf-8", errors="replace")
self.assertIn("describe(r[\"record_id\"])", source,
"降權清單要印出 frontmatter 的分類與標題")
def test_describe_prefers_frontmatter_over_the_prefix(self):
import tempfile
from unittest import mock
import xkb_evict_report as rep
import xkb_paths
tmp = tempfile.TemporaryDirectory()
self.addCleanup(tmp.cleanup)
# 不要把測試卡片寫進真的 cards/ ——那是資產。
card = Path(tmp.name) / "cat-check-1.md"
card.write_text("---\ncategory: 04-ai-tools-agents\n---\n# 日文 prompt 合集\n",
encoding="utf-8")
with mock.patch.object(xkb_paths, "CARDS_DIR", Path(tmp.name)):
title, category = rep.describe("02-seo-geo/cat-check-1")
self.assertEqual(category, "04-ai-tools-agents",
"分類要從 frontmatter 來,不是從 record_id 前綴")
self.assertEqual(title, "日文 prompt 合集")
def test_describe_survives_a_missing_card(self):
import xkb_evict_report as rep
self.assertEqual(rep.describe("02-seo-geo/does-not-exist"), ("", ""))
if __name__ == "__main__":
unittest.main()