@itradingai/aiwiki 0.2.10 → 0.2.12
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 +163 -115
- package/dist/src/app.js +200 -3
- package/dist/src/context.js +131 -0
- package/dist/src/frontmatter.js +55 -0
- package/dist/src/grounding.js +83 -0
- package/dist/src/ingest.js +72 -10
- package/dist/src/lint.js +197 -0
- package/dist/src/payload.js +160 -2
- package/dist/src/wiki-entry.js +180 -0
- package/dist/src/workspace.js +32 -0
- package/docs/AGENT_HANDOFF.md +80 -14
- package/docs/FAQ.md +38 -5
- package/docs/SHOWCASE.md +8 -3
- package/docs/USAGE.md +81 -10
- package/package.json +1 -1
- package/skill/LINT_PROTOCOL.md +42 -0
- package/skill/QUERY_PROTOCOL.md +38 -0
- package/skill/SKILL.md +122 -38
package/README.md
CHANGED
|
@@ -1,201 +1,249 @@
|
|
|
1
|
-

|
|
2
2
|
|
|
3
3
|
# AIWiki
|
|
4
4
|
|
|
5
|
-
AIWiki 是一个开源的 Agent-first
|
|
5
|
+
AIWiki 是一个开源的 Agent-first 本地 LLM-wiki CLI。
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
你把文章链接、网页正文或本地文本交给宿主 Agent;宿主 Agent 负责读取和理解内容;AIWiki 负责把结果稳定写进本地 Markdown 知识库,并生成可追踪、可查询、可持续整理的 Wiki 条目。
|
|
8
|
+
|
|
9
|
+
一句话说:AIWiki 不是网页抓取器,而是宿主 Agent 的本地 LLM-wiki 后端。
|
|
8
10
|
|
|
9
11
|
## 它解决什么
|
|
10
12
|
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
- 想让 Agent
|
|
13
|
+
- 链接和资料散在聊天记录里,后续很难复用。
|
|
14
|
+
- AI 总结过一次内容,但没有沉淀成可查询的知识条目。
|
|
15
|
+
- 想把资料卡、选题、大纲、Wiki 条目放进同一个本地知识库。
|
|
16
|
+
- 想让 Agent 负责理解内容,让 CLI 负责稳定落盘和追踪。
|
|
17
|
+
|
|
18
|
+
## 工作流
|
|
19
|
+
|
|
20
|
+
### Ingest:把资料写入本地 Wiki
|
|
21
|
+
|
|
22
|
+
```text
|
|
23
|
+
用户给 URL / 正文 / 文件
|
|
24
|
+
-> 宿主 Agent 读取内容并尽量生成 analysis / wiki_entry
|
|
25
|
+
-> aiwiki ingest-agent --stdin
|
|
26
|
+
-> AIWiki 写入 Raw / Source Card / Wiki Entry / Claim / Topic / Outline / Run Log
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Query:从 Wiki 调度知识
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
aiwiki context "AI Agent 出海机会"
|
|
33
|
+
aiwiki query "AI Agent 出海机会"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`context` 返回 JSON,主要给宿主 Agent 用;`query` 使用同一套检索结果,输出给人看的摘要。第一版是本地关键词检索,不是向量检索。
|
|
37
|
+
|
|
38
|
+
### Lint:检查知识库结构
|
|
15
39
|
|
|
16
|
-
|
|
40
|
+
```bash
|
|
41
|
+
aiwiki lint
|
|
42
|
+
```
|
|
17
43
|
|
|
18
|
-
|
|
19
|
-
- Source Card 资料卡
|
|
20
|
-
- Claim 建议
|
|
21
|
-
- 创意素材
|
|
22
|
-
- 选题候选
|
|
23
|
-
- 草稿大纲
|
|
24
|
-
- 处理摘要
|
|
25
|
-
- Obsidian 审阅入口
|
|
44
|
+
`lint` 会检查缺失链接、重复来源、fallback Wiki 条目、enriched 条目缺字段等问题,并写入 `dashboards/Lint Report.md`。
|
|
26
45
|
|
|
27
|
-
##
|
|
46
|
+
## 快速开始
|
|
28
47
|
|
|
29
|
-
|
|
48
|
+
### 第一步:安装 AIWiki CLI
|
|
49
|
+
|
|
50
|
+
让 AI 帮你安装时,可以把下面这段交给当前 Agent,改成自己的知识库路径:
|
|
30
51
|
|
|
31
52
|
```text
|
|
32
53
|
请帮我安装并配置 AIWiki。
|
|
33
54
|
安装命令:npm install -g @itradingai/aiwiki@latest
|
|
34
55
|
我的知识库路径:F:\knowledges
|
|
35
56
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
3. 执行 `aiwiki setup --path "我的知识库路径" --yes`,帮我完成知识库初始化。
|
|
40
|
-
4. 执行 `aiwiki agent list` 检查当前环境支持哪些宿主 Agent。
|
|
41
|
-
5. 优先为当前 AI/Agent 安装 AIWiki 对接;如果能自动安装,就执行 `aiwiki agent install` 或对应的 `--agent` 命令。
|
|
42
|
-
6. 如果当前 Agent 不支持自动安装,就执行 `aiwiki prompt agent`,然后把生成的对接协议整理好,告诉我应该粘贴到哪里。
|
|
43
|
-
7. 完成后,再执行 `aiwiki doctor` 和 `aiwiki status`,确认安装和配置是否正常。
|
|
44
|
-
8. 最后告诉我:
|
|
45
|
-
- 实际执行了哪些命令
|
|
46
|
-
- 知识库路径是什么
|
|
47
|
-
- Agent 对接是否完成
|
|
48
|
-
- 如果还差手动步骤,明确告诉我下一步怎么做
|
|
57
|
+
请检查 Node.js >=20,执行 aiwiki setup --path "我的知识库路径" --yes,
|
|
58
|
+
然后运行 aiwiki agent list / aiwiki agent install 完成宿主 Agent 对接。
|
|
59
|
+
最后执行 aiwiki doctor 和 aiwiki status,告诉我实际执行了哪些命令和还差什么手动步骤。
|
|
49
60
|
```
|
|
50
61
|
|
|
51
|
-
|
|
62
|
+
手动安装:
|
|
52
63
|
|
|
53
64
|
```bash
|
|
54
65
|
npx @itradingai/aiwiki@latest setup
|
|
55
66
|
aiwiki agent list
|
|
56
67
|
aiwiki agent install
|
|
57
|
-
aiwiki prompt agent
|
|
58
68
|
```
|
|
59
69
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
## 让宿主 Agent 直接安装 AIWiki
|
|
63
|
-
|
|
64
|
-
初始化知识库后,先扫一遍本机支持的宿主 Agent:
|
|
70
|
+
### 第二步:接入宿主 Agent
|
|
65
71
|
|
|
66
72
|
```bash
|
|
67
73
|
aiwiki agent list
|
|
74
|
+
aiwiki agent install
|
|
75
|
+
aiwiki agent check
|
|
68
76
|
```
|
|
69
77
|
|
|
70
|
-
|
|
78
|
+
也可以直接输出通用协议:
|
|
71
79
|
|
|
72
80
|
```bash
|
|
73
|
-
aiwiki agent
|
|
81
|
+
aiwiki prompt agent
|
|
74
82
|
```
|
|
75
83
|
|
|
76
|
-
|
|
84
|
+
### 第三步:第一次入库
|
|
77
85
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
aiwiki agent install --agent claude --yes
|
|
86
|
+
对宿主 Agent 发送:
|
|
87
|
+
|
|
88
|
+
```text
|
|
89
|
+
入库 https://example.com/article
|
|
83
90
|
```
|
|
84
91
|
|
|
85
|
-
|
|
92
|
+
宿主 Agent 读取正文后调用 `aiwiki ingest-agent --stdin`。用户不需要手动保存 payload,也不需要每次输入 `--path`。
|
|
93
|
+
|
|
94
|
+
### 第四步:从知识库提问
|
|
95
|
+
|
|
96
|
+
对宿主 Agent 说:
|
|
97
|
+
|
|
98
|
+
```text
|
|
99
|
+
从 AIWiki 里帮我了解 xxx
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
宿主 Agent 应优先调用:
|
|
86
103
|
|
|
87
104
|
```bash
|
|
88
|
-
aiwiki
|
|
105
|
+
aiwiki context "xxx"
|
|
89
106
|
```
|
|
90
107
|
|
|
91
|
-
|
|
108
|
+
人直接查询时可以运行:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
aiwiki query "xxx"
|
|
112
|
+
```
|
|
92
113
|
|
|
93
|
-
##
|
|
114
|
+
## AIWiki 会生成什么
|
|
94
115
|
|
|
95
|
-
|
|
116
|
+
成功入库会生成:
|
|
96
117
|
|
|
97
118
|
```text
|
|
98
|
-
|
|
119
|
+
02-raw/articles/
|
|
120
|
+
03-sources/article-cards/
|
|
121
|
+
04-claims/_suggestions/
|
|
122
|
+
05-wiki/source-knowledge/
|
|
123
|
+
06-assets/_suggestions/
|
|
124
|
+
07-topics/ready/
|
|
125
|
+
08-outputs/outlines/
|
|
126
|
+
09-runs/<run-id>/
|
|
99
127
|
```
|
|
100
128
|
|
|
101
|
-
|
|
129
|
+
其中 `05-wiki/source-knowledge/<slug>.md` 是默认 Wiki Entry。
|
|
102
130
|
|
|
103
|
-
|
|
131
|
+
### Agent-Enriched Wiki Entry
|
|
104
132
|
|
|
105
|
-
|
|
106
|
-
|
|
133
|
+
如果宿主 Agent 在 payload 中提供了 `analysis` 或 `wiki_entry`,AIWiki 会把这些总结、核心观点、知识点、概念、选题等内容写入 Wiki Entry。
|
|
134
|
+
|
|
135
|
+
frontmatter 会标记:
|
|
136
|
+
|
|
137
|
+
```yaml
|
|
138
|
+
generation_mode: "agent_enriched"
|
|
139
|
+
quality: "enriched"
|
|
140
|
+
generated_by: "host_agent"
|
|
141
|
+
llm_enriched: true
|
|
142
|
+
source_role: "input"
|
|
143
|
+
represents_user_view: false
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Deterministic Fallback Wiki Entry
|
|
147
|
+
|
|
148
|
+
如果宿主 Agent 只提供原文,AIWiki 仍会创建 Wiki Entry,但它只是可追溯脚手架,包含标题、来源、正文预览、反链和待补全区。
|
|
149
|
+
|
|
150
|
+
frontmatter 会标记:
|
|
151
|
+
|
|
152
|
+
```yaml
|
|
153
|
+
generation_mode: "deterministic_fallback"
|
|
154
|
+
quality: "scaffold"
|
|
155
|
+
generated_by: "aiwiki_cli"
|
|
156
|
+
llm_enriched: false
|
|
157
|
+
source_role: "input"
|
|
158
|
+
represents_user_view: false
|
|
107
159
|
```
|
|
108
160
|
|
|
161
|
+
AIWiki CLI 本身不调用 LLM,所以不会在没有 Agent 分析字段时承诺高质量提炼。
|
|
162
|
+
|
|
109
163
|
## 设计边界
|
|
110
164
|
|
|
111
|
-
AIWiki
|
|
165
|
+
AIWiki 做:
|
|
112
166
|
|
|
113
|
-
|
|
167
|
+
- 接收宿主 Agent payload。
|
|
168
|
+
- 写入本地 Markdown。
|
|
169
|
+
- 生成 frontmatter、wikilink、处理记录。
|
|
170
|
+
- 生成 Wiki Entry 容器。
|
|
171
|
+
- 支持 `context`、`query`、`next` 和 `lint`。
|
|
114
172
|
|
|
115
|
-
|
|
173
|
+
AIWiki 不做:
|
|
116
174
|
|
|
117
|
-
-
|
|
118
|
-
-
|
|
119
|
-
-
|
|
120
|
-
-
|
|
121
|
-
-
|
|
175
|
+
- 通用网页抓取。
|
|
176
|
+
- 微信公众号读取。
|
|
177
|
+
- 伪造浏览器头。
|
|
178
|
+
- 浏览器插件。
|
|
179
|
+
- CLI 内置 LLM。
|
|
180
|
+
- 自动高质量总结。
|
|
181
|
+
- 默认人工审核流程。
|
|
182
|
+
- 企业级 RBAC。
|
|
183
|
+
- 多知识库。
|
|
184
|
+
- 批量采集 / 定时采集 / RSS。
|
|
122
185
|
|
|
123
|
-
|
|
186
|
+
## Obsidian / Dataview
|
|
124
187
|
|
|
125
|
-
|
|
126
|
-
- 跨主题自动路由
|
|
127
|
-
- 批处理
|
|
128
|
-
- 定时或指定采集
|
|
129
|
-
- 长流程状态机
|
|
130
|
-
- 技术支持流程
|
|
188
|
+
AIWiki 生成的是标准 Markdown 和 frontmatter,不强依赖 Obsidian。
|
|
131
189
|
|
|
132
|
-
|
|
190
|
+
Obsidian 是推荐查看界面;Dataview 只是可选 dashboard 增强。AIWiki 不会自动安装 Dataview,也不会修改 `.obsidian`。
|
|
133
191
|
|
|
134
|
-
|
|
192
|
+
Review Queue 可以保留为回看入口,但不是 AIWiki 的主流程。
|
|
135
193
|
|
|
136
|
-
|
|
194
|
+
## 常见问题
|
|
195
|
+
|
|
196
|
+
### AIWiki 会自己抓网页吗?
|
|
197
|
+
|
|
198
|
+
不会。网页读取由宿主 Agent 完成,AIWiki 负责把 Agent 已经读到的内容写入本地知识库。
|
|
199
|
+
|
|
200
|
+
### 为什么会生成 05-wiki?
|
|
201
|
+
|
|
202
|
+
因为 AIWiki 的目标不是只保存资料,而是让资料进入可查询、可维护的 Wiki 知识层。
|
|
203
|
+
|
|
204
|
+
### 05-wiki 是否代表我的观点?
|
|
205
|
+
|
|
206
|
+
不一定。外部资料生成的 Wiki 条目默认代表“外部资料的结构化整理”,不代表你的个人观点。
|
|
207
|
+
|
|
208
|
+
### 什么内容才代表我的观点?
|
|
209
|
+
|
|
210
|
+
`source_role=output` 用于标记用户已发布文章、演讲稿、公众号文章等个人输出,并可配合 `represents_user_view: true`。外部资料默认是 `source_role: input`、`represents_user_view: false`。
|
|
211
|
+
|
|
212
|
+
### Dataview 必须安装吗?
|
|
213
|
+
|
|
214
|
+
不必须。没有 Dataview,也可以用普通 Markdown、Properties、Backlinks、Search 和 Graph View。
|
|
215
|
+
|
|
216
|
+
### Review Queue 还需要吗?
|
|
217
|
+
|
|
218
|
+
不是必需流程。它只适合低置信度、来源缺失、内容冲突、个人观点把关等回看场景。
|
|
137
219
|
|
|
138
220
|
## 文档
|
|
139
221
|
|
|
140
222
|
- [docs/USAGE.md](docs/USAGE.md)
|
|
141
223
|
- [docs/AGENT_HANDOFF.md](docs/AGENT_HANDOFF.md)
|
|
142
|
-
- [docs/OBSIDIAN_DATAVIEW_PLAN.md](docs/OBSIDIAN_DATAVIEW_PLAN.md)
|
|
143
224
|
- [docs/FAQ.md](docs/FAQ.md)
|
|
225
|
+
- [docs/SHOWCASE.md](docs/SHOWCASE.md)
|
|
144
226
|
- [docs/ROADMAP.md](docs/ROADMAP.md)
|
|
145
227
|
- [docs/RELEASE.md](docs/RELEASE.md)
|
|
146
228
|
- [CONTRIBUTING.md](CONTRIBUTING.md)
|
|
147
229
|
- [SECURITY.md](SECURITY.md)
|
|
148
|
-
- [docs/architecture.svg](docs/architecture.svg)
|
|
149
|
-
|
|
150
|
-
## 参与与反馈
|
|
151
|
-
|
|
152
|
-
如果你想提 bug、提需求,或者反馈宿主 Agent 对接问题,直接看:
|
|
153
|
-
|
|
154
|
-
- [CONTRIBUTING.md](CONTRIBUTING.md)
|
|
155
|
-
- [.github/ISSUE_TEMPLATE/bug_report.md](.github/ISSUE_TEMPLATE/bug_report.md)
|
|
156
|
-
- [.github/ISSUE_TEMPLATE/feature_request.md](.github/ISSUE_TEMPLATE/feature_request.md)
|
|
157
|
-
- [.github/ISSUE_TEMPLATE/agent_integration.md](.github/ISSUE_TEMPLATE/agent_integration.md)
|
|
158
|
-
|
|
159
|
-
## 联系与交流
|
|
160
|
-
|
|
161
|
-
项目专题介绍:[maxking.cc](https://maxking.cc/aiwiki)
|
|
162
|
-
|
|
163
|
-
<table>
|
|
164
|
-
<tr>
|
|
165
|
-
<td align="center" width="50%">
|
|
166
|
-
<img src="https://raw.githubusercontent.com/iTradingAI/aiwiki/refs/heads/main/docs/assets/join-group.png" alt="扫码进群交流" width="360">
|
|
167
|
-
<br>
|
|
168
|
-
<strong>扫码进群</strong>
|
|
169
|
-
</td>
|
|
170
|
-
<td align="center" width="50%">
|
|
171
|
-
<img src="https://raw.githubusercontent.com/iTradingAI/aiwiki/refs/heads/main/docs/assets/wechat-official-account.png" alt="扫码关注公众号" width="360">
|
|
172
|
-
<br>
|
|
173
|
-
<strong>关注公众号</strong>
|
|
174
|
-
</td>
|
|
175
|
-
</tr>
|
|
176
|
-
</table>
|
|
177
230
|
|
|
178
231
|
## 本地开发
|
|
179
232
|
|
|
180
233
|
```bash
|
|
181
234
|
npm install
|
|
182
235
|
npm run build
|
|
236
|
+
npm test
|
|
183
237
|
npm link
|
|
184
238
|
aiwiki setup --path "F:\knowledge_data\aiwiki-test" --yes
|
|
185
|
-
aiwiki prompt agent
|
|
186
239
|
aiwiki doctor
|
|
240
|
+
aiwiki ingest-agent --payload tests/fixtures/agent_payload.url.valid.json --path "F:\knowledge_data\aiwiki-test"
|
|
241
|
+
aiwiki context "AI Agent" --path "F:\knowledge_data\aiwiki-test"
|
|
242
|
+
aiwiki query "AI Agent" --path "F:\knowledge_data\aiwiki-test"
|
|
243
|
+
aiwiki next --path "F:\knowledge_data\aiwiki-test"
|
|
244
|
+
aiwiki lint --path "F:\knowledge_data\aiwiki-test"
|
|
187
245
|
```
|
|
188
246
|
|
|
189
|
-
## 最新动态
|
|
190
|
-
|
|
191
|
-
- `2026-05-13`:更新了本地 Markdown 导入的命名规则,明确优先使用文件标题或文件名生成外部文件名,不优先从正文反推标题,并调整了 README 中的入口顺序和手动安装说明。
|
|
192
|
-
- `2026-05-12`:公开前口径收口,统一 README、npm 元数据和公开文档入口。
|
|
193
|
-
- `2026-05-09`:完成 npm 公开发布准备,补齐发布前的 README 与交付信息,并让 CLI 版本号与 `package.json` / 发布包保持一致,便于安装、排查与版本确认。
|
|
194
|
-
- `2026-05-08`:完成中文化体验收口,包括默认生成中文 prompt、中文状态输出、中文目标描述,以及 README 和使用文档的中文本地化。
|
|
195
|
-
- `2026-05-08`:强化 Obsidian 工作流,把 Review Queue、Claims Review 等审阅队列提升为一等入口,方便在知识库里持续审阅和回看入库内容。
|
|
196
|
-
- `2026-05-07`:新增 Codex skill 安装能力,并补上 Agent 协议安装引导,让宿主 Agent 在正式入库前更容易完成对接。
|
|
197
|
-
- `2026-05-07`:持续打磨初始化体验,修复 setup 提示问题,避免静默套用默认值,并把首次使用流程改成交互式引导。
|
|
198
|
-
|
|
199
247
|
## License
|
|
200
248
|
|
|
201
249
|
MIT. See [LICENSE](LICENSE).
|
package/dist/src/app.js
CHANGED
|
@@ -4,7 +4,9 @@ import path from "node:path";
|
|
|
4
4
|
import { createInterface } from "node:readline/promises";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { flagBool, flagString, parseArgs } from "./args.js";
|
|
7
|
+
import { buildContext } from "./context.js";
|
|
7
8
|
import { deriveFileTitle, ingestFile, ingestPayload } from "./ingest.js";
|
|
9
|
+
import { lintWorkspace, renderLintReport, writeLintReport } from "./lint.js";
|
|
8
10
|
import { CliError, writeLine } from "./output.js";
|
|
9
11
|
import { confirmInit, directorySummary, doctor, exists, initWorkspace, promptForSetup, promptForInitPath, readConfig, resolveWorkspace, setDefaultWorkspace, statusSummary } from "./workspace.js";
|
|
10
12
|
export async function runCli(argv, streams = { stdout: process.stdout, stderr: process.stderr }) {
|
|
@@ -52,6 +54,10 @@ export async function runCli(argv, streams = { stdout: process.stdout, stderr: p
|
|
|
52
54
|
}
|
|
53
55
|
return 0;
|
|
54
56
|
}
|
|
57
|
+
if (command === "agent" && subcommand === "check") {
|
|
58
|
+
await printAgentCheck(streams.stdout, await discoverAgentTargets());
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
55
61
|
if (command === "agent" && (subcommand === "list" || !subcommand)) {
|
|
56
62
|
printAgentList(streams.stdout, await discoverAgentTargets());
|
|
57
63
|
return 0;
|
|
@@ -122,6 +128,41 @@ export async function runCli(argv, streams = { stdout: process.stdout, stderr: p
|
|
|
122
128
|
writeLine(streams.stdout, `处理次数: ${summary.runCount}`);
|
|
123
129
|
writeLine(streams.stdout, `失败次数: ${summary.failedCount}`);
|
|
124
130
|
writeLine(streams.stdout, `最近处理: ${summary.lastRunId ?? "无"}`);
|
|
131
|
+
await printStatusDetails(streams.stdout, root, summary.runCount);
|
|
132
|
+
return 0;
|
|
133
|
+
}
|
|
134
|
+
if (command === "context") {
|
|
135
|
+
const root = await resolveWorkspace(flagString(args, "path"));
|
|
136
|
+
const query = args.positional.slice(1).join(" ").trim();
|
|
137
|
+
if (!query) {
|
|
138
|
+
throw new CliError("请提供查询主题。");
|
|
139
|
+
}
|
|
140
|
+
writeLine(streams.stdout, JSON.stringify(await buildContext(root, query), null, 2));
|
|
141
|
+
return 0;
|
|
142
|
+
}
|
|
143
|
+
if (command === "query") {
|
|
144
|
+
const root = await resolveWorkspace(flagString(args, "path"));
|
|
145
|
+
const query = args.positional.slice(1).join(" ").trim();
|
|
146
|
+
if (!query) {
|
|
147
|
+
throw new CliError("请提供查询主题。");
|
|
148
|
+
}
|
|
149
|
+
writeLine(streams.stdout, renderQuery(await buildContext(root, query)));
|
|
150
|
+
return 0;
|
|
151
|
+
}
|
|
152
|
+
if (command === "next") {
|
|
153
|
+
const root = await resolveWorkspace(flagString(args, "path"));
|
|
154
|
+
const summary = await statusSummary(root);
|
|
155
|
+
const checks = await doctor(root);
|
|
156
|
+
const report = summary.runCount > 0 ? await lintWorkspace(root) : undefined;
|
|
157
|
+
await printNext(streams.stdout, root, summary.runCount, checks, await discoverAgentTargets(), report);
|
|
158
|
+
return 0;
|
|
159
|
+
}
|
|
160
|
+
if (command === "lint") {
|
|
161
|
+
const root = await resolveWorkspace(flagString(args, "path"));
|
|
162
|
+
const report = await lintWorkspace(root);
|
|
163
|
+
const reportPath = await writeLintReport(root, report);
|
|
164
|
+
writeLine(streams.stdout, renderLintReport(report));
|
|
165
|
+
writeLine(streams.stdout, `report: ${reportPath}`);
|
|
125
166
|
return 0;
|
|
126
167
|
}
|
|
127
168
|
if (command === "ingest-agent") {
|
|
@@ -203,12 +244,17 @@ function printHelp(stream) {
|
|
|
203
244
|
writeLine(stream, " aiwiki prompt agent");
|
|
204
245
|
writeLine(stream, " aiwiki doctor");
|
|
205
246
|
writeLine(stream, " aiwiki status");
|
|
247
|
+
writeLine(stream, " aiwiki context <query>");
|
|
248
|
+
writeLine(stream, " aiwiki query <query>");
|
|
249
|
+
writeLine(stream, " aiwiki next");
|
|
250
|
+
writeLine(stream, " aiwiki lint");
|
|
206
251
|
writeLine(stream, " aiwiki ingest-agent --stdin");
|
|
207
252
|
writeLine(stream, " aiwiki ingest-file --file <file>");
|
|
208
253
|
writeLine(stream, " aiwiki init --path <path> --yes --set-default");
|
|
209
254
|
writeLine(stream, " aiwiki config show");
|
|
210
255
|
writeLine(stream, " aiwiki ingest-agent --payload <file>");
|
|
211
256
|
writeLine(stream, " aiwiki ingest-url <url> --content-file <file>");
|
|
257
|
+
writeLine(stream, " aiwiki agent check");
|
|
212
258
|
}
|
|
213
259
|
async function discoverAgentTargets() {
|
|
214
260
|
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
@@ -288,6 +334,19 @@ function printAgentList(stream, targets) {
|
|
|
288
334
|
}
|
|
289
335
|
}
|
|
290
336
|
}
|
|
337
|
+
async function printAgentCheck(stream, targets) {
|
|
338
|
+
writeLine(stream, "AIWiki Agent 接入检查");
|
|
339
|
+
for (const target of targets) {
|
|
340
|
+
const installed = target.target ? await exists(target.target) : false;
|
|
341
|
+
writeLine(stream, `${target.id}: ${target.name} | detected=${target.detected ? "yes" : "no"} | installed=${installed ? "yes" : "no"} | installable=${target.installable ? "yes" : "no"}`);
|
|
342
|
+
if (target.detected && target.installable && !installed) {
|
|
343
|
+
writeLine(stream, ` 建议: aiwiki agent install --agent ${target.id} --yes`);
|
|
344
|
+
}
|
|
345
|
+
else if (target.detected && !target.installable) {
|
|
346
|
+
writeLine(stream, " 建议: aiwiki prompt agent");
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
291
350
|
async function installAgentSkill(options) {
|
|
292
351
|
const targets = await discoverAgentTargets();
|
|
293
352
|
const installable = targets.filter((target) => target.detected && target.installable);
|
|
@@ -360,10 +419,134 @@ function printAgentPrompt(stream) {
|
|
|
360
419
|
writeLine(stream, "");
|
|
361
420
|
writeLine(stream, "如果当前会话被用户明确设定为 AIWiki 入库助手,则用户只发送 URL 也默认触发入库。普通会话中不要把所有 URL 都自动入库。");
|
|
362
421
|
writeLine(stream, "");
|
|
363
|
-
writeLine(stream, "
|
|
364
|
-
writeLine(stream, "
|
|
422
|
+
writeLine(stream, "流程:读取网页正文;尽量生成 analysis/wiki_entry;生成 aiwiki.agent_payload.v1;通过 stdin 调用 `aiwiki ingest-agent --stdin`;读取 CLI 输出;向用户汇报 ingested、summary、wiki_entry、wiki_entry_quality、source_card、processing_summary。");
|
|
423
|
+
writeLine(stream, "回复措辞:成功时说“AIWiki 已完成入库,并生成 Wiki 条目。” 如果 wiki_entry_quality=scaffold,说明该条目只是可追溯脚手架,仍需宿主 Agent 后续补全。Dataview 是可选增强,不要替用户安装插件或修改 .obsidian。");
|
|
424
|
+
writeLine(stream, "");
|
|
425
|
+
writeLine(stream, "查询:当用户要求从 AIWiki 里了解某个主题时,调用 `aiwiki context <主题>`。");
|
|
426
|
+
writeLine(stream, "整理:当用户要求检查或整理知识库时,调用 `aiwiki lint`。");
|
|
427
|
+
writeLine(stream, "");
|
|
428
|
+
writeLine(stream, "禁止:让用户保存 payload;让用户每次输入 --path;声称 AIWiki CLI 负责网页抓取;声称 AIWiki CLI 会在没有 Agent 分析字段时自动高质量总结。");
|
|
429
|
+
}
|
|
430
|
+
async function printStatusDetails(stream, root, runCount) {
|
|
431
|
+
const counts = await contentCounts(root);
|
|
432
|
+
const lintPath = path.join(root, "dashboards", "Lint Report.md");
|
|
433
|
+
writeLine(stream, "");
|
|
434
|
+
writeLine(stream, "内容统计:");
|
|
435
|
+
writeLine(stream, `Wiki 条目: ${counts.wikiEntries}`);
|
|
436
|
+
writeLine(stream, `资料卡: ${counts.sourceCards}`);
|
|
437
|
+
writeLine(stream, `原文: ${counts.rawFiles}`);
|
|
438
|
+
writeLine(stream, `选题: ${counts.topics}`);
|
|
439
|
+
writeLine(stream, `大纲: ${counts.outlines}`);
|
|
440
|
+
writeLine(stream, `最近 lint: ${await exists(lintPath) ? await relativeMtime(root, lintPath) : "无"}`);
|
|
365
441
|
writeLine(stream, "");
|
|
366
|
-
writeLine(stream, "
|
|
442
|
+
writeLine(stream, "下一步建议:");
|
|
443
|
+
writeLine(stream, runCount === 0 ? "运行 `aiwiki agent install` 接入宿主 Agent,然后发送 `入库 <url>`。" : "运行 `aiwiki query <主题>` 查询知识,或运行 `aiwiki lint` 检查结构。");
|
|
444
|
+
}
|
|
445
|
+
async function printNext(stream, root, runCount, checks, targets, report) {
|
|
446
|
+
const missing = checks.filter((check) => check.status !== "ok");
|
|
447
|
+
const installableMissing = [];
|
|
448
|
+
for (const target of targets) {
|
|
449
|
+
if (target.detected && target.installable && target.target && !(await exists(target.target))) {
|
|
450
|
+
installableMissing.push(target);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
writeLine(stream, "AIWiki 下一步建议");
|
|
454
|
+
writeLine(stream, `知识库路径: ${root}`);
|
|
455
|
+
if (missing.length) {
|
|
456
|
+
writeLine(stream, "");
|
|
457
|
+
writeLine(stream, "先修复知识库结构:");
|
|
458
|
+
writeLine(stream, `- aiwiki setup --path "${root}" --yes`);
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
if (runCount === 0) {
|
|
462
|
+
writeLine(stream, "");
|
|
463
|
+
writeLine(stream, "还没有入库记录。");
|
|
464
|
+
writeLine(stream, "- aiwiki agent install");
|
|
465
|
+
writeLine(stream, "- 然后向宿主 Agent 发送 `入库 <url>`");
|
|
466
|
+
writeLine(stream, "- CLI 不抓网页;网页正文由宿主 Agent 提供。");
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
const actionableIssues = report?.issues.filter((issue) => issue.severity !== "info") ?? [];
|
|
470
|
+
if (actionableIssues.length) {
|
|
471
|
+
writeLine(stream, "");
|
|
472
|
+
writeLine(stream, `结构检查发现 ${actionableIssues.length} 个需要处理的问题。`);
|
|
473
|
+
writeLine(stream, "- aiwiki lint");
|
|
474
|
+
writeLine(stream, `- 查看报告: dashboards/Lint Report.md`);
|
|
475
|
+
writeLine(stream, "- 先处理 error / warning,再继续扩展查询或入库。");
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
writeLine(stream, "");
|
|
479
|
+
writeLine(stream, "已有入库记录,可以继续:");
|
|
480
|
+
writeLine(stream, "- aiwiki query <主题>");
|
|
481
|
+
writeLine(stream, "- aiwiki lint");
|
|
482
|
+
if (installableMissing.length) {
|
|
483
|
+
writeLine(stream, "");
|
|
484
|
+
writeLine(stream, "可补充宿主 Agent 接入:");
|
|
485
|
+
for (const target of installableMissing) {
|
|
486
|
+
writeLine(stream, `- aiwiki agent install --agent ${target.id} --yes`);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
function renderQuery(context) {
|
|
491
|
+
const lines = [`AIWiki 查询: ${context.query}`, ""];
|
|
492
|
+
appendQueryGroup(lines, "Wiki 条目", context.matches.wiki_entries);
|
|
493
|
+
appendQueryGroup(lines, "资料卡", context.matches.source_cards);
|
|
494
|
+
appendQueryGroup(lines, "选题", context.matches.topics);
|
|
495
|
+
appendQueryGroup(lines, "Claim 建议", context.matches.claims);
|
|
496
|
+
appendQueryGroup(lines, "大纲", context.matches.outlines);
|
|
497
|
+
appendQueryGroup(lines, "原文引用", context.matches.raw_refs);
|
|
498
|
+
if (context.warnings.length) {
|
|
499
|
+
lines.push("提示:", ...context.warnings.map((warning) => `- ${warning}`), "");
|
|
500
|
+
}
|
|
501
|
+
lines.push("Agent JSON:", `- aiwiki context "${context.query}"`);
|
|
502
|
+
return `${lines.join("\n")}\n`;
|
|
503
|
+
}
|
|
504
|
+
function appendQueryGroup(lines, label, items) {
|
|
505
|
+
lines.push(`${label}:`);
|
|
506
|
+
if (!items.length) {
|
|
507
|
+
lines.push("- 无", "");
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
for (const item of items.slice(0, 5)) {
|
|
511
|
+
lines.push(`- ${item.title} (${item.path})`);
|
|
512
|
+
if (item.summary) {
|
|
513
|
+
lines.push(` ${item.summary}`);
|
|
514
|
+
}
|
|
515
|
+
if (item.warnings.length) {
|
|
516
|
+
lines.push(` 提示: ${item.warnings.join(";")}`);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
lines.push("");
|
|
520
|
+
}
|
|
521
|
+
async function contentCounts(root) {
|
|
522
|
+
return {
|
|
523
|
+
wikiEntries: await countMarkdownFiles(path.join(root, "05-wiki")),
|
|
524
|
+
sourceCards: await countMarkdownFiles(path.join(root, "03-sources", "article-cards")),
|
|
525
|
+
rawFiles: await countMarkdownFiles(path.join(root, "02-raw", "articles")),
|
|
526
|
+
topics: await countMarkdownFiles(path.join(root, "07-topics", "ready")),
|
|
527
|
+
outlines: await countMarkdownFiles(path.join(root, "08-outputs", "outlines"))
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
async function countMarkdownFiles(dir) {
|
|
531
|
+
if (!(await exists(dir))) {
|
|
532
|
+
return 0;
|
|
533
|
+
}
|
|
534
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
535
|
+
let count = 0;
|
|
536
|
+
for (const entry of entries) {
|
|
537
|
+
const target = path.join(dir, entry.name);
|
|
538
|
+
if (entry.isDirectory()) {
|
|
539
|
+
count += await countMarkdownFiles(target);
|
|
540
|
+
}
|
|
541
|
+
else if (entry.isFile() && entry.name.toLowerCase().endsWith(".md")) {
|
|
542
|
+
count += 1;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
return count;
|
|
546
|
+
}
|
|
547
|
+
async function relativeMtime(root, target) {
|
|
548
|
+
const stats = await fs.stat(target);
|
|
549
|
+
return `${path.relative(root, target).replace(/\\/g, "/")} (${stats.mtime.toISOString()})`;
|
|
367
550
|
}
|
|
368
551
|
function doctorStatusText(status) {
|
|
369
552
|
if (status === "ok") {
|
|
@@ -389,6 +572,20 @@ function printIngestResult(stream, result) {
|
|
|
389
572
|
writeLine(stream, `run_dir: ${result.runDir}`);
|
|
390
573
|
writeLine(stream, `files: ${result.generatedFiles.length}`);
|
|
391
574
|
writeLine(stream, `processing_summary: ${result.agentReport.keyFiles.processingSummary}`);
|
|
575
|
+
if (result.agentReport.keyFiles.wikiEntry) {
|
|
576
|
+
writeLine(stream, `wiki_entry: ${result.agentReport.keyFiles.wikiEntry}`);
|
|
577
|
+
}
|
|
578
|
+
if (result.agentReport.wikiEntryGenerationMode) {
|
|
579
|
+
writeLine(stream, `wiki_entry_generation_mode: ${result.agentReport.wikiEntryGenerationMode}`);
|
|
580
|
+
}
|
|
581
|
+
if (result.agentReport.wikiEntryQuality) {
|
|
582
|
+
writeLine(stream, `wiki_entry_quality: ${result.agentReport.wikiEntryQuality}`);
|
|
583
|
+
}
|
|
584
|
+
writeLine(stream, `grounding_evidence_available: ${result.agentReport.grounding.evidence_available ? "yes" : "no"}`);
|
|
585
|
+
writeLine(stream, `grounding_evidence_channel: ${result.agentReport.grounding.evidence_channel}`);
|
|
586
|
+
writeLine(stream, `grounding_needs_review: ${result.agentReport.grounding.needs_review ? "yes" : "no"}`);
|
|
587
|
+
writeLine(stream, `grounding_markers: ${result.agentReport.grounding.suspicion_markers.length ? result.agentReport.grounding.suspicion_markers.join(",") : "none"}`);
|
|
588
|
+
writeLine(stream, `grounding_claims_with_quotes: ${result.agentReport.grounding.claim_quote_count}/${result.agentReport.grounding.claim_count}`);
|
|
392
589
|
if (result.agentReport.keyFiles.sourceCard) {
|
|
393
590
|
writeLine(stream, `source_card: ${result.agentReport.keyFiles.sourceCard}`);
|
|
394
591
|
}
|