trustwiki 0.1.0-alpha.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +170 -50
- package/README.zh.md +158 -45
- package/cli/bin.js +8 -2
- package/cli/citations.js +3 -3
- package/cli/config.js +4 -0
- package/cli/engine.js +27 -10
- package/cli/links.js +1 -1
- package/cli/report.js +3 -1
- package/cli/resolve.js +15 -8
- package/cli/rules/frontmatter-fields.js +11 -2
- package/cli/rules/page-orphan.js +5 -0
- package/cli/rules/placeholder-present.js +21 -6
- package/cli/rules/provenance-contradicted.js +1 -1
- package/cli/rules/provenance-excess-inferred.js +4 -1
- package/package.json +5 -2
- package/templates/demo-vault/notes/conflict-a.md +1 -1
- package/templates/eval-round3/a-vault/notes/agent-memory-options.md +11 -0
- package/templates/eval-round3/a-vault/notes/mcp-explained.md +12 -0
- package/templates/eval-round3/a-vault/notes/memory-mcp-combo.md +12 -0
- package/templates/eval-round3/b-vault/.trustwiki.json +5 -0
- package/templates/eval-round3/b-vault/index.md +7 -0
- package/templates/eval-round3/b-vault/notes/agent-memory-options.md +15 -0
- package/templates/eval-round3/b-vault/notes/mcp-explained.md +15 -0
- package/templates/eval-round3/b-vault/notes/memory-mcp-combo.md +13 -0
- package/templates/eval-round3/b-vault/sources/agent-memory-landscape.md +12 -0
- package/templates/eval-round3/b-vault/sources/mcp-overview.md +12 -0
- package/templates/eval-round3/task.txt +8 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 QianJinGuo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,86 +1,206 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="assets/hero.svg" alt="trustwiki — knowledge bases your agent can maintain, without lying to you" width="100%">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<a href="https://github.com/QianJinGuo/trustwiki/actions/workflows/ci.yml"><img src="https://github.com/QianJinGuo/trustwiki/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
7
|
+
<a href="https://www.npmjs.com/package/trustwiki"><img src="https://img.shields.io/npm/v/trustwiki" alt="npm"></a>
|
|
8
|
+
<img src="https://img.shields.io/node/v/trustwiki" alt="node >= 18">
|
|
9
|
+
<img src="https://img.shields.io/badge/license-MIT-green" alt="MIT license">
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
English · [简体中文](README.zh.md)
|
|
13
|
+
|
|
1
14
|
# trustwiki
|
|
2
15
|
|
|
3
|
-
**
|
|
16
|
+
**Your agent writes your notes. Who checks them?**
|
|
4
17
|
|
|
5
|
-
Every
|
|
18
|
+
Every knowledge base maintained by an AI agent eventually rots the same way:
|
|
19
|
+
claims nobody can trace, contradictions quietly rewritten away, links that
|
|
20
|
+
die. trustwiki is the checker — it reads your markdown vault and tells you
|
|
21
|
+
exactly where the rot is.
|
|
6
22
|
|
|
7
23
|
```bash
|
|
8
|
-
npx trustwiki lint ./your-
|
|
24
|
+
npx trustwiki lint ./your-notes
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
```text
|
|
28
|
+
notes/model-comparison.md
|
|
29
|
+
L12 error citation.target-missing citation target not found: sources/ghost.md
|
|
30
|
+
L14 warn provenance.excess-inferred 3/5 prose paragraphs uncited (>0.3)
|
|
31
|
+
L16 warn placeholder.present placeholder text: TODO
|
|
32
|
+
L18 error link.broken broken wikilink [[notes/dead-ref]]
|
|
33
|
+
|
|
34
|
+
Σ 3 errors, 5 warnings across 3 files
|
|
9
35
|
```
|
|
10
36
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
37
|
+
That is a real report on a vault seeded to fail. [See it run (10s GIF)](#2-see-it-run) · [What each finding means](#what-each-check-catches)
|
|
38
|
+
|
|
39
|
+
## Why it exists
|
|
40
|
+
|
|
41
|
+
AI agents already write knowledge bases — research wikis, team docs, personal
|
|
42
|
+
notes. Nobody proofreads them, because the volume is inhuman. The failure mode
|
|
43
|
+
is **fluent slop**: confident prose, zero provenance, contradictions resolved
|
|
44
|
+
by whoever edits last.
|
|
45
|
+
|
|
46
|
+
trustwiki is the discipline layer. It does **not** build your wiki — it makes
|
|
47
|
+
sure that when your agent does, every claim traces to a source, disagreements
|
|
48
|
+
stay visible, and decay is measured instead of discovered two months later.
|
|
49
|
+
|
|
50
|
+
It is three things:
|
|
16
51
|
|
|
17
|
-
|
|
18
|
-
-
|
|
19
|
-
|
|
20
|
-
- **Proven in production** — [operating stats](proof/STATS.md) from an agent-maintained wiki running since 2026-05
|
|
52
|
+
<p align="center">
|
|
53
|
+
<img src="assets/three-layers.svg" alt="Three layers: the checker (npx trustwiki lint), the spec (frozen citation grammar), the method (SKILL.md)" width="100%">
|
|
54
|
+
</p>
|
|
21
55
|
|
|
22
|
-
|
|
56
|
+
- **A checker** — 12 mechanical checks: citations, contradictions, broken links, index drift, orphans
|
|
57
|
+
- **A spec** — a frozen, versioned definition of "traceable": [schema/spec.md](schema/spec.md)
|
|
58
|
+
- **A method** — the four-phase discipline your agent installs: [SKILL.md](SKILL.md)
|
|
23
59
|
|
|
24
|
-
|
|
25
|
-
produce fluent slop: unattributed claims, silent contradiction resolution,
|
|
26
|
-
links that rot. trustwiki is the discipline layer — the linter, the schema,
|
|
27
|
-
and the method.
|
|
60
|
+
## Getting started
|
|
28
61
|
|
|
29
|
-
|
|
62
|
+
### 1. Try it on the demo (30 seconds, nothing to install)
|
|
30
63
|
|
|
31
64
|
```bash
|
|
32
65
|
git clone https://github.com/QianJinGuo/trustwiki && cd trustwiki
|
|
33
|
-
npx trustwiki lint templates/demo-vault
|
|
66
|
+
npx trustwiki lint templates/demo-vault # a vault seeded to fail: 8 findings, exit 1
|
|
67
|
+
npx trustwiki lint templates/starter-vault # the same structure built to pass: exit 0
|
|
34
68
|
```
|
|
35
69
|
|
|
36
|
-
|
|
70
|
+
### 2. See it run
|
|
37
71
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
72
|
+

|
|
73
|
+
|
|
74
|
+
### 3. Run it on your own vault
|
|
75
|
+
|
|
76
|
+
Already keep notes in markdown (Obsidian, Logseq, plain files)? Just point at
|
|
77
|
+
the directory. Zero config works:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
npx trustwiki lint ~/Documents/my-notes
|
|
45
81
|
```
|
|
46
82
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
the starting point for your own vault.
|
|
83
|
+
Most users then add one small `.trustwiki.json` at the vault root so the
|
|
84
|
+
checker understands your layout:
|
|
50
85
|
|
|
51
|
-
|
|
86
|
+
```json
|
|
87
|
+
{
|
|
88
|
+
"roots": ["notes", "sources"],
|
|
89
|
+
"index": "index.md",
|
|
90
|
+
"sourceDir": "sources"
|
|
91
|
+
}
|
|
92
|
+
```
|
|
52
93
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
94
|
+
- `roots` — which directories to scan
|
|
95
|
+
- `index` — your index file, if you keep one (enables index-drift checks)
|
|
96
|
+
- `sourceDir` — where raw captured sources live (enables citation-target
|
|
97
|
+
checks, and exempts those pages from "author's voice" rules — they quote,
|
|
98
|
+
they don't cite)
|
|
57
99
|
|
|
58
|
-
|
|
100
|
+
Full reference: [schema/spec.md](schema/spec.md).
|
|
59
101
|
|
|
60
|
-
Four phases — **Ingest, Synthesize, Evolve, Gate** — as an installable agent
|
|
61
|
-
skill: [SKILL.md](SKILL.md). Why each rule exists:
|
|
62
|
-
[docs/method.md](docs/method.md) (中文版: [method.zh.md](docs/method.zh.md)).
|
|
63
102
|
|
|
64
|
-
|
|
103
|
+
### Already have an Obsidian / Logseq vault?
|
|
65
104
|
|
|
66
|
-
|
|
105
|
+
It works on your vault today, as-is — broken `[[wikilinks]]`, real TODOs, and
|
|
106
|
+
dangling index entries are findable without any setup. If the provenance
|
|
107
|
+
culture is not (yet) your vault's culture, switch off those rules and keep the
|
|
108
|
+
checks that are language-agnostic:
|
|
67
109
|
|
|
68
110
|
```json
|
|
69
111
|
{
|
|
70
|
-
"
|
|
71
|
-
|
|
72
|
-
|
|
112
|
+
"rules": {
|
|
113
|
+
"provenance.excess-inferred": "off",
|
|
114
|
+
"provenance.low-confidence": "off",
|
|
115
|
+
"citation.target-missing": "off",
|
|
116
|
+
"frontmatter.required": "warn",
|
|
117
|
+
"frontmatter.fields": "off"
|
|
118
|
+
}
|
|
73
119
|
}
|
|
74
120
|
```
|
|
75
121
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
122
|
+
That is the honest floor: **broken-link and placeholder detection help any
|
|
123
|
+
markdown vault**; the citation layer becomes valuable when you start capturing
|
|
124
|
+
sources (see [SKILL.md](SKILL.md) Phase: Ingest).
|
|
125
|
+
|
|
126
|
+
### 4. Make your agent maintain the vault under the same rules
|
|
127
|
+
|
|
128
|
+
Install the method as an agent skill: copy
|
|
129
|
+
[SKILL.md](SKILL.md) into your agent's skills directory (Claude Code, Codex,
|
|
130
|
+
and most CLIs support project or global skills).
|
|
131
|
+
|
|
132
|
+
<p align="center">
|
|
133
|
+
<img src="assets/four-phases.svg" alt="The four-phase loop: Ingest → Synthesize → Evolve → Gate; findings loop back" width="100%">
|
|
134
|
+
</p>
|
|
135
|
+
|
|
136
|
+
It teaches your agent the four phases with the violation consequences that
|
|
137
|
+
motivated each rule. The short version of the whole method is one line:
|
|
138
|
+
**never write a claim your vault cannot trace.**
|
|
139
|
+
|
|
140
|
+
### 5. Gate your CI (optional)
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
npx trustwiki lint ./your-notes # exit 0 = clean, 1 = errors present
|
|
144
|
+
npx trustwiki lint ./your-notes --json # machine-readable for CI bots
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## What each check catches
|
|
148
|
+
|
|
149
|
+
| check | catches | example |
|
|
150
|
+
|---|---|---|
|
|
151
|
+
| `citation.malformed` | citations that don't parse — decoration, not provenance | `^[maybe a source?]` |
|
|
152
|
+
| `citation.target-missing` | cited files that don't exist | `^[sources/ghost.md]` |
|
|
153
|
+
| `provenance.excess-inferred` | pages that are mostly uncited prose | 3/5 paragraphs, no `^[…]` |
|
|
154
|
+
| `provenance.low-confidence` | pages admitting they're shaky | `confidence: 0.3` |
|
|
155
|
+
| `provenance.contradicted` | conflicts marked in body but not frontmatter (or vice versa) | callout without `contradicted_by` |
|
|
156
|
+
| `link.broken` | `[[wikilinks]]` that resolve to nothing | `[[notes/dead-ref]]` |
|
|
157
|
+
| `link.index-missing` | pages missing from the index; index entries that dangle | either direction |
|
|
158
|
+
| `page.orphan` | pages with almost no outbound links — islands rot first | 0 links |
|
|
159
|
+
| `frontmatter.required` / `.fields` | missing metadata; sources missing `sha256` (how silent edits get caught) | no `created` date |
|
|
160
|
+
| `placeholder.present` | TODO/FIXME posing as finished content | `TODO: finish` |
|
|
161
|
+
|
|
162
|
+
Severity of each is configurable (`error | warn | off`), warnings never fail
|
|
163
|
+
the build, and the full rule reference lives in
|
|
164
|
+
[schema/spec.md](schema/spec.md).
|
|
165
|
+
|
|
166
|
+
## Proven in production
|
|
167
|
+
|
|
168
|
+
<p align="center">
|
|
169
|
+
<img src="assets/precision-journey.svg" alt="Precision 49% to 93% after fixes; 8 foreign corpora, zero fabricated findings" width="100%">
|
|
170
|
+
</p>
|
|
171
|
+
|
|
172
|
+
This tool is the extracted discipline of an agent-maintained wiki that has
|
|
173
|
+
run since 2026-05: **8,658 pages, 4,162 raw sources, 0 lint errors** — every
|
|
174
|
+
number with source and verification date in [proof/STATS.md](proof/STATS.md).
|
|
175
|
+
|
|
176
|
+
And the method itself was A/B tested on an identical task — one agent with
|
|
177
|
+
SKILL.md, one without:
|
|
178
|
+
|
|
179
|
+
<p align="center">
|
|
180
|
+
<img src="assets/ab-experiment.svg" alt="Without the method: 3 lint errors, no links, no sources. With it: 0 errors, 11 links, cited and tamper-evident" width="100%">
|
|
181
|
+
</p>
|
|
182
|
+
|
|
183
|
+
Re-run the experiment yourself in 30 seconds:
|
|
184
|
+
[templates/eval-round3/](templates/eval-round3/) · full reports in [docs/eval/](docs/eval/).
|
|
185
|
+
|
|
186
|
+
## FAQ
|
|
187
|
+
|
|
188
|
+
**Is this for Obsidian/Logseq users or for agent builders?**
|
|
189
|
+
Both. If you keep markdown notes, `npx trustwiki lint` finds rot today. If
|
|
190
|
+
you build agents, [SKILL.md](SKILL.md) makes your agent write vaults that
|
|
191
|
+
pass the same check.
|
|
192
|
+
|
|
193
|
+
**Does it modify my files?**
|
|
194
|
+
No. It reads and reports only. There is no `--fix` on purpose: fixes that
|
|
195
|
+
matter require knowing which source supports the claim, and no tool can
|
|
196
|
+
guess that for you.
|
|
79
197
|
|
|
80
|
-
|
|
198
|
+
**Does it send my notes anywhere?**
|
|
199
|
+
No. Local files in, stdout out. Zero dependencies, zero telemetry.
|
|
81
200
|
|
|
82
|
-
|
|
83
|
-
|
|
201
|
+
**My vault isn't agent-written — is this still useful?**
|
|
202
|
+
Yes, if it has citations to check or links that can rot. The contradiction
|
|
203
|
+
and broken-link checks help any markdown knowledge base.
|
|
84
204
|
|
|
85
205
|
## License
|
|
86
206
|
|
package/README.zh.md
CHANGED
|
@@ -1,81 +1,194 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="assets/hero.svg" alt="trustwiki — 你的 agent 可以维护的知识库,前提是它不对你撒谎" width="100%">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<a href="https://github.com/QianJinGuo/trustwiki/actions/workflows/ci.yml"><img src="https://github.com/QianJinGuo/trustwiki/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
7
|
+
<a href="https://www.npmjs.com/package/trustwiki"><img src="https://img.shields.io/npm/v/trustwiki" alt="npm"></a>
|
|
8
|
+
<img src="https://img.shields.io/node/v/trustwiki" alt="node >= 18">
|
|
9
|
+
<img src="https://img.shields.io/badge/license-MIT-green" alt="MIT license">
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
[English](README.md) · 简体中文
|
|
13
|
+
|
|
1
14
|
# trustwiki
|
|
2
15
|
|
|
3
|
-
**你的 agent
|
|
16
|
+
**你的 agent 在替你写笔记。谁在检查它们?**
|
|
4
17
|
|
|
5
|
-
|
|
18
|
+
所有由 AI agent 维护的知识库,最终都以同一种方式腐烂:没人能溯源的论断、
|
|
19
|
+
被悄悄改写抹平的矛盾、死掉的链接。trustwiki 是检查器——它读你的 markdown
|
|
20
|
+
知识库,精确告诉你烂在哪里。
|
|
6
21
|
|
|
7
22
|
```bash
|
|
8
|
-
npx trustwiki lint ./your-
|
|
23
|
+
npx trustwiki lint ./your-notes
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
notes/model-comparison.md
|
|
28
|
+
L12 error citation.target-missing citation target not found: sources/ghost.md
|
|
29
|
+
L14 warn provenance.excess-inferred 3/5 prose paragraphs uncited (>0.3)
|
|
30
|
+
L16 warn placeholder.present placeholder text: TODO
|
|
31
|
+
L18 error link.broken broken wikilink [[notes/dead-ref]]
|
|
32
|
+
|
|
33
|
+
Σ 3 errors, 5 warnings across 3 files
|
|
9
34
|
```
|
|
10
35
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
36
|
+
这是一座"造出来就是为了失败"的知识库上的真实报告。[看它运行(10 秒 GIF)](#2-看它运行) · [每条 finding 是什么意思](#每条检查抓什么)
|
|
37
|
+
|
|
38
|
+
## 为什么有它
|
|
39
|
+
|
|
40
|
+
AI agent 已经在替人写知识库——研究 wiki、团队文档、个人笔记。没人校对,
|
|
41
|
+
因为工作量不是人能承受的。失败模式叫**流畅的 slop**:自信的散文、零溯源、
|
|
42
|
+
矛盾被最后编辑的人悄悄裁决。
|
|
43
|
+
|
|
44
|
+
trustwiki 是纪律层。它**不**替你长出 wiki——它保证的是:当你的 agent
|
|
45
|
+
动笔时,每个论断能追溯到来源,分歧保持可见,腐烂被仪器测出来而不是
|
|
46
|
+
两个月后被人撞见。
|
|
47
|
+
|
|
48
|
+
它是三样东西:
|
|
15
49
|
|
|
16
|
-
|
|
17
|
-
-
|
|
18
|
-
|
|
19
|
-
- **生产验证**——运行自 2026-05 的 agent 维护知识库的[活体统计](proof/STATS.md)
|
|
50
|
+
<p align="center">
|
|
51
|
+
<img src="assets/three-layers.svg" alt="三层:检查器(npx trustwiki lint)、规范(冻结引用文法)、方法(SKILL.md)" width="100%">
|
|
52
|
+
</p>
|
|
20
53
|
|
|
21
|
-
|
|
54
|
+
- **检查器**——12 条机械检查:引用、矛盾、断链、索引漂移、孤页
|
|
55
|
+
- **规范**——"可溯源"的冻结版本化定义:[schema/spec.zh.md](schema/spec.zh.md)
|
|
56
|
+
- **方法**——你的 agent 可安装的四阶段纪律:[SKILL.md](SKILL.md)
|
|
22
57
|
|
|
23
|
-
|
|
24
|
-
无出处的论断、被静默抹平的矛盾、烂掉的链接。trustwiki 是纪律层——
|
|
25
|
-
linter、schema、和方法。
|
|
58
|
+
## 上手
|
|
26
59
|
|
|
27
|
-
|
|
60
|
+
### 1. 在 demo 上试(30 秒,无需安装任何东西)
|
|
28
61
|
|
|
29
62
|
```bash
|
|
30
63
|
git clone https://github.com/QianJinGuo/trustwiki && cd trustwiki
|
|
31
|
-
npx trustwiki lint templates/demo-vault
|
|
64
|
+
npx trustwiki lint templates/demo-vault # 造出来为了失败的库:8 条 findings,退出码 1
|
|
65
|
+
npx trustwiki lint templates/starter-vault # 同样结构、造出来为了通过的库:退出码 0
|
|
32
66
|
```
|
|
33
67
|
|
|
34
|
-
|
|
68
|
+
### 2. 看它运行
|
|
35
69
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
70
|
+

|
|
71
|
+
|
|
72
|
+
### 3. 用在你自己的知识库上
|
|
73
|
+
|
|
74
|
+
已经在用 markdown 记笔记(Obsidian、Logseq、纯文件)?直接指向目录,
|
|
75
|
+
零配置可用:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
npx trustwiki lint ~/Documents/my-notes
|
|
43
79
|
```
|
|
44
80
|
|
|
45
|
-
|
|
46
|
-
失败"的库。`templates/starter-vault` 是同样结构、造出来就是为了通过的库——
|
|
47
|
-
也是你自己建库的起点。
|
|
81
|
+
然后多数人会加一个小小的 `.trustwiki.json`,让检查器理解你的目录布局:
|
|
48
82
|
|
|
49
|
-
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"roots": ["notes", "sources"],
|
|
86
|
+
"index": "index.md",
|
|
87
|
+
"sourceDir": "sources"
|
|
88
|
+
}
|
|
89
|
+
```
|
|
50
90
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
91
|
+
- `roots`——扫描哪些目录
|
|
92
|
+
- `index`——你的索引文件(可选,声明后启用索引漂移检查)
|
|
93
|
+
- `sourceDir`——原始抓取来源所在目录(启用引用目标检查;这些页豁免
|
|
94
|
+
"作者声音"类规则——它们引用来源,不需要引用自己)
|
|
54
95
|
|
|
55
|
-
|
|
96
|
+
完整参考:[schema/spec.zh.md](schema/spec.zh.md)。
|
|
56
97
|
|
|
57
|
-
四个阶段——**Ingest、Synthesize、Evolve、Gate**——封装为可安装的
|
|
58
|
-
agent skill:[SKILL.md](SKILL.md)。每条规则为什么存在:
|
|
59
|
-
[docs/method.zh.md](docs/method.zh.md)(English: [method.md](docs/method.md))。
|
|
60
98
|
|
|
61
|
-
|
|
99
|
+
### 已经有一个 Obsidian / Logseq 库?
|
|
62
100
|
|
|
63
|
-
|
|
101
|
+
今天就能直接用——断掉的 `[[wikilink]]`、真实的 TODO、悬空的索引项,零配置
|
|
102
|
+
即可找出。如果溯源文化还不是你的库的文化,把那部分规则先关掉,保留与语言
|
|
103
|
+
无关的检查:
|
|
64
104
|
|
|
65
105
|
```json
|
|
66
106
|
{
|
|
67
|
-
"
|
|
68
|
-
|
|
69
|
-
|
|
107
|
+
"rules": {
|
|
108
|
+
"provenance.excess-inferred": "off",
|
|
109
|
+
"provenance.low-confidence": "off",
|
|
110
|
+
"citation.target-missing": "off",
|
|
111
|
+
"frontmatter.required": "warn",
|
|
112
|
+
"frontmatter.fields": "off"
|
|
113
|
+
}
|
|
70
114
|
}
|
|
71
115
|
```
|
|
72
116
|
|
|
73
|
-
|
|
74
|
-
|
|
117
|
+
这是诚实的下限:**断链与占位符检查对任何 markdown 库都有用**;引用层在
|
|
118
|
+
你开始捕获来源后才会显现价值(见 [SKILL.md](SKILL.md) Phase: Ingest)。
|
|
119
|
+
|
|
120
|
+
### 4. 让你的 agent 按同一套规则维护
|
|
121
|
+
|
|
122
|
+
把方法装成 agent skill:复制 [SKILL.md](SKILL.md) 到你 agent 的 skills
|
|
123
|
+
目录(Claude Code、Codex 和多数 CLI 都支持项目级或全局 skill)。
|
|
124
|
+
|
|
125
|
+
<p align="center">
|
|
126
|
+
<img src="assets/four-phases.svg" alt="四阶段循环:Ingest → Synthesize → Evolve → Gate;findings 回流" width="100%">
|
|
127
|
+
</p>
|
|
128
|
+
|
|
129
|
+
它教你的 agent 四个阶段,以及每条规则背后的违规后果。整个方法的一句话
|
|
130
|
+
版本:**永远不要写下你的知识库无法溯源的论断。**
|
|
131
|
+
|
|
132
|
+
### 5. 进 CI 门禁(可选)
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
npx trustwiki lint ./your-notes # 退出码 0 = 干净,1 = 有 error
|
|
136
|
+
npx trustwiki lint ./your-notes --json # 机读输出,给 CI bot
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## 每条检查抓什么
|
|
140
|
+
|
|
141
|
+
| 检查 | 抓什么 | 例子 |
|
|
142
|
+
|---|---|---|
|
|
143
|
+
| `citation.malformed` | 解析不了的引用——是装饰不是溯源 | `^[maybe a source?]` |
|
|
144
|
+
| `citation.target-missing` | 被引用的文件不存在 | `^[sources/ghost.md]` |
|
|
145
|
+
| `provenance.excess-inferred` | 大部分段落无引用的页面 | 3/5 段落没有 `^[…]` |
|
|
146
|
+
| `provenance.low-confidence` | 自己承认心虚的页面 | `confidence: 0.3` |
|
|
147
|
+
| `provenance.contradicted` | 冲突只在正文标记、或只在 frontmatter 标记 | 有 callout 无 `contradicted_by` |
|
|
148
|
+
| `link.broken` | 解析不到任何东西的 `[[wikilink]]` | `[[notes/dead-ref]]` |
|
|
149
|
+
| `link.index-missing` | 页面不在索引里;或索引项悬空 | 双向都查 |
|
|
150
|
+
| `page.orphan` | 几乎没有出链的页面——孤岛先腐烂 | 0 条出链 |
|
|
151
|
+
| `frontmatter.required` / `.fields` | 缺元数据;来源页缺 `sha256`(静默篡改的发现手段) | 没有 `created` |
|
|
152
|
+
| `placeholder.present` | 冒充完成品的 TODO/FIXME | `TODO: finish` |
|
|
153
|
+
|
|
154
|
+
每条的严重度可配置(`error | warn | off`),warning 永远不导致失败,
|
|
155
|
+
完整规则参考在 [schema/spec.zh.md](schema/spec.zh.md)。
|
|
156
|
+
|
|
157
|
+
## 生产验证
|
|
158
|
+
|
|
159
|
+
<p align="center">
|
|
160
|
+
<img src="assets/precision-journey.svg" alt="精确率 49%→93%;8 个外部语料零捏造" width="100%">
|
|
161
|
+
</p>
|
|
162
|
+
|
|
163
|
+
本工具是一套从真实运行中的 agent 维护知识库抽出的纪律——该库自 2026-05
|
|
164
|
+
运行至今:**8,658 页、4,162 个原始来源、0 lint errors**——每个数字带来源
|
|
165
|
+
和核实日期,见 [proof/STATS.md](proof/STATS.md)。
|
|
166
|
+
|
|
167
|
+
方法本身也做了同任务 A/B 对照——一个 agent 带 SKILL.md,一个不带:
|
|
168
|
+
|
|
169
|
+
<p align="center">
|
|
170
|
+
<img src="assets/ab-experiment.svg" alt="无方法:3 个 lint error、无链接、无来源。有方法:0 error、11 链接、可溯源防篡改" width="100%">
|
|
171
|
+
</p>
|
|
172
|
+
|
|
173
|
+
30 秒复跑这个实验:[templates/eval-round3/](templates/eval-round3/) · 完整报告在 [docs/eval/](docs/eval/)。
|
|
174
|
+
|
|
175
|
+
## FAQ
|
|
176
|
+
|
|
177
|
+
**是给 Obsidian/Logseq 用户还是给 agent 开发者?**
|
|
178
|
+
都是。只要你用 markdown 记笔记,`npx trustwiki lint` 今天就能找出腐烂。
|
|
179
|
+
如果你开发 agent,[SKILL.md](SKILL.md) 让你的 agent 写出能通过同一检查
|
|
180
|
+
的知识库。
|
|
181
|
+
|
|
182
|
+
**它会修改我的文件吗?**
|
|
183
|
+
不会。只读、只报告。故意不做 `--fix`:真正要紧的修复需要知道哪个来源
|
|
184
|
+
支撑哪个论断,没有工具能替你猜。
|
|
75
185
|
|
|
76
|
-
|
|
186
|
+
**它会把我的笔记发到任何地方吗?**
|
|
187
|
+
不会。本地文件进,stdout 出。零依赖,零遥测。
|
|
77
188
|
|
|
78
|
-
|
|
189
|
+
**我的库不是 agent 写的——还有用吗?**
|
|
190
|
+
有用,只要它有可检查的引用或会烂的链接。矛盾和断链检查对任何 markdown
|
|
191
|
+
知识库都有效。
|
|
79
192
|
|
|
80
193
|
## 许可
|
|
81
194
|
|
package/cli/bin.js
CHANGED
|
@@ -16,14 +16,20 @@ let json = false, configFlag, bad;
|
|
|
16
16
|
for (let i = 2; i < args.length; i++) {
|
|
17
17
|
if (args[i] === '--json') json = true;
|
|
18
18
|
else if (args[i] === '--config') {
|
|
19
|
-
if (i + 1 >= args.length) { bad = '--config requires a file argument'; break; }
|
|
19
|
+
if (i + 1 >= args.length || args[i + 1].startsWith('--')) { bad = '--config requires a file argument'; break; }
|
|
20
20
|
configFlag = args[++i];
|
|
21
21
|
} else { bad = `unknown argument: ${args[i]}`; break; }
|
|
22
22
|
}
|
|
23
23
|
if (bad) { console.error(`trustwiki: ${bad}`); usage(); process.exit(2); }
|
|
24
24
|
const { config, error } = await loadConfig(vault, configFlag);
|
|
25
25
|
if (error) { console.error(`trustwiki: ${error.message}`); process.exit(2); }
|
|
26
|
-
|
|
26
|
+
let findings;
|
|
27
|
+
try {
|
|
28
|
+
findings = await lintVault(config.vaultPath, config);
|
|
29
|
+
} catch (e) {
|
|
30
|
+
console.error(`trustwiki: failed to lint ${config.vaultPath}: ${e.message}`);
|
|
31
|
+
process.exit(2);
|
|
32
|
+
}
|
|
27
33
|
console.log(json ? formatJson(findings) : formatText(findings));
|
|
28
34
|
// exitCode (not process.exit) so large stdout writes flush before teardown
|
|
29
35
|
process.exitCode = findings.some(f => f.severity === 'error') ? 1 : 0;
|
package/cli/citations.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const CITATION_RE = /\^\[([^\]\n]*)\]/g;
|
|
2
|
-
const RANGE_ANCHOR = /^(
|
|
3
|
-
const RANGE_COLON = /^(
|
|
2
|
+
const RANGE_ANCHOR = /^([^:\s]+)#L(\d+)-L(\d+)$/;
|
|
3
|
+
const RANGE_COLON = /^([^:\s]+):(\d+)-(\d+)$/;
|
|
4
4
|
|
|
5
5
|
export function parseCitation(raw) {
|
|
6
6
|
const trimmed = raw.trim();
|
|
@@ -24,7 +24,7 @@ function parseSource(part) {
|
|
|
24
24
|
|
|
25
25
|
function finish(path, start, end, part) {
|
|
26
26
|
path = path.trim();
|
|
27
|
-
if (!path ||
|
|
27
|
+
if (!path || /[\s:]/.test(path)) return { ok: false, reason: `bad source syntax: "${part}" (path cannot contain spaces or colons)` };
|
|
28
28
|
if (start !== null && (start < 1 || end < 1)) return { ok: false, reason: `line numbers must be positive in "${part}"` };
|
|
29
29
|
if (start !== null && start > end) return { ok: false, reason: `reversed line range in "${part}" (start > end)` };
|
|
30
30
|
return { ok: true, source: { path, start, end } };
|
package/cli/config.js
CHANGED
|
@@ -6,6 +6,7 @@ export const RULE_IDS = [
|
|
|
6
6
|
'link.broken', 'link.index-missing', 'link.type-mismatch', 'page.orphan',
|
|
7
7
|
'citation.malformed', 'citation.target-missing',
|
|
8
8
|
'provenance.excess-inferred', 'provenance.low-confidence', 'provenance.contradicted',
|
|
9
|
+
'config.index-unreadable',
|
|
9
10
|
];
|
|
10
11
|
|
|
11
12
|
const DEFAULT_SEVERITY = {
|
|
@@ -13,6 +14,7 @@ const DEFAULT_SEVERITY = {
|
|
|
13
14
|
'link.broken': 'error', 'link.index-missing': 'warn', 'link.type-mismatch': 'warn', 'page.orphan': 'warn',
|
|
14
15
|
'citation.malformed': 'error', 'citation.target-missing': 'error',
|
|
15
16
|
'provenance.excess-inferred': 'warn', 'provenance.low-confidence': 'warn', 'provenance.contradicted': 'warn',
|
|
17
|
+
'config.index-unreadable': 'warn',
|
|
16
18
|
};
|
|
17
19
|
|
|
18
20
|
export const DEFAULT_CONFIG = {
|
|
@@ -36,6 +38,8 @@ export async function loadConfig(vaultPath, explicitPath) {
|
|
|
36
38
|
user = JSON.parse(await readFile(configPath, 'utf8'));
|
|
37
39
|
} catch (e) {
|
|
38
40
|
if (e.code !== 'ENOENT') return { error: { message: `invalid ${configPath}: ${e.message}` } };
|
|
41
|
+
// an explicitly passed config file must exist — silent fallback would hide typos
|
|
42
|
+
if (explicitPath) return { error: { message: `config file not found: ${explicitPath}` } };
|
|
39
43
|
}
|
|
40
44
|
const rules = { ...DEFAULT_CONFIG.rules, ...(user.rules || {}) };
|
|
41
45
|
const unknown = Object.keys(rules).filter(id => !RULE_IDS.includes(id));
|
package/cli/engine.js
CHANGED
|
@@ -4,20 +4,34 @@ import { walkVault } from './walk.js';
|
|
|
4
4
|
import { parseFrontmatter } from './frontmatter.js';
|
|
5
5
|
import { extractWikilinks } from './links.js';
|
|
6
6
|
import { findCitations } from './citations.js';
|
|
7
|
+
import { normalizeTarget } from './resolve.js';
|
|
7
8
|
import { RULES } from './rules/index.js';
|
|
8
9
|
|
|
9
|
-
export
|
|
10
|
+
export { normalizeTarget };
|
|
10
11
|
|
|
11
12
|
// Mask fenced code blocks and inline code spans so their contents are never
|
|
12
13
|
// treated as live citations/wikilinks. Character- and line-preserving: every
|
|
13
|
-
// line number in the masked text matches the original file.
|
|
14
|
+
// line number in the masked text matches the original file. Fence state tracks
|
|
15
|
+
// the delimiter type — a ``` line inside a ~~~ fence is content, not a closer.
|
|
14
16
|
export function maskCode(body) {
|
|
15
17
|
const lines = body.split('\n');
|
|
16
|
-
|
|
18
|
+
const fenceRe = /^\s*(`{3,}|~{3,})/;
|
|
19
|
+
let fenceMark = null;
|
|
17
20
|
const masked = lines.map(line => {
|
|
18
|
-
|
|
19
|
-
if (
|
|
20
|
-
|
|
21
|
+
const m = line.match(fenceRe);
|
|
22
|
+
if (m) {
|
|
23
|
+
const mark = m[1][0];
|
|
24
|
+
if (!fenceMark) { fenceMark = mark; return ''; }
|
|
25
|
+
if (mark === fenceMark) { fenceMark = null; }
|
|
26
|
+
return '';
|
|
27
|
+
}
|
|
28
|
+
if (fenceMark) return '';
|
|
29
|
+
// mask inline HTML tag content (<tt>, <code>, <kbd>…) — regex literals like
|
|
30
|
+
// ^[a-zA-Z]*$ live there in real-world docs (k8s CEL reference)
|
|
31
|
+
return line
|
|
32
|
+
.replace(/<([a-z][a-z0-9]*)\b[^>]*>([\s\S]*?)<\/\1>/gi,
|
|
33
|
+
(_, tag, inner) => `<${tag}>${' '.repeat(inner.length)}</${tag}>`)
|
|
34
|
+
.replace(/`[^`\n]*`/g, x => '`' + ' '.repeat(Math.max(0, x.length - 2)) + '`');
|
|
21
35
|
});
|
|
22
36
|
return masked.join('\n');
|
|
23
37
|
}
|
|
@@ -48,13 +62,15 @@ export async function lintVault(vaultPath, config) {
|
|
|
48
62
|
if (config.index) {
|
|
49
63
|
try {
|
|
50
64
|
const raw = await readFile(join(vaultPath, config.index), 'utf8');
|
|
65
|
+
const indexMasked = maskCode(raw);
|
|
51
66
|
model.indexRaw = raw;
|
|
52
67
|
model.indexEntries = new Set();
|
|
53
68
|
model.indexLines = new Map();
|
|
54
|
-
const indexLines =
|
|
69
|
+
const indexLines = indexMasked.split('\n');
|
|
55
70
|
for (let i = 0; i < indexLines.length; i++) {
|
|
56
71
|
for (const m of indexLines[i].matchAll(/\[\[([^\]|\n]+)/g)) {
|
|
57
|
-
const norm = normalizeTarget(m[1]);
|
|
72
|
+
const norm = normalizeTarget(m[1].split('#')[0]);
|
|
73
|
+
if (!norm) continue;
|
|
58
74
|
model.indexEntries.add(norm);
|
|
59
75
|
if (!model.indexLines.has(norm)) model.indexLines.set(norm, i + 1);
|
|
60
76
|
}
|
|
@@ -71,7 +87,7 @@ export async function lintVault(vaultPath, config) {
|
|
|
71
87
|
const bodyStartLine = fm.bodyStartLine;
|
|
72
88
|
const body = maskCode(fm.body);
|
|
73
89
|
model.files.push({
|
|
74
|
-
relPath: rel, text, fm, body
|
|
90
|
+
relPath: rel, text, fm, body, bodyStartLine, // body is masked: rules never see code-fence content
|
|
75
91
|
links: extractWikilinks(body, bodyStartLine),
|
|
76
92
|
...findCitations(body, bodyStartLine),
|
|
77
93
|
paragraphs: proseParagraphs(body, bodyStartLine),
|
|
@@ -91,7 +107,8 @@ export async function lintVault(vaultPath, config) {
|
|
|
91
107
|
}
|
|
92
108
|
}
|
|
93
109
|
if (indexUnreadable) {
|
|
94
|
-
findings.push({ severity: '
|
|
110
|
+
findings.push({ severity: config.rules['config.index-unreadable'] || 'warn',
|
|
111
|
+
rule: 'config.index-unreadable', file: config.index, line: 1,
|
|
95
112
|
message: 'configured index could not be read; index rules disabled',
|
|
96
113
|
hint: 'check the path or remove the index key from .trustwiki.json' });
|
|
97
114
|
}
|
package/cli/links.js
CHANGED
package/cli/report.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
// strip ALL control chars including CR/LF and C1 — a hostile vault must not be
|
|
2
|
+
// able to forge report lines or overwrite terminal rows via file names
|
|
3
|
+
const CONTROL = /[\x00-\x1F\x7F-\x9F]/g;
|
|
2
4
|
const clean = s => String(s).replace(CONTROL, '');
|
|
3
5
|
|
|
4
6
|
export function formatText(findings) {
|
package/cli/resolve.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, statSync } from 'node:fs';
|
|
1
|
+
import { existsSync, statSync, realpathSync } from 'node:fs';
|
|
2
2
|
import { join, resolve, sep } from 'node:path';
|
|
3
3
|
|
|
4
4
|
export function normalizeTarget(t) { return t.trim().replace(/\.md$/, ''); }
|
|
@@ -13,13 +13,20 @@ export function resolveTarget(t, cfg) {
|
|
|
13
13
|
return cands;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
// Contained, regular-file existence check.
|
|
17
|
-
//
|
|
16
|
+
// Contained, regular-file existence check. Realpath-based: an in-vault symlink
|
|
17
|
+
// pointing outside the vault resolves outside and is rejected. Rejects `../`
|
|
18
|
+
// escapes and directory hits — a citation/probe must never leave the vault.
|
|
18
19
|
export function resolveInVault(vaultPath, cand) {
|
|
19
|
-
const
|
|
20
|
-
const abs = resolve(vaultRoot, cand);
|
|
21
|
-
if (abs !== vaultRoot && !abs.startsWith(vaultRoot + sep)) return null;
|
|
20
|
+
const abs = resolve(vaultPath, cand);
|
|
22
21
|
if (!existsSync(abs)) return null;
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
let rp, rootReal;
|
|
23
|
+
try {
|
|
24
|
+
rp = realpathSync(abs);
|
|
25
|
+
rootReal = realpathSync(vaultPath);
|
|
26
|
+
} catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
if (rp !== rootReal && !rp.startsWith(rootReal + sep)) return null;
|
|
30
|
+
try { if (!statSync(rp).isFile()) return null; } catch { return null; }
|
|
31
|
+
return rp;
|
|
25
32
|
}
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
const BASE = ['title', 'created', 'updated', 'type', 'tags'];
|
|
2
2
|
const SOURCE = ['source_url', 'ingested', 'sha256'];
|
|
3
|
-
const DATE_RE = /^\d{4}-\d{2}-\d{2}
|
|
3
|
+
const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
4
4
|
const SHA_RE = /^[0-9a-f]{64}$/i;
|
|
5
5
|
const STATES = new Set(['extracted', 'merged', 'inferred', 'ambiguous']);
|
|
6
6
|
|
|
7
|
+
function validDate(v) {
|
|
8
|
+
if (!DATE_RE.test(v)) return false;
|
|
9
|
+
const d = new Date(v + 'T00:00:00Z'); // calendar check, timezone-stable
|
|
10
|
+
return !Number.isNaN(d.getTime()) && d.toISOString().slice(0, 10) === v;
|
|
11
|
+
}
|
|
12
|
+
|
|
7
13
|
export const rule = {
|
|
8
14
|
id: 'frontmatter.fields',
|
|
9
15
|
run(model) {
|
|
@@ -18,7 +24,10 @@ export const rule = {
|
|
|
18
24
|
hint: 'see schema/spec.md — Frontmatter' });
|
|
19
25
|
const bad = [];
|
|
20
26
|
for (const k of ['created', 'updated']) {
|
|
21
|
-
if (f.fm.fields[k] && !
|
|
27
|
+
if (f.fm.fields[k] && !validDate(f.fm.fields[k])) bad.push(`${k} is not a valid ISO date`);
|
|
28
|
+
}
|
|
29
|
+
if (isSource && f.fm.fields.ingested && !validDate(f.fm.fields.ingested)) {
|
|
30
|
+
bad.push('ingested is not a valid ISO date');
|
|
22
31
|
}
|
|
23
32
|
if (f.fm.fields.provenance_state && !STATES.has(f.fm.fields.provenance_state)) {
|
|
24
33
|
bad.push(`provenance_state "${f.fm.fields.provenance_state}" is not one of extracted|merged|inferred|ambiguous`);
|
package/cli/rules/page-orphan.js
CHANGED
|
@@ -2,8 +2,13 @@ export const rule = {
|
|
|
2
2
|
id: 'page.orphan',
|
|
3
3
|
run(model) {
|
|
4
4
|
const out = [];
|
|
5
|
+
const srcDir = model.config.sourceDir;
|
|
5
6
|
for (const f of model.files) {
|
|
6
7
|
if (!f.fm.ok) continue;
|
|
8
|
+
// raw/source pages quote their source — outbound links are not their job
|
|
9
|
+
const isSource = (model.config.inferredSkipTypes || []).includes(f.fm.fields.type)
|
|
10
|
+
|| (srcDir && f.relPath.startsWith(`${srcDir}/`));
|
|
11
|
+
if (isSource) continue;
|
|
7
12
|
const n = f.links.length;
|
|
8
13
|
if (n < model.config.minOutboundLinks) out.push({ file: f.relPath, line: 1,
|
|
9
14
|
message: `only ${n} outbound link(s) — orphaned page`,
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
// markers are case-sensitive conventions (TODO/FIXME/TBD) or colon-anchored;
|
|
2
|
+
// bare lowercase "todo" in prose (Spanish "todo", domain terms like
|
|
3
|
+
// "to-do list entity") is vocabulary, not a marker — see eval round 2b.
|
|
4
|
+
const RE = null; // matching is inline in run() — kept for interface stability
|
|
2
5
|
|
|
3
6
|
export const rule = {
|
|
4
7
|
id: 'placeholder.present',
|
|
@@ -6,11 +9,23 @@ export const rule = {
|
|
|
6
9
|
const out = [];
|
|
7
10
|
for (const f of model.files) {
|
|
8
11
|
const head = f.body.split('\n').slice(0, 20).join('\n');
|
|
9
|
-
const m
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
for (const m of head.matchAll(/\b(TODO|FIXME|lorem ipsum|TBD)\b|\btodo\b/g)) {
|
|
13
|
+
const word = m[0];
|
|
14
|
+
const lower = word.toLowerCase();
|
|
15
|
+
const before = head.slice(Math.max(0, m.index - 2), m.index);
|
|
16
|
+
const after = head.slice(m.index + m[0].length, m.index + m[0].length + 2);
|
|
17
|
+
// markers: "TODO"/"FIXME" in caps, or any case followed by ":" —
|
|
18
|
+
// bare lowercase "todo" as a plain word (Spanish "todo", domain terms)
|
|
19
|
+
// is vocabulary, not a marker
|
|
20
|
+
const isMarker = word === 'TODO' || word === 'FIXME' || word === 'TBD'
|
|
21
|
+
|| /[::]\s*$/.test(after) || /^lorem/i.test(word);
|
|
22
|
+
if (!isMarker) continue;
|
|
23
|
+
out.push({ file: f.relPath,
|
|
24
|
+
line: f.bodyStartLine + head.slice(0, m.index).split('\n').length - 1,
|
|
25
|
+
message: `placeholder text: ${m[0]}`,
|
|
26
|
+
hint: 'unfinished content erodes trust — finish or remove' });
|
|
27
|
+
break; // one finding per file is enough to surface the pattern
|
|
28
|
+
}
|
|
14
29
|
}
|
|
15
30
|
return out;
|
|
16
31
|
},
|
|
@@ -9,7 +9,7 @@ export const rule = {
|
|
|
9
9
|
const m = f.body.match(CALLOUT);
|
|
10
10
|
const calloutTargets = m ? targetsIn(m[1]) : [];
|
|
11
11
|
const fmList = (f.fm?.fields?.contradicted_by || '').replace(/[\[\]]/g, '');
|
|
12
|
-
const fmTargets = fmList ? fmList.split(
|
|
12
|
+
const fmTargets = fmList ? fmList.split(/[\s,]+/).map(s => s.trim()).filter(Boolean).map(t => t.replace(/\.md$/, '')) : [];
|
|
13
13
|
if (m && !fmTargets.length) out.push({ file: f.relPath,
|
|
14
14
|
line: f.bodyStartLine + f.body.slice(0, m.index).split('\n').length - 1,
|
|
15
15
|
message: 'contradiction callout without contradicted_by in frontmatter',
|
|
@@ -4,7 +4,10 @@ export const rule = {
|
|
|
4
4
|
const out = [];
|
|
5
5
|
for (const f of model.files) {
|
|
6
6
|
if (!f.fm.ok) continue;
|
|
7
|
-
|
|
7
|
+
// exemption is location-based AND label-based: raw pages often lack the
|
|
8
|
+
// `source` type label, but living under sourceDir is dispositive
|
|
9
|
+
const underSourceDir = model.config.sourceDir && f.relPath.startsWith(`${model.config.sourceDir}/`);
|
|
10
|
+
if (model.config.inferredSkipTypes.includes(f.fm.fields.type) || underSourceDir) continue;
|
|
8
11
|
const prose = f.paragraphs.filter(p => p.isProse && p.text);
|
|
9
12
|
if (!prose.length) continue;
|
|
10
13
|
// a paragraph counts as cited only when a citation sits on its final line
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trustwiki",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Provenance linter and method for agent-maintained knowledge bases — every claim cited, contradictions surfaced, rot detected.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -24,8 +24,11 @@
|
|
|
24
24
|
"proof"
|
|
25
25
|
],
|
|
26
26
|
"license": "MIT",
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"registry": "https://registry.npmjs.org/"
|
|
29
|
+
},
|
|
27
30
|
"repository": {
|
|
28
31
|
"type": "git",
|
|
29
32
|
"url": "git+https://github.com/QianJinGuo/trustwiki.git"
|
|
30
33
|
}
|
|
31
|
-
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Agent 记忆方案对比
|
|
2
|
+
|
|
3
|
+
Agent 的跨会话记忆是目前 AI 工程的热点问题。没有记忆的 agent 每次对话都从零开始,无法积累对用户偏好和项目上下文的理解。
|
|
4
|
+
|
|
5
|
+
目前主流的记忆方案分为两类:
|
|
6
|
+
|
|
7
|
+
**文件式记忆**:把记忆存在 markdown 或文本文件中(如 CLAUDE.md、memory 目录)。优点是简单直接、可版本控制、人类可以直接查看和编辑。缺点是规模受限,当记忆量大了以后检索效率下降。
|
|
8
|
+
|
|
9
|
+
**向量库记忆**:把记忆切分成 chunk 后存入向量数据库,通过语义相似度检索。优点是可扩展到大规模记忆、检索语义相关内容更准。缺点是引入了额外的基础设施、调试困难、机器可读但人类不友好。
|
|
10
|
+
|
|
11
|
+
对于大多数团队,建议从文件式记忆起步,等规模需求出现后再引入向量检索。
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# MCP 是什么
|
|
2
|
+
|
|
3
|
+
Model Context Protocol(MCP)是 AI 应用连接外部世界的通用标准。它就像 AI 应用的 USB-C 接口:过去每个 AI 应用要为每个数据源单独写集成,现在只需要接入 MCP,就能统一连接各种工具和数据。
|
|
4
|
+
|
|
5
|
+
MCP 由 Anthropic 于 2024 年 11 月发布并开源。发布后迅速获得社区响应,成为 AI 工具生态中最受关注的协议之一。2025 年,OpenAI 和 Google DeepMind 相继宣布支持 MCP,这标志着 MCP 从单一公司的方案转变成了行业事实标准。
|
|
6
|
+
|
|
7
|
+
MCP 的核心架构包含三种原语:
|
|
8
|
+
- **Tools**:让 AI 可以调用外部函数,比如查询数据库、发送消息
|
|
9
|
+
- **Resources**:让 AI 可以读取文件和数据
|
|
10
|
+
- **Prompts**:预定义的提示模板
|
|
11
|
+
|
|
12
|
+
MCP 的意义在于把 AI 应用从"信息孤岛"变成了"生态节点"。开发者不再需要为每个 AI 应用重复开发集成,工具开发者也只需要写一次 MCP server 就能服务所有支持的 AI 应用。
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# 有记忆的 MCP Agent:两者如何组合
|
|
2
|
+
|
|
3
|
+
MCP 解决了 agent 与外部工具的连接问题,记忆解决了 agent 跨会话的经验积累问题。两者结合,才能构成一个真正能长期服务的 agent。
|
|
4
|
+
|
|
5
|
+
一个典型的组合架构:
|
|
6
|
+
- 用 MCP 连接工具生态(数据库、API、文件系统)
|
|
7
|
+
- 用文件式记忆保存用户的偏好、项目上下文、历史决策
|
|
8
|
+
- 每次会话开始时,agent 先读取记忆文件恢复上下文,再通过 MCP 调用所需工具
|
|
9
|
+
|
|
10
|
+
这种组合的优势在于关注点分离:MCP 管"能力",记忆管"经验"。两者独立演进互不干扰。
|
|
11
|
+
|
|
12
|
+
实践中需要注意的问题:记忆文件本身也会腐烂(过时信息、重复条目),需要定期整理。这是目前业界的普遍痛点。
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Agent 记忆方案对比
|
|
3
|
+
created: 2026-09-05
|
|
4
|
+
updated: 2026-09-05
|
|
5
|
+
type: note
|
|
6
|
+
tags: [memory, agents]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
Agent 跨会话记忆的两条主流路线:**文件式**(CLAUDE.md、memory 目录)与**向量库检索**。^[sources/agent-memory-landscape.md]
|
|
10
|
+
|
|
11
|
+
文件式的核心优势是三重可审计:可版本控制(git 追溯每次变更)、人类可读(直接打开检查)、agent 友好(无需额外检索层)。代价是规模上限——记忆量大后检索效率下降。^[sources/agent-memory-landscape.md]
|
|
12
|
+
|
|
13
|
+
向量库路线换取规模与语义召回,但引入基础设施与调试成本。值得注意 2026 年的趋势判断是**混合路线**:文件做索引层、向量做召回层。^[sources/agent-memory-landscape.md]
|
|
14
|
+
|
|
15
|
+
决策启发:这不是二选一,而是先后问题。相关:[[mcp-explained]]、[[memory-mcp-combo]]。
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: MCP 是什么
|
|
3
|
+
created: 2026-09-05
|
|
4
|
+
updated: 2026-09-05
|
|
5
|
+
type: note
|
|
6
|
+
tags: [mcp, protocol]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
MCP(Model Context Protocol)是 Anthropic 于 2024 年 11 月发布的开放协议,用 USB-C 类比其定位:统一 AI 应用与数据源之间的连接方式。^[sources/mcp-overview.md]
|
|
10
|
+
|
|
11
|
+
2025 年 OpenAI 与 Google DeepMind 相继采纳后,MCP 从单公司方案转变为行业事实标准——这是判断它能否长期投入的关键信号。^[sources/mcp-overview.md]
|
|
12
|
+
|
|
13
|
+
核心原语有三个:**tools**(可调用的外部函数)、**resources**(可读取的数据)、**prompts**(预定义模板)。理解三者的分工是设计 MCP 集成的基础。^[sources/mcp-overview.md]
|
|
14
|
+
|
|
15
|
+
实践含义:团队选连接方案时,MCP 的事实标准地位意味着集成成本方向变了——过去是"为每个 AI 应用写一次集成",现在收敛为"写一次 MCP server"。相关:[[agent-memory-options]]、[[memory-mcp-combo]]。
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: 有记忆的 MCP Agent
|
|
3
|
+
created: 2026-09-05
|
|
4
|
+
updated: 2026-09-05
|
|
5
|
+
type: note
|
|
6
|
+
tags: [mcp, memory, architecture]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
MCP 与记忆解决的是两个正交问题:MCP 管能力连接,记忆管经验积累。组合架构因此是关注点分离的,不是堆叠的。^[sources/mcp-overview.md] ^[sources/agent-memory-landscape.md]
|
|
10
|
+
|
|
11
|
+
按来源给出的趋势判断,2026 年的组合形态是:文件式记忆做索引(保存偏好、项目上下文、历史决策),向量召回做扩展层,MCP 统一工具面。^[sources/agent-memory-landscape.md]
|
|
12
|
+
|
|
13
|
+
一条来源没有展开、但工程师应该警惕的边界:记忆文件自身也会腐烂(过时条目、重复)。本知识库用 trustwiki 纪律对抗这个问题——这正是 Gate 阶段存在的理由(本句为工程推断,非来源内容)。^[sources/agent-memory-landscape.md] 相关:[[mcp-explained]]、[[agent-memory-options]]。
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Agent memory landscape (task source 2)
|
|
3
|
+
created: 2026-09-05
|
|
4
|
+
updated: 2026-09-05
|
|
5
|
+
type: source
|
|
6
|
+
tags: [memory, source]
|
|
7
|
+
source_url: internal-task-brief
|
|
8
|
+
ingested: 2026-09-05
|
|
9
|
+
sha256: 8e735b918c7874b2e5a46580f06abfeffac2adaaffd265c0b85bc3d9004ae2d6
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
Agent 跨会话记忆:文件式(CLAUDE.md/memory 目录)vs 向量库检索。文件式可版本控制、人类可读、agent 友好;向量库适合大规模但引入基础设施。混合路线(文件索引+向量召回)为 2026 趋势判断。
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: MCP Overview (task source 1)
|
|
3
|
+
created: 2026-09-05
|
|
4
|
+
updated: 2026-09-05
|
|
5
|
+
type: source
|
|
6
|
+
tags: [mcp, source]
|
|
7
|
+
source_url: internal-task-brief
|
|
8
|
+
ingested: 2026-09-05
|
|
9
|
+
sha256: 122a3aa6112694b91828b4a284e3badeb995fd797f6fdbce07f27c1d787e89d2
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
MCP: Anthropic 2024-11 发布的开放协议,USB-C 类比统一 AI 应用与数据源连接。2025 年 OpenAI、Google DeepMind 采纳,事实标准初步形成。核心原语:tools、resources、prompts。
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
任务:为团队知识库创建三个主题页(notes/ 目录下),基于以下两份来源材料:
|
|
2
|
+
1. 来源一(关于 MCP):Model Context Protocol 是 Anthropic 2024 年 11 月发布的开放协议,
|
|
3
|
+
用 USB-C 类比统一 AI 应用与数据源之间的连接。2025 年被 OpenAI、Google DeepMind 采纳,
|
|
4
|
+
事实标准初步形成。核心原语:tools、resources、prompts。
|
|
5
|
+
2. 来源二(关于 agent 记忆):agent 跨会话记忆的主流方案是文件式记忆(如 CLAUDE.md、
|
|
6
|
+
memory 目录)与向量库检索两类。文件式可版本控制、人类可读、对 agent 友好;
|
|
7
|
+
向量库适合大规模但引入基础设施。混合路线(文件为索引+向量做召回)是 2026 年的趋势判断。
|
|
8
|
+
要求:三页建议——「MCP 是什么」「agent 记忆方案对比」「两者如何组合:有记忆的 MCP agent」。
|