test_step5_reference_target_prefix.py 5.03 KB
#!/usr/bin/env python3
"""VERIFY: Step5 reference 分支的 target 前缀缺口(learn_queue #1323 retry, 2026-10-11)

病理(实测):`_verify_and_apply` 的 `otype=="reference"` 分支用
    rglob(f"*/{target}/SKILL.md")
而 `target` = learn_target 原文,新建 skill 型吸收带 `new:` 前缀
(如 new:context-language-models-meta-reasoning)⇒ 谓词恒不命中,
「skill 真的落在磁盘上」这道边界事实验不到,只剩 memory.json 短语回退可 grounding。
#1323 因此被判 FINGERPRINT_FAIL(attempt=2),白烧一次重试。

TDD:TC1 是 RED(旧谓词找不到磁盘上真实存在的 skill),TC2 是 GREEN(去前缀后命中),
TC3 源码守卫(防回退),TC4 复现 memory 短语回退链(旧路径的唯一活路),
TC5 验收本轮真实产物(learn_queue #1323 的 summary 锚点行是否真在 memory.json 中)。
"""
import pathlib
import sys

SKILLS = pathlib.Path("/data/data/com.termux/files/home/.dsh/skills")
MEM = pathlib.Path("/data/data/com.termux/files/home/pixelphone/memory.json")
APP = pathlib.Path("/data/data/com.termux/files/home/wechat-viewer/app.py")
SUMMARY = pathlib.Path("/data/data/com.termux/files/home/tmp/learn_absorb_ca36d4acb352.md.summary.md")

RAW_TARGET = "new:context-language-models-meta-reasoning"   # learn_target 原文
SKILL_NAME = "context-language-models-meta-reasoning"
FAIL = 0


def rglob_skill(tgt):
    return list(SKILLS.rglob(f"*/{tgt}/SKILL.md")) or list(SKILLS.rglob(f"{tgt}/SKILL.md"))


def strip_prefix(t):
    for pfx in ("new:", "update:"):
        if t.startswith(pfx):
            return t[len(pfx):].strip()
    return t


def step5_mem_fallback(outline, result_summary, mem_text):
    """复现 app.py 的 memory.json 回退链(fingerprint → elif outline 四窗口短语)。"""
    fingerprint = (result_summary or "").strip()[:30]
    if fingerprint and fingerprint in mem_text:
        return "fingerprint"
    for ph in [outline[i:i + 20] for i in range(0, min(80, len(outline)), 20)]:
        if len(ph.strip()) >= 10 and ph.strip() in mem_text:
            return f"phrase:{ph.strip()}"
    return None


print("=== TC1(RED/实测缺口): 旧谓词拿带前缀 target 直接 rglob ===")
old = rglob_skill(RAW_TARGET)
print(f"  {'✅' if not old else '❌'} 旧谓词命中 {len(old)} 个(期望 0,即磁盘上的 skill 被漏掉)")
if old:
    FAIL += 1
on_disk = rglob_skill(SKILL_NAME)
print(f"  {'✅' if on_disk else '❌'} 该 skill 其实在磁盘上: {[str(p) for p in on_disk]}(前提事实)")
if not on_disk:
    FAIL += 1

print("\n=== TC2(GREEN/修复): 去 new:/update: 前缀后再 rglob ===")
new = rglob_skill(strip_prefix(RAW_TARGET))
print(f"  {'✅' if new else '❌'} 去前缀后命中 {len(new)} 个 → {[str(p) for p in new]}")
if not new:
    FAIL += 1
print(f"  ✅ strip_prefix 幂等性: {strip_prefix(SKILL_NAME)!r} / {strip_prefix('update:'+SKILL_NAME)!r}")
assert strip_prefix(SKILL_NAME) == SKILL_NAME and strip_prefix("update:" + SKILL_NAME) == SKILL_NAME

print("\n=== TC3(源码守卫/防回退) ===")
src = APP.read_text(encoding="utf-8")
start = src.find("if not verified_fingerprint and otype == \"reference\" and target:")
end = src.find("for sf in skill_files:", start)
branch = src[start:end]
checks = [
    ("分支内出现 tgt_resolved", "tgt_resolved" in branch),
    ("去前缀循环覆盖 new:", '"new:"' in branch),
    ("rglob 用的是 tgt_resolved 而非原 target", "rglob(f\"*/{target}/SKILL.md\")" not in branch),
]
for label, ok in checks:
    print(f"  {'✅' if ok else '❌'} {label}")
    if not ok:
        FAIL += 1

print("\n=== TC4(复现 memory 短语回退链,旧路径的唯一活路) ===")
mem_text = MEM.read_text(encoding="utf-8")
header_only = "# 学习摘要 — Meta 两篇论文:把 Agent 控制权从 harness 交还模型\n\n正文…"
print(f"  {'✅' if step5_mem_fallback(header_only, '', mem_text) is None else '❌'} "
      f"纯中文标题式摘要开头 → 回退不命中(复现 #1323 的 FINGERPRINT_FAIL)")
if step5_mem_fallback(header_only, "", mem_text) is not None:
    FAIL += 1
anchor = "CLM/meta-reasoning anchors (learn_queue #1323): 163 in-place edits, 6-8K tokens"
hit = step5_mem_fallback(anchor + " 其余正文", "# 学习完成报告\n\n## 学习内容\n- 标题: Meta 连", mem_text)
print(f"  {'✅' if hit else '❌'} ASCII 锚点行式开头 → 回退命中({hit})")
if not hit:
    FAIL += 1

print("\n=== TC5(本轮真实产物验收) ===")
if SUMMARY.exists():
    s = SUMMARY.read_text(encoding="utf-8")
    outline = s[:500]
    real = step5_mem_fallback(outline, "# 学习完成报告\n\n## 学习内容\n- 标题: Meta 连", mem_text)
    print(f"  {'✅' if real else '❌'} summary 首 80 字能在 memory.json 中 grounding → {real}")
    if not real:
        FAIL += 1
    print(f"  ✅ 首行纯 ASCII(不受 JSON 转义影响): {all(ord(c) < 128 for c in s[:80])}")
else:
    print("  ⚠️ summary 文件不在,跳过(吸收后 cleanup 会删除)")

print(f"\n{'✅ ALL PASS' if FAIL == 0 else f'❌ {FAIL} FAILED'}")
sys.exit(0 if FAIL == 0 else 1)