* 锚点查找 fallback:AI 常把一条**真实的 raw 块锚点**(如 [[raw/papers/2024-10-lightrag#^p-54]]) * 误标到它正在读的摘要页路径上(写成 [[wiki/sources/lightrag#^p-54]])——锚点是对的、路径错了。 * * 程序兜底:当 path 是 wiki/sources/X.md 且 anchor 在该路径未找到时,按以下顺序到对应 raw 文件重试: * 1. 摘要页 frontmatter 里**声明的 `sources:`**(精确——能处理 `2024-10-lightrag` 这类日期前缀
( cmd: "read-block" | "read-section", originalPath: string, anchor: string, )
| 79 | * 仅对 anchor-not-found 类错误兜底,其他错误(路径不存在 / 权限等)原样返回。 |
| 80 | */ |
| 81 | async function readWithSourcesFallback( |
| 82 | cmd: "read-block" | "read-section", |
| 83 | originalPath: string, |
| 84 | anchor: string, |
| 85 | ) { |
| 86 | const primary = await callKCli([cmd, originalPath, anchor]); |
| 87 | if (primary.ok) return primary; |
| 88 | if (!primary.error.includes("未找到 anchor")) return primary; |
| 89 | const m = originalPath.match(/^wiki\/sources\/(.+\.md)$/); |
| 90 | if (!m) return primary; |
| 91 | const basename = m[1]; |
| 92 | |
| 93 | // 候选 raw 路径:优先用摘要页 frontmatter 声明的 sources(精确),再退到 basename 猜测。 |
| 94 | const candidates: string[] = []; |
| 95 | try { |
| 96 | const page = await getPage(originalPath); |
| 97 | const srcs = (page?.frontmatter as { sources?: unknown } | undefined)?.sources; |
| 98 | if (Array.isArray(srcs)) { |
| 99 | for (const s of srcs) { |
| 100 | if (typeof s !== "string") continue; |
| 101 | const mm = s.match(/\[\[([^\]|#]+)/); // 取 [[ 后、到 | / # / ]] 前的路径 |
| 102 | if (!mm) continue; |
| 103 | let p = mm[1].trim(); |
| 104 | if (!p.endsWith(".md")) p += ".md"; |
| 105 | if (p.startsWith("raw/") && isSafeRelPath(p)) candidates.push(p); |
| 106 | } |
| 107 | } |
| 108 | } catch { |
| 109 | // getPage 失败(页面损坏等)→ 忽略,退到 basename 猜测 |
| 110 | } |
| 111 | candidates.push(`raw/articles/${basename}`, `raw/papers/${basename}`); |
| 112 | |
| 113 | const seen = new Set<string>(); |
| 114 | for (const candidate of candidates) { |
| 115 | if (seen.has(candidate)) continue; |
| 116 | seen.add(candidate); |
| 117 | const retry = await callKCli([cmd, candidate, anchor]); |
| 118 | if (retry.ok) { |
| 119 | // eslint-disable-next-line no-console |
| 120 | console.log( |
| 121 | `[anchor-fallback] ${cmd} ${originalPath}#${anchor} → ${candidate}`, |
| 122 | ); |
| 123 | const augmented = |
| 124 | retry.data && typeof retry.data === "object" && !Array.isArray(retry.data) |
| 125 | ? { |
| 126 | ...(retry.data as Record<string, unknown>), |
| 127 | _fallback: { |
| 128 | from: originalPath, |
| 129 | to: candidate, |
| 130 | reason: "anchor 未在 wiki/sources 找到,已回退到对应 raw 文件", |
| 131 | }, |
| 132 | } |
| 133 | : retry.data; |
| 134 | return { ok: true as const, data: augmented }; |
| 135 | } |
| 136 | } |
| 137 | return primary; |
| 138 | } |
no test coverage detected