@itradingai/aiwiki 0.2.13 → 0.2.15
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 +60 -24
- 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 +100 -2
- package/docs/AGENT_HANDOFF.md +12 -0
- package/docs/USAGE.md +19 -1
- package/package.json +1 -1
- package/skill/SKILL.md +18 -5
package/dist/src/app.js
CHANGED
|
@@ -430,18 +430,29 @@ function printAgentPrompt(stream) {
|
|
|
430
430
|
}
|
|
431
431
|
async function printStatusDetails(stream, root, runCount) {
|
|
432
432
|
const counts = await contentCounts(root);
|
|
433
|
+
const summary = await statusSummary(root);
|
|
433
434
|
const lintPath = path.join(root, "dashboards", "Lint Report.md");
|
|
434
435
|
writeLine(stream, "");
|
|
435
|
-
writeLine(stream, "
|
|
436
|
-
writeLine(stream, `Wiki
|
|
437
|
-
writeLine(stream,
|
|
438
|
-
writeLine(stream,
|
|
439
|
-
writeLine(stream,
|
|
440
|
-
writeLine(stream,
|
|
441
|
-
writeLine(stream,
|
|
436
|
+
writeLine(stream, "Content stats:");
|
|
437
|
+
writeLine(stream, `Wiki entries: ${counts.wikiEntries}`);
|
|
438
|
+
writeLine(stream, `Source cards: ${counts.sourceCards}`);
|
|
439
|
+
writeLine(stream, `Raw files: ${counts.rawFiles}`);
|
|
440
|
+
writeLine(stream, `Topics: ${counts.topics}`);
|
|
441
|
+
writeLine(stream, `Outlines: ${counts.outlines}`);
|
|
442
|
+
writeLine(stream, `fallback_entries: ${summary.fallbackCount}`);
|
|
443
|
+
writeLine(stream, `grounding_review_entries: ${summary.groundingReviewCount}`);
|
|
444
|
+
writeLine(stream, `recent_lint: ${await exists(lintPath) ? await relativeMtime(root, lintPath) : "none"}`);
|
|
445
|
+
writeLine(stream, `lint_status: ${summary.lintStatus}`);
|
|
446
|
+
if (summary.lastSuccessRunId) {
|
|
447
|
+
writeLine(stream, `last_success: ${summary.lastSuccessRunId}`);
|
|
448
|
+
}
|
|
449
|
+
if (summary.lastFailureRunId) {
|
|
450
|
+
writeLine(stream, `last_failure: ${summary.lastFailureRunId}`);
|
|
451
|
+
}
|
|
452
|
+
writeLine(stream, `system_files: ${summary.systemFiles.map((item) => `${item.path}=${item.status}`).join(", ")}`);
|
|
442
453
|
writeLine(stream, "");
|
|
443
|
-
writeLine(stream, "
|
|
444
|
-
writeLine(stream, runCount
|
|
454
|
+
writeLine(stream, "Next action:");
|
|
455
|
+
writeLine(stream, recommendedNextAction(runCount, summary.lintStatus, summary.systemFiles.some((item) => item.status !== "ok")));
|
|
445
456
|
}
|
|
446
457
|
async function printNext(stream, root, runCount, checks, targets, report) {
|
|
447
458
|
const missing = checks.filter((check) => check.status !== "ok");
|
|
@@ -452,42 +463,67 @@ async function printNext(stream, root, runCount, checks, targets, report) {
|
|
|
452
463
|
}
|
|
453
464
|
}
|
|
454
465
|
writeLine(stream, "AIWiki 下一步建议");
|
|
455
|
-
writeLine(stream,
|
|
466
|
+
writeLine(stream, `workspace: ${root}`);
|
|
456
467
|
if (missing.length) {
|
|
457
468
|
writeLine(stream, "");
|
|
458
|
-
writeLine(stream, "
|
|
469
|
+
writeLine(stream, "Repair workspace structure first:");
|
|
459
470
|
writeLine(stream, `- aiwiki setup --path "${root}" --yes`);
|
|
471
|
+
writeLine(stream, "- repair_order: structure");
|
|
460
472
|
return;
|
|
461
473
|
}
|
|
462
|
-
|
|
474
|
+
const actionableIssues = report?.issues.filter((issue) => issue.severity !== "info") ?? [];
|
|
475
|
+
const errorCount = actionableIssues.filter((issue) => issue.severity === "error").length;
|
|
476
|
+
const warningCount = actionableIssues.filter((issue) => issue.severity === "warning").length;
|
|
477
|
+
if (errorCount > 0) {
|
|
463
478
|
writeLine(stream, "");
|
|
464
|
-
writeLine(stream,
|
|
465
|
-
writeLine(stream, "- aiwiki
|
|
466
|
-
writeLine(stream, "-
|
|
467
|
-
writeLine(stream, "-
|
|
479
|
+
writeLine(stream, `结构检查发现 ${errorCount} 个 error 问题。`);
|
|
480
|
+
writeLine(stream, "- aiwiki lint");
|
|
481
|
+
writeLine(stream, "- report: dashboards/Lint Report.md");
|
|
482
|
+
writeLine(stream, "- repair_order: lint_errors");
|
|
468
483
|
return;
|
|
469
484
|
}
|
|
470
|
-
|
|
471
|
-
if (actionableIssues.length) {
|
|
485
|
+
if (warningCount > 0) {
|
|
472
486
|
writeLine(stream, "");
|
|
473
|
-
writeLine(stream, `结构检查发现 ${
|
|
487
|
+
writeLine(stream, `结构检查发现 ${warningCount} 个 warning 问题。`);
|
|
474
488
|
writeLine(stream, "- aiwiki lint");
|
|
475
|
-
writeLine(stream,
|
|
476
|
-
writeLine(stream, "-
|
|
489
|
+
writeLine(stream, "- report: dashboards/Lint Report.md");
|
|
490
|
+
writeLine(stream, "- repair_order: lint_warnings");
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
if (runCount === 0) {
|
|
494
|
+
writeLine(stream, "");
|
|
495
|
+
writeLine(stream, "No ingest records yet.");
|
|
496
|
+
writeLine(stream, "- aiwiki agent install");
|
|
497
|
+
writeLine(stream, "- Then ask the host Agent to ingest a URL.");
|
|
498
|
+
writeLine(stream, "- AIWiki CLI does not fetch webpages; the host Agent supplies content.");
|
|
499
|
+
writeLine(stream, "- repair_order: empty_workspace");
|
|
477
500
|
return;
|
|
478
501
|
}
|
|
479
502
|
writeLine(stream, "");
|
|
480
|
-
writeLine(stream, "
|
|
481
|
-
writeLine(stream, "- aiwiki query
|
|
503
|
+
writeLine(stream, "Workspace is healthy enough for retrieval:");
|
|
504
|
+
writeLine(stream, "- aiwiki query <topic>");
|
|
482
505
|
writeLine(stream, "- aiwiki lint");
|
|
506
|
+
writeLine(stream, "- repair_order: healthy_query");
|
|
483
507
|
if (installableMissing.length) {
|
|
484
508
|
writeLine(stream, "");
|
|
485
|
-
writeLine(stream, "
|
|
509
|
+
writeLine(stream, "Optional host Agent setup:");
|
|
486
510
|
for (const target of installableMissing) {
|
|
487
511
|
writeLine(stream, `- aiwiki agent install --agent ${target.id} --yes`);
|
|
488
512
|
}
|
|
489
513
|
}
|
|
490
514
|
}
|
|
515
|
+
function recommendedNextAction(runCount, lintStatus, hasMissingSystemFiles) {
|
|
516
|
+
if (hasMissingSystemFiles) {
|
|
517
|
+
return "next_action: aiwiki setup --path <workspace> --yes";
|
|
518
|
+
}
|
|
519
|
+
if (lintStatus === "needs_attention") {
|
|
520
|
+
return "next_action: aiwiki lint";
|
|
521
|
+
}
|
|
522
|
+
if (runCount === 0) {
|
|
523
|
+
return "next_action: aiwiki agent install";
|
|
524
|
+
}
|
|
525
|
+
return "next_action: aiwiki query <topic>";
|
|
526
|
+
}
|
|
491
527
|
function renderQuery(context) {
|
|
492
528
|
const lines = [`AIWiki 查询: ${context.query}`, ""];
|
|
493
529
|
appendQueryGroup(lines, "Wiki 条目", context.matches.wiki_entries);
|
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
|
@@ -3,7 +3,9 @@ import os from "node:os";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { createInterface } from "node:readline/promises";
|
|
5
5
|
import { stdin as input, stdout as output } from "node:process";
|
|
6
|
+
import { frontmatterBoolean, frontmatterString, parseMarkdown } from "./frontmatter.js";
|
|
6
7
|
import { CliError } from "./output.js";
|
|
8
|
+
import { relativePath } from "./paths.js";
|
|
7
9
|
export const CONFIG_FILE = "aiwiki.yaml";
|
|
8
10
|
export const REQUIRED_DIRS = [
|
|
9
11
|
"02-raw/articles",
|
|
@@ -551,6 +553,14 @@ export async function doctor(rootPath) {
|
|
|
551
553
|
detail: absolute
|
|
552
554
|
});
|
|
553
555
|
}
|
|
556
|
+
for (const file of REQUIRED_FILES) {
|
|
557
|
+
const absolute = path.join(root, file);
|
|
558
|
+
checks.push({
|
|
559
|
+
name: file,
|
|
560
|
+
status: (await exists(absolute)) ? "ok" : "missing",
|
|
561
|
+
detail: absolute
|
|
562
|
+
});
|
|
563
|
+
}
|
|
554
564
|
const writeTarget = path.join(root, "_system", "logs", ".doctor-write-test");
|
|
555
565
|
try {
|
|
556
566
|
await fs.writeFile(writeTarget, "ok", "utf8");
|
|
@@ -566,7 +576,16 @@ export async function statusSummary(rootPath) {
|
|
|
566
576
|
const root = resolveRoot(rootPath);
|
|
567
577
|
const runsRoot = path.join(root, "09-runs");
|
|
568
578
|
if (!(await exists(runsRoot))) {
|
|
569
|
-
return {
|
|
579
|
+
return {
|
|
580
|
+
root,
|
|
581
|
+
runCount: 0,
|
|
582
|
+
failedCount: 0,
|
|
583
|
+
fallbackCount: await countWikiEntries(root, "deterministic_fallback"),
|
|
584
|
+
groundingReviewCount: await countGroundingReviewEntries(root),
|
|
585
|
+
lintStatus: await readLintStatus(root),
|
|
586
|
+
lintReportPath: await lintReportPath(root),
|
|
587
|
+
systemFiles: await systemFileSummary(root)
|
|
588
|
+
};
|
|
570
589
|
}
|
|
571
590
|
const entries = await fs.readdir(runsRoot, { withFileTypes: true });
|
|
572
591
|
const dirs = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
@@ -590,11 +609,20 @@ export async function statusSummary(rootPath) {
|
|
|
590
609
|
stats.push({ dir, mtimeMs: (await fs.stat(path.join(runsRoot, dir))).mtimeMs });
|
|
591
610
|
}
|
|
592
611
|
stats.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
612
|
+
const successDirs = dirs.filter((dir) => !dir.endsWith("-fetch-failed"));
|
|
613
|
+
const failureDirs = dirs.filter((dir) => dir.endsWith("-fetch-failed"));
|
|
593
614
|
return {
|
|
594
615
|
root,
|
|
595
616
|
runCount: dirs.length,
|
|
596
617
|
failedCount,
|
|
597
|
-
lastRunId: stats[0]?.dir
|
|
618
|
+
lastRunId: stats[0]?.dir,
|
|
619
|
+
lastSuccessRunId: await newestDir(root, successDirs),
|
|
620
|
+
lastFailureRunId: await newestDir(root, failureDirs),
|
|
621
|
+
fallbackCount: await countWikiEntries(root, "deterministic_fallback"),
|
|
622
|
+
groundingReviewCount: await countGroundingReviewEntries(root),
|
|
623
|
+
lintStatus: await readLintStatus(root),
|
|
624
|
+
lintReportPath: await lintReportPath(root),
|
|
625
|
+
systemFiles: await systemFileSummary(root)
|
|
598
626
|
};
|
|
599
627
|
}
|
|
600
628
|
export async function exists(target) {
|
|
@@ -613,3 +641,73 @@ function readScalar(text, key) {
|
|
|
613
641
|
function unquote(value) {
|
|
614
642
|
return value.replace(/^["']|["']$/g, "");
|
|
615
643
|
}
|
|
644
|
+
const REQUIRED_FILES = ["_system/purpose.md", "_system/index.md", "_system/log.md"];
|
|
645
|
+
async function systemFileSummary(root) {
|
|
646
|
+
const files = [];
|
|
647
|
+
for (const file of REQUIRED_FILES) {
|
|
648
|
+
files.push({ path: file, status: await exists(path.join(root, file)) ? "ok" : "missing" });
|
|
649
|
+
}
|
|
650
|
+
return files;
|
|
651
|
+
}
|
|
652
|
+
async function newestDir(root, dirs) {
|
|
653
|
+
const stats = [];
|
|
654
|
+
for (const dir of dirs) {
|
|
655
|
+
stats.push({ dir, mtimeMs: (await fs.stat(path.join(root, "09-runs", dir))).mtimeMs });
|
|
656
|
+
}
|
|
657
|
+
stats.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
658
|
+
return stats[0]?.dir;
|
|
659
|
+
}
|
|
660
|
+
async function countWikiEntries(root, generationMode) {
|
|
661
|
+
const files = await markdownFiles(path.join(root, "05-wiki", "source-knowledge"));
|
|
662
|
+
let count = 0;
|
|
663
|
+
for (const file of files) {
|
|
664
|
+
const parsed = parseMarkdown(await fs.readFile(file, "utf8"));
|
|
665
|
+
if (frontmatterString(parsed.frontmatter, "generation_mode") === generationMode) {
|
|
666
|
+
count += 1;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
return count;
|
|
670
|
+
}
|
|
671
|
+
async function countGroundingReviewEntries(root) {
|
|
672
|
+
const files = await markdownFiles(path.join(root, "05-wiki", "source-knowledge"));
|
|
673
|
+
let count = 0;
|
|
674
|
+
for (const file of files) {
|
|
675
|
+
const parsed = parseMarkdown(await fs.readFile(file, "utf8"));
|
|
676
|
+
if (frontmatterBoolean(parsed.frontmatter, "grounding_needs_review") === true) {
|
|
677
|
+
count += 1;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
return count;
|
|
681
|
+
}
|
|
682
|
+
async function readLintStatus(root) {
|
|
683
|
+
const reportPath = await lintReportPath(root);
|
|
684
|
+
if (!reportPath) {
|
|
685
|
+
return "missing";
|
|
686
|
+
}
|
|
687
|
+
const text = await fs.readFile(path.join(root, reportPath), "utf8");
|
|
688
|
+
if (/\[(error|warning)\]/.test(text)) {
|
|
689
|
+
return "needs_attention";
|
|
690
|
+
}
|
|
691
|
+
return "ok";
|
|
692
|
+
}
|
|
693
|
+
async function lintReportPath(root) {
|
|
694
|
+
const absolute = path.join(root, "dashboards", "Lint Report.md");
|
|
695
|
+
return await exists(absolute) ? relativePath(root, absolute) : undefined;
|
|
696
|
+
}
|
|
697
|
+
async function markdownFiles(dir) {
|
|
698
|
+
if (!(await exists(dir))) {
|
|
699
|
+
return [];
|
|
700
|
+
}
|
|
701
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
702
|
+
const files = [];
|
|
703
|
+
for (const entry of entries) {
|
|
704
|
+
const target = path.join(dir, entry.name);
|
|
705
|
+
if (entry.isDirectory()) {
|
|
706
|
+
files.push(...await markdownFiles(target));
|
|
707
|
+
}
|
|
708
|
+
else if (entry.isFile() && entry.name.toLowerCase().endsWith(".md")) {
|
|
709
|
+
files.push(target);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
return files;
|
|
713
|
+
}
|
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
|
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
|
|
|
@@ -406,3 +410,17 @@ aiwiki status
|
|
|
406
410
|
# System Purpose Files
|
|
407
411
|
|
|
408
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.
|
|
413
|
+
|
|
414
|
+
## Diagnostic Commands
|
|
415
|
+
|
|
416
|
+
`aiwiki doctor` checks the workspace directories, write permission, and required system files: `_system/purpose.md`, `_system/index.md`, and `_system/log.md`.
|
|
417
|
+
|
|
418
|
+
`aiwiki status` keeps the existing run-count summary and also reports:
|
|
419
|
+
|
|
420
|
+
- `fallback_entries`: Wiki entries generated as deterministic fallback/scaffold.
|
|
421
|
+
- `grounding_review_entries`: Wiki entries marked for grounding review.
|
|
422
|
+
- `lint_status`: whether a lint report is missing, clean, or needs attention.
|
|
423
|
+
- `system_files`: readiness of purpose, index, and log files.
|
|
424
|
+
- `next_action`: the recommended next command.
|
|
425
|
+
|
|
426
|
+
`aiwiki next` uses the same repair order: fix workspace structure first, then lint errors, lint warnings, empty-workspace onboarding, and finally healthy-state query guidance.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@itradingai/aiwiki",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.15",
|
|
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
|