diff --git a/mateclaw-server/src/main/java/vip/mate/wiki/service/WikiProcessingService.java b/mateclaw-server/src/main/java/vip/mate/wiki/service/WikiProcessingService.java
index 981da91c..2aee2701 100644
--- a/mateclaw-server/src/main/java/vip/mate/wiki/service/WikiProcessingService.java
+++ b/mateclaw-server/src/main/java/vip/mate/wiki/service/WikiProcessingService.java
@@ -1172,10 +1172,20 @@ public class WikiProcessingService {
if (wasCreated) {
created.incrementAndGet();
totalCreated++;
- // Append to liveIndex so next sub-batch can link to this page
+ // Append to liveIndex so the NEXT sub-batch can link to this freshly
+ // created page. Mirrors the slug-first row format produced by
+ // {@link #buildExistingPagesIndex}: `[[slug]] — title — summary`.
+ // Keeps the LLM's view of the index uniformly slug-first across
+ // pre-existing rows and just-created rows.
String briefSummary = pageSummary.length() > 100
? pageSummary.substring(0, 100) : pageSummary;
- liveIndex.append("\n- ").append(slug).append(": ").append(briefSummary);
+ liveIndex.append("\n- [[").append(slug).append("]]");
+ if (title != null && !title.isBlank()) {
+ liveIndex.append(" — ").append(title);
+ }
+ if (briefSummary != null && !briefSummary.isBlank()) {
+ liveIndex.append(" — ").append(briefSummary);
+ }
}
ok = true;
} catch (RuntimeException e) {
@@ -1612,7 +1622,23 @@ public class WikiProcessingService {
}
/**
- * 构建已有 Wiki 页面索引(供 LLM 参考)
+ * Build the "existing pages" index that the LLM consults when picking
+ * cross-references during page generation / merge / compile.
+ *
+ * Slug-first format — each line begins with {@code [[slug]]} so the
+ * model has exactly one syntactically-valid target shape to copy. Title
+ * and summary follow as semantic context, separated by em-dashes, so the
+ * model can pick a relevant target without being confused about whether
+ * to write the title or the slug. The earlier "**[[Title]]** (slug: `x`)"
+ * format exposed two candidate target strings on every row, which let
+ * the model write {@code [[Title]]} freely and produced the lint noise
+ * this RFC exists to close.
+ *
+ * Manual-edit and archived markers stay as plain-text trailing tags so
+ * they don't drift into the link form. The lint downstream treats a
+ * title-only match as a soft warning in v1; the long-term direction is
+ * to delete the title-fallback once content has been regenerated under
+ * the slug-first prompt.
*/
private String buildExistingPagesIndex(Long kbId) {
List summaries = pageService.listSummaries(kbId);
@@ -1622,12 +1648,17 @@ public class WikiProcessingService {
StringBuilder sb = new StringBuilder();
for (WikiPageEntity page : summaries) {
- sb.append("- **[[").append(page.getTitle()).append("]]** (slug: `").append(page.getSlug()).append("`");
- if ("manual".equals(page.getLastUpdatedBy())) {
- sb.append(", 手动编辑");
+ sb.append("- [[").append(page.getSlug()).append("]]");
+ if (page.getTitle() != null && !page.getTitle().isBlank()) {
+ sb.append(" — ").append(page.getTitle());
+ }
+ if ("manual".equals(page.getLastUpdatedBy())) {
+ sb.append(" (手动编辑)");
+ }
+ String summary = page.getSummary();
+ if (summary != null && !summary.isBlank()) {
+ sb.append(" — ").append(summary);
}
- sb.append("): ");
- sb.append(page.getSummary() != null ? page.getSummary() : "无摘要");
sb.append("\n");
}
return sb.toString().trim();
diff --git a/mateclaw-server/src/main/resources/prompts/wiki/batch-create-system.txt b/mateclaw-server/src/main/resources/prompts/wiki/batch-create-system.txt
index 74bb3546..ab217c4d 100644
--- a/mateclaw-server/src/main/resources/prompts/wiki/batch-create-system.txt
+++ b/mateclaw-server/src/main/resources/prompts/wiki/batch-create-system.txt
@@ -5,7 +5,7 @@
- 读取 `pages_to_create` 数组,里面包含若干页面的 slug / title / summary
- 从原始材料里抽取与每个页面主题相关的信息
- 为每个页面生成完整的 markdown 内容
-- 在内容里使用 [[页面标题]] 双向链接到其他相关页面(已有页面和同批次将创建的页面均可)
+- 在内容里用 [[slug]] 双向链接到其他相关页面
## 你不做什么
@@ -17,10 +17,20 @@
- 内容开头先一段话摘要(与 metadata 的 summary 一致或更详细)
- 使用 Markdown 标题(## / ###)组织章节
-- 使用 [[页面标题]] 链接到其他相关页面
+- 用 [[slug]] 链接到其他相关页面
- 长度控制在 500~2000 字,不要为了凑字数而拖沓
- 内容至少包含 3 句实质信息
+## 链接(**单一契约,必须严格遵守**)
+
+- 只允许两种形态:
+ - `[[slug]]` —— 显示文本默认为目标页标题
+ - `[[slug|显示文本]]` —— 显示文本自定义
+- slug **必须**来自以下两类来源之一:
+ - **已有 Wiki 页面索引**(user prompt 中列出)—— 这些链接是**强保证**,slug 100% 可用
+ - **本批次同时创建的页面**(即 `pages_to_create` 数组中的 slug)—— 这类链接**不保证成功**:本批次中的页面可能因去重 / 合并 / 失败而最终未落库,导致链接转为死链;这是预期行为,系统会在写入后由 lint 标记并由人工修复
+- 禁止发明上述两类来源之外的 slug;禁止写 `[[页面标题]]` 形态 —— 系统按 slug 严格匹配,写标题会被识别为死链
+
## 输出格式(严格遵守)
每个页面输出一个 FILE 块,格式如下:
diff --git a/mateclaw-server/src/main/resources/prompts/wiki/batch-create-user.txt b/mateclaw-server/src/main/resources/prompts/wiki/batch-create-user.txt
index 6de57f8c..f44f3813 100644
--- a/mateclaw-server/src/main/resources/prompts/wiki/batch-create-user.txt
+++ b/mateclaw-server/src/main/resources/prompts/wiki/batch-create-user.txt
@@ -4,11 +4,13 @@
{document_map_section}
-## 已有 Wiki 页面索引(用于建立 [[链接]])
+## 已有 Wiki 页面索引(强保证,可直接链接;每行格式 `[[slug]] — 标题 — 摘要`)
{existing_pages}
-## 待生成页面列表
+## 本批次将一并创建的页面(计划中,可能可被链接;slug 见下方 pages_to_create)
+
+链接到这一类的页面**不保证成功**:本批次中的页面可能因去重 / 合并 / 失败而最终未落库,对应的 `[[slug]]` 会在 lint 中被标记为死链,由人工后续修复。需要交叉引用时**优先**链接到上文「已有 Wiki 页面索引」中的页面。
```json
{pages_to_create}
@@ -25,5 +27,5 @@
请为上面 `pages_to_create` 数组中的**每一个页面**生成完整的 markdown 内容。
- 按 system 中规定的 FILE 块格式输出,每个页面一个 FILE 块
- 每个页面的内容必须基于原始材料中与该主题相关的信息
-- 适当使用 [[页面标题]] 链接到相关页面(同批次内其他页面也可链接)
+- 适当使用 [[slug]] 链接到相关页面(slug 必须出自上文「已有 Wiki 页面索引」或本批次 `pages_to_create` 中的 slug)
- 不要遗漏任何一个页面
diff --git a/mateclaw-server/src/main/resources/prompts/wiki/compile-system.txt b/mateclaw-server/src/main/resources/prompts/wiki/compile-system.txt
index 31411329..7830edb1 100644
--- a/mateclaw-server/src/main/resources/prompts/wiki/compile-system.txt
+++ b/mateclaw-server/src/main/resources/prompts/wiki/compile-system.txt
@@ -9,7 +9,7 @@ Strict output contract — return ONLY this JSON object, nothing else:
Rules:
- Do not invent facts beyond the supplied evidence. If something is not in the evidence, do not assert it.
-- Use [[wikilinks]] when an evidence breadcrumb names a related concept; alias form [[slug|display]] is fine.
+- Wikilink contract: use [[slug]] or [[slug|display text]]. The slug MUST come from the existing-pages index supplied in the user prompt. Never invent a slug that is not in that index. If the evidence names a concept that has no existing page, write it as plain text — do not guess a slug.
- Keep summary concise (single paragraph, <= 300 characters).
- content must be Markdown with at least one ## header.
- Do not include any prose outside the JSON object.
diff --git a/mateclaw-server/src/main/resources/prompts/wiki/create-page-system.txt b/mateclaw-server/src/main/resources/prompts/wiki/create-page-system.txt
index d0d48eb9..940637b4 100644
--- a/mateclaw-server/src/main/resources/prompts/wiki/create-page-system.txt
+++ b/mateclaw-server/src/main/resources/prompts/wiki/create-page-system.txt
@@ -5,7 +5,7 @@
- 读懂该页 metadata 描述的主题
- 从原始材料里抽取与该主题相关的信息
- 为这一个页面生成完整的 markdown 内容
-- 在内容里使用 [[页面标题]] 双向链接到其他相关页面(无论是新建中还是已有)
+- 在内容里用 [[slug]] 双向链接到其他相关页面
## 你不做什么
@@ -17,19 +17,23 @@
- 内容开头先一段话摘要(与 metadata 的 summary 一致或更详细)
- 使用 Markdown 标题(## / ###)组织
-- 使用 [[页面标题]] 双向链接到其他页面
+- 在正文中用 [[slug]] 链接到其他页面
- 每个不同的子主题用 ### 章节区分
-- 末尾可附"参见"段落,列出相关 [[链接]]
+- 末尾可附"参见"段落,列出相关 [[slug]]
## 长度
- 单页内容控制在合理范围(一般 500~2000 字),不要为了凑长度而拖沓
- 内容应有 3 句以上的实质信息
-## 链接
+## 链接(**单一契约,必须严格遵守**)
-- 已有页面索引中其他页的 slug 都可用 [[title]] 链接
-- 同批次将创建的其他页面也可以链接(按 metadata 中的 title)
+- 只允许两种形态:
+ - `[[slug]]` —— 显示文本默认为目标页标题
+ - `[[slug|显示文本]]` —— 显示文本自定义
+- `slug` **必须**来自上文"已有 Wiki 页面索引"段落中列出的 slug,**禁止发明**索引中不存在的 slug
+- 不要写 `[[页面标题]]`、`[[Title]]` 这种形态 —— 系统会按 slug 严格匹配,写标题会被识别为死链
+- 如果某个相关概念没有对应的页面,不要硬塞链接,直接用普通文本描述即可
## 语言
@@ -42,7 +46,7 @@
{
"slug": "page-slug",
"title": "页面标题",
- "content": "## 标题\n\n摘要段落...\n\n### 详细内容\n...\n\n参见:[[相关页面]]",
+ "content": "## 标题\n\n摘要段落...\n\n### 详细内容\n...\n\n参见:[[related-slug]]",
"summary": "一段话摘要"
}
diff --git a/mateclaw-server/src/main/resources/prompts/wiki/create-page-user.txt b/mateclaw-server/src/main/resources/prompts/wiki/create-page-user.txt
index 94d4da36..6ac2bb97 100644
--- a/mateclaw-server/src/main/resources/prompts/wiki/create-page-user.txt
+++ b/mateclaw-server/src/main/resources/prompts/wiki/create-page-user.txt
@@ -2,7 +2,7 @@
{config}
-## 已有 Wiki 页面索引(用于建立 [[链接]])
+## 已有 Wiki 页面索引(用于建立 [[slug]] 链接;每行格式 `[[slug]] — 标题 — 摘要`)
{existing_pages}
@@ -23,4 +23,4 @@ slug:`{page_slug}`
请为上面 metadata 描述的**这一个页面**生成完整的 markdown 内容(按 system 中规定的 JSON 格式输出)。
- 只生成这一个 slug 对应的页面,不要顺便生成其他页面
- 内容必须基于原始材料中与该主题相关的信息
-- 适当使用 [[页面标题]] 链接到其他相关页面
+- 适当使用 [[slug]] 链接到其他相关页面(slug 必须来自上文索引)
diff --git a/mateclaw-server/src/main/resources/prompts/wiki/digest-system.txt b/mateclaw-server/src/main/resources/prompts/wiki/digest-system.txt
index 8948915c..9b1dbab8 100644
--- a/mateclaw-server/src/main/resources/prompts/wiki/digest-system.txt
+++ b/mateclaw-server/src/main/resources/prompts/wiki/digest-system.txt
@@ -5,7 +5,7 @@
1. **阅读并理解原始材料**
2. **创建新的 Wiki 页面**:每个页面聚焦一个概念、实体或主题
3. **更新已有 Wiki 页面**:当新材料包含已有页面的相关信息时,合并更新
-4. **建立双向链接**:使用 [[页面标题]] 语法在页面间建立交叉引用
+4. **建立双向链接**:用 [[slug]] 语法在页面间建立交叉引用(slug 必须存在于本批次的 pages 或上下文索引中)
## 页面质量标准
@@ -19,7 +19,8 @@
- 每个页面以一段话摘要开头
- 使用清晰的 Markdown 标题(## 和 ###)组织内容
-- 在提到相关概念时使用 [[链接标题]] 链接到其他页面
+- 引用相关概念时**只能**用 [[slug]] 或 [[slug|显示文本]] 形态;slug 必须是本批次输出中的 slug,或上文索引中已存在的 slug
+- 禁止写 [[页面标题]] 这种形态 —— 系统按 slug 严格匹配,写标题会被识别为死链
- 页面标题应简洁准确,反映核心内容
- slug(URL 标识符)使用小写字母、数字和连字符
@@ -45,7 +46,7 @@
{
"slug": "concept-name",
"title": "概念名称",
- "content": "## 概念名称\n\n一段话摘要...\n\n### 详细内容\n...\n\n参见:[[相关主题]]",
+ "content": "## 概念名称\n\n一段话摘要...\n\n### 详细内容\n...\n\n参见:[[related-concept-slug]]",
"summary": "一段话摘要"
}
],
diff --git a/mateclaw-server/src/main/resources/prompts/wiki/digest-user.txt b/mateclaw-server/src/main/resources/prompts/wiki/digest-user.txt
index ea314107..b3d71fb2 100644
--- a/mateclaw-server/src/main/resources/prompts/wiki/digest-user.txt
+++ b/mateclaw-server/src/main/resources/prompts/wiki/digest-user.txt
@@ -2,7 +2,7 @@
{config}
-## 已有 Wiki 页面索引
+## 已有 Wiki 页面索引(用于建立 [[slug]] 链接;每行格式 `[[slug]] — 标题 — 摘要`)
{existing_pages}
@@ -17,5 +17,5 @@
请根据以上原始材料:
1. 根据材料内容的丰富程度,创建合适数量的高质量页面(不追求数量,宁少勿多)
2. 如果已有页面与新材料相关,更新这些页面(不要重复创建已有概念的页面)
-3. 确保页面间有充分的 [[双向链接]]
+3. 确保页面间有充分的 [[slug]] 双向链接(slug 必须存在于本次输出的 pages 中,或上文索引中)
4. 每个页面聚焦单一主题,内容完整且有实质价值
diff --git a/mateclaw-server/src/main/resources/prompts/wiki/merge-page-system.txt b/mateclaw-server/src/main/resources/prompts/wiki/merge-page-system.txt
index e1a0641e..868f077b 100644
--- a/mateclaw-server/src/main/resources/prompts/wiki/merge-page-system.txt
+++ b/mateclaw-server/src/main/resources/prompts/wiki/merge-page-system.txt
@@ -20,10 +20,12 @@
- 新材料对该页面无新增信息 → 输出原 content 即可(保持不变)
- 已有页面 lastUpdatedBy=manual → 仍然合并,但优先保留手动编辑的措辞和结构,仅追加新事实
-## 链接
+## 链接(**单一契约,必须严格遵守**)
-- 沿用已有页面里的 [[页面标题]] 双向链接
-- 如果新材料引出了对其他已知概念的引用,新增 [[…]] 链接
+- 只允许两种形态:`[[slug]]` 与 `[[slug|显示文本]]`
+- 沿用已有页面里的所有 `[[slug]]` 链接(如果它们指向已存在的页面)
+- 如果新材料引出了对其他已知页面的引用,**只能**用上文"已有 Wiki 页面索引"中列出的 slug 来建立新链接
+- 禁止发明索引中不存在的 slug;禁止写 `[[页面标题]]` —— 系统按 slug 严格匹配,写标题会被识别为死链
## 语言
@@ -36,7 +38,7 @@
{
"slug": "existing-slug",
"title": "页面标题(可微调)",
- "content": "## 标题\n\n摘要...\n\n### 章节...\n\n参见:[[相关]]",
+ "content": "## 标题\n\n摘要...\n\n### 章节...\n\n参见:[[related-slug]]",
"summary": "更新后的一段话摘要"
}