@itradingai/aiwiki 0.2.11 → 0.2.13
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/README.md +17 -3
- package/dist/src/app.js +165 -0
- package/dist/src/context.js +9 -1
- package/dist/src/grounding.js +83 -0
- package/dist/src/ingest.js +56 -10
- package/dist/src/lint.js +12 -3
- package/dist/src/payload.js +18 -1
- package/dist/src/wiki-entry.js +37 -8
- package/dist/src/workspace.js +119 -0
- package/docs/AGENT_HANDOFF.md +44 -1
- package/docs/USAGE.md +45 -1
- package/package.json +1 -1
- package/skill/SKILL.md +22 -1
package/README.md
CHANGED
|
@@ -30,9 +30,10 @@ AIWiki 是一个开源的 Agent-first 本地 LLM-wiki CLI。
|
|
|
30
30
|
|
|
31
31
|
```bash
|
|
32
32
|
aiwiki context "AI Agent 出海机会"
|
|
33
|
+
aiwiki query "AI Agent 出海机会"
|
|
33
34
|
```
|
|
34
35
|
|
|
35
|
-
`context` 返回 JSON,主要给宿主 Agent
|
|
36
|
+
`context` 返回 JSON,主要给宿主 Agent 用;`query` 使用同一套检索结果,输出给人看的摘要。第一版是本地关键词检索,不是向量检索。
|
|
36
37
|
|
|
37
38
|
### Lint:检查知识库结构
|
|
38
39
|
|
|
@@ -71,6 +72,7 @@ aiwiki agent install
|
|
|
71
72
|
```bash
|
|
72
73
|
aiwiki agent list
|
|
73
74
|
aiwiki agent install
|
|
75
|
+
aiwiki agent check
|
|
74
76
|
```
|
|
75
77
|
|
|
76
78
|
也可以直接输出通用协议:
|
|
@@ -103,6 +105,12 @@ aiwiki prompt agent
|
|
|
103
105
|
aiwiki context "xxx"
|
|
104
106
|
```
|
|
105
107
|
|
|
108
|
+
人直接查询时可以运行:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
aiwiki query "xxx"
|
|
112
|
+
```
|
|
113
|
+
|
|
106
114
|
## AIWiki 会生成什么
|
|
107
115
|
|
|
108
116
|
成功入库会生成:
|
|
@@ -131,6 +139,8 @@ generation_mode: "agent_enriched"
|
|
|
131
139
|
quality: "enriched"
|
|
132
140
|
generated_by: "host_agent"
|
|
133
141
|
llm_enriched: true
|
|
142
|
+
source_role: "input"
|
|
143
|
+
represents_user_view: false
|
|
134
144
|
```
|
|
135
145
|
|
|
136
146
|
### Deterministic Fallback Wiki Entry
|
|
@@ -144,6 +154,8 @@ generation_mode: "deterministic_fallback"
|
|
|
144
154
|
quality: "scaffold"
|
|
145
155
|
generated_by: "aiwiki_cli"
|
|
146
156
|
llm_enriched: false
|
|
157
|
+
source_role: "input"
|
|
158
|
+
represents_user_view: false
|
|
147
159
|
```
|
|
148
160
|
|
|
149
161
|
AIWiki CLI 本身不调用 LLM,所以不会在没有 Agent 分析字段时承诺高质量提炼。
|
|
@@ -156,7 +168,7 @@ AIWiki 做:
|
|
|
156
168
|
- 写入本地 Markdown。
|
|
157
169
|
- 生成 frontmatter、wikilink、处理记录。
|
|
158
170
|
- 生成 Wiki Entry 容器。
|
|
159
|
-
- 支持 `context` 和 `lint`。
|
|
171
|
+
- 支持 `context`、`query`、`next` 和 `lint`。
|
|
160
172
|
|
|
161
173
|
AIWiki 不做:
|
|
162
174
|
|
|
@@ -195,7 +207,7 @@ Review Queue 可以保留为回看入口,但不是 AIWiki 的主流程。
|
|
|
195
207
|
|
|
196
208
|
### 什么内容才代表我的观点?
|
|
197
209
|
|
|
198
|
-
|
|
210
|
+
`source_role=output` 用于标记用户已发布文章、演讲稿、公众号文章等个人输出,并可配合 `represents_user_view: true`。外部资料默认是 `source_role: input`、`represents_user_view: false`。
|
|
199
211
|
|
|
200
212
|
### Dataview 必须安装吗?
|
|
201
213
|
|
|
@@ -227,6 +239,8 @@ aiwiki setup --path "F:\knowledge_data\aiwiki-test" --yes
|
|
|
227
239
|
aiwiki doctor
|
|
228
240
|
aiwiki ingest-agent --payload tests/fixtures/agent_payload.url.valid.json --path "F:\knowledge_data\aiwiki-test"
|
|
229
241
|
aiwiki context "AI Agent" --path "F:\knowledge_data\aiwiki-test"
|
|
242
|
+
aiwiki query "AI Agent" --path "F:\knowledge_data\aiwiki-test"
|
|
243
|
+
aiwiki next --path "F:\knowledge_data\aiwiki-test"
|
|
230
244
|
aiwiki lint --path "F:\knowledge_data\aiwiki-test"
|
|
231
245
|
```
|
|
232
246
|
|
package/dist/src/app.js
CHANGED
|
@@ -54,6 +54,10 @@ export async function runCli(argv, streams = { stdout: process.stdout, stderr: p
|
|
|
54
54
|
}
|
|
55
55
|
return 0;
|
|
56
56
|
}
|
|
57
|
+
if (command === "agent" && subcommand === "check") {
|
|
58
|
+
await printAgentCheck(streams.stdout, await discoverAgentTargets());
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
57
61
|
if (command === "agent" && (subcommand === "list" || !subcommand)) {
|
|
58
62
|
printAgentList(streams.stdout, await discoverAgentTargets());
|
|
59
63
|
return 0;
|
|
@@ -124,6 +128,7 @@ export async function runCli(argv, streams = { stdout: process.stdout, stderr: p
|
|
|
124
128
|
writeLine(streams.stdout, `处理次数: ${summary.runCount}`);
|
|
125
129
|
writeLine(streams.stdout, `失败次数: ${summary.failedCount}`);
|
|
126
130
|
writeLine(streams.stdout, `最近处理: ${summary.lastRunId ?? "无"}`);
|
|
131
|
+
await printStatusDetails(streams.stdout, root, summary.runCount);
|
|
127
132
|
return 0;
|
|
128
133
|
}
|
|
129
134
|
if (command === "context") {
|
|
@@ -135,6 +140,23 @@ export async function runCli(argv, streams = { stdout: process.stdout, stderr: p
|
|
|
135
140
|
writeLine(streams.stdout, JSON.stringify(await buildContext(root, query), null, 2));
|
|
136
141
|
return 0;
|
|
137
142
|
}
|
|
143
|
+
if (command === "query") {
|
|
144
|
+
const root = await resolveWorkspace(flagString(args, "path"));
|
|
145
|
+
const query = args.positional.slice(1).join(" ").trim();
|
|
146
|
+
if (!query) {
|
|
147
|
+
throw new CliError("请提供查询主题。");
|
|
148
|
+
}
|
|
149
|
+
writeLine(streams.stdout, renderQuery(await buildContext(root, query)));
|
|
150
|
+
return 0;
|
|
151
|
+
}
|
|
152
|
+
if (command === "next") {
|
|
153
|
+
const root = await resolveWorkspace(flagString(args, "path"));
|
|
154
|
+
const summary = await statusSummary(root);
|
|
155
|
+
const checks = await doctor(root);
|
|
156
|
+
const report = summary.runCount > 0 ? await lintWorkspace(root) : undefined;
|
|
157
|
+
await printNext(streams.stdout, root, summary.runCount, checks, await discoverAgentTargets(), report);
|
|
158
|
+
return 0;
|
|
159
|
+
}
|
|
138
160
|
if (command === "lint") {
|
|
139
161
|
const root = await resolveWorkspace(flagString(args, "path"));
|
|
140
162
|
const report = await lintWorkspace(root);
|
|
@@ -223,6 +245,8 @@ function printHelp(stream) {
|
|
|
223
245
|
writeLine(stream, " aiwiki doctor");
|
|
224
246
|
writeLine(stream, " aiwiki status");
|
|
225
247
|
writeLine(stream, " aiwiki context <query>");
|
|
248
|
+
writeLine(stream, " aiwiki query <query>");
|
|
249
|
+
writeLine(stream, " aiwiki next");
|
|
226
250
|
writeLine(stream, " aiwiki lint");
|
|
227
251
|
writeLine(stream, " aiwiki ingest-agent --stdin");
|
|
228
252
|
writeLine(stream, " aiwiki ingest-file --file <file>");
|
|
@@ -230,6 +254,7 @@ function printHelp(stream) {
|
|
|
230
254
|
writeLine(stream, " aiwiki config show");
|
|
231
255
|
writeLine(stream, " aiwiki ingest-agent --payload <file>");
|
|
232
256
|
writeLine(stream, " aiwiki ingest-url <url> --content-file <file>");
|
|
257
|
+
writeLine(stream, " aiwiki agent check");
|
|
233
258
|
}
|
|
234
259
|
async function discoverAgentTargets() {
|
|
235
260
|
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
@@ -309,6 +334,19 @@ function printAgentList(stream, targets) {
|
|
|
309
334
|
}
|
|
310
335
|
}
|
|
311
336
|
}
|
|
337
|
+
async function printAgentCheck(stream, targets) {
|
|
338
|
+
writeLine(stream, "AIWiki Agent 接入检查");
|
|
339
|
+
for (const target of targets) {
|
|
340
|
+
const installed = target.target ? await exists(target.target) : false;
|
|
341
|
+
writeLine(stream, `${target.id}: ${target.name} | detected=${target.detected ? "yes" : "no"} | installed=${installed ? "yes" : "no"} | installable=${target.installable ? "yes" : "no"}`);
|
|
342
|
+
if (target.detected && target.installable && !installed) {
|
|
343
|
+
writeLine(stream, ` 建议: aiwiki agent install --agent ${target.id} --yes`);
|
|
344
|
+
}
|
|
345
|
+
else if (target.detected && !target.installable) {
|
|
346
|
+
writeLine(stream, " 建议: aiwiki prompt agent");
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
312
350
|
async function installAgentSkill(options) {
|
|
313
351
|
const targets = await discoverAgentTargets();
|
|
314
352
|
const installable = targets.filter((target) => target.detected && target.installable);
|
|
@@ -378,6 +416,7 @@ function printAgentPrompt(stream) {
|
|
|
378
416
|
writeLine(stream, "- 收录 <url>");
|
|
379
417
|
writeLine(stream, "- 存一下 <url>");
|
|
380
418
|
writeLine(stream, "- aiwiki <url>");
|
|
419
|
+
writeLine(stream, "Before ingesting, querying, or reorganizing, read `_system/purpose.md` and keep material aligned with the knowledge-base goal, scope, and unsuitable-content rules.");
|
|
381
420
|
writeLine(stream, "");
|
|
382
421
|
writeLine(stream, "如果当前会话被用户明确设定为 AIWiki 入库助手,则用户只发送 URL 也默认触发入库。普通会话中不要把所有 URL 都自动入库。");
|
|
383
422
|
writeLine(stream, "");
|
|
@@ -389,6 +428,127 @@ function printAgentPrompt(stream) {
|
|
|
389
428
|
writeLine(stream, "");
|
|
390
429
|
writeLine(stream, "禁止:让用户保存 payload;让用户每次输入 --path;声称 AIWiki CLI 负责网页抓取;声称 AIWiki CLI 会在没有 Agent 分析字段时自动高质量总结。");
|
|
391
430
|
}
|
|
431
|
+
async function printStatusDetails(stream, root, runCount) {
|
|
432
|
+
const counts = await contentCounts(root);
|
|
433
|
+
const lintPath = path.join(root, "dashboards", "Lint Report.md");
|
|
434
|
+
writeLine(stream, "");
|
|
435
|
+
writeLine(stream, "内容统计:");
|
|
436
|
+
writeLine(stream, `Wiki 条目: ${counts.wikiEntries}`);
|
|
437
|
+
writeLine(stream, `资料卡: ${counts.sourceCards}`);
|
|
438
|
+
writeLine(stream, `原文: ${counts.rawFiles}`);
|
|
439
|
+
writeLine(stream, `选题: ${counts.topics}`);
|
|
440
|
+
writeLine(stream, `大纲: ${counts.outlines}`);
|
|
441
|
+
writeLine(stream, `最近 lint: ${await exists(lintPath) ? await relativeMtime(root, lintPath) : "无"}`);
|
|
442
|
+
writeLine(stream, "");
|
|
443
|
+
writeLine(stream, "下一步建议:");
|
|
444
|
+
writeLine(stream, runCount === 0 ? "运行 `aiwiki agent install` 接入宿主 Agent,然后发送 `入库 <url>`。" : "运行 `aiwiki query <主题>` 查询知识,或运行 `aiwiki lint` 检查结构。");
|
|
445
|
+
}
|
|
446
|
+
async function printNext(stream, root, runCount, checks, targets, report) {
|
|
447
|
+
const missing = checks.filter((check) => check.status !== "ok");
|
|
448
|
+
const installableMissing = [];
|
|
449
|
+
for (const target of targets) {
|
|
450
|
+
if (target.detected && target.installable && target.target && !(await exists(target.target))) {
|
|
451
|
+
installableMissing.push(target);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
writeLine(stream, "AIWiki 下一步建议");
|
|
455
|
+
writeLine(stream, `知识库路径: ${root}`);
|
|
456
|
+
if (missing.length) {
|
|
457
|
+
writeLine(stream, "");
|
|
458
|
+
writeLine(stream, "先修复知识库结构:");
|
|
459
|
+
writeLine(stream, `- aiwiki setup --path "${root}" --yes`);
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
if (runCount === 0) {
|
|
463
|
+
writeLine(stream, "");
|
|
464
|
+
writeLine(stream, "还没有入库记录。");
|
|
465
|
+
writeLine(stream, "- aiwiki agent install");
|
|
466
|
+
writeLine(stream, "- 然后向宿主 Agent 发送 `入库 <url>`");
|
|
467
|
+
writeLine(stream, "- CLI 不抓网页;网页正文由宿主 Agent 提供。");
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
const actionableIssues = report?.issues.filter((issue) => issue.severity !== "info") ?? [];
|
|
471
|
+
if (actionableIssues.length) {
|
|
472
|
+
writeLine(stream, "");
|
|
473
|
+
writeLine(stream, `结构检查发现 ${actionableIssues.length} 个需要处理的问题。`);
|
|
474
|
+
writeLine(stream, "- aiwiki lint");
|
|
475
|
+
writeLine(stream, `- 查看报告: dashboards/Lint Report.md`);
|
|
476
|
+
writeLine(stream, "- 先处理 error / warning,再继续扩展查询或入库。");
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
writeLine(stream, "");
|
|
480
|
+
writeLine(stream, "已有入库记录,可以继续:");
|
|
481
|
+
writeLine(stream, "- aiwiki query <主题>");
|
|
482
|
+
writeLine(stream, "- aiwiki lint");
|
|
483
|
+
if (installableMissing.length) {
|
|
484
|
+
writeLine(stream, "");
|
|
485
|
+
writeLine(stream, "可补充宿主 Agent 接入:");
|
|
486
|
+
for (const target of installableMissing) {
|
|
487
|
+
writeLine(stream, `- aiwiki agent install --agent ${target.id} --yes`);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
function renderQuery(context) {
|
|
492
|
+
const lines = [`AIWiki 查询: ${context.query}`, ""];
|
|
493
|
+
appendQueryGroup(lines, "Wiki 条目", context.matches.wiki_entries);
|
|
494
|
+
appendQueryGroup(lines, "资料卡", context.matches.source_cards);
|
|
495
|
+
appendQueryGroup(lines, "选题", context.matches.topics);
|
|
496
|
+
appendQueryGroup(lines, "Claim 建议", context.matches.claims);
|
|
497
|
+
appendQueryGroup(lines, "大纲", context.matches.outlines);
|
|
498
|
+
appendQueryGroup(lines, "原文引用", context.matches.raw_refs);
|
|
499
|
+
if (context.warnings.length) {
|
|
500
|
+
lines.push("提示:", ...context.warnings.map((warning) => `- ${warning}`), "");
|
|
501
|
+
}
|
|
502
|
+
lines.push("Agent JSON:", `- aiwiki context "${context.query}"`);
|
|
503
|
+
return `${lines.join("\n")}\n`;
|
|
504
|
+
}
|
|
505
|
+
function appendQueryGroup(lines, label, items) {
|
|
506
|
+
lines.push(`${label}:`);
|
|
507
|
+
if (!items.length) {
|
|
508
|
+
lines.push("- 无", "");
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
for (const item of items.slice(0, 5)) {
|
|
512
|
+
lines.push(`- ${item.title} (${item.path})`);
|
|
513
|
+
if (item.summary) {
|
|
514
|
+
lines.push(` ${item.summary}`);
|
|
515
|
+
}
|
|
516
|
+
if (item.warnings.length) {
|
|
517
|
+
lines.push(` 提示: ${item.warnings.join(";")}`);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
lines.push("");
|
|
521
|
+
}
|
|
522
|
+
async function contentCounts(root) {
|
|
523
|
+
return {
|
|
524
|
+
wikiEntries: await countMarkdownFiles(path.join(root, "05-wiki")),
|
|
525
|
+
sourceCards: await countMarkdownFiles(path.join(root, "03-sources", "article-cards")),
|
|
526
|
+
rawFiles: await countMarkdownFiles(path.join(root, "02-raw", "articles")),
|
|
527
|
+
topics: await countMarkdownFiles(path.join(root, "07-topics", "ready")),
|
|
528
|
+
outlines: await countMarkdownFiles(path.join(root, "08-outputs", "outlines"))
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
async function countMarkdownFiles(dir) {
|
|
532
|
+
if (!(await exists(dir))) {
|
|
533
|
+
return 0;
|
|
534
|
+
}
|
|
535
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
536
|
+
let count = 0;
|
|
537
|
+
for (const entry of entries) {
|
|
538
|
+
const target = path.join(dir, entry.name);
|
|
539
|
+
if (entry.isDirectory()) {
|
|
540
|
+
count += await countMarkdownFiles(target);
|
|
541
|
+
}
|
|
542
|
+
else if (entry.isFile() && entry.name.toLowerCase().endsWith(".md")) {
|
|
543
|
+
count += 1;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
return count;
|
|
547
|
+
}
|
|
548
|
+
async function relativeMtime(root, target) {
|
|
549
|
+
const stats = await fs.stat(target);
|
|
550
|
+
return `${path.relative(root, target).replace(/\\/g, "/")} (${stats.mtime.toISOString()})`;
|
|
551
|
+
}
|
|
392
552
|
function doctorStatusText(status) {
|
|
393
553
|
if (status === "ok") {
|
|
394
554
|
return "正常";
|
|
@@ -422,6 +582,11 @@ function printIngestResult(stream, result) {
|
|
|
422
582
|
if (result.agentReport.wikiEntryQuality) {
|
|
423
583
|
writeLine(stream, `wiki_entry_quality: ${result.agentReport.wikiEntryQuality}`);
|
|
424
584
|
}
|
|
585
|
+
writeLine(stream, `grounding_evidence_available: ${result.agentReport.grounding.evidence_available ? "yes" : "no"}`);
|
|
586
|
+
writeLine(stream, `grounding_evidence_channel: ${result.agentReport.grounding.evidence_channel}`);
|
|
587
|
+
writeLine(stream, `grounding_needs_review: ${result.agentReport.grounding.needs_review ? "yes" : "no"}`);
|
|
588
|
+
writeLine(stream, `grounding_markers: ${result.agentReport.grounding.suspicion_markers.length ? result.agentReport.grounding.suspicion_markers.join(",") : "none"}`);
|
|
589
|
+
writeLine(stream, `grounding_claims_with_quotes: ${result.agentReport.grounding.claim_quote_count}/${result.agentReport.grounding.claim_count}`);
|
|
425
590
|
if (result.agentReport.keyFiles.sourceCard) {
|
|
426
591
|
writeLine(stream, `source_card: ${result.agentReport.keyFiles.sourceCard}`);
|
|
427
592
|
}
|
package/dist/src/context.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { promises as fs } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { frontmatterArray, frontmatterString, parseMarkdown } from "./frontmatter.js";
|
|
3
|
+
import { frontmatterArray, frontmatterBoolean, frontmatterString, parseMarkdown } from "./frontmatter.js";
|
|
4
4
|
import { relativePath } from "./paths.js";
|
|
5
5
|
import { exists } from "./workspace.js";
|
|
6
6
|
const GROUPS = [
|
|
@@ -75,9 +75,14 @@ async function searchDir(root, dir, tokens, weight) {
|
|
|
75
75
|
}
|
|
76
76
|
const generationMode = frontmatterString(parsed.frontmatter, "generation_mode");
|
|
77
77
|
const quality = frontmatterString(parsed.frontmatter, "quality");
|
|
78
|
+
const groundingMarkers = frontmatterArray(parsed.frontmatter, "grounding_markers");
|
|
79
|
+
const groundingNeedsReview = frontmatterBoolean(parsed.frontmatter, "grounding_needs_review");
|
|
78
80
|
const warnings = generationMode === "deterministic_fallback"
|
|
79
81
|
? ["该 Wiki Entry 是 deterministic fallback,仅包含来源、正文预览和待补全区。"]
|
|
80
82
|
: [];
|
|
83
|
+
if (groundingNeedsReview) {
|
|
84
|
+
warnings.push(`Grounding needs review${groundingMarkers.length ? `: ${groundingMarkers.join(", ")}` : ""}.`);
|
|
85
|
+
}
|
|
81
86
|
matches.push({
|
|
82
87
|
title,
|
|
83
88
|
path: rel,
|
|
@@ -87,6 +92,9 @@ async function searchDir(root, dir, tokens, weight) {
|
|
|
87
92
|
source_url: frontmatterString(parsed.frontmatter, "source_url") ?? "",
|
|
88
93
|
generation_mode: generationMode,
|
|
89
94
|
quality,
|
|
95
|
+
grounding_evidence_available: frontmatterBoolean(parsed.frontmatter, "grounding_evidence_available"),
|
|
96
|
+
grounding_needs_review: groundingNeedsReview,
|
|
97
|
+
grounding_markers: groundingMarkers,
|
|
90
98
|
warnings
|
|
91
99
|
});
|
|
92
100
|
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
export function buildGroundingReport(payload) {
|
|
2
|
+
const claims = payload.analysis?.claims ?? [];
|
|
3
|
+
const reusableKnowledge = payload.analysis?.reusable_knowledge ?? [];
|
|
4
|
+
const content = payload.source.content ?? "";
|
|
5
|
+
let evidenceQuoteCount = 0;
|
|
6
|
+
let unsupportedClaimCount = 0;
|
|
7
|
+
const markers = new Set();
|
|
8
|
+
for (const claim of claims) {
|
|
9
|
+
const quote = claim.source_quote?.trim();
|
|
10
|
+
if (quote && content.includes(quote)) {
|
|
11
|
+
evidenceQuoteCount += 1;
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
unsupportedClaimCount += 1;
|
|
15
|
+
markers.add(quote ? "source_quote_not_found" : "unsupported_claims");
|
|
16
|
+
}
|
|
17
|
+
for (const item of reusableKnowledge) {
|
|
18
|
+
const quote = item.source_quote?.trim();
|
|
19
|
+
if (quote && content.includes(quote)) {
|
|
20
|
+
evidenceQuoteCount += 1;
|
|
21
|
+
}
|
|
22
|
+
else if (quote) {
|
|
23
|
+
markers.add("source_quote_not_found");
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const coverageSuspectedIncomplete = hasSparseAnalysisForLongContent(payload);
|
|
27
|
+
if (coverageSuspectedIncomplete) {
|
|
28
|
+
markers.add("coverage_suspected_incomplete");
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
evidence_available: evidenceQuoteCount > 0,
|
|
32
|
+
evidence_channel: evidenceQuoteCount > 0 ? "host_supplied" : "none",
|
|
33
|
+
claim_count: claims.length,
|
|
34
|
+
claim_quote_count: claims.filter((claim) => claim.source_quote?.trim() && content.includes(claim.source_quote.trim())).length,
|
|
35
|
+
unsupported_claim_count: unsupportedClaimCount,
|
|
36
|
+
coverage_suspected_incomplete: coverageSuspectedIncomplete,
|
|
37
|
+
needs_review: markers.size > 0,
|
|
38
|
+
suspicion_markers: [...markers]
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export function groundingWarnings(report) {
|
|
42
|
+
const warnings = [];
|
|
43
|
+
if (report.unsupported_claim_count > 0) {
|
|
44
|
+
warnings.push(`grounding: ${report.unsupported_claim_count} claim(s) lack a source_quote found in raw content.`);
|
|
45
|
+
}
|
|
46
|
+
if (report.coverage_suspected_incomplete) {
|
|
47
|
+
warnings.push("grounding: coverage_suspected_incomplete is heuristic and needs review.");
|
|
48
|
+
}
|
|
49
|
+
return warnings;
|
|
50
|
+
}
|
|
51
|
+
export function groundingFrontmatterLines(grounding) {
|
|
52
|
+
return [
|
|
53
|
+
`grounding_evidence_available: ${grounding.evidence_available ? "true" : "false"}`,
|
|
54
|
+
`grounding_evidence_channel: "${grounding.evidence_channel}"`,
|
|
55
|
+
`grounding_needs_review: ${grounding.needs_review ? "true" : "false"}`,
|
|
56
|
+
`grounding_markers: ${yamlStringArray(grounding.suspicion_markers)}`,
|
|
57
|
+
`grounding_claim_count: "${grounding.claim_count}"`,
|
|
58
|
+
`grounding_claim_quote_count: "${grounding.claim_quote_count}"`,
|
|
59
|
+
`grounding_unsupported_claim_count: "${grounding.unsupported_claim_count}"`,
|
|
60
|
+
`coverage_suspected_incomplete: ${grounding.coverage_suspected_incomplete ? "true" : "false"}`
|
|
61
|
+
];
|
|
62
|
+
}
|
|
63
|
+
function hasSparseAnalysisForLongContent(payload) {
|
|
64
|
+
if (!payload.analysis) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
const contentLength = (payload.source.content ?? "").replace(/\s+/g, "").length;
|
|
68
|
+
if (contentLength < 500) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
const extractedSignals = payload.analysis.key_points.length +
|
|
72
|
+
payload.analysis.reusable_knowledge.length +
|
|
73
|
+
payload.analysis.related_concepts.length +
|
|
74
|
+
payload.analysis.use_cases.length +
|
|
75
|
+
payload.analysis.topic_candidates.length;
|
|
76
|
+
return extractedSignals < 3;
|
|
77
|
+
}
|
|
78
|
+
function yamlStringArray(values) {
|
|
79
|
+
return `[${values.map((value) => `"${escapeYaml(value)}"`).join(", ")}]`;
|
|
80
|
+
}
|
|
81
|
+
function escapeYaml(value) {
|
|
82
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
83
|
+
}
|
package/dist/src/ingest.js
CHANGED
|
@@ -2,6 +2,7 @@ import { promises as fs } from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { randomBytes } from "node:crypto";
|
|
4
4
|
import { normalizePayload } from "./payload.js";
|
|
5
|
+
import { buildGroundingReport, groundingFrontmatterLines, groundingWarnings } from "./grounding.js";
|
|
5
6
|
import { appendRunIdBeforeExt, relativePath, safeJoin, slugify } from "./paths.js";
|
|
6
7
|
import { initWorkspace } from "./workspace.js";
|
|
7
8
|
import { renderWikiEntry } from "./wiki-entry.js";
|
|
@@ -17,10 +18,11 @@ export async function ingestPayload(rootPath, rawPayload) {
|
|
|
17
18
|
const generatedFiles = [];
|
|
18
19
|
await writeFile(path.join(runDir, "payload.json"), `${JSON.stringify(payload, null, 2)}\n`, generatedFiles);
|
|
19
20
|
if (payload.source.fetch_status === "failed") {
|
|
21
|
+
const grounding = buildGroundingReport(payload);
|
|
20
22
|
await writeSummary(root, runDir, payload, generatedFiles, [
|
|
21
23
|
...payload.warnings,
|
|
22
24
|
"宿主 Agent 未能提供正文,AIWiki CLI 没有自行抓取网页。"
|
|
23
|
-
]);
|
|
25
|
+
], undefined, grounding);
|
|
24
26
|
return { runId: runDirName, runDir, generatedFiles, warnings: payload.warnings, agentReport: buildAgentReport(root, runDir, payload, generatedFiles) };
|
|
25
27
|
}
|
|
26
28
|
const slug = slugify(payload.source.title ?? payload.source.url);
|
|
@@ -28,22 +30,23 @@ export async function ingestPayload(rootPath, rawPayload) {
|
|
|
28
30
|
const collisionWarnings = [];
|
|
29
31
|
const longTermTargets = await chooseLongTermTargets(root, slug, runId, collisionWarnings);
|
|
30
32
|
const links = buildArtifactLinks(root, slug, runDirName, runStartedAt, longTermTargets);
|
|
33
|
+
const grounding = buildGroundingReport(payload);
|
|
31
34
|
await writeFile(path.join(runDir, "raw.md"), contentFile(payload, content, links), generatedFiles);
|
|
32
|
-
await writeFile(path.join(runDir, "source-card.md"), sourceCard(payload, runDirName, links), generatedFiles);
|
|
35
|
+
await writeFile(path.join(runDir, "source-card.md"), sourceCard(payload, runDirName, links, grounding), generatedFiles);
|
|
33
36
|
const wikiEntryResult = renderWikiEntry(payload, links);
|
|
34
37
|
await writeFile(path.join(runDir, "wiki-entry.md"), wikiEntryResult.markdown, generatedFiles);
|
|
35
38
|
await writeFile(path.join(runDir, "creative-assets.md"), creativeAssets(payload, links), generatedFiles);
|
|
36
39
|
await writeFile(path.join(runDir, "topics.md"), topics(payload, links), generatedFiles);
|
|
37
40
|
await writeFile(path.join(runDir, "draft-outline.md"), outline(payload, links), generatedFiles);
|
|
38
41
|
await writeFile(longTermTargets.raw, contentFile(payload, content, links), generatedFiles);
|
|
39
|
-
await writeFile(longTermTargets.sourceCard, sourceCard(payload, runDirName, links), generatedFiles);
|
|
42
|
+
await writeFile(longTermTargets.sourceCard, sourceCard(payload, runDirName, links, grounding), generatedFiles);
|
|
40
43
|
await writeFile(longTermTargets.wikiEntry, wikiEntryResult.markdown, generatedFiles);
|
|
41
|
-
await writeFile(longTermTargets.claims, claims(payload, links), generatedFiles);
|
|
44
|
+
await writeFile(longTermTargets.claims, claims(payload, links, grounding), generatedFiles);
|
|
42
45
|
await writeFile(longTermTargets.assets, creativeAssets(payload, links), generatedFiles);
|
|
43
46
|
await writeFile(longTermTargets.topics, topics(payload, links), generatedFiles);
|
|
44
47
|
await writeFile(longTermTargets.outline, outline(payload, links), generatedFiles);
|
|
45
|
-
const warnings = [...payload.warnings, ...collisionWarnings];
|
|
46
|
-
await writeSummary(root, runDir, payload, generatedFiles, warnings, links);
|
|
48
|
+
const warnings = [...payload.warnings, ...groundingWarnings(grounding), ...collisionWarnings];
|
|
49
|
+
await writeSummary(root, runDir, payload, generatedFiles, warnings, links, grounding);
|
|
47
50
|
return { runId, runDir, generatedFiles, warnings, agentReport: buildAgentReport(root, runDir, payload, generatedFiles) };
|
|
48
51
|
}
|
|
49
52
|
export async function ingestFile(rootPath, filePath) {
|
|
@@ -111,7 +114,7 @@ async function writeFile(target, content, generatedFiles) {
|
|
|
111
114
|
}
|
|
112
115
|
generatedFiles.push(target);
|
|
113
116
|
}
|
|
114
|
-
async function writeSummary(root, runDir, payload, generatedFiles, warnings, links) {
|
|
117
|
+
async function writeSummary(root, runDir, payload, generatedFiles, warnings, links, grounding = buildGroundingReport(payload)) {
|
|
115
118
|
const summaryPath = path.join(runDir, "processing-summary.md");
|
|
116
119
|
const files = [...generatedFiles, summaryPath];
|
|
117
120
|
const runId = path.basename(runDir);
|
|
@@ -130,6 +133,7 @@ async function writeSummary(root, runDir, payload, generatedFiles, warnings, lin
|
|
|
130
133
|
`captured_at: "${escapeYaml(payload.source.captured_at)}"`,
|
|
131
134
|
`run_id: "${escapeYaml(runId)}"`,
|
|
132
135
|
...(links ? relationshipFrontmatter(links) : []),
|
|
136
|
+
...groundingFrontmatterLines(grounding),
|
|
133
137
|
`tags: ["aiwiki/run"]`,
|
|
134
138
|
"---",
|
|
135
139
|
"",
|
|
@@ -145,6 +149,13 @@ async function writeSummary(root, runDir, payload, generatedFiles, warnings, lin
|
|
|
145
149
|
"告警:",
|
|
146
150
|
...(warnings.length ? warnings.map((warning) => `- ${warning}`) : ["- none"]),
|
|
147
151
|
"",
|
|
152
|
+
"Grounding:",
|
|
153
|
+
`- evidence_available: ${grounding.evidence_available ? "yes" : "no"}`,
|
|
154
|
+
`- evidence_channel: ${grounding.evidence_channel}`,
|
|
155
|
+
`- needs_review: ${grounding.needs_review ? "yes" : "no"}`,
|
|
156
|
+
`- suspicion_markers: ${grounding.suspicion_markers.length ? grounding.suspicion_markers.join(", ") : "none"}`,
|
|
157
|
+
`- claims_with_quotes: ${grounding.claim_quote_count}/${grounding.claim_count}`,
|
|
158
|
+
"",
|
|
148
159
|
"下一步审阅:",
|
|
149
160
|
"- 请在 Obsidian 中人工审阅资料卡、Claim 建议、素材建议、选题和大纲。",
|
|
150
161
|
"- AIWiki CLI 不负责网页抓取稳定性。"
|
|
@@ -181,7 +192,7 @@ function contentFile(payload, content, links) {
|
|
|
181
192
|
""
|
|
182
193
|
].join("\n");
|
|
183
194
|
}
|
|
184
|
-
function sourceCard(payload, runId, links) {
|
|
195
|
+
function sourceCard(payload, runId, links, grounding) {
|
|
185
196
|
return [
|
|
186
197
|
"---",
|
|
187
198
|
`aiwiki_id: "${escapeYaml(`${links.slug}:source-card`)}"`,
|
|
@@ -197,6 +208,7 @@ function sourceCard(payload, runId, links) {
|
|
|
197
208
|
`captured_at: "${escapeYaml(payload.source.captured_at)}"`,
|
|
198
209
|
`run_id: "${escapeYaml(runId)}"`,
|
|
199
210
|
...relationshipFrontmatter(links),
|
|
211
|
+
...groundingFrontmatterLines(grounding),
|
|
200
212
|
`aliases: ["${escapeYaml(payload.source.title ?? "Untitled")}"]`,
|
|
201
213
|
`tags: ["aiwiki/source-card"]`,
|
|
202
214
|
"---",
|
|
@@ -216,10 +228,19 @@ function sourceCard(payload, runId, links) {
|
|
|
216
228
|
"## 摘要",
|
|
217
229
|
"",
|
|
218
230
|
trimPreview(payload.source.content ?? payload.source.fetch_notes ?? ""),
|
|
231
|
+
"",
|
|
232
|
+
"## Grounding 状态",
|
|
233
|
+
"",
|
|
234
|
+
`- 证据通道:${grounding.evidence_channel}`,
|
|
235
|
+
`- 需要复核:${grounding.needs_review ? "yes" : "no"}`,
|
|
236
|
+
`- 疑似标记:${grounding.suspicion_markers.length ? grounding.suspicion_markers.join(", ") : "none"}`,
|
|
219
237
|
""
|
|
220
238
|
].join("\n");
|
|
221
239
|
}
|
|
222
|
-
function claims(payload, links) {
|
|
240
|
+
function claims(payload, links, grounding) {
|
|
241
|
+
const claimLines = payload.analysis?.claims.length
|
|
242
|
+
? payload.analysis.claims.flatMap((item, index) => claimSuggestionLines(index + 1, item.claim, item.confidence, item.source_quote, payload.source.content ?? ""))
|
|
243
|
+
: ["- 暂无宿主 Agent 提供的 Claim。"];
|
|
223
244
|
return [
|
|
224
245
|
"---",
|
|
225
246
|
`aiwiki_id: "${escapeYaml(`${links.slug}:claims`)}"`,
|
|
@@ -233,6 +254,7 @@ function claims(payload, links) {
|
|
|
233
254
|
`captured_at: "${escapeYaml(payload.source.captured_at)}"`,
|
|
234
255
|
`run_id: "${escapeYaml(links.runId)}"`,
|
|
235
256
|
...relationshipFrontmatter(links),
|
|
257
|
+
...groundingFrontmatterLines(grounding),
|
|
236
258
|
`tags: ["aiwiki/claims"]`,
|
|
237
259
|
"---",
|
|
238
260
|
"",
|
|
@@ -242,6 +264,16 @@ function claims(payload, links) {
|
|
|
242
264
|
`- 资料卡:${obsidianLink(links.sourceCard, "资料卡")}`,
|
|
243
265
|
`- 原文:${obsidianLink(links.raw, "原文")}`,
|
|
244
266
|
`- 待人工审阅:${payload.source.title ?? "Untitled"}`,
|
|
267
|
+
"",
|
|
268
|
+
"## Grounding",
|
|
269
|
+
"",
|
|
270
|
+
`- evidence_channel: ${grounding.evidence_channel}`,
|
|
271
|
+
`- needs_review: ${grounding.needs_review ? "yes" : "no"}`,
|
|
272
|
+
`- suspicion_markers: ${grounding.suspicion_markers.length ? grounding.suspicion_markers.join(", ") : "none"}`,
|
|
273
|
+
"",
|
|
274
|
+
"## 建议",
|
|
275
|
+
"",
|
|
276
|
+
...claimLines,
|
|
245
277
|
""
|
|
246
278
|
].join("\n");
|
|
247
279
|
}
|
|
@@ -327,6 +359,19 @@ function outline(payload, links) {
|
|
|
327
359
|
""
|
|
328
360
|
].join("\n");
|
|
329
361
|
}
|
|
362
|
+
function claimSuggestionLines(index, claim, confidence, sourceQuote, content) {
|
|
363
|
+
const quote = sourceQuote?.trim();
|
|
364
|
+
const supported = Boolean(quote && content.includes(quote));
|
|
365
|
+
return [
|
|
366
|
+
`### Claim ${index}`,
|
|
367
|
+
"",
|
|
368
|
+
`- claim: ${claim}`,
|
|
369
|
+
`- confidence: ${confidence ?? "unknown"}`,
|
|
370
|
+
`- evidence_status: ${supported ? "host_quote_found" : "needs_review"}`,
|
|
371
|
+
...(quote ? [`- source_quote: ${quote}`] : ["- source_quote: missing"]),
|
|
372
|
+
""
|
|
373
|
+
];
|
|
374
|
+
}
|
|
330
375
|
function trimPreview(value) {
|
|
331
376
|
return value.trim().slice(0, 500) || "待人工补充。";
|
|
332
377
|
}
|
|
@@ -352,7 +397,8 @@ function buildAgentReport(root, runDir, payload, generatedFiles) {
|
|
|
352
397
|
reviewQueue: "dashboards/Review Queue.md"
|
|
353
398
|
},
|
|
354
399
|
wikiEntryGenerationMode: fetchFailed ? undefined : (payload.wiki_entry || payload.analysis ? "agent_enriched" : "deterministic_fallback"),
|
|
355
|
-
wikiEntryQuality: fetchFailed ? undefined : (payload.wiki_entry || payload.analysis ? "enriched" : "scaffold")
|
|
400
|
+
wikiEntryQuality: fetchFailed ? undefined : (payload.wiki_entry || payload.analysis ? "enriched" : "scaffold"),
|
|
401
|
+
grounding: buildGroundingReport(payload)
|
|
356
402
|
};
|
|
357
403
|
}
|
|
358
404
|
function estimateFitScore(payload, content) {
|
package/dist/src/lint.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { promises as fs } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { frontmatterBoolean, frontmatterString, parseMarkdown } from "./frontmatter.js";
|
|
3
|
+
import { frontmatterArray, frontmatterBoolean, frontmatterString, parseMarkdown } from "./frontmatter.js";
|
|
4
4
|
import { relativePath, safeJoin } from "./paths.js";
|
|
5
5
|
import { exists } from "./workspace.js";
|
|
6
6
|
export async function lintWorkspace(rootPath, now = new Date().toISOString()) {
|
|
@@ -57,8 +57,17 @@ export async function lintWorkspace(rootPath, now = new Date().toISOString()) {
|
|
|
57
57
|
issues.push({ severity: "warning", path: entry.path, message: "agent_enriched Wiki Entry 缺少 key_points。", suggestion: "让宿主 Agent 提供 analysis.key_points。" });
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
|
-
if (frontmatterBoolean(entry.frontmatter, "
|
|
61
|
-
|
|
60
|
+
if (frontmatterBoolean(entry.frontmatter, "grounding_needs_review") === true) {
|
|
61
|
+
const markers = frontmatterArray(entry.frontmatter, "grounding_markers");
|
|
62
|
+
issues.push({
|
|
63
|
+
severity: "warning",
|
|
64
|
+
path: entry.path,
|
|
65
|
+
message: `Wiki Entry 需要 grounding 复核${markers.length ? `: ${markers.join(", ")}` : "。"}`,
|
|
66
|
+
suggestion: "检查 source_quote 是否能在 Raw 中找到;coverage_suspected_incomplete 仅代表启发式疑似风险。"
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
if (frontmatterBoolean(entry.frontmatter, "represents_user_view") === true && frontmatterString(entry.frontmatter, "source_role") !== "output") {
|
|
70
|
+
issues.push({ severity: "warning", path: entry.path, message: "只有 output 角色应标记为代表用户观点。", suggestion: "将 represents_user_view 改为 false,或将 source_role 改为 output。" });
|
|
62
71
|
}
|
|
63
72
|
}
|
|
64
73
|
issues.push(...duplicateIssues(sourceCards, "source_url", "重复 URL"));
|
package/dist/src/payload.js
CHANGED
|
@@ -35,6 +35,8 @@ export function normalizePayload(raw, runStartedAt) {
|
|
|
35
35
|
if (!kind) {
|
|
36
36
|
throw new Error("source.kind is required");
|
|
37
37
|
}
|
|
38
|
+
const sourceRole = normalizeSourceRole(stringValue(sourceRaw.source_role) ?? stringValue(sourceRaw.role), warnings);
|
|
39
|
+
const representsUserView = booleanValue(sourceRaw.represents_user_view) ?? sourceRole === "output";
|
|
38
40
|
const requestRaw = isRecord(raw.request) ? raw.request : {};
|
|
39
41
|
const requestedOutputs = Array.isArray(requestRaw.outputs)
|
|
40
42
|
? requestRaw.outputs.filter((item) => typeof item === "string")
|
|
@@ -65,6 +67,8 @@ export function normalizePayload(raw, runStartedAt) {
|
|
|
65
67
|
target_kb: stringValue(raw.target_kb),
|
|
66
68
|
source: {
|
|
67
69
|
kind,
|
|
70
|
+
source_role: sourceRole,
|
|
71
|
+
represents_user_view: representsUserView,
|
|
68
72
|
url: stringValue(sourceRaw.url),
|
|
69
73
|
title: titleRepair.value,
|
|
70
74
|
author: stringValue(sourceRaw.author),
|
|
@@ -87,6 +91,16 @@ export function normalizePayload(raw, runStartedAt) {
|
|
|
87
91
|
warnings
|
|
88
92
|
};
|
|
89
93
|
}
|
|
94
|
+
function normalizeSourceRole(value, warnings) {
|
|
95
|
+
if (!value) {
|
|
96
|
+
return "input";
|
|
97
|
+
}
|
|
98
|
+
if (value === "input" || value === "processing" || value === "output") {
|
|
99
|
+
return value;
|
|
100
|
+
}
|
|
101
|
+
warnings.push(`source_role 已忽略:${value} 不是 input、processing 或 output。`);
|
|
102
|
+
return "input";
|
|
103
|
+
}
|
|
90
104
|
function normalizeFetchStatus(value, content) {
|
|
91
105
|
if (value === "failed") {
|
|
92
106
|
return "failed";
|
|
@@ -110,6 +124,9 @@ function rejectWriteControlFields(raw) {
|
|
|
110
124
|
function stringValue(value) {
|
|
111
125
|
return typeof value === "string" ? value : undefined;
|
|
112
126
|
}
|
|
127
|
+
function booleanValue(value) {
|
|
128
|
+
return typeof value === "boolean" ? value : undefined;
|
|
129
|
+
}
|
|
113
130
|
function isRecord(value) {
|
|
114
131
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
115
132
|
}
|
|
@@ -172,7 +189,7 @@ function reusableKnowledgeArray(value, warnings) {
|
|
|
172
189
|
return [{ content: item.trim() }];
|
|
173
190
|
}
|
|
174
191
|
if (isRecord(item) && typeof item.content === "string" && item.content.trim()) {
|
|
175
|
-
return [{ title: stringValue(item.title), content: item.content.trim() }];
|
|
192
|
+
return [{ title: stringValue(item.title), content: item.content.trim(), source_quote: stringValue(item.source_quote) }];
|
|
176
193
|
}
|
|
177
194
|
return [];
|
|
178
195
|
});
|
package/dist/src/wiki-entry.js
CHANGED
|
@@ -1,24 +1,26 @@
|
|
|
1
|
+
import { buildGroundingReport, groundingFrontmatterLines } from "./grounding.js";
|
|
1
2
|
export function renderWikiEntry(payload, links) {
|
|
2
3
|
const enriched = Boolean(payload.wiki_entry || payload.analysis);
|
|
3
4
|
const mode = enriched ? "agent_enriched" : "deterministic_fallback";
|
|
4
5
|
const quality = enriched ? "enriched" : "scaffold";
|
|
5
6
|
const title = payload.wiki_entry?.title ?? payload.source.title ?? "Untitled";
|
|
6
|
-
const
|
|
7
|
-
const
|
|
7
|
+
const grounding = buildGroundingReport(payload);
|
|
8
|
+
const frontmatter = wikiFrontmatter(payload, links, title, mode, quality, grounding);
|
|
9
|
+
const body = enriched ? enrichedBody(payload, links, title, grounding) : fallbackBody(payload, links, title, grounding);
|
|
8
10
|
return {
|
|
9
11
|
mode,
|
|
10
12
|
quality,
|
|
11
13
|
markdown: `${frontmatter}\n${body}`
|
|
12
14
|
};
|
|
13
15
|
}
|
|
14
|
-
function wikiFrontmatter(payload, links, title, mode, quality) {
|
|
16
|
+
function wikiFrontmatter(payload, links, title, mode, quality, grounding) {
|
|
15
17
|
return [
|
|
16
18
|
"---",
|
|
17
19
|
`aiwiki_id: "${escapeYaml(`${links.slug}:wiki-entry`)}"`,
|
|
18
20
|
`type: "wiki_entry"`,
|
|
19
|
-
`wiki_type: "
|
|
20
|
-
`source_role: "
|
|
21
|
-
`represents_user_view: false`,
|
|
21
|
+
`wiki_type: "${wikiType(payload)}"`,
|
|
22
|
+
`source_role: "${payload.source.source_role}"`,
|
|
23
|
+
`represents_user_view: ${payload.source.represents_user_view ? "true" : "false"}`,
|
|
22
24
|
`status: "active"`,
|
|
23
25
|
`generation_mode: "${mode}"`,
|
|
24
26
|
`quality: "${quality}"`,
|
|
@@ -40,12 +42,23 @@ function wikiFrontmatter(payload, links, title, mode, quality) {
|
|
|
40
42
|
...(mode === "agent_enriched" ? [`summary: "${escapeYaml(payload.wiki_entry?.summary ?? payload.analysis?.summary ?? "")}"`] : []),
|
|
41
43
|
`topics: ${yamlStringArray(payload.analysis?.related_concepts ?? [])}`,
|
|
42
44
|
`claims: ${yamlStringArray(payload.analysis?.claims.map((claim) => claim.claim) ?? [])}`,
|
|
45
|
+
...groundingFrontmatterLines(grounding),
|
|
43
46
|
`tags: ["aiwiki/wiki-entry"]`,
|
|
44
47
|
"---"
|
|
45
48
|
].join("\n");
|
|
46
49
|
}
|
|
47
|
-
function
|
|
50
|
+
function wikiType(payload) {
|
|
51
|
+
if (payload.source.source_role === "output") {
|
|
52
|
+
return "personal_knowledge";
|
|
53
|
+
}
|
|
54
|
+
if (payload.source.source_role === "processing") {
|
|
55
|
+
return "thought_note";
|
|
56
|
+
}
|
|
57
|
+
return "source_knowledge";
|
|
58
|
+
}
|
|
59
|
+
function enrichedBody(payload, links, title, grounding) {
|
|
48
60
|
const sections = [`# ${title}`, ""];
|
|
61
|
+
appendGroundingReview(sections, grounding);
|
|
49
62
|
if (payload.wiki_entry?.summary || payload.analysis?.summary) {
|
|
50
63
|
sections.push("## 一句话总结", "", payload.wiki_entry?.summary ?? payload.analysis?.summary ?? "", "");
|
|
51
64
|
}
|
|
@@ -81,7 +94,7 @@ function bodyHasHeading(markdown, heading) {
|
|
|
81
94
|
const escaped = heading.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
82
95
|
return new RegExp(`^#{1,6}\\s+${escaped}\\s*$`, "m").test(markdown);
|
|
83
96
|
}
|
|
84
|
-
function fallbackBody(payload, links, title) {
|
|
97
|
+
function fallbackBody(payload, links, title, grounding) {
|
|
85
98
|
const preview = trimPreview(payload.source.content ?? payload.source.fetch_notes ?? "", 1000);
|
|
86
99
|
return [
|
|
87
100
|
`# ${title}`,
|
|
@@ -112,6 +125,8 @@ function fallbackBody(payload, links, title) {
|
|
|
112
125
|
"- 适用场景",
|
|
113
126
|
"- 可转化选题",
|
|
114
127
|
"",
|
|
128
|
+
...groundingReviewLines(grounding),
|
|
129
|
+
"",
|
|
115
130
|
sourceSection(links)
|
|
116
131
|
].join("\n");
|
|
117
132
|
}
|
|
@@ -134,6 +149,20 @@ function sourceSection(links) {
|
|
|
134
149
|
`- Run: ${obsidianLink(links.runSummary, "处理记录")}`
|
|
135
150
|
].join("\n");
|
|
136
151
|
}
|
|
152
|
+
function appendGroundingReview(sections, grounding) {
|
|
153
|
+
if (!grounding.needs_review) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
sections.push("## Grounding 复核", "", ...groundingReviewLines(grounding), "");
|
|
157
|
+
}
|
|
158
|
+
function groundingReviewLines(grounding) {
|
|
159
|
+
return [
|
|
160
|
+
`- 证据通道:${grounding.evidence_channel}`,
|
|
161
|
+
`- 需要复核:${grounding.needs_review ? "yes" : "no"}`,
|
|
162
|
+
`- 疑似标记:${grounding.suspicion_markers.length ? grounding.suspicion_markers.join(", ") : "none"}`,
|
|
163
|
+
`- Claim 引用覆盖:${grounding.claim_quote_count}/${grounding.claim_count}`
|
|
164
|
+
];
|
|
165
|
+
}
|
|
137
166
|
function obsidianLink(vaultPath, label) {
|
|
138
167
|
return `[[${vaultPath.replace(/\\/g, "/").replace(/\.md$/i, "")}|${label}]]`;
|
|
139
168
|
}
|
package/dist/src/workspace.js
CHANGED
|
@@ -21,6 +21,94 @@ export const REQUIRED_DIRS = [
|
|
|
21
21
|
"_system/logs"
|
|
22
22
|
];
|
|
23
23
|
const WORKSPACE_SEEDS = [
|
|
24
|
+
{
|
|
25
|
+
path: "_system/purpose.md",
|
|
26
|
+
content: `# AIWiki Knowledge Base Purpose
|
|
27
|
+
|
|
28
|
+
This file defines what this knowledge base is for. Host Agents should read it before ingesting, querying, or reorganizing content.
|
|
29
|
+
|
|
30
|
+
## Goal
|
|
31
|
+
|
|
32
|
+
Build a local, traceable AI knowledge base that turns useful articles, notes, and source material into Obsidian-ready Markdown.
|
|
33
|
+
|
|
34
|
+
## Suitable Materials
|
|
35
|
+
|
|
36
|
+
- Articles, notes, transcripts, and references that can become source cards, wiki entries, claims, topics, outlines, or reusable assets.
|
|
37
|
+
- External materials with clear source information.
|
|
38
|
+
- User-owned drafts or published work when the user explicitly says the material represents their own output.
|
|
39
|
+
|
|
40
|
+
## Unsuitable Materials
|
|
41
|
+
|
|
42
|
+
- Content without a usable source or context.
|
|
43
|
+
- Purely private, sensitive, illegal, or unsafe material.
|
|
44
|
+
- Generic web noise that cannot become reusable knowledge.
|
|
45
|
+
- Claims that cannot be tied back to evidence.
|
|
46
|
+
|
|
47
|
+
## Multi-Knowledge-Base Boundary
|
|
48
|
+
|
|
49
|
+
This base AIWiki workspace is a single knowledge base. If the user later creates multiple knowledge bases, each one should have its own purpose file and Agents should route material according to that local purpose.
|
|
50
|
+
|
|
51
|
+
## Agent Rules
|
|
52
|
+
|
|
53
|
+
- Respect this purpose before ingesting material.
|
|
54
|
+
- Keep evidence and inference separate.
|
|
55
|
+
- Do not treat external input as the user's own view unless the user says so.
|
|
56
|
+
- Prefer traceable source cards and wiki entries over unsupported summaries.
|
|
57
|
+
`
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
path: "_system/index.md",
|
|
61
|
+
content: `# AIWiki System Index
|
|
62
|
+
|
|
63
|
+
Use this file as the human and Agent entry point for the knowledge base.
|
|
64
|
+
|
|
65
|
+
## Core Areas
|
|
66
|
+
|
|
67
|
+
- [[02-raw/articles|Raw Articles]]
|
|
68
|
+
- [[03-sources/article-cards|Source Cards]]
|
|
69
|
+
- [[04-claims/_suggestions|Claim Suggestions]]
|
|
70
|
+
- [[05-wiki|Wiki Entries]]
|
|
71
|
+
- [[06-assets/_suggestions|Asset Suggestions]]
|
|
72
|
+
- [[07-topics/ready|Topic Pipeline]]
|
|
73
|
+
- [[08-outputs/outlines|Draft Outlines]]
|
|
74
|
+
- [[09-runs|Processing Runs]]
|
|
75
|
+
|
|
76
|
+
## Dashboards
|
|
77
|
+
|
|
78
|
+
- [[dashboards/AIWiki Home|AIWiki Home]]
|
|
79
|
+
- [[dashboards/Review Queue|Review Queue]]
|
|
80
|
+
- [[dashboards/Recent Runs|Recent Runs]]
|
|
81
|
+
- [[dashboards/Lint Report|Lint Report]]
|
|
82
|
+
|
|
83
|
+
## System Files
|
|
84
|
+
|
|
85
|
+
- [[_system/purpose|Purpose]]
|
|
86
|
+
- [[_system/log|Log]]
|
|
87
|
+
- [[_system/schemas/aiwiki-frontmatter|Frontmatter Schema]]
|
|
88
|
+
|
|
89
|
+
## Common Commands
|
|
90
|
+
|
|
91
|
+
\`\`\`bash
|
|
92
|
+
aiwiki status
|
|
93
|
+
aiwiki next
|
|
94
|
+
aiwiki query "<topic>"
|
|
95
|
+
aiwiki context "<topic>"
|
|
96
|
+
aiwiki lint
|
|
97
|
+
\`\`\`
|
|
98
|
+
`
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
path: "_system/log.md",
|
|
102
|
+
content: `# AIWiki System Log
|
|
103
|
+
|
|
104
|
+
This lightweight log is reserved for important workspace events. It keeps the base edition file-first and does not require a database.
|
|
105
|
+
|
|
106
|
+
## Entries
|
|
107
|
+
|
|
108
|
+
<!-- Add manual or future automated events below. -->
|
|
109
|
+
|
|
110
|
+
`
|
|
111
|
+
},
|
|
24
112
|
{
|
|
25
113
|
path: "dashboards/AIWiki Home.md",
|
|
26
114
|
content: `# AIWiki 首页
|
|
@@ -29,9 +117,12 @@ AIWiki 的 Obsidian 入口。Dataview 是可选增强;未安装时仍可使用
|
|
|
29
117
|
|
|
30
118
|
## 原生链接入口
|
|
31
119
|
|
|
120
|
+
- [[dashboards/Wiki Entries|Wiki 条目]]
|
|
121
|
+
- [[dashboards/Source Cards|资料卡]]
|
|
32
122
|
- [[dashboards/Review Queue|待审队列]]
|
|
33
123
|
- [[dashboards/Recent Runs|最近处理]]
|
|
34
124
|
- [[dashboards/Topic Pipeline|选题管线]]
|
|
125
|
+
- [[dashboards/Lint Report|结构检查]]
|
|
35
126
|
- [[_system/schemas/aiwiki-frontmatter|字段说明]]
|
|
36
127
|
|
|
37
128
|
## 最近收录
|
|
@@ -51,6 +142,34 @@ FROM "03-sources/article-cards" or "04-claims/_suggestions" or "06-assets/_sugge
|
|
|
51
142
|
WHERE status = "to-review"
|
|
52
143
|
SORT created_at DESC
|
|
53
144
|
\`\`\`
|
|
145
|
+
`
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
path: "dashboards/Wiki Entries.md",
|
|
149
|
+
content: `# Wiki 条目
|
|
150
|
+
|
|
151
|
+
AIWiki 每次成功入库都会生成 Wiki Entry。这里是知识层入口,不要求先经过 Review Queue 才能查询。
|
|
152
|
+
|
|
153
|
+
\`\`\`dataview
|
|
154
|
+
TABLE wiki_type, source_role, represents_user_view, quality, source_card, raw_file, updated_at
|
|
155
|
+
FROM "05-wiki"
|
|
156
|
+
WHERE type = "wiki_entry"
|
|
157
|
+
SORT updated_at DESC
|
|
158
|
+
\`\`\`
|
|
159
|
+
`
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
path: "dashboards/Source Cards.md",
|
|
163
|
+
content: `# 资料卡
|
|
164
|
+
|
|
165
|
+
资料卡用于追踪来源、原文、Claim 建议、素材建议、选题和大纲。
|
|
166
|
+
|
|
167
|
+
\`\`\`dataview
|
|
168
|
+
TABLE status, source_url, wiki_entry, raw_note, captured_at
|
|
169
|
+
FROM "03-sources/article-cards"
|
|
170
|
+
WHERE type = "source_card"
|
|
171
|
+
SORT captured_at DESC
|
|
172
|
+
\`\`\`
|
|
54
173
|
`
|
|
55
174
|
},
|
|
56
175
|
{
|
package/docs/AGENT_HANDOFF.md
CHANGED
|
@@ -22,6 +22,8 @@ AIWiki CLI 负责:校验 payload、写本地文件、生成 Wiki Entry、输
|
|
|
22
22
|
|
|
23
23
|
AIWiki CLI 不做通用网页抓取,也不调用 LLM。网页读取失败时,Agent 仍然要调用 CLI 记录失败原因;高质量 Wiki 内容由宿主 Agent 提供。
|
|
24
24
|
|
|
25
|
+
证据和疑似风险要分开:`source_quote` 等能回到原文的字段是宿主提供的 evidence/provenance;`coverage_suspected_incomplete`、`unsupported_claims`、`needs_review` 是 AIWiki 的复核提示,不代表已经证明遗漏或错误。
|
|
26
|
+
|
|
25
27
|
## 标准流程
|
|
26
28
|
|
|
27
29
|
1. 读取用户给的 URL、正文、附件或消息。
|
|
@@ -63,7 +65,9 @@ AIWiki 会修复常见 UTF-8 mojibake,但这只是兜底;宿主 Agent 仍应
|
|
|
63
65
|
"content": "这里是宿主 Agent 读取到的正文内容。",
|
|
64
66
|
"fetcher": "host-agent",
|
|
65
67
|
"fetch_status": "ok",
|
|
66
|
-
"captured_at": "2026-05-07T10:00:00+08:00"
|
|
68
|
+
"captured_at": "2026-05-07T10:00:00+08:00",
|
|
69
|
+
"source_role": "input",
|
|
70
|
+
"represents_user_view": false
|
|
67
71
|
},
|
|
68
72
|
"analysis": {
|
|
69
73
|
"summary": "一句话总结。",
|
|
@@ -73,6 +77,13 @@ AIWiki 会修复常见 UTF-8 mojibake,但这只是兜底;宿主 Agent 仍应
|
|
|
73
77
|
],
|
|
74
78
|
"related_concepts": [
|
|
75
79
|
"概念 A"
|
|
80
|
+
],
|
|
81
|
+
"claims": [
|
|
82
|
+
{
|
|
83
|
+
"claim": "强主张应绑定原文证据",
|
|
84
|
+
"confidence": "high",
|
|
85
|
+
"source_quote": "这里是宿主 Agent 读取到的正文内容。"
|
|
86
|
+
}
|
|
76
87
|
]
|
|
77
88
|
},
|
|
78
89
|
"request": {
|
|
@@ -141,6 +152,7 @@ AIWiki 已完成入库,并生成 Wiki 条目。
|
|
|
141
152
|
摘要:<summary>
|
|
142
153
|
Wiki 条目:<wiki_entry>
|
|
143
154
|
质量模式:<wiki_entry_quality> / <wiki_entry_generation_mode>
|
|
155
|
+
Grounding:<grounding_evidence_channel> / review=<grounding_needs_review> / markers=<grounding_markers>
|
|
144
156
|
资料卡:<source_card>
|
|
145
157
|
处理记录:<processing_summary>
|
|
146
158
|
```
|
|
@@ -163,6 +175,12 @@ Obsidian 入口:<dashboard>
|
|
|
163
175
|
aiwiki context "<主题>"
|
|
164
176
|
```
|
|
165
177
|
|
|
178
|
+
`context` 是给宿主 Agent 使用的 JSON。如果用户是在终端里直接想看结果,可以运行:
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
aiwiki query "<主题>"
|
|
182
|
+
```
|
|
183
|
+
|
|
166
184
|
当用户说“整理 / 检查知识库”时,调用:
|
|
167
185
|
|
|
168
186
|
```bash
|
|
@@ -170,3 +188,28 @@ aiwiki lint
|
|
|
170
188
|
```
|
|
171
189
|
|
|
172
190
|
`context` 返回 JSON,注意其中的 `generation_mode`、`quality` 和 `warnings`。如果结果是 `deterministic_fallback` / `scaffold`,回复时要说明它只是可追溯脚手架,不是高质量知识提炼。
|
|
191
|
+
|
|
192
|
+
`context` 也可能返回 grounding 字段。回复用户时可以把 `grounding_needs_review: true` 解释为“这条资料需要复核证据或覆盖度”,不要说成“AIWiki 已确认漏掉重点”。
|
|
193
|
+
|
|
194
|
+
## Source Role
|
|
195
|
+
|
|
196
|
+
默认外部资料使用:
|
|
197
|
+
|
|
198
|
+
```json
|
|
199
|
+
"source_role": "input",
|
|
200
|
+
"represents_user_view": false
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
只有当用户明确导入自己的已发布文章、演讲稿、公众号文章等个人输出时,才使用:
|
|
204
|
+
|
|
205
|
+
```json
|
|
206
|
+
"source_role": "output",
|
|
207
|
+
"represents_user_view": true
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
不要把外部资料标成代表用户观点。
|
|
211
|
+
# Knowledge Base Purpose
|
|
212
|
+
|
|
213
|
+
Before ingesting, querying, or reorganizing material, read `_system/purpose.md` in the target AIWiki workspace. Treat it as the local contract for what belongs in this knowledge base, what should stay out, and how future multi-knowledge-base routing should be handled.
|
|
214
|
+
|
|
215
|
+
If the material does not fit the purpose file, do not force it into the knowledge base as confirmed knowledge. Record the mismatch, ask for review when needed, or keep it as a traceable source rather than a claim.
|
package/docs/USAGE.md
CHANGED
|
@@ -10,6 +10,8 @@ AIWiki CLI 不负责网页抓取。Qclaw、Codex、Claude Code、Cursor、Gemini
|
|
|
10
10
|
|
|
11
11
|
AIWiki CLI 也不调用 LLM。高质量 Wiki Entry 来自宿主 Agent 提供的 `analysis` 或 `wiki_entry`;如果没有这些字段,AIWiki 会生成可追溯的 deterministic fallback 条目,只包含来源、反链、正文预览和待补全区。
|
|
12
12
|
|
|
13
|
+
AIWiki 会把证据通道和疑似风险分开记录。`source_quote` 等宿主 Agent 提供的原文引用属于证据通道;`coverage_suspected_incomplete`、`unsupported_claims`、`needs_review` 等属于 AIWiki 生成的启发式复核信号,不等于已经证明内容遗漏。
|
|
14
|
+
|
|
13
15
|
## 1. 一次性设置
|
|
14
16
|
|
|
15
17
|
发布后直接运行交互式 setup:
|
|
@@ -56,6 +58,7 @@ aiwiki status
|
|
|
56
58
|
|
|
57
59
|
```bash
|
|
58
60
|
aiwiki agent list
|
|
61
|
+
aiwiki agent check
|
|
59
62
|
```
|
|
60
63
|
|
|
61
64
|
再启动安装向导:
|
|
@@ -88,6 +91,8 @@ aiwiki prompt agent
|
|
|
88
91
|
|
|
89
92
|
把输出内容安装成宿主 Agent 的 skill,或粘贴到宿主 Agent 的项目/会话说明里。不同 Agent 的安装入口不同,所以 AIWiki 提供自动安装向导和通用协议两条路径。
|
|
90
93
|
|
|
94
|
+
`aiwiki agent check` 用来确认本机检测到哪些宿主 Agent、哪些已经安装 AIWiki 对接文件、哪些还需要运行 `aiwiki agent install --agent <id> --yes`。
|
|
95
|
+
|
|
91
96
|
## 3. 日常使用
|
|
92
97
|
|
|
93
98
|
宿主 Agent 已经加载 AIWiki 协议后,把下面的话发给它,并替换链接:
|
|
@@ -125,6 +130,11 @@ processing_summary: 09-runs/20260507-153012-abc123/processing-summary.md
|
|
|
125
130
|
wiki_entry: 05-wiki/source-knowledge/article-slug.md
|
|
126
131
|
wiki_entry_generation_mode: agent_enriched
|
|
127
132
|
wiki_entry_quality: enriched
|
|
133
|
+
grounding_evidence_available: yes
|
|
134
|
+
grounding_evidence_channel: host_supplied
|
|
135
|
+
grounding_needs_review: no
|
|
136
|
+
grounding_markers: none
|
|
137
|
+
grounding_claims_with_quotes: 1/1
|
|
128
138
|
source_card: 03-sources/article-cards/article-slug.md
|
|
129
139
|
draft_outline: 09-runs/20260507-153012-abc123/draft-outline.md
|
|
130
140
|
dashboard: dashboards/AIWiki Home.md
|
|
@@ -205,6 +215,26 @@ Wiki Entry 有两种质量模式:
|
|
|
205
215
|
- `agent_enriched` / `enriched`:宿主 Agent 提供了 `analysis` 或 `wiki_entry`。
|
|
206
216
|
- `deterministic_fallback` / `scaffold`:AIWiki 只生成来源、反链、正文预览和待补全区。
|
|
207
217
|
|
|
218
|
+
Artifact 角色保持固定:
|
|
219
|
+
|
|
220
|
+
- `03-sources/article-cards` 是 trace-first 的资料卡:保留来源、反链、原文预览和 grounding 状态,不承担完整知识正文。
|
|
221
|
+
- `05-wiki/source-knowledge` 是 enriched knowledge surface:宿主 Agent 的 `analysis` / `wiki_entry` 会进入这里。
|
|
222
|
+
- `02-raw/articles` 是原始证据层,后续摘要、Claim 和复核都应能回查这里。
|
|
223
|
+
|
|
224
|
+
Grounding 字段都是 additive,可被旧工具忽略:
|
|
225
|
+
|
|
226
|
+
- `grounding_evidence_available`:是否存在可回查的宿主证据。
|
|
227
|
+
- `grounding_evidence_channel`:`host_supplied` 或 `none`。
|
|
228
|
+
- `grounding_needs_review`:是否需要复核。
|
|
229
|
+
- `grounding_markers`:例如 `unsupported_claims`、`source_quote_not_found`、`coverage_suspected_incomplete`。
|
|
230
|
+
- `coverage_suspected_incomplete`:长文提取过少时的启发式疑似标记,不是确定遗漏结论。
|
|
231
|
+
|
|
232
|
+
Wiki Entry 还会记录来源角色:
|
|
233
|
+
|
|
234
|
+
- `source_role: input`:默认值,外部文章、网页、书籍、视频等资料;`represents_user_view: false`。
|
|
235
|
+
- `source_role: output`:用户已发布文章、演讲稿、公众号文章等个人输出;通常可配合 `represents_user_view: true`。
|
|
236
|
+
- `source_role: processing`:用户自己的草稿、笔记、思考过程;默认不直接代表最终观点。
|
|
237
|
+
|
|
208
238
|
### Obsidian 链接规则
|
|
209
239
|
|
|
210
240
|
AIWiki 生成的 Markdown 按 Obsidian vault 内路径组织,文件正文会使用 wikilink:
|
|
@@ -229,6 +259,8 @@ AIWiki 生成的 Markdown 按 Obsidian vault 内路径组织,文件正文会
|
|
|
229
259
|
|
|
230
260
|
```text
|
|
231
261
|
dashboards/AIWiki Home.md
|
|
262
|
+
dashboards/Wiki Entries.md
|
|
263
|
+
dashboards/Source Cards.md
|
|
232
264
|
dashboards/Review Queue.md
|
|
233
265
|
dashboards/Recent Runs.md
|
|
234
266
|
dashboards/Topic Pipeline.md
|
|
@@ -271,15 +303,24 @@ docs/AGENT_HANDOFF.md
|
|
|
271
303
|
|
|
272
304
|
```bash
|
|
273
305
|
aiwiki context "AI Agent"
|
|
306
|
+
aiwiki query "AI Agent"
|
|
274
307
|
```
|
|
275
308
|
|
|
309
|
+
`context` 返回 JSON 给宿主 Agent 使用;`query` 使用同一套检索结果,输出给人看的分组摘要。
|
|
310
|
+
|
|
276
311
|
检查知识库结构:
|
|
277
312
|
|
|
278
313
|
```bash
|
|
279
314
|
aiwiki lint
|
|
280
315
|
```
|
|
281
316
|
|
|
282
|
-
|
|
317
|
+
查看下一步建议:
|
|
318
|
+
|
|
319
|
+
```bash
|
|
320
|
+
aiwiki next
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
`lint` 输出报告并写入 `dashboards/Lint Report.md`。
|
|
283
324
|
|
|
284
325
|
## 8. 高级调试
|
|
285
326
|
|
|
@@ -362,3 +403,6 @@ aiwiki status
|
|
|
362
403
|
- 成功读取时,`03-sources/article-cards` 下出现资料卡。
|
|
363
404
|
- 成功读取时,`05-wiki/source-knowledge` 下出现 Wiki Entry。
|
|
364
405
|
- 抓取失败时,`09-runs/<run-id>-fetch-failed` 下出现失败记录。
|
|
406
|
+
# System Purpose Files
|
|
407
|
+
|
|
408
|
+
`aiwiki setup` now also seeds `_system/purpose.md`, `_system/index.md`, and `_system/log.md` when they are missing. These files give humans and host Agents a stable entry point for the knowledge-base goal, scope, common folders, common commands, and lightweight event notes. Re-running setup preserves user edits.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@itradingai/aiwiki",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.13",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Agent-first AI knowledge base CLI for turning articles, links and notes into Obsidian-ready source cards, topics, outlines and reusable knowledge assets.",
|
|
6
6
|
"license": "MIT",
|
package/skill/SKILL.md
CHANGED
|
@@ -74,7 +74,9 @@ Because AIWiki CLI does not call an LLM, high-quality summary and knowledge extr
|
|
|
74
74
|
"content": "Article body read by the host Agent.",
|
|
75
75
|
"fetcher": "host-agent",
|
|
76
76
|
"fetch_status": "ok",
|
|
77
|
-
"captured_at": "2026-05-07T10:00:00+08:00"
|
|
77
|
+
"captured_at": "2026-05-07T10:00:00+08:00",
|
|
78
|
+
"source_role": "input",
|
|
79
|
+
"represents_user_view": false
|
|
78
80
|
},
|
|
79
81
|
"analysis": {
|
|
80
82
|
"summary": "One-sentence summary.",
|
|
@@ -130,6 +132,14 @@ aiwiki context "<topic>"
|
|
|
130
132
|
|
|
131
133
|
Use the returned JSON to answer. Prefer Wiki Entries first. Do not scan `02-raw` by default unless the Wiki result is insufficient, the user asks to verify the original text, or sources conflict.
|
|
132
134
|
|
|
135
|
+
For direct human terminal output, use:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
aiwiki query "<topic>"
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
`query` renders the same retrieval basis as `context`; do not treat it as a separate search system.
|
|
142
|
+
|
|
133
143
|
## Lint Protocol
|
|
134
144
|
|
|
135
145
|
When the user asks to整理 / 检查 / lint the knowledge base, call:
|
|
@@ -140,6 +150,17 @@ aiwiki lint
|
|
|
140
150
|
|
|
141
151
|
Explain warnings and errors as structure-health feedback. Do not frame lint as a requirement that the user manually review every item.
|
|
142
152
|
|
|
153
|
+
## Source Role
|
|
154
|
+
|
|
155
|
+
Use the default source role for external material:
|
|
156
|
+
|
|
157
|
+
```json
|
|
158
|
+
"source_role": "input",
|
|
159
|
+
"represents_user_view": false
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Only use `source_role: "output"` with `represents_user_view: true` when the user is importing their own published writing, talk transcript, newsletter, or similar authored output. Do not mark external material as the user's view.
|
|
163
|
+
|
|
143
164
|
## Obsidian + Dataview Boundary
|
|
144
165
|
|
|
145
166
|
- AIWiki can be used with plain Markdown.
|