@itradingai/aiwiki 0.2.16 → 0.2.19
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 +41 -12
- package/dist/src/app.js +226 -23
- package/dist/src/context.js +173 -16
- package/dist/src/ingest.js +52 -27
- package/dist/src/lint.js +84 -1
- package/dist/src/payload.js +25 -10
- package/dist/src/wiki-entry.js +3 -3
- package/dist/src/workspace.js +18 -9
- package/docs/20260607-aiwiki-feature-pruning-plan.md +468 -0
- package/docs/20260607-aiwiki-long-term-operating-roadmap.md +409 -0
- package/docs/AGENT_HANDOFF.md +59 -7
- package/docs/FAQ.md +4 -2
- package/docs/README.md +3 -4
- package/docs/ROADMAP.md +4 -0
- package/docs/USAGE.md +74 -12
- package/docs/development-log.md +227 -0
- package/package.json +12 -2
- package/skill/LINT_PROTOCOL.md +16 -7
- package/skill/QUERY_PROTOCOL.md +21 -4
- package/skill/SKILL.md +65 -5
- package/skill/UPGRADE_NOTES.md +22 -0
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ AIWiki 是一个开源的 Agent-first 本地 LLM-wiki CLI。
|
|
|
23
23
|
用户给 URL / 正文 / 文件
|
|
24
24
|
-> 宿主 Agent 读取内容并尽量生成 analysis / wiki_entry
|
|
25
25
|
-> aiwiki ingest-agent --stdin
|
|
26
|
-
-> AIWiki 写入 Raw / Source Card / Wiki Entry / Claim / Topic / Outline /
|
|
26
|
+
-> AIWiki 写入 Raw / Source Card / Wiki Entry / Run Summary;有明确内容或请求时再写 Claim / Topic / Outline / Asset
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
### Query:从 Wiki 调度知识
|
|
@@ -55,7 +55,7 @@ aiwiki lint
|
|
|
55
55
|
我的知识库路径:F:\knowledges
|
|
56
56
|
|
|
57
57
|
请检查 Node.js >=20,执行 aiwiki setup --path "我的知识库路径" --yes,
|
|
58
|
-
然后运行 aiwiki agent
|
|
58
|
+
然后运行 aiwiki agent sync --yes 和 aiwiki agent check --json 完成宿主 Agent 对接。
|
|
59
59
|
最后执行 aiwiki doctor 和 aiwiki status,告诉我实际执行了哪些命令和还差什么手动步骤。
|
|
60
60
|
```
|
|
61
61
|
|
|
@@ -63,16 +63,15 @@ aiwiki lint
|
|
|
63
63
|
|
|
64
64
|
```bash
|
|
65
65
|
npx @itradingai/aiwiki@latest setup
|
|
66
|
-
aiwiki agent
|
|
67
|
-
aiwiki agent
|
|
66
|
+
aiwiki agent sync --yes
|
|
67
|
+
aiwiki agent check --json
|
|
68
68
|
```
|
|
69
69
|
|
|
70
70
|
### 第二步:接入宿主 Agent
|
|
71
71
|
|
|
72
72
|
```bash
|
|
73
|
-
aiwiki agent
|
|
74
|
-
aiwiki agent
|
|
75
|
-
aiwiki agent check
|
|
73
|
+
aiwiki agent sync --yes
|
|
74
|
+
aiwiki agent check --json
|
|
76
75
|
```
|
|
77
76
|
|
|
78
77
|
也可以直接输出通用协议:
|
|
@@ -118,15 +117,11 @@ aiwiki query "xxx"
|
|
|
118
117
|
```text
|
|
119
118
|
02-raw/articles/
|
|
120
119
|
03-sources/article-cards/
|
|
121
|
-
04-claims/_suggestions/
|
|
122
120
|
05-wiki/source-knowledge/
|
|
123
|
-
06-assets/_suggestions/
|
|
124
|
-
07-topics/ready/
|
|
125
|
-
08-outputs/outlines/
|
|
126
121
|
09-runs/<run-id>/
|
|
127
122
|
```
|
|
128
123
|
|
|
129
|
-
其中 `05-wiki/source-knowledge/<
|
|
124
|
+
其中 `02-raw/articles/`、`03-sources/article-cards/`、`05-wiki/source-knowledge/` 和 `09-runs/<run-id>/` 是核心产物。`04-claims/_suggestions/`、`06-assets/_suggestions/`、`07-topics/ready/`、`08-outputs/outlines/` 只在 payload 有对应内容或 `request.outputs` 明确请求时生成。
|
|
130
125
|
|
|
131
126
|
### Agent-Enriched Wiki Entry
|
|
132
127
|
|
|
@@ -247,3 +242,37 @@ aiwiki lint --path "F:\knowledge_data\aiwiki-test"
|
|
|
247
242
|
## License
|
|
248
243
|
|
|
249
244
|
MIT. See [LICENSE](LICENSE).
|
|
245
|
+
|
|
246
|
+
## Query Filters
|
|
247
|
+
|
|
248
|
+
AIWiki retrieval is local Markdown/frontmatter search. It is intentionally lightweight: no vector search, no database, no external search, and no RAG-over-wiki.
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
aiwiki context "AI Agent" --type wiki_entries --source-role input --wiki-type source_knowledge --status active --limit 5
|
|
252
|
+
aiwiki query "AI Agent" --type source_cards --status to-review --limit 3
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
`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.
|
|
256
|
+
|
|
257
|
+
## Agent Skill Sync and Upgrade
|
|
258
|
+
|
|
259
|
+
AIWiki is Agent-first: after installing or upgrading the npm package, sync the packaged AIWiki skill into the local Agent environment.
|
|
260
|
+
|
|
261
|
+
First install and later upgrades use the same safe command:
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
npm install -g @itradingai/aiwiki@latest
|
|
265
|
+
aiwiki agent sync --yes
|
|
266
|
+
aiwiki agent check
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
For one Agent:
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
aiwiki agent sync --agent codex --yes
|
|
273
|
+
aiwiki agent sync --agent claude --yes
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
`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.
|
|
277
|
+
|
|
278
|
+
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
|
@@ -6,7 +6,7 @@ import { fileURLToPath } from "node:url";
|
|
|
6
6
|
import { flagBool, flagString, parseArgs } from "./args.js";
|
|
7
7
|
import { buildContext } from "./context.js";
|
|
8
8
|
import { deriveFileTitle, ingestFile, ingestPayload } from "./ingest.js";
|
|
9
|
-
import { filterLintReport, lintWorkspace, renderLintReport, renderLintSummary, writeLintReport } from "./lint.js";
|
|
9
|
+
import { attachAppliedSafeFixes, filterLintReport, lintWorkspace, removeEmptyOptionalDirs, renderLintReport, renderLintSummary, writeLintReport } from "./lint.js";
|
|
10
10
|
import { CliError, writeLine } from "./output.js";
|
|
11
11
|
import { confirmInit, directorySummary, doctor, exists, initWorkspace, promptForSetup, promptForInitPath, readConfig, resolveWorkspace, setDefaultWorkspace, statusSummary } from "./workspace.js";
|
|
12
12
|
export async function runCli(argv, streams = { stdout: process.stdout, stderr: process.stderr }) {
|
|
@@ -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;
|
|
@@ -36,8 +44,8 @@ export async function runCli(argv, streams = { stdout: process.stdout, stderr: p
|
|
|
36
44
|
writeLine(streams.stdout, `默认知识库: ${defaultConfig.defaultPath}`);
|
|
37
45
|
writeLine(streams.stdout, `用户配置: ${defaultConfig.configPath}`);
|
|
38
46
|
writeLine(streams.stdout, "Obsidian 入口: dashboards/AIWiki Home.md");
|
|
39
|
-
writeLine(streams.stdout, "下一步: 运行 `aiwiki agent
|
|
40
|
-
writeLine(streams.stdout, "Agent 设置完成后:
|
|
47
|
+
writeLine(streams.stdout, "下一步: 运行 `aiwiki agent sync --yes`,同步 AIWiki 宿主 Agent 对接文件。");
|
|
48
|
+
writeLine(streams.stdout, "Agent 设置完成后: 让宿主 Agent 提供正文并调用 `aiwiki ingest-agent --stdin`,或运行 `aiwiki ingest-file --file <file>`。");
|
|
41
49
|
return 0;
|
|
42
50
|
}
|
|
43
51
|
if (command === "agent" && subcommand === "install") {
|
|
@@ -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
|
|
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") {
|
|
@@ -160,7 +184,8 @@ export async function runCli(argv, streams = { stdout: process.stdout, stderr: p
|
|
|
160
184
|
if (command === "lint") {
|
|
161
185
|
const root = await resolveWorkspace(flagString(args, "path"));
|
|
162
186
|
const severity = parseLintSeverity(flagString(args, "severity"));
|
|
163
|
-
const
|
|
187
|
+
const appliedSafeFixes = flagBool(args, "fix-empty-dirs") ? await removeEmptyOptionalDirs(root) : [];
|
|
188
|
+
const report = filterLintReport(attachAppliedSafeFixes(await lintWorkspace(root), appliedSafeFixes), severity);
|
|
164
189
|
if (flagBool(args, "json")) {
|
|
165
190
|
writeLine(streams.stdout, JSON.stringify(report, null, 2));
|
|
166
191
|
return 0;
|
|
@@ -244,23 +269,51 @@ function printHelp(stream) {
|
|
|
244
269
|
writeLine(stream, "用法:");
|
|
245
270
|
writeLine(stream, " aiwiki setup");
|
|
246
271
|
writeLine(stream, " aiwiki setup --path <path> --yes");
|
|
247
|
-
writeLine(stream, " aiwiki agent
|
|
248
|
-
writeLine(stream, " aiwiki agent
|
|
249
|
-
writeLine(stream, " aiwiki agent
|
|
250
|
-
writeLine(stream, " aiwiki
|
|
272
|
+
writeLine(stream, " aiwiki agent sync --yes");
|
|
273
|
+
writeLine(stream, " aiwiki agent check --json");
|
|
274
|
+
writeLine(stream, " aiwiki ingest-agent --stdin");
|
|
275
|
+
writeLine(stream, " aiwiki ingest-file --file <file>");
|
|
251
276
|
writeLine(stream, " aiwiki doctor");
|
|
252
277
|
writeLine(stream, " aiwiki status");
|
|
253
278
|
writeLine(stream, " aiwiki context <query>");
|
|
254
279
|
writeLine(stream, " aiwiki query <query>");
|
|
255
|
-
writeLine(stream, " aiwiki next");
|
|
256
280
|
writeLine(stream, " aiwiki lint");
|
|
257
|
-
writeLine(stream, " aiwiki
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
writeLine(stream, "
|
|
261
|
-
writeLine(stream, "
|
|
262
|
-
writeLine(stream, "
|
|
281
|
+
writeLine(stream, " aiwiki lint --fix-empty-dirs --json");
|
|
282
|
+
}
|
|
283
|
+
function printAgentHelp(stream) {
|
|
284
|
+
writeLine(stream, "AIWiki Agent commands");
|
|
285
|
+
writeLine(stream, "");
|
|
286
|
+
writeLine(stream, "Agent-first setup and upgrade:");
|
|
287
|
+
writeLine(stream, " aiwiki agent sync --yes");
|
|
288
|
+
writeLine(stream, " aiwiki agent sync --agent codex --yes");
|
|
289
|
+
writeLine(stream, " aiwiki agent sync --agent codex --dry-run");
|
|
290
|
+
writeLine(stream, " aiwiki agent sync --json --yes");
|
|
291
|
+
writeLine(stream, "");
|
|
292
|
+
writeLine(stream, "Status:");
|
|
263
293
|
writeLine(stream, " aiwiki agent check");
|
|
294
|
+
writeLine(stream, " aiwiki agent check --json");
|
|
295
|
+
writeLine(stream, "");
|
|
296
|
+
writeLine(stream, "Compatibility:");
|
|
297
|
+
writeLine(stream, " aiwiki agent install --agent codex --yes");
|
|
298
|
+
writeLine(stream, " aiwiki agent install --agent codex --yes --force");
|
|
299
|
+
writeLine(stream, "");
|
|
300
|
+
writeLine(stream, "sync is idempotent: missing targets are installed, current targets are left unchanged, and different targets are backed up before overwrite.");
|
|
301
|
+
}
|
|
302
|
+
function printContextHelp(stream) {
|
|
303
|
+
writeLine(stream, "AIWiki context/query");
|
|
304
|
+
writeLine(stream, "");
|
|
305
|
+
writeLine(stream, "Local Markdown/frontmatter retrieval for host Agents and humans:");
|
|
306
|
+
writeLine(stream, " aiwiki context <topic> --limit 10");
|
|
307
|
+
writeLine(stream, " aiwiki query <topic> --type wiki_entries --status active");
|
|
308
|
+
writeLine(stream, "");
|
|
309
|
+
writeLine(stream, "Filters:");
|
|
310
|
+
writeLine(stream, " --type wiki_entries|source_cards|claims|topics|outlines|raw_refs");
|
|
311
|
+
writeLine(stream, " --source-role input|processing|output");
|
|
312
|
+
writeLine(stream, " --wiki-type source_knowledge|personal_knowledge");
|
|
313
|
+
writeLine(stream, " --status active|to-review|ready|draft");
|
|
314
|
+
writeLine(stream, " --limit <1-50>");
|
|
315
|
+
writeLine(stream, "");
|
|
316
|
+
writeLine(stream, "context JSON includes query_scope, result_quality, recommended_next_action, match_reasons, quality_signals, and related_refs.");
|
|
264
317
|
}
|
|
265
318
|
function parseLintSeverity(value) {
|
|
266
319
|
if (value === undefined) {
|
|
@@ -362,6 +415,40 @@ async function printAgentCheck(stream, targets) {
|
|
|
362
415
|
}
|
|
363
416
|
}
|
|
364
417
|
}
|
|
418
|
+
async function printAgentCheckDetailed(stream, targets, json = false) {
|
|
419
|
+
const checked = await Promise.all(targets.map(async (target) => ({
|
|
420
|
+
...target,
|
|
421
|
+
state: await inspectAgentTarget(target),
|
|
422
|
+
installed: target.target ? await exists(target.target) : false
|
|
423
|
+
})));
|
|
424
|
+
if (json) {
|
|
425
|
+
writeLine(stream, JSON.stringify({
|
|
426
|
+
schema_version: "aiwiki.agent_check.v1",
|
|
427
|
+
generated_at: new Date().toISOString(),
|
|
428
|
+
targets: checked.map((target) => ({
|
|
429
|
+
id: target.id,
|
|
430
|
+
name: target.name,
|
|
431
|
+
detected: target.detected,
|
|
432
|
+
installable: target.installable,
|
|
433
|
+
installed: target.installed,
|
|
434
|
+
state: target.state,
|
|
435
|
+
source: target.source,
|
|
436
|
+
target: target.target
|
|
437
|
+
}))
|
|
438
|
+
}, null, 2));
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
writeLine(stream, "AIWiki Agent check");
|
|
442
|
+
for (const target of checked) {
|
|
443
|
+
writeLine(stream, `${target.id}: ${target.name} | detected=${target.detected ? "yes" : "no"} | installed=${target.installed ? "yes" : "no"} | installable=${target.installable ? "yes" : "no"} | state=${target.state}`);
|
|
444
|
+
if (target.detected && target.installable && (target.state === "missing" || target.state === "different")) {
|
|
445
|
+
writeLine(stream, ` suggested: aiwiki agent sync --agent ${target.id} --yes`);
|
|
446
|
+
}
|
|
447
|
+
else if (target.detected && !target.installable) {
|
|
448
|
+
writeLine(stream, " suggested: aiwiki prompt agent");
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
365
452
|
async function installAgentSkill(options) {
|
|
366
453
|
const targets = await discoverAgentTargets();
|
|
367
454
|
const installable = targets.filter((target) => target.detected && target.installable);
|
|
@@ -400,8 +487,8 @@ async function installAgentSkill(options) {
|
|
|
400
487
|
return undefined;
|
|
401
488
|
}
|
|
402
489
|
}
|
|
403
|
-
await
|
|
404
|
-
return selected;
|
|
490
|
+
const result = await copyInstallFileSafe(selected.source, selected.target, options.force);
|
|
491
|
+
return { ...selected, ...result };
|
|
405
492
|
}
|
|
406
493
|
async function askQuestion(streams, question) {
|
|
407
494
|
if (!process.stdin.isTTY) {
|
|
@@ -415,7 +502,28 @@ async function askQuestion(streams, question) {
|
|
|
415
502
|
rl.close();
|
|
416
503
|
}
|
|
417
504
|
}
|
|
505
|
+
async function syncAgentSkills(options) {
|
|
506
|
+
const targets = await discoverAgentTargets();
|
|
507
|
+
const selected = options.agentId ? targets.find((target) => target.id === options.agentId) : undefined;
|
|
508
|
+
if (!selected && options.agentId) {
|
|
509
|
+
throw new CliError(`未知宿主 Agent: ${options.agentId}`);
|
|
510
|
+
}
|
|
511
|
+
const syncTargets = selected ? [selected] : targets.filter((target) => target.detected && target.installable);
|
|
512
|
+
if (!syncTargets.length) {
|
|
513
|
+
throw new CliError("No detected installable Agent targets. Run aiwiki agent list or aiwiki prompt agent.");
|
|
514
|
+
}
|
|
515
|
+
if (!options.yes && !options.dryRun) {
|
|
516
|
+
throw new CliError("agent sync modifies Agent skill files. Re-run with --yes, or use --dry-run to preview.");
|
|
517
|
+
}
|
|
518
|
+
return {
|
|
519
|
+
schema_version: "aiwiki.agent_sync.v1",
|
|
520
|
+
generated_at: new Date().toISOString(),
|
|
521
|
+
dry_run: options.dryRun,
|
|
522
|
+
results: await Promise.all(syncTargets.map((target) => syncAgentTarget(target, options.dryRun)))
|
|
523
|
+
};
|
|
524
|
+
}
|
|
418
525
|
async function copyInstallFile(source, target, force) {
|
|
526
|
+
return copyInstallFileSafe(source, target, force);
|
|
419
527
|
await fs.access(source);
|
|
420
528
|
if (!force && (await exists(target))) {
|
|
421
529
|
throw new CliError(`目标文件已存在: ${target}。如需覆盖,请加 --force。`);
|
|
@@ -423,6 +531,84 @@ async function copyInstallFile(source, target, force) {
|
|
|
423
531
|
await fs.mkdir(path.dirname(target), { recursive: true });
|
|
424
532
|
await fs.copyFile(source, target);
|
|
425
533
|
}
|
|
534
|
+
async function copyInstallFileSafe(source, target, force) {
|
|
535
|
+
await fs.access(source);
|
|
536
|
+
const targetExists = await exists(target);
|
|
537
|
+
if (!force && targetExists) {
|
|
538
|
+
throw new CliError(`目标文件已存在: ${target}。如需覆盖,请运行 aiwiki agent sync --agent <id> --yes,或为 install 加 --force。`);
|
|
539
|
+
}
|
|
540
|
+
if (targetExists && await sameFileContent(source, target)) {
|
|
541
|
+
return { action: "current" };
|
|
542
|
+
}
|
|
543
|
+
await fs.mkdir(path.dirname(target), { recursive: true });
|
|
544
|
+
const backupPath = targetExists ? await backupFile(target) : undefined;
|
|
545
|
+
await fs.copyFile(source, target);
|
|
546
|
+
return { action: targetExists ? "updated" : "installed", backupPath };
|
|
547
|
+
}
|
|
548
|
+
async function inspectAgentTarget(target) {
|
|
549
|
+
if (!target.installable || !target.source || !target.target) {
|
|
550
|
+
return "unsupported";
|
|
551
|
+
}
|
|
552
|
+
if (!(await exists(target.target))) {
|
|
553
|
+
return "missing";
|
|
554
|
+
}
|
|
555
|
+
return await sameFileContent(target.source, target.target) ? "current" : "different";
|
|
556
|
+
}
|
|
557
|
+
async function syncAgentTarget(target, dryRun) {
|
|
558
|
+
const state = await inspectAgentTarget(target);
|
|
559
|
+
const base = {
|
|
560
|
+
id: target.id,
|
|
561
|
+
name: target.name,
|
|
562
|
+
detected: target.detected,
|
|
563
|
+
installable: target.installable,
|
|
564
|
+
state,
|
|
565
|
+
target: target.target,
|
|
566
|
+
source: target.source,
|
|
567
|
+
changed: false,
|
|
568
|
+
dryRun
|
|
569
|
+
};
|
|
570
|
+
if (state === "unsupported" || !target.source || !target.target) {
|
|
571
|
+
return { ...base, action: "unsupported", note: target.note };
|
|
572
|
+
}
|
|
573
|
+
if (state === "current") {
|
|
574
|
+
return { ...base, action: "current" };
|
|
575
|
+
}
|
|
576
|
+
if (dryRun) {
|
|
577
|
+
return { ...base, action: state === "missing" ? "would_install" : "would_update", changed: true };
|
|
578
|
+
}
|
|
579
|
+
const result = await copyInstallFileSafe(target.source, target.target, true);
|
|
580
|
+
return { ...base, action: result.action, backupPath: result.backupPath, changed: result.action !== "current" };
|
|
581
|
+
}
|
|
582
|
+
async function sameFileContent(source, target) {
|
|
583
|
+
try {
|
|
584
|
+
const [sourceText, targetText] = await Promise.all([fs.readFile(source, "utf8"), fs.readFile(target, "utf8")]);
|
|
585
|
+
return sourceText === targetText;
|
|
586
|
+
}
|
|
587
|
+
catch {
|
|
588
|
+
return false;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
async function backupFile(target) {
|
|
592
|
+
const parsed = path.parse(target);
|
|
593
|
+
const stamp = new Date().toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
|
|
594
|
+
const backupPath = path.join(parsed.dir, `${parsed.base}.bak-${stamp}`);
|
|
595
|
+
await fs.copyFile(target, backupPath);
|
|
596
|
+
return backupPath;
|
|
597
|
+
}
|
|
598
|
+
function printAgentSyncResult(stream, report) {
|
|
599
|
+
writeLine(stream, "AIWiki Agent sync");
|
|
600
|
+
writeLine(stream, `dry_run: ${report.dry_run ? "yes" : "no"}`);
|
|
601
|
+
for (const item of report.results) {
|
|
602
|
+
writeLine(stream, `${item.id}: ${item.name} | state=${item.state} | action=${item.action} | changed=${item.changed ? "yes" : "no"}`);
|
|
603
|
+
if (item.target) {
|
|
604
|
+
writeLine(stream, ` target: ${item.target}`);
|
|
605
|
+
}
|
|
606
|
+
if (item.backupPath) {
|
|
607
|
+
writeLine(stream, ` backup: ${item.backupPath}`);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
writeLine(stream, "next: restart or reload the target Agent so it reads the synced AIWiki skill.");
|
|
611
|
+
}
|
|
426
612
|
function printAgentPrompt(stream) {
|
|
427
613
|
writeLine(stream, "AIWiki Agent 中文提示");
|
|
428
614
|
writeLine(stream, "");
|
|
@@ -439,7 +625,7 @@ function printAgentPrompt(stream) {
|
|
|
439
625
|
writeLine(stream, "回复措辞:成功时说“AIWiki 已完成入库,并生成 Wiki 条目。” 如果 wiki_entry_quality=scaffold,说明该条目只是可追溯脚手架,仍需宿主 Agent 后续补全。Dataview 是可选增强,不要替用户安装插件或修改 .obsidian。");
|
|
440
626
|
writeLine(stream, "");
|
|
441
627
|
writeLine(stream, "查询:当用户要求从 AIWiki 里了解某个主题时,调用 `aiwiki context <主题>`。");
|
|
442
|
-
writeLine(stream, "
|
|
628
|
+
writeLine(stream, "整理:当用户要求检查或整理知识库时,先调用 `aiwiki lint --json`;若只有 safe fix 且用户允许整理,再调用 `aiwiki lint --fix-empty-dirs --json`,随后重跑 `aiwiki lint --json`。");
|
|
443
629
|
writeLine(stream, "");
|
|
444
630
|
writeLine(stream, "禁止:让用户保存 payload;让用户每次输入 --path;声称 AIWiki CLI 负责网页抓取;声称 AIWiki CLI 会在没有 Agent 分析字段时自动高质量总结。");
|
|
445
631
|
}
|
|
@@ -508,7 +694,7 @@ async function printNext(stream, root, runCount, checks, targets, report) {
|
|
|
508
694
|
if (runCount === 0) {
|
|
509
695
|
writeLine(stream, "");
|
|
510
696
|
writeLine(stream, "No ingest records yet.");
|
|
511
|
-
writeLine(stream, "- aiwiki agent
|
|
697
|
+
writeLine(stream, "- aiwiki agent sync --yes");
|
|
512
698
|
writeLine(stream, "- Then ask the host Agent to ingest a URL.");
|
|
513
699
|
writeLine(stream, "- AIWiki CLI does not fetch webpages; the host Agent supplies content.");
|
|
514
700
|
writeLine(stream, "- repair_order: empty_workspace");
|
|
@@ -535,12 +721,25 @@ function recommendedNextAction(runCount, lintStatus, hasMissingSystemFiles) {
|
|
|
535
721
|
return "next_action: aiwiki lint";
|
|
536
722
|
}
|
|
537
723
|
if (runCount === 0) {
|
|
538
|
-
return "next_action: aiwiki agent
|
|
724
|
+
return "next_action: aiwiki agent sync --yes";
|
|
539
725
|
}
|
|
540
726
|
return "next_action: aiwiki query <topic>";
|
|
541
727
|
}
|
|
728
|
+
function contextOptions(args) {
|
|
729
|
+
const limit = flagString(args, "limit");
|
|
730
|
+
return {
|
|
731
|
+
filters: {
|
|
732
|
+
type: flagString(args, "type"),
|
|
733
|
+
source_role: flagString(args, "source-role"),
|
|
734
|
+
wiki_type: flagString(args, "wiki-type"),
|
|
735
|
+
status: flagString(args, "status")
|
|
736
|
+
},
|
|
737
|
+
limit: limit === undefined ? undefined : Number(limit)
|
|
738
|
+
};
|
|
739
|
+
}
|
|
542
740
|
function renderQuery(context) {
|
|
543
741
|
const lines = [`AIWiki 查询: ${context.query}`, ""];
|
|
742
|
+
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
743
|
appendQueryGroup(lines, "Wiki 条目", context.matches.wiki_entries);
|
|
545
744
|
appendQueryGroup(lines, "资料卡", context.matches.source_cards);
|
|
546
745
|
appendQueryGroup(lines, "选题", context.matches.topics);
|
|
@@ -561,9 +760,13 @@ function appendQueryGroup(lines, label, items) {
|
|
|
561
760
|
}
|
|
562
761
|
for (const item of items.slice(0, 5)) {
|
|
563
762
|
lines.push(`- ${item.title} (${item.path})`);
|
|
763
|
+
lines.push(` score=${item.score}; reasons=${item.match_reasons.join(",") || "unknown"}; quality=${item.quality_signals.join(",") || "none"}`);
|
|
564
764
|
if (item.summary) {
|
|
565
765
|
lines.push(` ${item.summary}`);
|
|
566
766
|
}
|
|
767
|
+
if (item.related_refs.length) {
|
|
768
|
+
lines.push(` related: ${item.related_refs.slice(0, 5).join("; ")}`);
|
|
769
|
+
}
|
|
567
770
|
if (item.warnings.length) {
|
|
568
771
|
lines.push(` 提示: ${item.warnings.join(";")}`);
|
|
569
772
|
}
|