Skip to content

feat(wiki): AI 内容巡检、单页检查与闭环验证 - #38

Draft
lyingbug wants to merge 6 commits into
agent/unify-wiki-lint-repairfrom
cursor/wiki-ai-lint-closed-loop-7c41
Draft

feat(wiki): AI 内容巡检、单页检查与闭环验证#38
lyingbug wants to merge 6 commits into
agent/unify-wiki-lint-repairfrom
cursor/wiki-ai-lint-closed-loop-7c41

Conversation

@lyingbug

@lyingbug lyingbug commented Aug 3, 2026

Copy link
Copy Markdown
Owner

叠加在 Tencent#2451 之上,补齐它缺的部分:主动的 AI 巡检、单页检查、跨页面缺陷检测,以及从"检出"到"修复已验证"的闭环。base 设为 agent/unify-wiki-lint-repair,所以 diff 只包含本次改动。

问题

Tencent#2451 只实现了确定性规则(断链、孤立页、内容过少、失效引用)。这意味着:

  • 没有主动的 AI 内容检查 —— 语义问题只能靠研究助手在问答时偶然发现;
  • 每次巡检都是全库,无法只检查眼前这一页;
  • 只读单个页面的审查器结构性地发现不了大半缺陷:两个应当合并的页面各自都没问题、总结不完整要读了来源文档才知道、写错了是相对来源文档而言的;
  • 语义问题的验证只检查"版本号变了",等于没有闭环。

做了什么

1. 巡检统一为一个 run,带 mode 与 scope

