@itradingai/aiwiki 0.2.12 → 0.2.14
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/dist/src/app.js +1 -0
- package/dist/src/ingest.js +83 -5
- package/dist/src/payload.js +55 -0
- package/dist/src/wiki-entry.js +42 -1
- package/dist/src/workspace.js +88 -0
- package/docs/AGENT_HANDOFF.md +17 -0
- package/docs/USAGE.md +8 -1
- package/package.json +1 -1
- package/skill/SKILL.md +18 -5
package/dist/src/app.js
CHANGED
|
@@ -416,6 +416,7 @@ function printAgentPrompt(stream) {
|
|
|
416
416
|
writeLine(stream, "- 收录 <url>");
|
|
417
417
|
writeLine(stream, "- 存一下 <url>");
|
|
418
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.");
|
|
419
420
|
writeLine(stream, "");
|
|
420
421
|
writeLine(stream, "如果当前会话被用户明确设定为 AIWiki 入库助手,则用户只发送 URL 也默认触发入库。普通会话中不要把所有 URL 都自动入库。");
|
|
421
422
|
writeLine(stream, "");
|
package/dist/src/ingest.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { promises as fs } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { randomBytes } from "node:crypto";
|
|
3
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
4
4
|
import { normalizePayload } from "./payload.js";
|
|
5
5
|
import { buildGroundingReport, groundingFrontmatterLines, groundingWarnings } from "./grounding.js";
|
|
6
6
|
import { appendRunIdBeforeExt, relativePath, safeJoin, slugify } from "./paths.js";
|
|
@@ -27,9 +27,11 @@ export async function ingestPayload(rootPath, rawPayload) {
|
|
|
27
27
|
}
|
|
28
28
|
const slug = slugify(payload.source.title ?? payload.source.url);
|
|
29
29
|
const content = payload.source.content ?? "";
|
|
30
|
+
const contentFingerprint = createContentFingerprint(content);
|
|
30
31
|
const collisionWarnings = [];
|
|
32
|
+
await detectDuplicateContent(root, payload, contentFingerprint, collisionWarnings);
|
|
31
33
|
const longTermTargets = await chooseLongTermTargets(root, slug, runId, collisionWarnings);
|
|
32
|
-
const links = buildArtifactLinks(root, slug, runDirName, runStartedAt, longTermTargets);
|
|
34
|
+
const links = buildArtifactLinks(root, slug, runDirName, runStartedAt, contentFingerprint, longTermTargets);
|
|
33
35
|
const grounding = buildGroundingReport(payload);
|
|
34
36
|
await writeFile(path.join(runDir, "raw.md"), contentFile(payload, content, links), generatedFiles);
|
|
35
37
|
await writeFile(path.join(runDir, "source-card.md"), sourceCard(payload, runDirName, links, grounding), generatedFiles);
|
|
@@ -102,6 +104,42 @@ async function chooseLongTermTarget(root, dir, fileName, runId, warnings) {
|
|
|
102
104
|
warnings.push(`collision renamed: ${relativePath(root, target)} -> ${relativePath(root, renamedTarget)}`);
|
|
103
105
|
return renamedTarget;
|
|
104
106
|
}
|
|
107
|
+
async function detectDuplicateContent(root, payload, contentFingerprint, warnings) {
|
|
108
|
+
const rawDir = safeJoin(root, "02-raw", "articles");
|
|
109
|
+
let entries;
|
|
110
|
+
try {
|
|
111
|
+
entries = await fs.readdir(rawDir);
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
if (error.code === "ENOENT") {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
const sourceUrl = payload.source.url ?? "";
|
|
120
|
+
for (const entry of entries) {
|
|
121
|
+
if (!entry.toLowerCase().endsWith(".md")) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const existingPath = path.join(rawDir, entry);
|
|
125
|
+
const existing = await fs.readFile(existingPath, "utf8");
|
|
126
|
+
if (!frontmatterValue(existing, "content_fingerprint", contentFingerprint)) {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
const sameSource = sourceUrl
|
|
130
|
+
? frontmatterValue(existing, "source_url", sourceUrl)
|
|
131
|
+
: frontmatterValue(existing, "title", payload.source.title ?? "Untitled");
|
|
132
|
+
if (sameSource) {
|
|
133
|
+
warnings.push(`duplicate content fingerprint: ${contentFingerprint} already exists at ${relativePath(root, existingPath)}; new run kept separate and long-term files will not overwrite existing files.`);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function frontmatterValue(markdown, key, expected) {
|
|
139
|
+
const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
140
|
+
const escapedExpected = expected.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
141
|
+
return new RegExp(`^${escapedKey}:\\s*"?${escapedExpected}"?\\s*$`, "m").test(markdown);
|
|
142
|
+
}
|
|
105
143
|
async function writeFile(target, content, generatedFiles) {
|
|
106
144
|
try {
|
|
107
145
|
await fs.writeFile(target, content, { encoding: "utf8", flag: "wx" });
|
|
@@ -132,6 +170,7 @@ async function writeSummary(root, runDir, payload, generatedFiles, warnings, lin
|
|
|
132
170
|
`created_at: "${escapeYaml(createdAt)}"`,
|
|
133
171
|
`captured_at: "${escapeYaml(payload.source.captured_at)}"`,
|
|
134
172
|
`run_id: "${escapeYaml(runId)}"`,
|
|
173
|
+
...(links ? [`content_fingerprint: "${escapeYaml(links.contentFingerprint)}"`] : []),
|
|
135
174
|
...(links ? relationshipFrontmatter(links) : []),
|
|
136
175
|
...groundingFrontmatterLines(grounding),
|
|
137
176
|
`tags: ["aiwiki/run"]`,
|
|
@@ -176,6 +215,7 @@ function contentFile(payload, content, links) {
|
|
|
176
215
|
`created_at: "${escapeYaml(links.createdAt)}"`,
|
|
177
216
|
`captured_at: "${escapeYaml(payload.source.captured_at)}"`,
|
|
178
217
|
`run_id: "${escapeYaml(links.runId)}"`,
|
|
218
|
+
`content_fingerprint: "${escapeYaml(links.contentFingerprint)}"`,
|
|
179
219
|
...relationshipFrontmatter(links),
|
|
180
220
|
`tags: ["aiwiki/raw"]`,
|
|
181
221
|
"---",
|
|
@@ -207,6 +247,7 @@ function sourceCard(payload, runId, links, grounding) {
|
|
|
207
247
|
`created_at: "${escapeYaml(links.createdAt)}"`,
|
|
208
248
|
`captured_at: "${escapeYaml(payload.source.captured_at)}"`,
|
|
209
249
|
`run_id: "${escapeYaml(runId)}"`,
|
|
250
|
+
`content_fingerprint: "${escapeYaml(links.contentFingerprint)}"`,
|
|
210
251
|
...relationshipFrontmatter(links),
|
|
211
252
|
...groundingFrontmatterLines(grounding),
|
|
212
253
|
`aliases: ["${escapeYaml(payload.source.title ?? "Untitled")}"]`,
|
|
@@ -229,6 +270,13 @@ function sourceCard(payload, runId, links, grounding) {
|
|
|
229
270
|
"",
|
|
230
271
|
trimPreview(payload.source.content ?? payload.source.fetch_notes ?? ""),
|
|
231
272
|
"",
|
|
273
|
+
"## Problem / Evidence / Reuse",
|
|
274
|
+
"",
|
|
275
|
+
`- problem_solved: ${payload.analysis?.summary ?? "needs host Agent analysis"}`,
|
|
276
|
+
`- evidence_boundary: ${grounding.needs_review ? "review required before treating analysis as fact" : "host supplied evidence available"}`,
|
|
277
|
+
`- reuse_scenarios: ${payload.analysis?.use_cases.length ? payload.analysis.use_cases.join(", ") : "not specified"}`,
|
|
278
|
+
`- content_fingerprint: ${links.contentFingerprint}`,
|
|
279
|
+
"",
|
|
232
280
|
"## Grounding 状态",
|
|
233
281
|
"",
|
|
234
282
|
`- 证据通道:${grounding.evidence_channel}`,
|
|
@@ -253,6 +301,7 @@ function claims(payload, links, grounding) {
|
|
|
253
301
|
`created_at: "${escapeYaml(links.createdAt)}"`,
|
|
254
302
|
`captured_at: "${escapeYaml(payload.source.captured_at)}"`,
|
|
255
303
|
`run_id: "${escapeYaml(links.runId)}"`,
|
|
304
|
+
`content_fingerprint: "${escapeYaml(links.contentFingerprint)}"`,
|
|
256
305
|
...relationshipFrontmatter(links),
|
|
257
306
|
...groundingFrontmatterLines(grounding),
|
|
258
307
|
`tags: ["aiwiki/claims"]`,
|
|
@@ -274,6 +323,11 @@ function claims(payload, links, grounding) {
|
|
|
274
323
|
"## 建议",
|
|
275
324
|
"",
|
|
276
325
|
...claimLines,
|
|
326
|
+
"## Evidence Boundary",
|
|
327
|
+
"",
|
|
328
|
+
"- Claims with a matching source_quote are traceable to the provided source content.",
|
|
329
|
+
"- Claims without a matching source_quote remain suggestions and need human or host-Agent review before reuse.",
|
|
330
|
+
"",
|
|
277
331
|
""
|
|
278
332
|
].join("\n");
|
|
279
333
|
}
|
|
@@ -290,6 +344,7 @@ function creativeAssets(payload, links) {
|
|
|
290
344
|
`created_at: "${escapeYaml(links.createdAt)}"`,
|
|
291
345
|
`captured_at: "${escapeYaml(payload.source.captured_at)}"`,
|
|
292
346
|
`run_id: "${escapeYaml(links.runId)}"`,
|
|
347
|
+
`content_fingerprint: "${escapeYaml(links.contentFingerprint)}"`,
|
|
293
348
|
...relationshipFrontmatter(links),
|
|
294
349
|
`tags: ["aiwiki/assets"]`,
|
|
295
350
|
"---",
|
|
@@ -316,6 +371,7 @@ function topics(payload, links) {
|
|
|
316
371
|
`created_at: "${escapeYaml(links.createdAt)}"`,
|
|
317
372
|
`captured_at: "${escapeYaml(payload.source.captured_at)}"`,
|
|
318
373
|
`run_id: "${escapeYaml(links.runId)}"`,
|
|
374
|
+
`content_fingerprint: "${escapeYaml(links.contentFingerprint)}"`,
|
|
319
375
|
...relationshipFrontmatter(links),
|
|
320
376
|
`tags: ["aiwiki/topics"]`,
|
|
321
377
|
"---",
|
|
@@ -342,6 +398,7 @@ function outline(payload, links) {
|
|
|
342
398
|
`created_at: "${escapeYaml(links.createdAt)}"`,
|
|
343
399
|
`captured_at: "${escapeYaml(payload.source.captured_at)}"`,
|
|
344
400
|
`run_id: "${escapeYaml(links.runId)}"`,
|
|
401
|
+
`content_fingerprint: "${escapeYaml(links.contentFingerprint)}"`,
|
|
345
402
|
...relationshipFrontmatter(links),
|
|
346
403
|
`tags: ["aiwiki/outline"]`,
|
|
347
404
|
"---",
|
|
@@ -354,11 +411,28 @@ function outline(payload, links) {
|
|
|
354
411
|
"",
|
|
355
412
|
"1. 背景",
|
|
356
413
|
"2. 关键观点",
|
|
357
|
-
"3.
|
|
358
|
-
|
|
414
|
+
"3. 证据与推断边界",
|
|
415
|
+
"4. 可复用判断与方法",
|
|
416
|
+
"5. 适用场景",
|
|
417
|
+
"6. 可继续链接的条目",
|
|
418
|
+
`7. 来源:${payload.source.title ?? "Untitled"}`,
|
|
419
|
+
"",
|
|
420
|
+
"## Host Agent Outline Hints",
|
|
421
|
+
"",
|
|
422
|
+
...outlineHintLines(payload),
|
|
359
423
|
""
|
|
360
424
|
].join("\n");
|
|
361
425
|
}
|
|
426
|
+
function outlineHintLines(payload) {
|
|
427
|
+
const outline = payload.analysis?.outline?.sections ?? [];
|
|
428
|
+
const links = payload.analysis?.suggested_links ?? [];
|
|
429
|
+
const lines = [
|
|
430
|
+
...(outline.length ? outline.map((item) => `- outline_section: ${item}`) : []),
|
|
431
|
+
...(payload.analysis?.reusable_judgments.length ? payload.analysis.reusable_judgments.map((item) => `- reusable_judgment: ${item.judgment}`) : []),
|
|
432
|
+
...(links.length ? links.map((item) => `- suggested_link: ${item.title}${item.target ? ` -> ${item.target}` : ""}`) : [])
|
|
433
|
+
];
|
|
434
|
+
return lines.length ? lines : ["- No enriched outline hints supplied by the host Agent."];
|
|
435
|
+
}
|
|
362
436
|
function claimSuggestionLines(index, claim, confidence, sourceQuote, content) {
|
|
363
437
|
const quote = sourceQuote?.trim();
|
|
364
438
|
const supported = Boolean(quote && content.includes(quote));
|
|
@@ -447,11 +521,12 @@ function findGeneratedFileInDir(root, files, dir) {
|
|
|
447
521
|
const match = files.find((file) => relativePath(root, file).startsWith(`${dir}/`));
|
|
448
522
|
return match ? relativePath(root, match) : undefined;
|
|
449
523
|
}
|
|
450
|
-
function buildArtifactLinks(root, slug, runDirName, createdAt, longTermTargets) {
|
|
524
|
+
function buildArtifactLinks(root, slug, runDirName, createdAt, contentFingerprint, longTermTargets) {
|
|
451
525
|
return {
|
|
452
526
|
slug,
|
|
453
527
|
runId: runDirName,
|
|
454
528
|
createdAt,
|
|
529
|
+
contentFingerprint,
|
|
455
530
|
raw: relativePath(root, longTermTargets.raw),
|
|
456
531
|
sourceCard: relativePath(root, longTermTargets.sourceCard),
|
|
457
532
|
wikiEntry: relativePath(root, longTermTargets.wikiEntry),
|
|
@@ -462,6 +537,9 @@ function buildArtifactLinks(root, slug, runDirName, createdAt, longTermTargets)
|
|
|
462
537
|
runSummary: `09-runs/${runDirName}/processing-summary.md`
|
|
463
538
|
};
|
|
464
539
|
}
|
|
540
|
+
function createContentFingerprint(content) {
|
|
541
|
+
return `sha256:${createHash("sha256").update(content.replace(/\r\n/g, "\n"), "utf8").digest("hex")}`;
|
|
542
|
+
}
|
|
465
543
|
function relationshipFrontmatter(links) {
|
|
466
544
|
return [
|
|
467
545
|
`wiki_entry: "${escapeYaml(obsidianLink(links.wikiEntry, "Wiki 条目"))}"`,
|
package/dist/src/payload.js
CHANGED
|
@@ -142,10 +142,15 @@ function normalizeAnalysis(value, warnings) {
|
|
|
142
142
|
summary: stringValue(value.summary),
|
|
143
143
|
key_points: stringArray(value.key_points, "analysis.key_points", warnings),
|
|
144
144
|
reusable_knowledge: reusableKnowledgeArray(value.reusable_knowledge, warnings),
|
|
145
|
+
entities: stringArray(value.entities, "analysis.entities", warnings),
|
|
146
|
+
concepts: stringArray(value.concepts, "analysis.concepts", warnings),
|
|
147
|
+
tensions: stringArray(value.tensions, "analysis.tensions", warnings),
|
|
148
|
+
reusable_judgments: reusableJudgmentsArray(value.reusable_judgments, warnings),
|
|
145
149
|
related_concepts: stringArray(value.related_concepts, "analysis.related_concepts", warnings),
|
|
146
150
|
use_cases: stringArray(value.use_cases, "analysis.use_cases", warnings),
|
|
147
151
|
topic_candidates: stringArray(value.topic_candidates, "analysis.topic_candidates", warnings),
|
|
148
152
|
claims: claimsArray(value.claims, warnings),
|
|
153
|
+
suggested_links: suggestedLinksArray(value.suggested_links, warnings),
|
|
149
154
|
outline: outlineValue(value.outline, warnings)
|
|
150
155
|
};
|
|
151
156
|
return hasAnalysisContent(analysis) ? analysis : undefined;
|
|
@@ -194,6 +199,51 @@ function reusableKnowledgeArray(value, warnings) {
|
|
|
194
199
|
return [];
|
|
195
200
|
});
|
|
196
201
|
}
|
|
202
|
+
function reusableJudgmentsArray(value, warnings) {
|
|
203
|
+
if (value === undefined) {
|
|
204
|
+
return [];
|
|
205
|
+
}
|
|
206
|
+
if (!Array.isArray(value)) {
|
|
207
|
+
warnings.push("analysis.reusable_judgments ignored: expected an array.");
|
|
208
|
+
return [];
|
|
209
|
+
}
|
|
210
|
+
return value.flatMap((item) => {
|
|
211
|
+
if (typeof item === "string" && item.trim()) {
|
|
212
|
+
return [{ judgment: item.trim() }];
|
|
213
|
+
}
|
|
214
|
+
if (isRecord(item) && typeof item.judgment === "string" && item.judgment.trim()) {
|
|
215
|
+
return [{
|
|
216
|
+
title: stringValue(item.title),
|
|
217
|
+
judgment: item.judgment.trim(),
|
|
218
|
+
rationale: stringValue(item.rationale),
|
|
219
|
+
source_quote: stringValue(item.source_quote)
|
|
220
|
+
}];
|
|
221
|
+
}
|
|
222
|
+
return [];
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
function suggestedLinksArray(value, warnings) {
|
|
226
|
+
if (value === undefined) {
|
|
227
|
+
return [];
|
|
228
|
+
}
|
|
229
|
+
if (!Array.isArray(value)) {
|
|
230
|
+
warnings.push("analysis.suggested_links ignored: expected an array.");
|
|
231
|
+
return [];
|
|
232
|
+
}
|
|
233
|
+
return value.flatMap((item) => {
|
|
234
|
+
if (typeof item === "string" && item.trim()) {
|
|
235
|
+
return [{ title: item.trim() }];
|
|
236
|
+
}
|
|
237
|
+
if (isRecord(item) && typeof item.title === "string" && item.title.trim()) {
|
|
238
|
+
return [{
|
|
239
|
+
title: item.title.trim(),
|
|
240
|
+
target: stringValue(item.target),
|
|
241
|
+
reason: stringValue(item.reason)
|
|
242
|
+
}];
|
|
243
|
+
}
|
|
244
|
+
return [];
|
|
245
|
+
});
|
|
246
|
+
}
|
|
197
247
|
function claimsArray(value, warnings) {
|
|
198
248
|
if (value === undefined) {
|
|
199
249
|
return [];
|
|
@@ -252,10 +302,15 @@ function hasAnalysisContent(analysis) {
|
|
|
252
302
|
return Boolean(analysis.summary ||
|
|
253
303
|
analysis.key_points.length ||
|
|
254
304
|
analysis.reusable_knowledge.length ||
|
|
305
|
+
analysis.entities.length ||
|
|
306
|
+
analysis.concepts.length ||
|
|
307
|
+
analysis.tensions.length ||
|
|
308
|
+
analysis.reusable_judgments.length ||
|
|
255
309
|
analysis.related_concepts.length ||
|
|
256
310
|
analysis.use_cases.length ||
|
|
257
311
|
analysis.topic_candidates.length ||
|
|
258
312
|
analysis.claims.length ||
|
|
313
|
+
analysis.suggested_links.length ||
|
|
259
314
|
analysis.outline);
|
|
260
315
|
}
|
|
261
316
|
function hasCustomOutputRequest(outputs) {
|
package/dist/src/wiki-entry.js
CHANGED
|
@@ -37,10 +37,11 @@ function wikiFrontmatter(payload, links, title, mode, quality, grounding) {
|
|
|
37
37
|
`outline_file: "${escapeYaml(links.outline)}"`,
|
|
38
38
|
`run_summary: "${escapeYaml(links.runSummary)}"`,
|
|
39
39
|
`run_id: "${escapeYaml(links.runId)}"`,
|
|
40
|
+
`content_fingerprint: "${escapeYaml(links.contentFingerprint)}"`,
|
|
40
41
|
`created_at: "${escapeYaml(links.createdAt)}"`,
|
|
41
42
|
`updated_at: "${escapeYaml(links.createdAt)}"`,
|
|
42
43
|
...(mode === "agent_enriched" ? [`summary: "${escapeYaml(payload.wiki_entry?.summary ?? payload.analysis?.summary ?? "")}"`] : []),
|
|
43
|
-
`topics: ${yamlStringArray(payload.analysis?.related_concepts ?? [])}`,
|
|
44
|
+
`topics: ${yamlStringArray([...(payload.analysis?.related_concepts ?? []), ...(payload.analysis?.concepts ?? [])])}`,
|
|
44
45
|
`claims: ${yamlStringArray(payload.analysis?.claims.map((claim) => claim.claim) ?? [])}`,
|
|
45
46
|
...groundingFrontmatterLines(grounding),
|
|
46
47
|
`tags: ["aiwiki/wiki-entry"]`,
|
|
@@ -83,9 +84,13 @@ function enrichedBody(payload, links, title, grounding) {
|
|
|
83
84
|
else {
|
|
84
85
|
sections.push("## 核心观点", "", ...listOrFallback(payload.analysis?.key_points ?? [], "待宿主 Agent 补充。"), "");
|
|
85
86
|
sections.push("## 可复用知识点", "", ...knowledgeList(payload), "");
|
|
87
|
+
sections.push("## Reusable Judgments", "", ...judgmentList(payload), "");
|
|
88
|
+
sections.push("## Entities and Concepts", "", ...entityConceptList(payload), "");
|
|
89
|
+
sections.push("## Tensions", "", ...listOrFallback(payload.analysis?.tensions ?? [], "No explicit tension supplied by the host Agent."), "");
|
|
86
90
|
sections.push("## 相关概念", "", ...listOrFallback(payload.analysis?.related_concepts ?? [], "待宿主 Agent 补充。"), "");
|
|
87
91
|
sections.push("## 适合用于什么场景", "", ...listOrFallback(payload.analysis?.use_cases ?? [], "待宿主 Agent 补充。"), "");
|
|
88
92
|
sections.push("## 可转化的选题", "", ...listOrFallback(payload.analysis?.topic_candidates ?? [], "待宿主 Agent 补充。"), "");
|
|
93
|
+
sections.push("## Suggested Links", "", ...suggestedLinkList(payload), "");
|
|
89
94
|
}
|
|
90
95
|
sections.push(sourceSection(links));
|
|
91
96
|
return sections.join("\n");
|
|
@@ -137,6 +142,42 @@ function knowledgeList(payload) {
|
|
|
137
142
|
}
|
|
138
143
|
return items.flatMap((item) => item.title ? [`### ${item.title}`, "", item.content] : [`- ${item.content}`]);
|
|
139
144
|
}
|
|
145
|
+
function judgmentList(payload) {
|
|
146
|
+
const items = payload.analysis?.reusable_judgments ?? [];
|
|
147
|
+
if (!items.length) {
|
|
148
|
+
return ["No reusable judgment supplied by the host Agent."];
|
|
149
|
+
}
|
|
150
|
+
return items.flatMap((item) => [
|
|
151
|
+
item.title ? `### ${item.title}` : "### Judgment",
|
|
152
|
+
"",
|
|
153
|
+
`- judgment: ${item.judgment}`,
|
|
154
|
+
...(item.rationale ? [`- rationale: ${item.rationale}`] : []),
|
|
155
|
+
...(item.source_quote ? ["- evidence boundary: host supplied quote"] : ["- evidence boundary: needs review if reused as a factual claim"]),
|
|
156
|
+
""
|
|
157
|
+
]);
|
|
158
|
+
}
|
|
159
|
+
function entityConceptList(payload) {
|
|
160
|
+
const entities = payload.analysis?.entities ?? [];
|
|
161
|
+
const concepts = payload.analysis?.concepts ?? [];
|
|
162
|
+
if (!entities.length && !concepts.length) {
|
|
163
|
+
return ["No explicit entities or concepts supplied by the host Agent."];
|
|
164
|
+
}
|
|
165
|
+
return [
|
|
166
|
+
...(entities.length ? [`- entities: ${entities.join(", ")}`] : []),
|
|
167
|
+
...(concepts.length ? [`- concepts: ${concepts.join(", ")}`] : [])
|
|
168
|
+
];
|
|
169
|
+
}
|
|
170
|
+
function suggestedLinkList(payload) {
|
|
171
|
+
const links = payload.analysis?.suggested_links ?? [];
|
|
172
|
+
if (!links.length) {
|
|
173
|
+
return ["No suggested links supplied by the host Agent."];
|
|
174
|
+
}
|
|
175
|
+
return links.map((link) => {
|
|
176
|
+
const target = link.target ? ` -> ${link.target}` : "";
|
|
177
|
+
const reason = link.reason ? ` (${link.reason})` : "";
|
|
178
|
+
return `- ${link.title}${target}${reason}`;
|
|
179
|
+
});
|
|
180
|
+
}
|
|
140
181
|
function listOrFallback(values, fallback) {
|
|
141
182
|
return values.length ? values.map((value) => `- ${value}`) : [fallback];
|
|
142
183
|
}
|
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 首页
|
package/docs/AGENT_HANDOFF.md
CHANGED
|
@@ -101,6 +101,18 @@ AIWiki 会修复常见 UTF-8 mojibake,但这只是兜底;宿主 Agent 仍应
|
|
|
101
101
|
}
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
+
## Analysis 增强字段
|
|
105
|
+
|
|
106
|
+
`analysis` 仍然向后兼容;旧 payload 不需要修改。宿主 Agent 能提供时,可以额外写入这些可选字段:
|
|
107
|
+
|
|
108
|
+
- `entities`:文章中可复查的人、产品、组织、地点等实体。
|
|
109
|
+
- `concepts`:可沉淀为知识条目的概念、方法或框架。
|
|
110
|
+
- `tensions`:原文中的冲突、取舍、不确定性或反常识点。
|
|
111
|
+
- `reusable_judgments`:可复用判断,建议同时提供 `rationale` 和 `source_quote`。
|
|
112
|
+
- `suggested_links`:建议关联到已有 Wiki 条目的线索和原因。
|
|
113
|
+
|
|
114
|
+
不能确认的内容不要编造。AIWiki 会为成功入库的正文写入 `content_fingerprint`;重复入库同一来源同一正文时会保留新 run、输出 warning,并避免覆盖已有长期文件。
|
|
115
|
+
|
|
104
116
|
## 失败 payload
|
|
105
117
|
|
|
106
118
|
```json
|
|
@@ -208,3 +220,8 @@ aiwiki lint
|
|
|
208
220
|
```
|
|
209
221
|
|
|
210
222
|
不要把外部资料标成代表用户观点。
|
|
223
|
+
# Knowledge Base Purpose
|
|
224
|
+
|
|
225
|
+
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.
|
|
226
|
+
|
|
227
|
+
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
|
@@ -12,6 +12,8 @@ AIWiki CLI 也不调用 LLM。高质量 Wiki Entry 来自宿主 Agent 提供的
|
|
|
12
12
|
|
|
13
13
|
AIWiki 会把证据通道和疑似风险分开记录。`source_quote` 等宿主 Agent 提供的原文引用属于证据通道;`coverage_suspected_incomplete`、`unsupported_claims`、`needs_review` 等属于 AIWiki 生成的启发式复核信号,不等于已经证明内容遗漏。
|
|
14
14
|
|
|
15
|
+
成功入库的正文会写入稳定的 `content_fingerprint`。如果同一来源同一正文重复入库,AIWiki 会保留新的 run 记录、给出重复 fingerprint warning,并把长期文件改名保存,避免静默覆盖已有知识资产。
|
|
16
|
+
|
|
15
17
|
## 1. 一次性设置
|
|
16
18
|
|
|
17
19
|
发布后直接运行交互式 setup:
|
|
@@ -215,6 +217,8 @@ Wiki Entry 有两种质量模式:
|
|
|
215
217
|
- `agent_enriched` / `enriched`:宿主 Agent 提供了 `analysis` 或 `wiki_entry`。
|
|
216
218
|
- `deterministic_fallback` / `scaffold`:AIWiki 只生成来源、反链、正文预览和待补全区。
|
|
217
219
|
|
|
220
|
+
`analysis` 可以继续只传旧字段,也可以补充 `entities`、`concepts`、`tensions`、`reusable_judgments`、`suggested_links`。这些字段会进入 Wiki Entry,帮助用户区分“实体/概念”“可复用判断”“证据边界”和“后续可链接条目”,但不会被 AIWiki 当作已经证实的事实。
|
|
221
|
+
|
|
218
222
|
Artifact 角色保持固定:
|
|
219
223
|
|
|
220
224
|
- `03-sources/article-cards` 是 trace-first 的资料卡:保留来源、反链、原文预览和 grounding 状态,不承担完整知识正文。
|
|
@@ -251,7 +255,7 @@ AIWiki 生成的 Markdown 按 Obsidian vault 内路径组织,文件正文会
|
|
|
251
255
|
- `05-wiki/source-knowledge` 是默认知识入口;`03-sources/article-cards` 会链接到 Wiki 条目、原文、Claim 建议、素材建议、选题、大纲和本次处理记录。
|
|
252
256
|
- `02-raw/articles`、`04-claims/_suggestions`、`06-assets/_suggestions`、`07-topics/ready`、`08-outputs/outlines` 会回链到资料卡,Obsidian 的 Backlinks/Graph View 可以串起同一篇资料。
|
|
253
257
|
- `09-runs/<run-id>/processing-summary.md` 会把本次生成的 Markdown 文件列成可点击 wikilink;`payload.json` 不是 Markdown,保留普通路径。
|
|
254
|
-
- frontmatter 会写入 `aiwiki_id`、`type`、`status`、`slug`、`source_url`、`created_at`、`captured_at`、`run_id`、`source_card`、`raw_note`、`claims_note`、`assets_note`、`topics_note`、`outline_note`、`run_summary`、`tags` 等字段,便于后续用 Obsidian Search / Properties / Dataview 做筛选。
|
|
258
|
+
- frontmatter 会写入 `aiwiki_id`、`type`、`status`、`slug`、`source_url`、`content_fingerprint`、`created_at`、`captured_at`、`run_id`、`source_card`、`raw_note`、`claims_note`、`assets_note`、`topics_note`、`outline_note`、`run_summary`、`tags` 等字段,便于后续用 Obsidian Search / Properties / Dataview 做筛选。
|
|
255
259
|
|
|
256
260
|
### Obsidian 数据库入口
|
|
257
261
|
|
|
@@ -403,3 +407,6 @@ aiwiki status
|
|
|
403
407
|
- 成功读取时,`03-sources/article-cards` 下出现资料卡。
|
|
404
408
|
- 成功读取时,`05-wiki/source-knowledge` 下出现 Wiki Entry。
|
|
405
409
|
- 抓取失败时,`09-runs/<run-id>-fetch-failed` 下出现失败记录。
|
|
410
|
+
# System Purpose Files
|
|
411
|
+
|
|
412
|
+
`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.14",
|
|
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
|
@@ -9,14 +9,27 @@ Use this skill when the user asks an Agent to process one URL, article body, or
|
|
|
9
9
|
|
|
10
10
|
AIWiki CLI does not fetch webpages and does not call an LLM. The host Agent reads and understands the source; AIWiki validates, writes, links, tracks, queries, and lints local Markdown knowledge files.
|
|
11
11
|
|
|
12
|
+
## Knowledge Base Purpose
|
|
13
|
+
|
|
14
|
+
Before ingesting, querying, linting, or reorganizing material, read `_system/purpose.md` in the target AIWiki workspace when it exists. Treat it as the local contract for:
|
|
15
|
+
|
|
16
|
+
- what this knowledge base is trying to solve
|
|
17
|
+
- what material belongs here
|
|
18
|
+
- what material should stay out
|
|
19
|
+
- how uncertain or off-scope material should be handled
|
|
20
|
+
- how this knowledge base should remain separable from future knowledge bases
|
|
21
|
+
|
|
22
|
+
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.
|
|
23
|
+
|
|
12
24
|
## Ingest Flow
|
|
13
25
|
|
|
14
26
|
1. Read the URL, message, attachment, or user-provided body.
|
|
15
|
-
2.
|
|
16
|
-
3.
|
|
17
|
-
4.
|
|
18
|
-
5.
|
|
19
|
-
6.
|
|
27
|
+
2. Read `_system/purpose.md` and decide whether the material fits this knowledge base.
|
|
28
|
+
3. Build an `aiwiki.agent_payload.v1` payload with `source` and `request`.
|
|
29
|
+
4. If you understand the source, also provide `analysis` and/or `wiki_entry`.
|
|
30
|
+
5. Do not include output paths in the payload. The CLI decides where files are written.
|
|
31
|
+
6. If webpage reading fails, still build a payload with `source.fetch_status` set to `failed` and include `source.fetch_notes`.
|
|
32
|
+
7. Prefer stdin so the user does not need to save a payload file:
|
|
20
33
|
|
|
21
34
|
```bash
|
|
22
35
|
aiwiki ingest-agent --stdin
|