@itradingai/aiwiki 0.2.20 → 0.2.22

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.
@@ -0,0 +1,323 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/iTradingAI/aiwiki/main/docs/assets/aiwiki-hero.png" alt="AIWiki" width="100%" />
3
+ </p>
4
+
5
+ <p align="center">
6
+ <a href="./README.md">English</a> |
7
+ <a href="./docs/README.zh-CN.md">中文文档</a> |
8
+ <a href="./docs/USAGE.zh-CN.md">使用指南</a> |
9
+ <a href="./docs/FAQ.zh-CN.md">常见问题</a> |
10
+ <a href="https://www.npmjs.com/package/@itradingai/aiwiki">npm</a>
11
+ </p>
12
+
13
+ # AIWiki
14
+
15
+ [![npm version](https://img.shields.io/npm/v/@itradingai/aiwiki.svg)](https://www.npmjs.com/package/@itradingai/aiwiki)
16
+ [![Node.js >=20](https://img.shields.io/badge/node-%3E%3D20-339933.svg)](https://nodejs.org/)
17
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
18
+
19
+ **把 AI 助手读过的资料,变成以后可以查询、复用、整理的本地知识库。**
20
+
21
+ AIWiki 是给 AI 助手使用的本地 Markdown 知识库。
22
+
23
+ 你把链接、文章、文件或笔记交给 AI 助手;AI 助手负责读取和理解;AIWiki 负责把结果写成结构化、可追踪、可复用的 Markdown 知识文件。
24
+
25
+ ## 快速开始
26
+
27
+ 先选一个本地文件夹作为 AIWiki 知识库。示例:
28
+
29
+ ```text
30
+ Windows: D:\AIWiki
31
+ macOS/Linux: ~/AIWiki
32
+ 项目内测试: ./aiwiki-test
33
+ ```
34
+
35
+ 把下面这段复制给 Codex、Claude Code、QClaw、OpenClaw 或其他本地 AI 编程助手:
36
+
37
+ ```text
38
+ 请帮我安装并配置 AIWiki。
39
+
40
+ 请先检查 Node.js 是否已安装,并确认 node --version >=20。
41
+ 如果没有安装 Node.js,或版本低于 20,请先停止,并告诉我如何升级,不要继续运行 npm install。
42
+
43
+ 我的知识库路径是:
44
+
45
+ <替换成我的 AIWiki 知识库路径>
46
+
47
+ 请运行这些命令:
48
+
49
+ npm install -g @itradingai/aiwiki@latest
50
+ aiwiki setup --path "<替换成我的 AIWiki 知识库路径>" --yes
51
+ aiwiki agent sync --yes
52
+ aiwiki agent sync --path "<替换成我的 AIWiki 知识库路径>" --yes
53
+ aiwiki agent check --json
54
+ aiwiki agent check --path "<替换成我的 AIWiki 知识库路径>" --json
55
+ aiwiki doctor --path "<替换成我的 AIWiki 知识库路径>"
56
+ aiwiki status --path "<替换成我的 AIWiki 知识库路径>"
57
+
58
+ 最后请告诉我:
59
+
60
+ 1. AIWiki 是否安装成功
61
+ 2. 哪些 AI 助手目标已经同步
62
+ 3. 知识库根目录指导是否已经写入
63
+ 4. 我是否需要重启或重新加载 AI 助手
64
+ 5. 下一步应该怎么用
65
+ ```
66
+
67
+ AIWiki 有两层接入:
68
+
69
+ - `aiwiki agent sync --yes`:把 AIWiki 的 skill / command 同步到本机支持的 AI 助手环境。
70
+ - `aiwiki agent sync --path "<workspace>" --yes`:在知识库根目录写入指导,让以后进入这个目录的 Agent 也知道要优先使用 `aiwiki` 命令。
71
+
72
+ 同步后,如果助手没有立刻识别 AIWiki,需要重启或重新加载助手。
73
+
74
+ ### 安装成功后应该看到什么
75
+
76
+ 完成后,AI 助手应该能确认:
77
+
78
+ - `aiwiki` 已安装,并能输出版本号
79
+ - 知识库路径已经创建,并通过 `aiwiki doctor`
80
+ - Agent 接入状态是 `installed`、`updated` 或 `current`
81
+ - `aiwiki agent sync --path` 已经写入知识库根指导
82
+ - `aiwiki status` 能返回当前知识库状态和下一步动作
83
+
84
+ ## 第一次使用
85
+
86
+ ### 入库资料
87
+
88
+ 对 AI 助手说:
89
+
90
+ ```text
91
+ 把这个资料入库到 AIWiki:
92
+ <url>
93
+ ```
94
+
95
+ 或者:
96
+
97
+ ```text
98
+ 把这段笔记保存到 AIWiki:
99
+ <粘贴你的笔记>
100
+ ```
101
+
102
+ AI 助手读取资料后,会调用 AIWiki 写入本地知识库。
103
+
104
+ ### 从知识库提问
105
+
106
+ 对 AI 助手说:
107
+
108
+ ```text
109
+ AIWiki 里关于 <主题> 有什么?
110
+ ```
111
+
112
+ 助手应该优先调用:
113
+
114
+ ```bash
115
+ aiwiki context "<主题>"
116
+ ```
117
+
118
+ 如果人在终端里直接查询,可以用:
119
+
120
+ ```bash
121
+ aiwiki query "<主题>"
122
+ ```
123
+
124
+ ### 检查知识库
125
+
126
+ 对 AI 助手说:
127
+
128
+ ```text
129
+ 帮我检查并整理 AIWiki 知识库。
130
+ ```
131
+
132
+ 助手应该先调用:
133
+
134
+ ```bash
135
+ aiwiki lint --json
136
+ ```
137
+
138
+ 如果只包含安全修复,并且你允许整理,可以继续调用:
139
+
140
+ ```bash
141
+ aiwiki lint --fix-empty-dirs --json
142
+ aiwiki lint --json
143
+ ```
144
+
145
+ ## AIWiki 会生成什么
146
+
147
+ 一次成功入库会生成一组可追踪的知识文件:
148
+
149
+ ```text
150
+ 02-raw/articles/ 原始资料记录
151
+ 03-sources/article-cards/ 资料卡
152
+ 05-wiki/source-knowledge/ 可复用 Wiki 条目
153
+ 09-runs/<run-id>/ 本次处理记录
154
+ ```
155
+
156
+ 当 AI 助手提供了更丰富的结构化内容时,还可能生成:
157
+
158
+ ```text
159
+ 04-claims/_suggestions/ Claim 候选
160
+ 06-assets/_suggestions/ 可复用素材或写作资产
161
+ 07-topics/ready/ 选题候选
162
+ 08-outputs/outlines/ 大纲草稿
163
+ ```
164
+
165
+ Wiki Entry 是主要复用层;Raw 和 Source Card 保留来源和追踪关系,方便以后回查。
166
+
167
+ 可直接查看:
168
+
169
+ - [`examples/demo-run/`](examples/demo-run/)
170
+ - [`examples/obsidian-vault-sample/`](examples/obsidian-vault-sample/)
171
+
172
+ ## AIWiki 解决什么问题
173
+
174
+ 很多资料最后都死在三个地方:
175
+
176
+ - 收藏夹里,再也没有打开
177
+ - AI 聊天记录里,后面无法复用
178
+ - 笔记软件里,保存了但没有变成产出
179
+
180
+ AIWiki 的作用是让 AI 助手把读过的资料整理成本地 Markdown 知识库。
181
+
182
+ 你不只是保存链接,而是在沉淀以后能查询、能复用、能持续整理的知识资产。
183
+
184
+ ## 典型场景
185
+
186
+ - **阅读时顺手沉淀资料**:把文章发给 AI 助手,让 AIWiki 生成资料卡、Wiki 条目和处理记录。
187
+ - **为后续写作做准备**:当助手提供了足够结构化内容时,把观点、概念、选题和大纲素材一起沉淀下来。
188
+ - **从自己的资料库里追问**:围绕某个主题提问,让助手先从 AIWiki 取回本地上下文,再组织回答。
189
+
190
+ ## 工作原理
191
+
192
+ ```text
193
+ 用户给 URL / 文件 / 笔记 / 正文
194
+ -> AI 助手读取并理解
195
+ -> AIWiki 写入结构化 Markdown 文件
196
+ -> 助手以后用 aiwiki context 取回上下文
197
+ -> aiwiki lint 检查结构和一致性
198
+ ```
199
+
200
+ AIWiki 分清职责:
201
+
202
+ - AI 助手负责读取和理解资料
203
+ - AIWiki 负责写入、链接、查询和检查本地知识库
204
+ - Markdown 保持可读、可编辑、可迁移、可版本管理
205
+
206
+ ## 灵感来源
207
+
208
+ AIWiki 受两类思路启发:
209
+
210
+ - **LLM Wiki**:把原始资料编译成一个持续维护的 Wiki,而不是每次提问都重新从原文找答案。
211
+ - **内容工作流**:好的资料不应该只停留在摘要里,还应该变成素材、选题、大纲和后续表达的积木。
212
+
213
+ AIWiki 不是简单拼接两套方法。
214
+
215
+ 它把这些思路整理成一条更容易执行的 AI 助手工作流:
216
+
217
+ ```text
218
+ 资料
219
+ -> 资料卡
220
+ -> Wiki 条目
221
+ -> 可复用素材
222
+ -> 选题
223
+ -> 大纲
224
+ -> 后续创作 / 研究 / 决策
225
+ ```
226
+
227
+ ## Agent 接入
228
+
229
+ AIWiki 面向 AI 助手驱动的工作流。
230
+
231
+ 目前支持自动同步的本地助手目标包括:
232
+
233
+ - Codex
234
+ - Claude Code
235
+ - QClaw
236
+ - OpenClaw
237
+
238
+ 让 AI 助手运行:
239
+
240
+ ```bash
241
+ aiwiki agent sync --yes
242
+ aiwiki agent sync --path "<workspace>" --yes
243
+ aiwiki agent check --json
244
+ aiwiki agent check --path "<workspace>" --json
245
+ ```
246
+
247
+ 不支持自动写入的宿主,可以输出通用协议:
248
+
249
+ ```bash
250
+ aiwiki prompt agent
251
+ ```
252
+
253
+ `npm install` 不会偷偷修改 AI 助手配置。同步是显式动作,可重复执行,并会在覆盖已变化的 skill 前创建备份。
254
+
255
+ ## Obsidian / Dataview
256
+
257
+ AIWiki 写的是普通 Markdown 和 frontmatter。
258
+
259
+ Obsidian 是推荐查看界面,但不是硬依赖。Dataview 是可选 dashboard 增强。
260
+
261
+ AIWiki 不会自动安装 Dataview,也不会修改 `.obsidian`。
262
+
263
+ ## 设计边界
264
+
265
+ AIWiki 不是:
266
+
267
+ - 网页爬虫
268
+ - 微信公众号读取器
269
+ - 浏览器插件
270
+ - 内置 LLM
271
+ - 向量数据库
272
+ - 所有 RAG 系统的替代品
273
+ - Obsidian 插件
274
+ - 默认人工审核队列
275
+ - 多知识库管理器
276
+ - RSS 或定时采集系统
277
+
278
+ AIWiki 接收 AI 助手已经读到的内容,并把它变成本地 Markdown 知识库。
279
+
280
+ ## 社区
281
+
282
+ AIWiki 由 iTradingAI 开源维护。
283
+
284
+ 中文用户可以扫码加入交流群,或关注公众号获取更新、案例和使用讨论。
285
+
286
+ | 微信交流群 | 公众号 |
287
+ | --- | --- |
288
+ | ![加入微信群](https://raw.githubusercontent.com/iTradingAI/aiwiki/main/docs/assets/join-group.png) | ![微信公众号](https://raw.githubusercontent.com/iTradingAI/aiwiki/main/docs/assets/wechat-official-account.png) |
289
+
290
+ ## 文档
291
+
292
+ - [中文文档首页](docs/README.zh-CN.md)
293
+ - [使用指南](docs/USAGE.zh-CN.md)
294
+ - [Agent 接入说明](docs/AGENT_HANDOFF.zh-CN.md)
295
+ - [常见问题](docs/FAQ.zh-CN.md)
296
+ - [案例展示](docs/SHOWCASE.zh-CN.md)
297
+ - [路线图](docs/ROADMAP.zh-CN.md)
298
+ - [发布说明](docs/RELEASE.zh-CN.md)
299
+
300
+ ## 本地开发
301
+
302
+ ```bash
303
+ npm install
304
+ npm run build
305
+ npm test
306
+ npm link
307
+ ```
308
+
309
+ 使用临时知识库测试:
310
+
311
+ ```bash
312
+ aiwiki setup --path "./aiwiki-test" --yes
313
+ aiwiki doctor --path "./aiwiki-test"
314
+ aiwiki status --path "./aiwiki-test"
315
+ aiwiki ingest-agent --payload tests/fixtures/agent_payload.url.valid.json --path "./aiwiki-test"
316
+ aiwiki context "AI Agent" --path "./aiwiki-test"
317
+ aiwiki query "AI Agent" --path "./aiwiki-test"
318
+ aiwiki lint --path "./aiwiki-test"
319
+ ```
320
+
321
+ ## License
322
+
323
+ MIT. See [LICENSE](LICENSE).
package/dist/src/app.js CHANGED
@@ -65,6 +65,7 @@ export async function runCli(argv, streams = { stdout: process.stdout, stderr: p
65
65
  if (command === "agent" && subcommand === "sync") {
66
66
  const result = await syncAgentSkills({
67
67
  agentId: flagString(args, "agent"),
68
+ workspaceRoot: flagString(args, "path"),
68
69
  yes: flagBool(args, "yes"),
69
70
  dryRun: flagBool(args, "dry-run"),
70
71
  json: flagBool(args, "json"),
@@ -79,7 +80,7 @@ export async function runCli(argv, streams = { stdout: process.stdout, stderr: p
79
80
  return 0;
80
81
  }
81
82
  if (command === "agent" && subcommand === "check") {
82
- await printAgentCheckDetailed(streams.stdout, await discoverAgentTargets(), flagBool(args, "json"));
83
+ await printAgentCheckDetailed(streams.stdout, await discoverAgentTargets(flagString(args, "path")), flagBool(args, "json"));
83
84
  return 0;
84
85
  }
85
86
  if (command === "agent" && (subcommand === "list" || !subcommand)) {
@@ -287,10 +288,12 @@ function printAgentHelp(stream) {
287
288
  writeLine(stream, " aiwiki agent sync --yes");
288
289
  writeLine(stream, " aiwiki agent sync --agent codex --yes");
289
290
  writeLine(stream, " aiwiki agent sync --agent codex --dry-run");
291
+ writeLine(stream, " aiwiki agent sync --path <workspace> --yes");
290
292
  writeLine(stream, " aiwiki agent sync --json --yes");
291
293
  writeLine(stream, "");
292
294
  writeLine(stream, "Status:");
293
295
  writeLine(stream, " aiwiki agent check");
296
+ writeLine(stream, " aiwiki agent check --path <workspace> --json");
294
297
  writeLine(stream, " aiwiki agent check --json");
295
298
  writeLine(stream, "");
296
299
  writeLine(stream, "Compatibility:");
@@ -324,7 +327,21 @@ function parseLintSeverity(value) {
324
327
  }
325
328
  throw new CliError("lint --severity must be error, warning, or info");
326
329
  }
327
- async function discoverAgentTargets() {
330
+ const AIWIKI_AGENT_GUIDANCE_START = "<!-- AIWIKI:AGENT-GUIDANCE:START -->";
331
+ const AIWIKI_AGENT_GUIDANCE_END = "<!-- AIWIKI:AGENT-GUIDANCE:END -->";
332
+ const REQUIRED_AGENT_GUIDANCE_TERMS = [
333
+ "aiwiki setup",
334
+ "aiwiki agent sync",
335
+ "aiwiki agent check",
336
+ "aiwiki lint --json",
337
+ "aiwiki lint --fix-empty-dirs --json",
338
+ "aiwiki ingest-file",
339
+ "aiwiki ingest-agent",
340
+ "aiwiki status",
341
+ "aiwiki query",
342
+ "aiwiki context"
343
+ ];
344
+ async function discoverAgentTargets(workspaceRoot) {
328
345
  const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
329
346
  const skillSource = path.join(packageRoot, "skill", "SKILL.md");
330
347
  const promptSource = path.join(packageRoot, "docs", "AGENT_HANDOFF.md");
@@ -334,7 +351,8 @@ async function discoverAgentTargets() {
334
351
  const claudeHome = process.env.CLAUDE_HOME ? path.resolve(process.env.CLAUDE_HOME) : path.join(os.homedir(), ".claude");
335
352
  const opencodeHome = process.env.OPENCODE_HOME ? path.resolve(process.env.OPENCODE_HOME) : path.join(os.homedir(), ".opencode");
336
353
  const hermesHome = process.env.HERMES_HOME ? path.resolve(process.env.HERMES_HOME) : path.join(process.env.LOCALAPPDATA ?? path.join(os.homedir(), "AppData", "Local"), "hermes");
337
- return [
354
+ const workspace = workspaceRoot ? path.resolve(workspaceRoot) : undefined;
355
+ const targets = [
338
356
  {
339
357
  id: "codex",
340
358
  name: "Codex",
@@ -392,6 +410,18 @@ async function discoverAgentTargets() {
392
410
  note: "已检测到,但暂未确认稳定的 skill 目录。请先使用 aiwiki prompt agent。"
393
411
  }
394
412
  ];
413
+ if (workspace) {
414
+ targets.unshift({
415
+ id: "workspace",
416
+ name: "Workspace AGENTS.md",
417
+ detected: await exists(workspace),
418
+ installable: true,
419
+ kind: "root_guidance",
420
+ target: path.join(workspace, "AGENTS.md"),
421
+ note: "安装 marker-bounded 根指导,要求宿主 Agent 在整理、检查、入库、查询、复用时优先调用 aiwiki CLI。"
422
+ });
423
+ }
424
+ return targets;
395
425
  }
396
426
  function printAgentList(stream, targets) {
397
427
  writeLine(stream, "AIWiki 宿主 Agent 目标");
@@ -432,6 +462,7 @@ async function printAgentCheckDetailed(stream, targets, json = false) {
432
462
  installable: target.installable,
433
463
  installed: target.installed,
434
464
  state: target.state,
465
+ suggested_action: suggestedAgentAction(target),
435
466
  source: target.source,
436
467
  target: target.target
437
468
  }))
@@ -441,14 +472,21 @@ async function printAgentCheckDetailed(stream, targets, json = false) {
441
472
  writeLine(stream, "AIWiki Agent check");
442
473
  for (const target of checked) {
443
474
  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");
475
+ const suggested = suggestedAgentAction(target);
476
+ if (suggested) {
477
+ writeLine(stream, ` suggested: ${suggested}`);
449
478
  }
450
479
  }
451
480
  }
481
+ function suggestedAgentAction(target) {
482
+ if (target.detected && target.installable && (target.state === "missing" || target.state === "different")) {
483
+ return target.id === "workspace" ? `aiwiki agent sync --path "${path.dirname(target.target ?? ".")}" --yes` : `aiwiki agent sync --agent ${target.id} --yes`;
484
+ }
485
+ if (target.detected && !target.installable) {
486
+ return "aiwiki prompt agent";
487
+ }
488
+ return undefined;
489
+ }
452
490
  async function installAgentSkill(options) {
453
491
  const targets = await discoverAgentTargets();
454
492
  const installable = targets.filter((target) => target.detected && target.installable);
@@ -503,7 +541,7 @@ async function askQuestion(streams, question) {
503
541
  }
504
542
  }
505
543
  async function syncAgentSkills(options) {
506
- const targets = await discoverAgentTargets();
544
+ const targets = await discoverAgentTargets(options.workspaceRoot);
507
545
  const selected = options.agentId ? targets.find((target) => target.id === options.agentId) : undefined;
508
546
  if (!selected && options.agentId) {
509
547
  throw new CliError(`未知宿主 Agent: ${options.agentId}`);
@@ -546,6 +584,9 @@ async function copyInstallFileSafe(source, target, force) {
546
584
  return { action: targetExists ? "updated" : "installed", backupPath };
547
585
  }
548
586
  async function inspectAgentTarget(target) {
587
+ if (target.kind === "root_guidance") {
588
+ return inspectWorkspaceGuidanceTarget(target);
589
+ }
549
590
  if (!target.installable || !target.source || !target.target) {
550
591
  return "unsupported";
551
592
  }
@@ -567,7 +608,7 @@ async function syncAgentTarget(target, dryRun) {
567
608
  changed: false,
568
609
  dryRun
569
610
  };
570
- if (state === "unsupported" || !target.source || !target.target) {
611
+ if (state === "unsupported" || !target.target || (!target.source && target.kind !== "root_guidance")) {
571
612
  return { ...base, action: "unsupported", note: target.note };
572
613
  }
573
614
  if (state === "current") {
@@ -576,9 +617,78 @@ async function syncAgentTarget(target, dryRun) {
576
617
  if (dryRun) {
577
618
  return { ...base, action: state === "missing" ? "would_install" : "would_update", changed: true };
578
619
  }
620
+ if (target.kind === "root_guidance") {
621
+ const result = await syncWorkspaceGuidanceTarget(target);
622
+ return { ...base, action: result.action, backupPath: result.backupPath, changed: result.action !== "current" };
623
+ }
579
624
  const result = await copyInstallFileSafe(target.source, target.target, true);
580
625
  return { ...base, action: result.action, backupPath: result.backupPath, changed: result.action !== "current" };
581
626
  }
627
+ async function inspectWorkspaceGuidanceTarget(target) {
628
+ if (!target.target || !target.detected) {
629
+ return "missing";
630
+ }
631
+ if (!(await exists(target.target))) {
632
+ return "missing";
633
+ }
634
+ const content = await fs.readFile(target.target, "utf8");
635
+ const block = extractWorkspaceGuidanceBlock(content);
636
+ if (!block) {
637
+ return "different";
638
+ }
639
+ return block.trim() === workspaceGuidanceBlock().trim() && REQUIRED_AGENT_GUIDANCE_TERMS.every((term) => block.includes(term)) ? "current" : "different";
640
+ }
641
+ async function syncWorkspaceGuidanceTarget(target) {
642
+ if (!target.target) {
643
+ return { action: "unsupported" };
644
+ }
645
+ const targetExists = await exists(target.target);
646
+ const existing = targetExists ? await fs.readFile(target.target, "utf8") : "";
647
+ const next = mergeWorkspaceGuidance(existing);
648
+ if (targetExists && existing === next) {
649
+ return { action: "current" };
650
+ }
651
+ const backupPath = targetExists ? await backupFile(target.target) : undefined;
652
+ await fs.mkdir(path.dirname(target.target), { recursive: true });
653
+ await fs.writeFile(target.target, next, "utf8");
654
+ return { action: targetExists ? "updated" : "installed", backupPath };
655
+ }
656
+ function extractWorkspaceGuidanceBlock(content) {
657
+ const start = content.indexOf(AIWIKI_AGENT_GUIDANCE_START);
658
+ const end = content.indexOf(AIWIKI_AGENT_GUIDANCE_END);
659
+ if (start === -1 || end === -1 || end < start) {
660
+ return undefined;
661
+ }
662
+ return content.slice(start, end + AIWIKI_AGENT_GUIDANCE_END.length);
663
+ }
664
+ function mergeWorkspaceGuidance(existing) {
665
+ const block = workspaceGuidanceBlock();
666
+ const start = existing.indexOf(AIWIKI_AGENT_GUIDANCE_START);
667
+ const end = existing.indexOf(AIWIKI_AGENT_GUIDANCE_END);
668
+ if (start !== -1 && end !== -1 && end > start) {
669
+ return `${existing.slice(0, start)}${block}${existing.slice(end + AIWIKI_AGENT_GUIDANCE_END.length)}`;
670
+ }
671
+ const trimmed = existing.trimEnd();
672
+ return `${trimmed}${trimmed ? "\n\n" : ""}${block}\n`;
673
+ }
674
+ function workspaceGuidanceBlock() {
675
+ return `${AIWIKI_AGENT_GUIDANCE_START}
676
+ # AIWiki Agent Command Contract
677
+
678
+ When a user asks to organize, inspect, ingest, query, reuse, or maintain this AIWiki workspace, call the AIWiki CLI first. Do not start with generic file search, grep/find scans, or ad hoc note edits unless the AIWiki command cannot answer the request.
679
+
680
+ Required command-first loop:
681
+
682
+ 1. Ensure the workspace exists with \`aiwiki setup --path <workspace> --yes\`.
683
+ 2. Keep host-Agent guidance current with \`aiwiki agent sync --path <workspace> --yes\` and verify with \`aiwiki agent check --path <workspace> --json\`.
684
+ 3. Inspect structure with \`aiwiki lint --json --path <workspace>\`; apply only safe fixes with \`aiwiki lint --fix-empty-dirs --json --path <workspace>\` when allowed, then rerun \`aiwiki lint --json --path <workspace>\`.
685
+ 4. Ingest local material with \`aiwiki ingest-file --file <file> --path <workspace>\` or structured Agent material with \`aiwiki ingest-agent --stdin --path <workspace>\`.
686
+ 5. Check progress with \`aiwiki status --path <workspace>\`.
687
+ 6. Retrieve reusable knowledge with \`aiwiki query <topic> --path <workspace>\` for human-readable output or \`aiwiki context <topic> --path <workspace>\` for Agent JSON.
688
+
689
+ Use fallback shell/file search only after the relevant AIWiki command has been tried or when the command is unavailable. If you fall back, say which AIWiki command was insufficient and why.
690
+ ${AIWIKI_AGENT_GUIDANCE_END}`;
691
+ }
582
692
  async function sameFileContent(source, target) {
583
693
  try {
584
694
  const [sourceText, targetText] = await Promise.all([fs.readFile(source, "utf8"), fs.readFile(target, "utf8")]);