@itradingai/aiwiki 0.2.16 → 0.2.18

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 CHANGED
@@ -247,3 +247,37 @@ aiwiki lint --path "F:\knowledge_data\aiwiki-test"
247
247
  ## License
248
248
 
249
249
  MIT. See [LICENSE](LICENSE).
250
+
251
+ ## Query Filters
252
+
253
+ AIWiki retrieval is local Markdown/frontmatter search. It is intentionally lightweight: no vector search, no database, no external search, and no RAG-over-wiki.
254
+
255
+ ```bash
256
+ aiwiki context "AI Agent" --type wiki_entries --source-role input --wiki-type source_knowledge --status active --limit 5
257
+ aiwiki query "AI Agent" --type source_cards --status to-review --limit 3
258
+ ```
259
+
260
+ `context` returns Agent-readable JSON with `query_scope`, `result_quality`, `recommended_next_action`, `match_reasons`, `quality_signals`, and `related_refs`. `query` uses the same retrieval path and shows the match reasons and quality hints for humans.
261
+
262
+ ## Agent Skill Sync and Upgrade
263
+
264
+ AIWiki is Agent-first: after installing or upgrading the npm package, sync the packaged AIWiki skill into the local Agent environment.
265
+
266
+ First install and later upgrades use the same safe command:
267
+
268
+ ```bash
269
+ npm install -g @itradingai/aiwiki@latest
270
+ aiwiki agent sync --yes
271
+ aiwiki agent check
272
+ ```
273
+
274
+ For one Agent:
275
+
276
+ ```bash
277
+ aiwiki agent sync --agent codex --yes
278
+ aiwiki agent sync --agent claude --yes
279
+ ```
280
+
281
+ `agent sync` is idempotent. Missing targets are installed, current targets are left unchanged, and changed old skill files are backed up before overwrite. Use `--dry-run` to preview and `--json` when an AI Agent needs stable machine-readable status.
282
+
283
+ After sync, restart or reload the target Agent so it reads the new AIWiki skill. To roll back, copy the generated `.bak-<timestamp>` file back over the target skill file.
package/dist/src/app.js CHANGED
@@ -17,6 +17,14 @@ export async function runCli(argv, streams = { stdout: process.stdout, stderr: p
17
17
  writeLine(streams.stdout, `aiwiki ${await packageVersion()}`);
18
18
  return 0;
19
19
  }
20
+ if (command === "agent" && (subcommand === "help" || args.flags.has("help"))) {
21
+ printAgentHelp(streams.stdout);
22
+ return 0;
23
+ }
24
+ if ((command === "context" || command === "query") && args.flags.has("help")) {
25
+ printContextHelp(streams.stdout);
26
+ return 0;
27
+ }
20
28
  if (args.flags.has("help") || !command || command === "help" || command === "-h") {
21
29
  printHelp(streams.stdout);
22
30
  return 0;
@@ -54,8 +62,24 @@ export async function runCli(argv, streams = { stdout: process.stdout, stderr: p
54
62
  }
55
63
  return 0;
56
64
  }
65
+ if (command === "agent" && subcommand === "sync") {
66
+ const result = await syncAgentSkills({
67
+ agentId: flagString(args, "agent"),
68
+ yes: flagBool(args, "yes"),
69
+ dryRun: flagBool(args, "dry-run"),
70
+ json: flagBool(args, "json"),
71
+ streams
72
+ });
73
+ if (flagBool(args, "json")) {
74
+ writeLine(streams.stdout, JSON.stringify(result, null, 2));
75
+ }
76
+ else {
77
+ printAgentSyncResult(streams.stdout, result);
78
+ }
79
+ return 0;
80
+ }
57
81
  if (command === "agent" && subcommand === "check") {
58
- await printAgentCheck(streams.stdout, await discoverAgentTargets());
82
+ await printAgentCheckDetailed(streams.stdout, await discoverAgentTargets(), flagBool(args, "json"));
59
83
  return 0;
60
84
  }
61
85
  if (command === "agent" && (subcommand === "list" || !subcommand)) {
@@ -137,7 +161,7 @@ export async function runCli(argv, streams = { stdout: process.stdout, stderr: p
137
161
  if (!query) {
138
162
  throw new CliError("请提供查询主题。");
139
163
  }
140
- writeLine(streams.stdout, JSON.stringify(await buildContext(root, query), null, 2));
164
+ writeLine(streams.stdout, JSON.stringify(await buildContext(root, query, contextOptions(args)), null, 2));
141
165
  return 0;
142
166
  }
143
167
  if (command === "query") {
@@ -146,7 +170,7 @@ export async function runCli(argv, streams = { stdout: process.stdout, stderr: p
146
170
  if (!query) {
147
171
  throw new CliError("请提供查询主题。");
148
172
  }
149
- writeLine(streams.stdout, renderQuery(await buildContext(root, query)));
173
+ writeLine(streams.stdout, renderQuery(await buildContext(root, query, contextOptions(args))));
150
174
  return 0;
151
175
  }
152
176
  if (command === "next") {
@@ -247,6 +271,8 @@ function printHelp(stream) {
247
271
  writeLine(stream, " aiwiki agent list");
248
272
  writeLine(stream, " aiwiki agent install");
249
273
  writeLine(stream, " aiwiki agent install --agent codex --yes");
274
+ writeLine(stream, " aiwiki agent sync --yes");
275
+ writeLine(stream, " aiwiki agent check --json");
250
276
  writeLine(stream, " aiwiki prompt agent");
251
277
  writeLine(stream, " aiwiki doctor");
252
278
  writeLine(stream, " aiwiki status");
@@ -262,6 +288,41 @@ function printHelp(stream) {
262
288
  writeLine(stream, " aiwiki ingest-url <url> --content-file <file>");
263
289
  writeLine(stream, " aiwiki agent check");
264
290
  }
291
+ function printAgentHelp(stream) {
292
+ writeLine(stream, "AIWiki Agent commands");
293
+ writeLine(stream, "");
294
+ writeLine(stream, "Agent-first setup and upgrade:");
295
+ writeLine(stream, " aiwiki agent sync --yes");
296
+ writeLine(stream, " aiwiki agent sync --agent codex --yes");
297
+ writeLine(stream, " aiwiki agent sync --agent codex --dry-run");
298
+ writeLine(stream, " aiwiki agent sync --json --yes");
299
+ writeLine(stream, "");
300
+ writeLine(stream, "Status:");
301
+ writeLine(stream, " aiwiki agent check");
302
+ writeLine(stream, " aiwiki agent check --json");
303
+ writeLine(stream, "");
304
+ writeLine(stream, "Compatibility:");
305
+ writeLine(stream, " aiwiki agent install --agent codex --yes");
306
+ writeLine(stream, " aiwiki agent install --agent codex --yes --force");
307
+ writeLine(stream, "");
308
+ writeLine(stream, "sync is idempotent: missing targets are installed, current targets are left unchanged, and different targets are backed up before overwrite.");
309
+ }
310
+ function printContextHelp(stream) {
311
+ writeLine(stream, "AIWiki context/query");
312
+ writeLine(stream, "");
313
+ writeLine(stream, "Local Markdown/frontmatter retrieval for host Agents and humans:");
314
+ writeLine(stream, " aiwiki context <topic> --limit 10");
315
+ writeLine(stream, " aiwiki query <topic> --type wiki_entries --status active");
316
+ writeLine(stream, "");
317
+ writeLine(stream, "Filters:");
318
+ writeLine(stream, " --type wiki_entries|source_cards|claims|topics|outlines|raw_refs");
319
+ writeLine(stream, " --source-role input|processing|output");
320
+ writeLine(stream, " --wiki-type source_knowledge|personal_knowledge");
321
+ writeLine(stream, " --status active|to-review|ready|draft");
322
+ writeLine(stream, " --limit <1-50>");
323
+ writeLine(stream, "");
324
+ writeLine(stream, "context JSON includes query_scope, result_quality, recommended_next_action, match_reasons, quality_signals, and related_refs.");
325
+ }
265
326
  function parseLintSeverity(value) {
266
327
  if (value === undefined) {
267
328
  return undefined;
@@ -362,6 +423,40 @@ async function printAgentCheck(stream, targets) {
362
423
  }
363
424
  }
364
425
  }
426
+ async function printAgentCheckDetailed(stream, targets, json = false) {
427
+ const checked = await Promise.all(targets.map(async (target) => ({
428
+ ...target,
429
+ state: await inspectAgentTarget(target),
430
+ installed: target.target ? await exists(target.target) : false
431
+ })));
432
+ if (json) {
433
+ writeLine(stream, JSON.stringify({
434
+ schema_version: "aiwiki.agent_check.v1",
435
+ generated_at: new Date().toISOString(),
436
+ targets: checked.map((target) => ({
437
+ id: target.id,
438
+ name: target.name,
439
+ detected: target.detected,
440
+ installable: target.installable,
441
+ installed: target.installed,
442
+ state: target.state,
443
+ source: target.source,
444
+ target: target.target
445
+ }))
446
+ }, null, 2));
447
+ return;
448
+ }
449
+ writeLine(stream, "AIWiki Agent check");
450
+ for (const target of checked) {
451
+ writeLine(stream, `${target.id}: ${target.name} | detected=${target.detected ? "yes" : "no"} | installed=${target.installed ? "yes" : "no"} | installable=${target.installable ? "yes" : "no"} | state=${target.state}`);
452
+ if (target.detected && target.installable && (target.state === "missing" || target.state === "different")) {
453
+ writeLine(stream, ` suggested: aiwiki agent sync --agent ${target.id} --yes`);
454
+ }
455
+ else if (target.detected && !target.installable) {
456
+ writeLine(stream, " suggested: aiwiki prompt agent");
457
+ }
458
+ }
459
+ }
365
460
  async function installAgentSkill(options) {
366
461
  const targets = await discoverAgentTargets();
367
462
  const installable = targets.filter((target) => target.detected && target.installable);
@@ -400,8 +495,8 @@ async function installAgentSkill(options) {
400
495
  return undefined;
401
496
  }
402
497
  }
403
- await copyInstallFile(selected.source, selected.target, options.force);
404
- return selected;
498
+ const result = await copyInstallFileSafe(selected.source, selected.target, options.force);
499
+ return { ...selected, ...result };
405
500
  }
406
501
  async function askQuestion(streams, question) {
407
502
  if (!process.stdin.isTTY) {
@@ -415,7 +510,28 @@ async function askQuestion(streams, question) {
415
510
  rl.close();
416
511
  }
417
512
  }
513
+ async function syncAgentSkills(options) {
514
+ const targets = await discoverAgentTargets();
515
+ const selected = options.agentId ? targets.find((target) => target.id === options.agentId) : undefined;
516
+ if (!selected && options.agentId) {
517
+ throw new CliError(`未知宿主 Agent: ${options.agentId}`);
518
+ }
519
+ const syncTargets = selected ? [selected] : targets.filter((target) => target.detected && target.installable);
520
+ if (!syncTargets.length) {
521
+ throw new CliError("No detected installable Agent targets. Run aiwiki agent list or aiwiki prompt agent.");
522
+ }
523
+ if (!options.yes && !options.dryRun) {
524
+ throw new CliError("agent sync modifies Agent skill files. Re-run with --yes, or use --dry-run to preview.");
525
+ }
526
+ return {
527
+ schema_version: "aiwiki.agent_sync.v1",
528
+ generated_at: new Date().toISOString(),
529
+ dry_run: options.dryRun,
530
+ results: await Promise.all(syncTargets.map((target) => syncAgentTarget(target, options.dryRun)))
531
+ };
532
+ }
418
533
  async function copyInstallFile(source, target, force) {
534
+ return copyInstallFileSafe(source, target, force);
419
535
  await fs.access(source);
420
536
  if (!force && (await exists(target))) {
421
537
  throw new CliError(`目标文件已存在: ${target}。如需覆盖,请加 --force。`);
@@ -423,6 +539,84 @@ async function copyInstallFile(source, target, force) {
423
539
  await fs.mkdir(path.dirname(target), { recursive: true });
424
540
  await fs.copyFile(source, target);
425
541
  }
542
+ async function copyInstallFileSafe(source, target, force) {
543
+ await fs.access(source);
544
+ const targetExists = await exists(target);
545
+ if (!force && targetExists) {
546
+ throw new CliError(`目标文件已存在: ${target}。如需覆盖,请运行 aiwiki agent sync --agent <id> --yes,或为 install 加 --force。`);
547
+ }
548
+ if (targetExists && await sameFileContent(source, target)) {
549
+ return { action: "current" };
550
+ }
551
+ await fs.mkdir(path.dirname(target), { recursive: true });
552
+ const backupPath = targetExists ? await backupFile(target) : undefined;
553
+ await fs.copyFile(source, target);
554
+ return { action: targetExists ? "updated" : "installed", backupPath };
555
+ }
556
+ async function inspectAgentTarget(target) {
557
+ if (!target.installable || !target.source || !target.target) {
558
+ return "unsupported";
559
+ }
560
+ if (!(await exists(target.target))) {
561
+ return "missing";
562
+ }
563
+ return await sameFileContent(target.source, target.target) ? "current" : "different";
564
+ }
565
+ async function syncAgentTarget(target, dryRun) {
566
+ const state = await inspectAgentTarget(target);
567
+ const base = {
568
+ id: target.id,
569
+ name: target.name,
570
+ detected: target.detected,
571
+ installable: target.installable,
572
+ state,
573
+ target: target.target,
574
+ source: target.source,
575
+ changed: false,
576
+ dryRun
577
+ };
578
+ if (state === "unsupported" || !target.source || !target.target) {
579
+ return { ...base, action: "unsupported", note: target.note };
580
+ }
581
+ if (state === "current") {
582
+ return { ...base, action: "current" };
583
+ }
584
+ if (dryRun) {
585
+ return { ...base, action: state === "missing" ? "would_install" : "would_update", changed: true };
586
+ }
587
+ const result = await copyInstallFileSafe(target.source, target.target, true);
588
+ return { ...base, action: result.action, backupPath: result.backupPath, changed: result.action !== "current" };
589
+ }
590
+ async function sameFileContent(source, target) {
591
+ try {
592
+ const [sourceText, targetText] = await Promise.all([fs.readFile(source, "utf8"), fs.readFile(target, "utf8")]);
593
+ return sourceText === targetText;
594
+ }
595
+ catch {
596
+ return false;
597
+ }
598
+ }
599
+ async function backupFile(target) {
600
+ const parsed = path.parse(target);
601
+ const stamp = new Date().toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
602
+ const backupPath = path.join(parsed.dir, `${parsed.base}.bak-${stamp}`);
603
+ await fs.copyFile(target, backupPath);
604
+ return backupPath;
605
+ }
606
+ function printAgentSyncResult(stream, report) {
607
+ writeLine(stream, "AIWiki Agent sync");
608
+ writeLine(stream, `dry_run: ${report.dry_run ? "yes" : "no"}`);
609
+ for (const item of report.results) {
610
+ writeLine(stream, `${item.id}: ${item.name} | state=${item.state} | action=${item.action} | changed=${item.changed ? "yes" : "no"}`);
611
+ if (item.target) {
612
+ writeLine(stream, ` target: ${item.target}`);
613
+ }
614
+ if (item.backupPath) {
615
+ writeLine(stream, ` backup: ${item.backupPath}`);
616
+ }
617
+ }
618
+ writeLine(stream, "next: restart or reload the target Agent so it reads the synced AIWiki skill.");
619
+ }
426
620
  function printAgentPrompt(stream) {
427
621
  writeLine(stream, "AIWiki Agent 中文提示");
428
622
  writeLine(stream, "");
@@ -508,7 +702,7 @@ async function printNext(stream, root, runCount, checks, targets, report) {
508
702
  if (runCount === 0) {
509
703
  writeLine(stream, "");
510
704
  writeLine(stream, "No ingest records yet.");
511
- writeLine(stream, "- aiwiki agent install");
705
+ writeLine(stream, "- aiwiki agent sync --yes");
512
706
  writeLine(stream, "- Then ask the host Agent to ingest a URL.");
513
707
  writeLine(stream, "- AIWiki CLI does not fetch webpages; the host Agent supplies content.");
514
708
  writeLine(stream, "- repair_order: empty_workspace");
@@ -535,12 +729,25 @@ function recommendedNextAction(runCount, lintStatus, hasMissingSystemFiles) {
535
729
  return "next_action: aiwiki lint";
536
730
  }
537
731
  if (runCount === 0) {
538
- return "next_action: aiwiki agent install";
732
+ return "next_action: aiwiki agent sync --yes";
539
733
  }
540
734
  return "next_action: aiwiki query <topic>";
541
735
  }
736
+ function contextOptions(args) {
737
+ const limit = flagString(args, "limit");
738
+ return {
739
+ filters: {
740
+ type: flagString(args, "type"),
741
+ source_role: flagString(args, "source-role"),
742
+ wiki_type: flagString(args, "wiki-type"),
743
+ status: flagString(args, "status")
744
+ },
745
+ limit: limit === undefined ? undefined : Number(limit)
746
+ };
747
+ }
542
748
  function renderQuery(context) {
543
749
  const lines = [`AIWiki 查询: ${context.query}`, ""];
750
+ lines.push(`结果质量: matches=${context.result_quality.total_matches}, best_score=${context.result_quality.best_score}, has_wiki_entry=${context.result_quality.has_wiki_entry ? "yes" : "no"}`, `下一步建议: ${context.recommended_next_action}`, `查询范围: groups=${context.query_scope.searched_groups.join(",") || "none"}, limit=${context.query_scope.limit}, filters=${JSON.stringify(context.query_scope.filters)}`, "");
544
751
  appendQueryGroup(lines, "Wiki 条目", context.matches.wiki_entries);
545
752
  appendQueryGroup(lines, "资料卡", context.matches.source_cards);
546
753
  appendQueryGroup(lines, "选题", context.matches.topics);
@@ -561,9 +768,13 @@ function appendQueryGroup(lines, label, items) {
561
768
  }
562
769
  for (const item of items.slice(0, 5)) {
563
770
  lines.push(`- ${item.title} (${item.path})`);
771
+ lines.push(` score=${item.score}; reasons=${item.match_reasons.join(",") || "unknown"}; quality=${item.quality_signals.join(",") || "none"}`);
564
772
  if (item.summary) {
565
773
  lines.push(` ${item.summary}`);
566
774
  }
775
+ if (item.related_refs.length) {
776
+ lines.push(` related: ${item.related_refs.slice(0, 5).join("; ")}`);
777
+ }
567
778
  if (item.warnings.length) {
568
779
  lines.push(` 提示: ${item.warnings.join(";")}`);
569
780
  }
@@ -11,13 +11,28 @@ const GROUPS = [
11
11
  { key: "outlines", dir: "08-outputs/outlines", weight: 2 },
12
12
  { key: "raw_refs", dir: "02-raw/articles", weight: 1 }
13
13
  ];
14
- export async function buildContext(rootPath, query, now = new Date().toISOString()) {
14
+ const DEFAULT_LIMIT = 10;
15
+ export async function buildContext(rootPath, query, options = {}, now = new Date().toISOString()) {
15
16
  const root = path.resolve(rootPath);
16
17
  const tokens = tokenize(query);
18
+ const limit = normalizeLimit(options.limit);
19
+ const filters = normalizeFilters(options.filters);
17
20
  const result = {
18
21
  schema_version: "aiwiki.context.v1",
19
22
  query,
20
23
  generated_at: now,
24
+ query_scope: {
25
+ filters,
26
+ limit,
27
+ searched_groups: []
28
+ },
29
+ result_quality: {
30
+ total_matches: 0,
31
+ best_score: 0,
32
+ has_wiki_entry: false,
33
+ warnings: []
34
+ },
35
+ recommended_next_action: "broaden_query",
21
36
  matches: {
22
37
  wiki_entries: [],
23
38
  source_cards: [],
@@ -26,34 +41,41 @@ export async function buildContext(rootPath, query, now = new Date().toISOString
26
41
  outlines: [],
27
42
  raw_refs: []
28
43
  },
29
- suggested_answer_structure: ["主题概览", "核心观点", "已有资料依据", "可复用判断", "下一步建议"],
44
+ suggested_answer_structure: ["topic overview", "core claims", "available evidence", "reuse judgment", "next action"],
30
45
  warnings: []
31
46
  };
32
47
  if (!tokens.length) {
33
48
  result.warnings.push("query is empty after tokenization");
49
+ finalizeQuality(result);
34
50
  return result;
35
51
  }
36
52
  for (const group of GROUPS.filter((item) => item.key !== "raw_refs")) {
53
+ if (!groupAllowed(group.key, filters.type)) {
54
+ continue;
55
+ }
37
56
  const dir = path.join(root, group.dir);
38
57
  if (!(await exists(dir))) {
39
58
  continue;
40
59
  }
41
- const matches = await searchDir(root, dir, tokens, group.weight);
42
- result.matches[group.key].push(...matches.slice(0, 10));
60
+ result.query_scope.searched_groups.push(group.key);
61
+ const matches = await searchDir(root, dir, tokens, group.weight, group.key, filters);
62
+ result.matches[group.key].push(...matches.slice(0, limit));
43
63
  }
44
- if (!result.matches.wiki_entries.length) {
45
- result.warnings.push("未命中 Wiki Entry,结果可能来自资料卡、选题或原文引用。");
64
+ if (!result.matches.wiki_entries.length && groupAllowed("raw_refs", filters.type)) {
65
+ result.warnings.push("No Wiki Entry matched; results may come from source cards, topics, outlines, or raw references.");
46
66
  const rawGroup = GROUPS.find((item) => item.key === "raw_refs");
47
67
  if (rawGroup) {
48
68
  const rawDir = path.join(root, rawGroup.dir);
49
69
  if (await exists(rawDir)) {
50
- result.matches.raw_refs.push(...(await searchDir(root, rawDir, tokens, rawGroup.weight)).slice(0, 10));
70
+ result.query_scope.searched_groups.push(rawGroup.key);
71
+ result.matches.raw_refs.push(...(await searchDir(root, rawDir, tokens, rawGroup.weight, rawGroup.key, filters)).slice(0, limit));
51
72
  }
52
73
  }
53
74
  }
75
+ finalizeQuality(result);
54
76
  return result;
55
77
  }
56
- async function searchDir(root, dir, tokens, weight) {
78
+ async function searchDir(root, dir, tokens, weight, groupKey, filters) {
57
79
  const files = await listMarkdownFiles(dir);
58
80
  const matches = [];
59
81
  for (const file of files) {
@@ -61,12 +83,24 @@ async function searchDir(root, dir, tokens, weight) {
61
83
  const parsed = parseMarkdown(text);
62
84
  const rel = relativePath(root, file);
63
85
  const title = frontmatterString(parsed.frontmatter, "title") ?? path.basename(file, ".md");
86
+ const type = frontmatterString(parsed.frontmatter, "type") ?? groupKey;
87
+ const status = frontmatterString(parsed.frontmatter, "status");
88
+ const sourceRole = frontmatterString(parsed.frontmatter, "source_role");
89
+ const wikiType = frontmatterString(parsed.frontmatter, "wiki_type");
90
+ if (!passesFilters({ type, groupKey, status, source_role: sourceRole, wiki_type: wikiType }, filters)) {
91
+ continue;
92
+ }
93
+ const topics = frontmatterArray(parsed.frontmatter, "topics");
94
+ const tags = frontmatterArray(parsed.frontmatter, "tags");
95
+ const relatedRefs = relatedReferences(parsed.frontmatter, parsed.body);
96
+ const sourceUrl = frontmatterString(parsed.frontmatter, "source_url") ?? "";
64
97
  const haystack = [
65
98
  rel,
66
99
  title,
67
- frontmatterString(parsed.frontmatter, "source_url") ?? "",
68
- frontmatterArray(parsed.frontmatter, "topics").join(" "),
69
- frontmatterArray(parsed.frontmatter, "tags").join(" "),
100
+ sourceUrl,
101
+ topics.join(" "),
102
+ tags.join(" "),
103
+ relatedRefs.join(" "),
70
104
  parsed.body
71
105
  ].join("\n").toLowerCase();
72
106
  const hits = tokens.filter((token) => haystack.includes(token.toLowerCase())).length;
@@ -77,8 +111,9 @@ async function searchDir(root, dir, tokens, weight) {
77
111
  const quality = frontmatterString(parsed.frontmatter, "quality");
78
112
  const groundingMarkers = frontmatterArray(parsed.frontmatter, "grounding_markers");
79
113
  const groundingNeedsReview = frontmatterBoolean(parsed.frontmatter, "grounding_needs_review");
114
+ const groundingEvidenceAvailable = frontmatterBoolean(parsed.frontmatter, "grounding_evidence_available");
80
115
  const warnings = generationMode === "deterministic_fallback"
81
- ? [" Wiki Entry deterministic fallback,仅包含来源、正文预览和待补全区。"]
116
+ ? ["This Wiki Entry is a deterministic fallback; it may need host-agent enrichment."]
82
117
  : [];
83
118
  if (groundingNeedsReview) {
84
119
  warnings.push(`Grounding needs review${groundingMarkers.length ? `: ${groundingMarkers.join(", ")}` : ""}.`);
@@ -88,11 +123,19 @@ async function searchDir(root, dir, tokens, weight) {
88
123
  path: rel,
89
124
  summary: frontmatterString(parsed.frontmatter, "summary") ?? summarize(parsed.body, quality),
90
125
  score: Number(((hits / tokens.length) * weight).toFixed(2)),
91
- topics: frontmatterArray(parsed.frontmatter, "topics"),
92
- source_url: frontmatterString(parsed.frontmatter, "source_url") ?? "",
126
+ type,
127
+ topics,
128
+ tags,
129
+ source_url: sourceUrl,
130
+ status,
131
+ source_role: sourceRole,
132
+ wiki_type: wikiType,
133
+ match_reasons: matchReasons(tokens, { rel, title, body: parsed.body, topics, tags, relatedRefs, sourceUrl }),
134
+ quality_signals: qualitySignals({ quality, generationMode, groundingEvidenceAvailable, groundingNeedsReview, status, relatedRefs }),
135
+ related_refs: relatedRefs,
93
136
  generation_mode: generationMode,
94
137
  quality,
95
- grounding_evidence_available: frontmatterBoolean(parsed.frontmatter, "grounding_evidence_available"),
138
+ grounding_evidence_available: groundingEvidenceAvailable,
96
139
  grounding_needs_review: groundingNeedsReview,
97
140
  grounding_markers: groundingMarkers,
98
141
  warnings
@@ -125,7 +168,121 @@ function tokenize(value) {
125
168
  function summarize(body, quality) {
126
169
  const compact = body.replace(/\s+/g, " ").trim();
127
170
  if (quality === "scaffold") {
128
- return "仅有正文预览,未生成高质量摘要。";
171
+ return "Only a scaffold preview is available; enrich before relying on it as a final answer.";
129
172
  }
130
173
  return compact.length > 180 ? `${compact.slice(0, 180)}...` : compact;
131
174
  }
175
+ function normalizeLimit(value) {
176
+ if (typeof value !== "number" || !Number.isFinite(value)) {
177
+ return DEFAULT_LIMIT;
178
+ }
179
+ return Math.max(1, Math.min(50, Math.floor(value)));
180
+ }
181
+ function normalizeFilters(filters) {
182
+ return Object.fromEntries(Object.entries(filters ?? {})
183
+ .filter(([, value]) => typeof value === "string" && value.trim())
184
+ .map(([key, value]) => [key, String(value).trim()]));
185
+ }
186
+ function groupAllowed(groupKey, typeFilter) {
187
+ if (!typeFilter) {
188
+ return true;
189
+ }
190
+ return normalizeType(typeFilter) === normalizeType(groupKey);
191
+ }
192
+ function passesFilters(item, filters) {
193
+ if (filters.type && normalizeType(filters.type) !== normalizeType(item.type) && normalizeType(filters.type) !== normalizeType(item.groupKey)) {
194
+ return false;
195
+ }
196
+ if (filters.status && filters.status !== item.status) {
197
+ return false;
198
+ }
199
+ if (filters.source_role && filters.source_role !== item.source_role) {
200
+ return false;
201
+ }
202
+ if (filters.wiki_type && filters.wiki_type !== item.wiki_type) {
203
+ return false;
204
+ }
205
+ return true;
206
+ }
207
+ function normalizeType(value) {
208
+ return value.replace(/-/g, "_").toLowerCase();
209
+ }
210
+ function relatedReferences(frontmatter, body) {
211
+ const refs = [
212
+ frontmatterString(frontmatter, "source_card"),
213
+ frontmatterString(frontmatter, "raw_file"),
214
+ frontmatterString(frontmatter, "claims_note"),
215
+ frontmatterString(frontmatter, "assets_note"),
216
+ frontmatterString(frontmatter, "topics_note"),
217
+ frontmatterString(frontmatter, "outline_note"),
218
+ ...extractWikilinks(body)
219
+ ].filter((value) => Boolean(value));
220
+ return Array.from(new Set(refs));
221
+ }
222
+ function extractWikilinks(body) {
223
+ const refs = [];
224
+ const pattern = /\[\[([^\]|]+)(?:\|[^\]]+)?\]\]/g;
225
+ for (const match of body.matchAll(pattern)) {
226
+ refs.push(match[1].trim());
227
+ }
228
+ return refs;
229
+ }
230
+ function matchReasons(tokens, fields) {
231
+ const reasons = new Set();
232
+ for (const token of tokens.map((item) => item.toLowerCase())) {
233
+ if (fields.title.toLowerCase().includes(token))
234
+ reasons.add("title");
235
+ if (fields.rel.toLowerCase().includes(token))
236
+ reasons.add("path");
237
+ if (fields.sourceUrl.toLowerCase().includes(token))
238
+ reasons.add("source_url");
239
+ if (fields.topics.join(" ").toLowerCase().includes(token))
240
+ reasons.add("topics");
241
+ if (fields.tags.join(" ").toLowerCase().includes(token))
242
+ reasons.add("tags");
243
+ if (fields.relatedRefs.join(" ").toLowerCase().includes(token))
244
+ reasons.add("relationships");
245
+ if (fields.body.toLowerCase().includes(token))
246
+ reasons.add("body");
247
+ }
248
+ return Array.from(reasons);
249
+ }
250
+ function qualitySignals(item) {
251
+ const signals = [];
252
+ if (item.quality)
253
+ signals.push(`quality:${item.quality}`);
254
+ if (item.status)
255
+ signals.push(`status:${item.status}`);
256
+ if (item.generationMode)
257
+ signals.push(`generation_mode:${item.generationMode}`);
258
+ if (item.groundingEvidenceAvailable === true)
259
+ signals.push("grounding:evidence_available");
260
+ if (item.groundingEvidenceAvailable === false)
261
+ signals.push("grounding:no_evidence_flag");
262
+ if (item.groundingNeedsReview)
263
+ signals.push("grounding:needs_review");
264
+ if (item.relatedRefs.length)
265
+ signals.push("relationships:present");
266
+ return signals;
267
+ }
268
+ function finalizeQuality(result) {
269
+ const all = Object.values(result.matches).flat();
270
+ result.result_quality = {
271
+ total_matches: all.length,
272
+ best_score: all.reduce((best, item) => Math.max(best, item.score), 0),
273
+ has_wiki_entry: result.matches.wiki_entries.length > 0,
274
+ warnings: result.warnings
275
+ };
276
+ if (!all.length) {
277
+ result.recommended_next_action = "broaden_query_or_ingest_source";
278
+ }
279
+ else if (!result.matches.wiki_entries.length) {
280
+ result.recommended_next_action = "review_source_cards_then_create_wiki_entry";
281
+ }
282
+ else if (all.some((item) => item.grounding_needs_review || item.quality === "scaffold")) {
283
+ result.recommended_next_action = "review_grounding_or_enrich_entry";
284
+ }
285
+ else {
286
+ result.recommended_next_action = "use_matches_for_answer";
287
+ }
288
+ }
@@ -247,3 +247,57 @@ Lint 输出会包含摘要、最高优先级问题、分级报告,以及建议
247
247
  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.
248
248
 
249
249
  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.
250
+
251
+ ## Context JSON for Retrieval
252
+
253
+ When answering from AIWiki, prefer:
254
+
255
+ ```bash
256
+ aiwiki context "<topic>" --limit 10
257
+ ```
258
+
259
+ Use filters when the user intent is narrow:
260
+
261
+ ```bash
262
+ aiwiki context "<topic>" --type wiki_entries --source-role input --wiki-type source_knowledge --status active
263
+ aiwiki context "<topic>" --type source_cards --status to-review
264
+ ```
265
+
266
+ `context` is local Markdown/frontmatter retrieval only. Do not describe it as vector search, RAG, a database query, or external web search.
267
+
268
+ Read these fields before responding:
269
+
270
+ - `query_scope`: what was searched and which filters were applied.
271
+ - `result_quality.total_matches` and `result_quality.has_wiki_entry`: whether the answer is grounded in a Wiki Entry or only in supporting artifacts.
272
+ - `recommended_next_action`: whether to answer, enrich, review grounding, or broaden the query.
273
+ - `match_reasons`: why each result matched, such as title, body, tags, topics, relationships, or source URL.
274
+ - `quality_signals`: scaffold, enriched, status, grounding, and relationship hints.
275
+ - `related_refs`: local wikilinks and frontmatter relationships such as `source_card`, `raw_file`, and run artifacts.
276
+
277
+ If `quality_signals` contains `quality:scaffold` or `grounding:needs_review`, tell the user the result is a traceable lead that needs enrichment or review. Do not present it as confirmed final knowledge.
278
+
279
+ ## Agent Skill Upgrade Flow
280
+
281
+ When the user asks you to install, update, upgrade, or repair AIWiki integration, run:
282
+
283
+ ```bash
284
+ aiwiki agent sync --yes
285
+ aiwiki agent check
286
+ ```
287
+
288
+ Use JSON when you need exact state:
289
+
290
+ ```bash
291
+ aiwiki agent sync --json --yes
292
+ aiwiki agent check --json
293
+ ```
294
+
295
+ Interpret sync results:
296
+
297
+ - `installed`: target did not exist and now has the packaged skill.
298
+ - `current`: installed skill already matches the packaged skill.
299
+ - `updated`: installed skill differed; the old file was backed up and replaced.
300
+ - `would_install` / `would_update`: dry-run preview only.
301
+ - `unsupported`: no safe automatic target is known; use `aiwiki prompt agent`.
302
+
303
+ After sync, report the target path, backup path when present, and that the user may need to restart or reload the Agent. Do not claim the new skill is active until the Agent has reloaded it.
@@ -0,0 +1,347 @@
1
+ # AIWiki 定位调整与产品方案
2
+
3
+ 本文基于 `20260607-aiwiki定位.md` 的判断,并结合当前 AIWiki 仓库现状整理。结论是:文档大方向可取,但不能把 AIWiki 改成重学习、重记忆、重人工复习的 Knowledge MEMO。AIWiki 更适合继续做 Agent-first 本地上下文编译器。
4
+
5
+ ## 一句话定位
6
+
7
+ AIWiki 不是替代搜索,也不是替你读书,而是把你真正需要反复调用的资料、判断和输出,编译成 AI 能查询、能引用、能检查、能继续使用的本地知识资产。
8
+
9
+ 更锋利的对外表达:
10
+
11
+ > 公开知识交给搜索。
12
+ > 私有判断、项目经验、输出风格、证据链,交给 AIWiki。
13
+
14
+ ## 建议取舍判断
15
+
16
+ | 文档建议 | 判断 | 原因 | 落地优先级 |
17
+ | --- | --- | --- | --- |
18
+ | 不把 AIWiki 宣传成普通个人知识库或资料索引 | 可取 | 当前 README 已强调 Agent-first 和本地 LLM-wiki 后端,但还可以更明确地区分搜索、收藏夹和上下文编译器 | P0 |
19
+ | 保留 Markdown Wiki 形态 | 可取 | AIWiki 面向人和 Agent 协作,Markdown 的可读、可 diff、可回滚、可被 Agent 编辑是核心优势 | P0 |
20
+ | 强化个人判断层 | 强烈可取 | 当前 payload 已有 `source_role=processing`,但产品心智和目录/命令输出还没有把 personal insight 变成一等资产 | P0 |
21
+ | 强化输出资产层 | 强烈可取 | 当前已有 `source_role=output` 和 `represents_user_view=true` 边界,这是 AIWiki 最有差异化的一层 | P0 |
22
+ | `context` 从资料检索升级为任务上下文包 | 强烈可取 | 当前 `context` 已是 JSON 结构,适合加 `mode` 和按任务分组的结果,不需要推倒重做 | P0 |
23
+ | `query` 输出按资料类型分组 | 可取 | 这是低成本心智升级:从“搜索结果”变成“知识资产调度” | P1 |
24
+ | `lint` 从结构检查升级为知识质量检查 | 可取 | 当前 lint 已有元数据边界、fallback、grounding 检查,可继续加入 insight/output/evidence 使用关系检查 | P1 |
25
+ | `next` 从命令导航升级为加工建议 | 可取 | 适合帮助用户把资料推进到 insight、output、topic 或 review,而不是只提示下一条 CLI 命令 | P1 |
26
+ | 新增 Retain / Review | 部分可取 | 可以做轻量回看,但不应成为主流程,也不要第一阶段引入 FSRS 或 Anki 式学习系统 | P2 |
27
+ | 把 AIWiki 改成纯人脑内化工具 | 不建议 | 会偏离当前 Agent-first CLI 边界,也会让产品过重 | 不做 |
28
+ | 引入多知识库、自动采集、内置网页抓取 | 不建议 | 与当前仓库 AGENTS.md 和 README 边界冲突;这些属于 AIWiki Pro 或宿主 Agent/服务层,不属于基础 CLI | 不做 |
29
+
30
+ ## 产品原则
31
+
32
+ 1. AIWiki 只沉淀值得复用的上下文,不收藏所有公开知识。
33
+ 2. 外部资料默认不代表用户观点。
34
+ 3. 用户自己的输出和判断比外部输入更值钱。
35
+ 4. AIWiki CLI 不调用 LLM;高质量分析由宿主 Agent 提供,AIWiki 负责规范、落盘、追踪、检查。
36
+ 5. 保持单知识库、单命令 `aiwiki`、本地 Markdown 的基本边界。
37
+ 6. Review / Retain 是增强闭环,不是默认主流程。
38
+
39
+ ## 三类知识资产模型
40
+
41
+ ### 1. Source Knowledge:来源证据
42
+
43
+ 用于保存外部文章、网页、报告、视频转写、书籍摘录等。
44
+
45
+ 建议字段:
46
+
47
+ ```yaml
48
+ source_role: input
49
+ wiki_type: source_knowledge
50
+ represents_user_view: false
51
+ ```
52
+
53
+ 价值标准:
54
+
55
+ - 是否能作为证据引用。
56
+ - 是否能支撑观点或反驳观点。
57
+ - 是否能生成选题、方案、脚本或研究方向。
58
+ - 是否能和已有资料、判断、输出建立连接。
59
+
60
+ ### 2. Personal Insight:个人判断
61
+
62
+ 用于保存用户或宿主 Agent 帮用户整理出的判断、取舍、冲突、决策理由。
63
+
64
+ 建议先复用现有 `source_role=processing`,并把 `wiki_type` 从宽泛的 `thought_note` 收窄为更明确的 `personal_insight` 或 `decision_note`。第一阶段可以兼容旧值。
65
+
66
+ 建议字段:
67
+
68
+ ```yaml
69
+ type: insight_note
70
+ source_role: processing
71
+ wiki_type: personal_insight
72
+ represents_user_view: true
73
+ confidence: low | medium | high
74
+ based_on:
75
+ - 03-sources/article-cards/example.md
76
+ - 05-wiki/source-knowledge/example.md
77
+ used_for:
78
+ - article
79
+ - product_decision
80
+ - client_solution
81
+ - research
82
+ ```
83
+
84
+ 条目应回答:
85
+
86
+ - 我认同什么,为什么。
87
+ - 我反对什么,为什么。
88
+ - 对项目、文章、产品或客户方案有什么启发。
89
+ - 能否转成文章观点、产品决策或执行动作。
90
+ - 和我已有判断是否冲突。
91
+
92
+ ### 3. Output Knowledge:可复用输出
93
+
94
+ 用于保存用户已经发布或交付过的文章、方案、PRD、README、报价、视频脚本、群内回答等。
95
+
96
+ 建议字段:
97
+
98
+ ```yaml
99
+ source_role: output
100
+ wiki_type: personal_knowledge
101
+ represents_user_view: true
102
+ ```
103
+
104
+ 价值标准:
105
+
106
+ - 能反映用户真实观点。
107
+ - 能反映用户表达风格。
108
+ - 能被新文章、新方案、新脚本复用。
109
+ - 能回溯到支撑它的 source 和 insight。
110
+
111
+ ## 命令方案
112
+
113
+ ### `aiwiki context`
114
+
115
+ 定位:从“相关资料包”升级为“任务上下文包”。
116
+
117
+ 新增 `--mode`:
118
+
119
+ ```bash
120
+ aiwiki context "AI知识库是否还有必要" --mode write
121
+ aiwiki context "AIWiki产品定位" --mode decide
122
+ aiwiki context "Karpathy LLM Wiki" --mode research
123
+ aiwiki context "我的公众号文章风格" --mode style
124
+ aiwiki context "某个知识点" --mode learn
125
+ ```
126
+
127
+ 建议返回结构:
128
+
129
+ ```json
130
+ {
131
+ "schema_version": "aiwiki.context.v2",
132
+ "query": "AIWiki产品定位",
133
+ "mode": "decide",
134
+ "external_sources": [],
135
+ "personal_insights": [],
136
+ "past_outputs": [],
137
+ "conflicts": [],
138
+ "usable_arguments": [],
139
+ "missing_context": [],
140
+ "recommended_next_action": ""
141
+ }
142
+ ```
143
+
144
+ 兼容策略:
145
+
146
+ - 保留 v1 `matches` 结构一段时间。
147
+ - v2 可以先在结果中新增字段,不立刻移除旧字段。
148
+ - `--mode` 缺省为 `general`。
149
+
150
+ ### `aiwiki query`
151
+
152
+ 定位:给人看的知识资产摘要,不是普通搜索结果。
153
+
154
+ 建议输出分组:
155
+
156
+ ```text
157
+ 外部资料
158
+ - ...
159
+
160
+ 个人判断
161
+ - ...
162
+
163
+ 已发布输出
164
+ - ...
165
+
166
+ 证据与冲突
167
+ - ...
168
+
169
+ 建议下一步
170
+ - ...
171
+ ```
172
+
173
+ 优先级:
174
+
175
+ - 命中 output 和 insight 时,展示在 source 前面。
176
+ - source 只作为证据,不默认当成结论。
177
+
178
+ ### `aiwiki lint`
179
+
180
+ 定位:从文件结构健康检查升级为知识资产健康检查。
181
+
182
+ 保留现有检查:
183
+
184
+ - 缺少系统文件。
185
+ - Source Card 没有 Wiki Entry。
186
+ - Wiki Entry 缺少 source_card / raw_file。
187
+ - fallback scaffold 过期。
188
+ - grounding needs review。
189
+ - `represents_user_view=true` 但不是 output。
190
+ - 重复 URL、重复标题、断链。
191
+
192
+ 新增质量检查:
193
+
194
+ - 某主题只有 source,没有 insight。
195
+ - 某 insight 没有 `based_on`。
196
+ - 某 output 没有关联 source 或 insight。
197
+ - source 被错误标记为代表用户观点。
198
+ - claim 没有 source quote 或证据边界。
199
+ - 某主题存在冲突观点但没有决策结论。
200
+ - enriched entry 只有摘要,没有 reusable judgment / reusable knowledge。
201
+
202
+ ### `aiwiki next`
203
+
204
+ 定位:从命令导航升级为加工建议。
205
+
206
+ 建议输出:
207
+
208
+ ```text
209
+ 当前最值得做:
210
+ - 这 3 篇 source 只有摘要,没有个人判断。
211
+ - 这个主题已有 5 条 source,可以生成主题页。
212
+ - 这条 output 没有证据链,建议补 linked sources。
213
+ - 这个观点和历史输出存在冲突,建议进入 review。
214
+ ```
215
+
216
+ ### `aiwiki review` 或 `aiwiki retain`
217
+
218
+ 定位:轻量回看,不做重学习系统。
219
+
220
+ 第一版只做候选生成:
221
+
222
+ ```text
223
+ 今日建议回看:
224
+ - 3 条个人 insight
225
+ - 2 条高价值 source knowledge
226
+ - 1 条已输出文章中的核心观点
227
+
228
+ 可生成卡片:
229
+ - 观点卡
230
+ - 证据卡
231
+ - 反驳卡
232
+ - 案例卡
233
+ - 输出卡
234
+ ```
235
+
236
+ 暂不做:
237
+
238
+ - FSRS。
239
+ - 每日强制复习。
240
+ - Anki 兼容。
241
+ - 学习进度系统。
242
+
243
+ ## README 与对外话术
244
+
245
+ 建议把开头改得更锋利:
246
+
247
+ ```text
248
+ AIWiki 是一个 Agent-first 本地上下文编译器。
249
+
250
+ 它不替代搜索,也不替你读书,而是把你真正需要反复调用的资料、判断和输出,编译成 AI 能查询、能引用、能检查、能继续使用的本地知识资产。
251
+ ```
252
+
253
+ “它解决什么”建议改成:
254
+
255
+ - 公开资料搜索得到,但你的使用场景、判断和输出痕迹搜不到。
256
+ - 链接和资料散在聊天记录里,后续很难被 Agent 复用。
257
+ - AI 总结过一次内容,但没有沉淀成可追踪、可查询、可检查的上下文资产。
258
+ - 想把来源证据、个人判断、选题、大纲和已输出内容放进同一个本地知识系统。
259
+ - 想让宿主 Agent 负责理解内容,让 AIWiki 负责稳定落盘、连接和检查。
260
+
261
+ 不建议继续主打:
262
+
263
+ ```text
264
+ 打造你的 AI 个人知识库
265
+ ```
266
+
267
+ 这个说法太泛,容易被理解成“AI 时代还要不要收藏夹”。
268
+
269
+ ## 分阶段落地
270
+
271
+ ### P0:定位与 schema 心智对齐
272
+
273
+ 目标:不大改代码,先把产品定位和现有字段解释清楚。
274
+
275
+ 任务:
276
+
277
+ 1. 更新 README、FAQ、USAGE 的定位话术。
278
+ 2. 在 docs 中新增“三类资产模型”说明。
279
+ 3. 在 Agent handoff / skill 中要求宿主 Agent 区分 source、insight、output。
280
+ 4. 明确 `source_role=input` 永远不默认代表用户观点。
281
+ 5. 为 `processing` 角色补充 personal insight 示例 payload。
282
+
283
+ 验收:
284
+
285
+ - 新用户能在 README 前 30 秒理解 AIWiki 不是搜索替代品。
286
+ - 文档能解释 source、insight、output 三层差异。
287
+ - 不引入新命令、不破坏旧 payload。
288
+
289
+ ### P1:任务上下文与质量检查
290
+
291
+ 目标:让 AIWiki 从“能查到资料”升级为“能为任务组织上下文”。
292
+
293
+ 任务:
294
+
295
+ 1. `context` 增加 `--mode`。
296
+ 2. `context` 输出新增 `external_sources`、`personal_insights`、`past_outputs`、`conflicts`、`usable_arguments`、`missing_context`。
297
+ 3. `query` 输出按资产类型分组。
298
+ 4. `lint` 增加 insight/output/evidence 质量检查。
299
+ 5. `next` 增加加工建议。
300
+
301
+ 验收:
302
+
303
+ - 同一个 query 在 `write`、`decide`、`research`、`style` 下返回不同的建议结构。
304
+ - 有 insight/output 时优先暴露,不被 source 淹没。
305
+ - `lint` 能指出“只有资料、没有个人判断”和“输出没有证据链”。
306
+
307
+ ### P2:轻量回看闭环
308
+
309
+ 目标:吸收 Retain 思想,但不把 AIWiki 变成学习软件。
310
+
311
+ 任务:
312
+
313
+ 1. 新增 `review` 或 `retain` 的候选生成命令。
314
+ 2. 支持观点卡、证据卡、反驳卡、案例卡、输出卡。
315
+ 3. Review Queue 只作为入口,不成为默认强制流程。
316
+
317
+ 验收:
318
+
319
+ - 命令能生成今日回看候选。
320
+ - 不需要用户维护复杂学习计划。
321
+ - 不引入 FSRS 或重型记忆系统。
322
+
323
+ ## 不做清单
324
+
325
+ - 不做通用网页抓取。
326
+ - 不做多知识库。
327
+ - 不做 CLI 内置 LLM。
328
+ - 不做默认人工审核流程。
329
+ - 不把 Review Queue 变成强制主流程。
330
+ - 不把 AIWiki 改成 Anki 或 Knowledge MEMO。
331
+ - 不用 `aiwiki-pro` 作为用户命令。
332
+
333
+ ## 推荐任务拆分
334
+
335
+ 1. `AIWIKI-POS-001`:更新 README / FAQ / USAGE 定位话术。
336
+ 2. `AIWIKI-POS-002`:补充三类资产模型文档和示例 payload。
337
+ 3. `AIWIKI-CTX-001`:为 `context` 增加 `--mode` 和 v2 结果字段。
338
+ 4. `AIWIKI-QRY-001`:让 `query` 按 source / insight / output 分组展示。
339
+ 5. `AIWIKI-LINT-001`:增加知识质量 lint。
340
+ 6. `AIWIKI-NEXT-001`:让 `next` 输出加工建议。
341
+ 7. `AIWIKI-REV-001`:设计轻量 review / retain 候选生成。
342
+
343
+ ## 最终判断
344
+
345
+ 这份定位文档对 AIWiki 是利好。它指出了“普通个人知识库/资料索引”在 AI 时代价值下降,这个判断应该接受;但它没有否定 AIWiki 的 Agent-first 本地上下文后端方向。真正可取的调整,是把 AIWiki 从“存知识”进一步收窄成“存上下文、判断和使用痕迹”。
346
+
347
+ AIWiki 下一步最该做的不是扩功能,而是把已有地基讲清楚、排出优先级,然后补齐 personal insight、output knowledge、task context 和 quality lint 这四块。
package/docs/USAGE.md CHANGED
@@ -434,3 +434,59 @@ aiwiki status
434
434
  - `next_action`: the recommended next command.
435
435
 
436
436
  `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.
437
+
438
+ ## Query and Context Filters
439
+
440
+ `aiwiki context` and `aiwiki query` use local Markdown/frontmatter search. They do not use vector search, a database, external search, or RAG-over-wiki.
441
+
442
+ Useful filters:
443
+
444
+ ```bash
445
+ aiwiki context "AI Agent" --type wiki_entries --source-role input --wiki-type source_knowledge --status active --limit 5
446
+ aiwiki query "AI Agent" --type source_cards --status to-review --limit 3
447
+ ```
448
+
449
+ Supported filters:
450
+
451
+ - `--type`: one result group, such as `wiki_entries`, `source_cards`, `claims`, `topics`, `outlines`, or `raw_refs`.
452
+ - `--source-role`: frontmatter `source_role`, usually `input`, `processing`, or `output`.
453
+ - `--wiki-type`: frontmatter `wiki_type`, such as `source_knowledge` or `personal_knowledge`.
454
+ - `--status`: frontmatter status, such as `active`, `to-review`, `ready`, or `draft`.
455
+ - `--limit`: per-group result limit, clamped from 1 to 50.
456
+
457
+ The JSON result keeps the stable `schema_version: "aiwiki.context.v1"` and now also includes:
458
+
459
+ - `query_scope`: filters, limit, and searched groups.
460
+ - `result_quality`: total matches, best score, whether a Wiki Entry was found, and warnings.
461
+ - `recommended_next_action`: for example `use_matches_for_answer`, `review_grounding_or_enrich_entry`, or `broaden_query_or_ingest_source`.
462
+ - Per match: `match_reasons`, `quality_signals`, and `related_refs`.
463
+
464
+ Use `match_reasons` to explain why a result matched. Use `quality_signals` before answering confidently: scaffold or grounding-review entries should be treated as traceable leads, not final knowledge.
465
+
466
+ ## Agent Skill Sync
467
+
468
+ AIWiki does not modify Agent configuration during `npm install`. After first install or upgrade, sync the packaged skill explicitly:
469
+
470
+ ```bash
471
+ aiwiki agent sync --yes
472
+ aiwiki agent check
473
+ ```
474
+
475
+ For one host Agent:
476
+
477
+ ```bash
478
+ aiwiki agent sync --agent codex --yes
479
+ aiwiki agent sync --agent claude --yes
480
+ ```
481
+
482
+ Useful Agent-facing options:
483
+
484
+ ```bash
485
+ aiwiki agent sync --dry-run
486
+ aiwiki agent sync --json --yes
487
+ aiwiki agent check --json
488
+ aiwiki agent help
489
+ aiwiki context --help
490
+ ```
491
+
492
+ `agent sync` is safe for first install and cross-version upgrades. It installs missing targets, leaves current targets unchanged, and backs up changed installed skill files before overwrite. If a backup is created, tell the user the backup path and that rollback means copying the backup file back to the target path.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@itradingai/aiwiki",
3
- "version": "0.2.16",
3
+ "version": "0.2.18",
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",
@@ -15,15 +15,23 @@ Use this protocol when the user asks:
15
15
  aiwiki lint
16
16
  ```
17
17
 
18
- 2. Read the terminal report.
19
- 3. Mention the report path, usually:
18
+ 2. When you need machine-readable triage, call:
19
+
20
+ ```bash
21
+ aiwiki lint --json
22
+ aiwiki next
23
+ ```
24
+
25
+ 3. Read the terminal report.
26
+ 4. Mention the report path, usually:
20
27
 
21
28
  ```text
22
29
  dashboards/Lint Report.md
23
30
  ```
24
31
 
25
- 4. Explain warnings and errors as structure-health feedback.
26
- 5. Do not frame lint as "the user must manually audit every note".
32
+ 5. Explain warnings and errors as structure-health feedback.
33
+ 6. Do not frame lint as "the user must manually audit every note".
34
+ 7. If `aiwiki next` recommends `aiwiki agent sync --yes`, treat Agent skill setup as the next operational step before asking the user to ingest or query more material.
27
35
 
28
36
  ## Issue Meaning
29
37
 
@@ -39,4 +47,3 @@ Prefer small, traceable fixes:
39
47
  - Fix broken wikilinks.
40
48
  - Add missing `source_card` or `raw_file` paths.
41
49
  - Ask the host Agent to provide `analysis` for scaffold entries.
42
-
@@ -16,11 +16,29 @@ Use this protocol when the user asks:
16
16
  aiwiki context "<topic>"
17
17
  ```
18
18
 
19
+ Add filters when the user intent is specific:
20
+
21
+ ```bash
22
+ aiwiki context "<topic>" --type wiki_entries --status active --limit 5
23
+ aiwiki context "<topic>" --source-role input --wiki-type source_knowledge --limit 5
24
+ aiwiki context "<topic>" --source-role output --limit 5
25
+ aiwiki context "<topic>" --type source_cards --status to-review --limit 5
26
+ ```
27
+
19
28
  3. Read the JSON result.
20
29
  4. Prefer `matches.wiki_entries` before source cards, claims, topics, outlines, or raw refs.
21
- 5. Pay attention to `generation_mode`, `quality`, and `warnings`.
22
- 6. If the best Wiki Entry has `quality: "scaffold"`, tell the user that the entry is a traceable fallback and may need Agent enrichment.
23
- 7. Do not scan `02-raw` by default unless:
30
+ 5. Read `query_scope` to understand what was searched.
31
+ 6. Read `result_quality` before deciding how confident the answer can be.
32
+ 7. Follow `recommended_next_action`:
33
+ - `use_matches_for_answer`: answer from the matches.
34
+ - `review_grounding_or_enrich_entry`: answer cautiously and suggest enrichment/review.
35
+ - `review_source_cards_then_create_wiki_entry`: explain that only source-level material exists.
36
+ - `broaden_query_or_ingest_source`: ask for a broader query or ingest more material.
37
+ 8. Use `match_reasons` to explain why an item was selected.
38
+ 9. Use `quality_signals` to decide whether the result is enriched, scaffold, needs grounding review, or only has weak source support.
39
+ 10. Use `related_refs` to mention useful local follow-up files.
40
+ 11. If the best Wiki Entry has `quality: "scaffold"` or `quality_signals` includes `grounding:needs_review`, tell the user that the entry is a traceable lead and may need Agent enrichment or evidence review.
41
+ 12. Do not scan `02-raw` by default unless:
24
42
  - Wiki Entries are insufficient.
25
43
  - The user asks to verify the original source.
26
44
  - There is a source conflict.
@@ -35,4 +53,3 @@ Include:
35
53
  - Source basis.
36
54
  - Known gaps or scaffold warnings.
37
55
  - Suggested next step.
38
-
package/skill/SKILL.md CHANGED
@@ -3,12 +3,46 @@ name: aiwiki
3
3
  description: Agent-first AIWiki workflow for turning one URL/body into local Wiki knowledge files.
4
4
  ---
5
5
 
6
+ <!-- aiwiki-skill-version: 0.2.18 -->
7
+
6
8
  # AIWiki Skill
7
9
 
8
10
  Use this skill when the user asks an Agent to process one URL, article body, or local text file with the `aiwiki` keyword, or says phrases like `入库 <url>` / `收录 <url>` / `从 AIWiki 里了解 <topic>`.
9
11
 
10
12
  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
13
 
14
+ ## Agent-First Setup and Upgrade
15
+
16
+ When the user asks you to install, update, or repair AIWiki Agent integration, prefer the idempotent sync command:
17
+
18
+ ```bash
19
+ aiwiki agent sync --yes
20
+ ```
21
+
22
+ For a specific host:
23
+
24
+ ```bash
25
+ aiwiki agent sync --agent codex --yes
26
+ aiwiki agent sync --agent claude --yes
27
+ ```
28
+
29
+ Use `--dry-run` to preview without writing, and `--json` when you need machine-readable status:
30
+
31
+ ```bash
32
+ aiwiki agent sync --agent codex --dry-run
33
+ aiwiki agent sync --json --yes
34
+ aiwiki agent check --json
35
+ ```
36
+
37
+ Sync behavior:
38
+
39
+ - missing installed skill: install the packaged AIWiki skill
40
+ - current installed skill: leave unchanged
41
+ - different installed skill: backup the old file, then overwrite with the packaged skill
42
+ - unsupported host: do not write; use `aiwiki prompt agent` as a manual fallback
43
+
44
+ After sync, tell the user the target path, any backup path, and that the target Agent may need to restart or reload before the new skill is active. Never edit Agent config during `npm install`; sync is the explicit safe step.
45
+
12
46
  ## Knowledge Base Purpose
13
47
 
14
48
  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:
@@ -143,7 +177,26 @@ When the user asks to understand a topic from AIWiki, call:
143
177
  aiwiki context "<topic>"
144
178
  ```
145
179
 
146
- 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.
180
+ Use filters when the user's intent is clear:
181
+
182
+ ```bash
183
+ aiwiki context "<topic>" --type wiki_entries --status active --limit 5
184
+ aiwiki context "<topic>" --source-role output --limit 5
185
+ aiwiki context "<topic>" --type source_cards --status to-review --limit 5
186
+ ```
187
+
188
+ Use the returned JSON to answer. Prefer Wiki Entries first, but read these fields before responding:
189
+
190
+ - `query_scope`: filters, limit, and searched groups
191
+ - `result_quality`: total matches, best score, and whether a Wiki Entry was found
192
+ - `recommended_next_action`: whether to answer, broaden the query, enrich, or review grounding
193
+ - `match_reasons`: why each result matched
194
+ - `quality_signals`: scaffold, enriched, grounding, status, and relationship hints
195
+ - `related_refs`: local wikilinks and frontmatter relationships
196
+
197
+ Do not scan `02-raw` by default unless the Wiki result is insufficient, the user asks to verify the original text, or sources conflict.
198
+
199
+ If `quality_signals` contains `quality:scaffold` or `grounding:needs_review`, tell the user the result is a traceable lead that needs enrichment or review. Do not present it as final confirmed knowledge.
147
200
 
148
201
  For direct human terminal output, use:
149
202
 
@@ -0,0 +1,22 @@
1
+ # AIWiki Skill Upgrade Notes
2
+
3
+ Use this note when an Agent finishes `aiwiki agent sync`.
4
+
5
+ ## Current Major Changes
6
+
7
+ - Agent integration should use `aiwiki agent sync --yes` for both first install and upgrades.
8
+ - Sync backs up changed installed skill files before overwriting them.
9
+ - `aiwiki agent check --json` and `aiwiki agent sync --json --yes` provide machine-readable status for Agents.
10
+ - `aiwiki context` supports `--type`, `--source-role`, `--wiki-type`, `--status`, and `--limit`.
11
+ - Context JSON includes `query_scope`, `result_quality`, `recommended_next_action`, `match_reasons`, `quality_signals`, and `related_refs`.
12
+ - Scaffold or grounding-review matches are traceable leads, not final confirmed knowledge.
13
+
14
+ ## Agent Reply After Sync
15
+
16
+ Tell the user:
17
+
18
+ - which Agent targets were installed, current, updated, or unsupported
19
+ - where the new skill was written
20
+ - where the old skill was backed up, if any
21
+ - that the Agent may need a restart or reload
22
+ - that rollback is possible by copying the backup file back to the target path