mode = static / ai / fullscope = 全库 / 指定页面。全库 AI 巡检和单页检查是同一套机制的两种取值,共用进度、持久化、消解逻辑。并发槽位按 (kb, scope_key) 加锁,检查单页不会被"全库正在巡检"挡住。

  • POST /wiki/lint-runs {mode}
  • POST /wiki/page-checks/*slug {mode}(新增)
  • 不带 mode 默认 static,客户端无法意外产生模型开销

2. AI 巡检按"判断单元"组织,而不是逐页扫描

检测器注册表,每个检测器声明它判断的单元,都是两段式:廉价的数据库候选生成 → 每单元一次受限模型调用

检测器 单元 候选生成 产出
page-content 一个页面 有改动的页面,未审查过的优先 混入多主题、页内矛盾、内容过时、缺少依据
source-grounding 页面 + 来源文档 source_refs 且有改动的页面,附带"引用 N/M 段"覆盖度 与来源矛盾(写错了)总结不完整
duplicate-pages 两个页面 标题 trigram 索引(复用构建期去重的同一索引)+ 同来源兄弟页;已互相链接的对子丢弃 应当合并的重复页面

全量两两比较是 O(n²),所以 duplicate-pages 本质上就是它的候选生成器。

3. 成本被限住

  • 每次 run 有调用预算(默认 24 / 硬上限 240 / KB 可配),按权重分给检测器,每个至少保底 1 次,用不完的份额释放给后面的检测器 —— 加检测器只改变看什么,不改变花多少钱
  • wiki_review_ledger(detector, unit) 记 unit_hash,输入没变直接从库里回答;UI 明确显示"N 个单元未变化已跳过",而不是谎称"没发现问题"
  • 单次调用:正文截断、来源节选上限、零温度、限制补全长度、无工具、并发 2、90s 超时
  • lint_model_id 可与修复模型分开,选更便宜的;留空回退

4. 结果可信度

对"某段文字有问题"的判定必须逐字引用页面原文,同时解决三件事:过滤幻觉、给出跨 run 稳定的 fingerprint、让修复可验证。另有类型白名单、置信度阈值、每单元最多 3 条。

5. 闭环验证

每类问题先走确定性、零成本检查,只有它无法定论才花一次复检调用:

类型 确定性检查
引用锚定的内容问题 被引用的片段是否已被改写(常见情况,零成本)
incomplete_summary 正文增长 ≥15% 或引用分块数增加 —— 换个说法会被拒
duplicate_pages 已合并(一页消失/归档)或已互相链接

只有"引用还在但页面别处改了"这种真正含糊的情况(矛盾可从任一侧解决)才复检。复检会先按 fingerprint 定位这个问题自己的单元,定位不到就失败并回退到"页面必须真的推进过" —— 否则复检 (A, C) 干净会被误读成 (A, B) 的问题已消失。

6. 重复页面变得可修复:wiki_merge_pages

手工"改写 A + 删除 B"会丢掉 B 的 aliases / source_refs / chunk_refs下次同批文档入库时重复页面会被重新造出来。新工具做的是转移:幸存页吸收别名(含标题)、来源文档、引用分块,入链改指过来,之后才删除。已接入 Wiki 修订智能体及其系统提示词。

7. 消解按三个维度收窄

一次 run 只看了一小片,"这次没报"不能推广成"问题没了"。按来源(规则 vs AI)、类型(本次实际跑的检测器)、范围(引用锚定 → 该检测器实际读过的页面;对子/页面+来源 → 只能由那个确切单元按 fingerprint 关闭)收窄。

8. 前端重新设计

  • 抽出 wiki/health/ 组件:WikiHealthScanPanelWikiIssueCardWikiPageCheckBarissueMeta.ts(标签词汇不再在抽屉和页面弹层里重复)
  • 抽屉首先呈现要花钱的那个选择:三种巡检模式,各自标注成本与能发现什么;跑完报告实际花销和覆盖的检测器
  • 状态改为 tab(triage 时最常切的轴),类型/来源收进筛选开关
  • 卡片给出判断所需的信息:引用片段、重复对的两个页面、来源文档及引用覆盖度
  • 页面阅读区新增"检查本页"

Validation

  • go build ./...go vet ./internal/...go test ./internal/...
  • npm run type-checknpm test(282 passed)、npm run build
  • sqlite3 :memory: < migrations/sqlite/000000_init.up.sql
  • 新增测试覆盖:过滤器(未知类型 / 低置信度 / 编造引用 / 重复片段)、fingerprint 身份(引用锚定与单元标识两种)、预算分配保底、检测器与问题类型一一对应、候选查询的排序与 ledger 排除与页面类型/来源过滤、ledger 按 (detector, unit) 键控、消解的来源与类型与页面范围、per-scope 并发槽位、合并的 provenance 转移与不可逆操作守卫、incomplete_summary 覆盖度增长后置条件、复检单元定位

文档

docs/wiki/核心功能/Wiki构建与质量巡检.md:构建链路各阶段、每个检测器能看到什么和看不到什么、成本在哪里被限住、修复凭什么算验证通过,以及新增一类问题的步骤。

未做 / 已知取舍

  • 页面之间没有向量相似度(仓库里 Wiki 页面还没有 embedding 索引),duplicate-pages 依赖标题 trigram + 同来源两个信号。trigram 是 PostgreSQL 专属,取不到时该信号静默降级而不是让检测器失败。
  • source-grounding 只对照来源文档的前若干段,并明确告知模型"节选之外的缺失不算矛盾",避免因节选不全而误报。
  • 目录/簇级别的问题(一组页面应合并为一页、目录归类错误)尚未覆盖 —— 按同一套注册表扩展即可,文档里写了步骤。
  • 未做端到端的真实模型验证(环境没有可用的 LLM 凭据),提示词的实际召回/误报率需要在真实知识库上调一轮。
Open in Web Open in Cursor 

cursoragent and others added 6 commits August 3, 2026 12:56
The lint lifecycle only had deterministic rules, so nothing detected defects in
what a page actually says, and every scan was whole-wiki.

Runs now declare a mode (static rules, AI review, or both) and a scope (the
whole wiki, or named pages), which makes "check this page" a first-class
operation on the same durable run machinery as a full scan.

The AI review is deliberately the cheapest thing that can still find a semantic
defect: one small tool-free call per page, over a truncated body, only on pages
whose content changed since their last review, under a per-run page budget that
is also the call budget. Findings must quote the page verbatim, which both
filters hallucinations and gives each finding a stable fingerprint.

That quote is what closes the repair loop. Verifying an AI finding first checks
whether the quoted span survived the edit — free, deterministic, and true in the
common case. Only an edit that changed the page somewhere else spends a single
recheck call, and reconciliation is now scoped per source and per page so
neither detector family can retire the other's findings.

Co-authored-by: lyingbug <lyingbug@users.noreply.github.com>
A per-page reviewer is structurally blind to most of what is wrong with a
generated wiki. Nothing about either of two duplicate pages is wrong on its own;
a summary that omits half its source looks fine until you read the source; a
wrong fact is only wrong relative to the document it came from.

So the review is now a registry of detectors, each declaring the unit it judges,
and each with the same two-stage shape: cheap database-only candidate generation
that proposes units worth looking at, then at most one bounded model call per
unit. Three detectors ship: page-content (mixed subjects, contradictions,
outdated statements, unsupported claims), source-grounding (facts the source
contradicts, subjects the source covers and the page omits), and duplicate-pages
(pairs that should be merged, found via the title trigram index and shared source
documents rather than by comparing every page with every other).

A run's call budget is shared across detectors by weight, with a guaranteed call
each and unused share released to the detectors after it — so adding a detector
changes what a run looks at, not what it costs. The ledger is keyed by (detector,
unit) so an unchanged unit is answered from the database.

Reconciliation follows the same distinction: a finding anchored to a quoted span
may be closed by absence over the pages its detector read, while a finding about
a pair or a page-and-source may only be closed by the exact unit that owns it.
Verification is deterministic first — rewritten quote, grown coverage, merged or
linked pair — and only spends a recheck call when that cannot settle it.

Duplicate findings were also unrepairable, so wiki_merge_pages gives the fixer
agent a real merge: the survivor absorbs the other page's aliases, source
documents and citations, which is what stops the next ingest from recreating the
duplicate.

Co-authored-by: lyingbug <lyingbug@users.noreply.github.com>
The problem centre had one "run rule scan" button, three chip rows competing for
the top of the drawer, and a monolithic card list inlined in an 8500-line
component. It also had no way to express what the backend can now do.

The drawer now leads with the choice that costs money: three labelled scan modes
with their cost and what each can find, so starting a scan is never an accidental
model-spend decision, and after a run it reports what was actually spent and which
detectors covered it. Status became a tab strip, since that is the axis a user
changes while triaging, and type/source moved behind a filter toggle.

The issue card, the scan panel and the page-check control are now components under
wiki/health, with the label vocabulary in one module instead of duplicated between
the drawer and the page popup. Cards show what a reader needs to judge a finding:
the quoted span for a claim about text, both pages for a duplicate, and the source
document with its citation coverage for a grounding finding.

Each page also gets its own check control in the reader, so re-examining the page
in front of you no longer means scanning the whole wiki.

Co-authored-by: lyingbug <lyingbug@users.noreply.github.com>
…ecture

Explains what each review detector can and cannot see, why the unit of judgement
decides that, where the cost of a run is bounded, and what makes a repair
verifiable — plus the steps to add a new class of defect.

Co-authored-by: lyingbug <lyingbug@users.noreply.github.com>
A recheck asked the detector for one candidate and reviewed whatever came back.
For a finding identified by its unit that is the wrong question: re-checking the
pair (A, C) and finding it clean says nothing about a finding on (A, B), and the
fingerprint comparison would then read as "no longer present" and resolve the
issue on evidence that never concerned it.

The recheck now locates the issue's own unit by fingerprint and fails when it
cannot, which falls back to requiring real page progress. Quote-anchored findings
are unaffected: for those the page is the unit.

Also covers the candidate query and the ledger with repository tests, since the
ordering, the per-detector keying, and the page-type and source-document filters
are what every detector's cost profile depends on.

Co-authored-by: lyingbug <lyingbug@users.noreply.github.com>
An AI-only run never enters the static phase, so its bar sat at the static
phase's opening value until the first model call returned — and the frontend,
which inferred the phase from the percentage alone, labelled that wait "rule
check". On a large wiki the wait is real: planning walks candidates and probes
the title index once per seed.

The phase boundary is now one named constant on each side, an AI-only run
publishes it as soon as planning finishes, and the label is derived from the run's
mode rather than guessed from its percentage.

Co-authored-by: lyingbug <lyingbug@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants