diff --git a/AGENTS.md b/AGENTS.md
index 58db05d..09c0b2c 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -276,6 +276,9 @@ output**)。單筆修正只治標;規則修正才治本。
語料要有跨頁共用的核心詞。便利值會變成盲點的形狀——ASCII 檔名讓 `git status`
的路徑轉義 bug 溜過,互不重疊的語料讓 BM25 的 idf 退化 bug 溜過。
新增檢查時先問:**這組測資和真實輸入差在哪?差異處就是沒被測到的地方。**
+- **斷言要驗「結果可用」,不是驗「字串存在」**:`assert "
fastmcp # mcp/server.py 薄殼
+markdown-it-py # 自檢用:以 CommonMark 解析驗證產出的圖片連結真的能 render
+ # (原為 fastmcp 的傳遞依賴,明確宣告以免上游調整後自檢失效)
diff --git a/tools/convert/convert.py b/tools/convert/convert.py
index dcf556f..38143d5 100644
--- a/tools/convert/convert.py
+++ b/tools/convert/convert.py
@@ -125,7 +125,8 @@ def _vision_pdf(root, pdf_path, assets, conv_name):
blocks = []
for i, (img, text) in enumerate(zip(images, texts), 1):
rel = img.relative_to(md_dir).as_posix()
- blocks.append(f"\n\n\n\n{text}")
+ # <> 包住:檔名含空格或不平衡括號時,未包住的連結會失效
+ blocks.append(f"\n\n\n\n{text}")
return "\n\n".join(blocks), "ok", "from_vision"
diff --git a/tools/convert/from_docx.py b/tools/convert/from_docx.py
index c02ecb5..d284865 100644
--- a/tools/convert/from_docx.py
+++ b/tools/convert/from_docx.py
@@ -50,7 +50,9 @@ def _par_md(par, doc, assets_dir, md_dir, counter):
rel = (assets_dir / name).relative_to(md_dir).as_posix()
else:
rel = name # dry-run:僅佔位
- parts.append(f"")
+ # 連結目標以 <> 包住:來源檔名可能含空格或不平衡括號,兩者都會讓
+ # Markdown 連結失效(圖片存在卻整行退化成純文字)
+ parts.append(f"")
parts.append(run.text)
text = "".join(parts).strip()
lvl = _heading_level(par)
diff --git a/tools/convert/from_pdf.py b/tools/convert/from_pdf.py
index c450ff0..ffc375a 100644
--- a/tools/convert/from_pdf.py
+++ b/tools/convert/from_pdf.py
@@ -42,7 +42,8 @@ def convert(src, assets_dir=None, md_dir=None):
assets_dir.mkdir(parents=True, exist_ok=True)
pix.save(str(assets_dir / name))
rel = (assets_dir / name).relative_to(md_dir).as_posix()
- blocks.append(f"")
+ # <> 包住:檔名含空格或不平衡括號時,未包住的連結會失效
+ blocks.append(f"")
doc.close()
return "\n\n".join(blocks), "ok"
diff --git a/tools/convert/from_pptx.py b/tools/convert/from_pptx.py
index 28ac585..ad43374 100644
--- a/tools/convert/from_pptx.py
+++ b/tools/convert/from_pptx.py
@@ -45,7 +45,8 @@ def convert(src, assets_dir=None, md_dir=None):
rel = (assets_dir / name).relative_to(md_dir).as_posix()
else:
rel = name
- blocks.append(f"")
+ # <> 包住:檔名含空格或不平衡括號時,未包住的連結會失效
+ blocks.append(f"")
if slide.has_notes_slide:
notes = slide.notes_slide.notes_text_frame.text.strip()
if notes:
diff --git a/tools/convert/selfcheck.py b/tools/convert/selfcheck.py
index ad84361..bddd5fc 100644
--- a/tools/convert/selfcheck.py
+++ b/tools/convert/selfcheck.py
@@ -26,13 +26,39 @@ import to_image
# 測資檔名一律用「中文 + 空格」——真實來源檔就長這樣,而 git status --porcelain
# 對這兩者都會加引號轉義。ASCII 檔名的測資曾讓 ingest 的 preflight bug 溜過(§12)。
-DOCX, XLSX, PDF, SCAN = ("壓測 報告.docx", "測試案例 清單.xlsx",
- "規格 說明.pdf", "掃描件 無文字層.pdf")
+DOCX, XLSX, PDF, SCAN = ("壓測 報告(1).docx", "測試案例 清單.xlsx",
+ "規格 說明.pdf", "掃描件(2 期.pdf")
PPTX, HTML, TXT, DOC = ("結案 簡報.pptx", "指引 快照.html",
"不支援 筆記.txt", "舊版 報告.doc")
+def assert_links_ok(md, base):
+ """每個圖片連結都要真的 render 成
、且目標檔存在。
+
+ 只斷言 "![" 在不在會讓損毀連結矇混過關——含空格/不平衡括號的檔名會產出
+ 語法有效但解析失敗的連結,圖檔明明在磁碟上卻整行退化成純文字(§13.4)。
+ """
+ import re
+ import urllib.parse
+ from markdown_it import MarkdownIt
+
+ want = md.count("![")
+ html = MarkdownIt("commonmark").render(md)
+ got = html.count("
= 3, f"應至少驗到 docx/pdf/pptx 各一個圖片連結,實際 {linked}"
+ print(f"PASS: 圖片連結可 render 且目標存在({linked} 個,檔名含空格/括號)")
+
# 冪等:同檔再跑一次 → skip,manifest 不變
before = (root / "raw/manifest.json").read_text(encoding="utf-8")
convert.main([str(fx / DOCX), "--root", str(root)])
@@ -228,6 +264,7 @@ def main():
assert len(conv_rel) == 1 and mv["files"][conv_rel[0]]["converter"] == "from_vision", mv
md_v = (root_v / conv_rel[0]).read_text(encoding="utf-8")
assert "可疑交易監控" in md_v and "(vision" in md_v and "![page 1]" in md_v, md_v
+ assert_links_ok(md_v, root_v / "raw/converted") # vision 頁的連結同樣要能 render
print("PASS: convert.py --vision 端到端(needs_ocr → 本地 vision 轉錄)")
print("ALL PASS")