@jerryjiao/knowflow 0.3.0

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,367 @@
1
+ #!/usr/bin/env node
2
+ /** KnowFlow CLI */
3
+
4
+ import { program } from 'commander';
5
+ import chalk from 'chalk';
6
+ import { spawnSync } from 'node:child_process';
7
+ import {
8
+ cpSync,
9
+ existsSync,
10
+ mkdirSync,
11
+ readFileSync,
12
+ readdirSync,
13
+ writeFileSync,
14
+ } from 'node:fs';
15
+ import { dirname, join, parse, resolve } from 'node:path';
16
+ import { fileURLToPath } from 'node:url';
17
+
18
+ const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
19
+ const SCRIPTS = join(PACKAGE_ROOT, 'scripts');
20
+ const VERSION = JSON.parse(readFileSync(join(PACKAGE_ROOT, 'package.json'), 'utf8')).version;
21
+ const RC_NAME = '.knowflowrc';
22
+ const DEFAULT_CONFIG = {
23
+ wiki: { root: './wiki', rawDir: './raw' },
24
+ graph: { output: './graph/graph.html' },
25
+ health: { minFileSize: 100 },
26
+ };
27
+
28
+ function findProjectRoot(start = process.cwd()) {
29
+ let current = resolve(start);
30
+ const filesystemRoot = parse(current).root;
31
+ while (true) {
32
+ if (existsSync(join(current, RC_NAME))) return current;
33
+ if (current === filesystemRoot) return resolve(start);
34
+ current = dirname(current);
35
+ }
36
+ }
37
+
38
+ function loadProject(start = process.cwd()) {
39
+ const root = findProjectRoot(start);
40
+ const rcPath = join(root, RC_NAME);
41
+ let userConfig = {};
42
+ if (existsSync(rcPath)) {
43
+ try {
44
+ userConfig = JSON.parse(readFileSync(rcPath, 'utf8'));
45
+ } catch (error) {
46
+ throw new Error(`无法解析 ${rcPath}: ${error.message}`);
47
+ }
48
+ }
49
+
50
+ const config = {
51
+ wiki: { ...DEFAULT_CONFIG.wiki, ...userConfig.wiki },
52
+ graph: { ...DEFAULT_CONFIG.graph, ...userConfig.graph },
53
+ health: { ...DEFAULT_CONFIG.health, ...userConfig.health },
54
+ };
55
+ const pathValue = (value, label) => {
56
+ if (typeof value !== 'string' || value.trim() === '') throw new Error(`${label} 必须是非空路径`);
57
+ return resolve(root, value);
58
+ };
59
+
60
+ return {
61
+ root,
62
+ config,
63
+ wikiDir: pathValue(config.wiki.root, 'wiki.root'),
64
+ rawDir: pathValue(config.wiki.rawDir, 'wiki.rawDir'),
65
+ graphHtml: pathValue(config.graph.output, 'graph.output'),
66
+ };
67
+ }
68
+
69
+ function projectEnv(project) {
70
+ return {
71
+ ...process.env,
72
+ KNOWFLOW_ROOT: project.root,
73
+ KNOWFLOW_WIKI_DIR: project.wikiDir,
74
+ KNOWFLOW_RAW_DIR: project.rawDir,
75
+ KNOWFLOW_GRAPH_OUTPUT: project.graphHtml,
76
+ KNOWFLOW_HEALTH_EXCLUDE_ORPHAN: (project.config.health.excludeOrphanDirs ?? []).join(':'),
77
+ };
78
+ }
79
+
80
+ function run(command, args, opts = {}) {
81
+ const result = spawnSync(command, args, {
82
+ cwd: opts.cwd,
83
+ env: opts.env,
84
+ encoding: 'utf8',
85
+ stdio: opts.silent ? 'pipe' : 'inherit',
86
+ timeout: opts.timeout ?? 120_000,
87
+ shell: false,
88
+ });
89
+ if (result.error) throw result.error;
90
+ if (result.status !== 0) {
91
+ const detail = opts.silent ? (result.stderr || result.stdout || '').trim() : '';
92
+ const error = new Error(detail || `${command} 退出,状态码 ${result.status}`);
93
+ error.status = result.status;
94
+ throw error;
95
+ }
96
+ return result.stdout || '';
97
+ }
98
+
99
+ function countFiles(dir, ext = '.md') {
100
+ if (!existsSync(dir)) return 0;
101
+ let count = 0;
102
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
103
+ const path = join(dir, entry.name);
104
+ if (entry.isDirectory()) count += countFiles(path, ext);
105
+ else if (entry.name.endsWith(ext)) count++;
106
+ }
107
+ return count;
108
+ }
109
+
110
+ function countLines(dir) {
111
+ if (!existsSync(dir)) return 0;
112
+ let lines = 0;
113
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
114
+ const path = join(dir, entry.name);
115
+ if (entry.isDirectory()) lines += countLines(path);
116
+ else if (entry.name.endsWith('.md')) {
117
+ try { lines += readFileSync(path, 'utf8').split('\n').length; } catch {}
118
+ }
119
+ }
120
+ return lines;
121
+ }
122
+
123
+ function hasApiKey(project) {
124
+ if (process.env.ZHIPUAI_API_KEY) return true;
125
+ try {
126
+ return readFileSync(join(project.root, '.env'), 'utf8')
127
+ .split(/\r?\n/)
128
+ .some(line => /^\s*ZHIPUAI_API_KEY\s*=\s*[^$\s]/.test(line));
129
+ } catch {
130
+ return false;
131
+ }
132
+ }
133
+
134
+ function initialise(directory) {
135
+ const root = resolve(directory);
136
+ mkdirSync(root, { recursive: true });
137
+ const rcPath = join(root, RC_NAME);
138
+ if (!existsSync(rcPath)) {
139
+ writeFileSync(rcPath, `${JSON.stringify(DEFAULT_CONFIG, null, 2)}\n`, 'utf8');
140
+ console.log(chalk.green(`✅ 已创建 ${rcPath}`));
141
+ } else {
142
+ console.log(chalk.yellow(`⚠️ ${rcPath} 已存在,保留原配置`));
143
+ }
144
+
145
+ const project = loadProject(root);
146
+ for (const dir of [
147
+ project.wikiDir,
148
+ ...['sources', 'entities', 'concepts', 'comparisons'].map(name => join(project.wikiDir, name)),
149
+ project.rawDir,
150
+ ...['web', 'twitter', 'xiaohongshu', 'wechat'].map(name => join(project.rawDir, name)),
151
+ dirname(project.graphHtml),
152
+ ]) mkdirSync(dir, { recursive: true });
153
+
154
+ const templatesDir = join(root, 'templates');
155
+ mkdirSync(templatesDir, { recursive: true });
156
+ for (const name of readdirSync(join(PACKAGE_ROOT, 'templates'))) {
157
+ const source = join(PACKAGE_ROOT, 'templates', name);
158
+ const destination = join(templatesDir, name);
159
+ if (!existsSync(destination)) cpSync(source, destination);
160
+ }
161
+
162
+ const indexPath = join(project.wikiDir, 'index.md');
163
+ if (!existsSync(indexPath)) {
164
+ writeFileSync(indexPath, [
165
+ '# KnowFlow Wiki',
166
+ '',
167
+ '欢迎使用 KnowFlow。这里是知识库的首页与导航入口。',
168
+ '',
169
+ '## 开始使用',
170
+ '',
171
+ '- 将来源页放在 `sources/`',
172
+ '- 将实体页放在 `entities/`',
173
+ '- 将概念页放在 `concepts/`',
174
+ '- 使用双中括号 Wiki 链接建立页面之间的联系',
175
+ '',
176
+ ].join('\n'), 'utf8');
177
+ }
178
+ console.log(chalk.green(`✅ KnowFlow 项目已初始化: ${root}`));
179
+ }
180
+
181
+ const banner = `\n${chalk.cyan.bold(' KnowFlow')} ${chalk.dim(`v${VERSION}`)}\n${chalk.green(' AI 知识流系统 — 将 URL 变成结构化 Wiki + 知识图谱')}\n`;
182
+
183
+ program
184
+ .name('knowflow')
185
+ .description('AI 知识流系统 — 将 URL 变成结构化 Wiki + 知识图谱')
186
+ .version(VERSION)
187
+ .addHelpText('beforeAll', banner)
188
+ .addHelpCommand();
189
+
190
+ program
191
+ .command('init [directory]')
192
+ .description('在指定目录初始化 KnowFlow 项目(默认当前目录)')
193
+ .action((directory = '.') => initialise(directory));
194
+
195
+ program
196
+ .command('ingest <url-or-text>')
197
+ .description('采集 URL 或文本,自动识别来源并保存到 raw 目录')
198
+ .option('-s, --source <type>', '指定来源类型', 'auto')
199
+ .action((input, opts) => {
200
+ const project = loadProject();
201
+ console.log(chalk.blue('🔗 开始采集素材...'));
202
+ try {
203
+ run('bash', [join(SCRIPTS, 'ingest.sh'), input, opts.source], {
204
+ cwd: project.root,
205
+ env: projectEnv(project),
206
+ });
207
+ console.log(chalk.green('✅ 采集完成!'));
208
+ } catch (error) {
209
+ console.error(chalk.red('❌ 采集失败:'), error.message);
210
+ process.exitCode = 1;
211
+ }
212
+ });
213
+
214
+ program
215
+ .command('query <text>')
216
+ .description('混合检索知识库(向量搜索 + 关键词匹配)')
217
+ .option('-n, --top <n>', '返回结果数量', value => {
218
+ const parsed = Number.parseInt(value, 10);
219
+ if (!Number.isInteger(parsed) || parsed < 1 || parsed > 100) throw new Error('--top 必须是 1-100 的整数');
220
+ return parsed;
221
+ }, 5)
222
+ .action((text, opts) => {
223
+ const project = loadProject();
224
+ if (!hasApiKey(project)) {
225
+ console.error(chalk.yellow('⚠️ 未检测到 ZHIPUAI_API_KEY,请在项目 .env 或环境变量中配置。'));
226
+ process.exitCode = 1;
227
+ return;
228
+ }
229
+ try {
230
+ run(process.execPath, [join(SCRIPTS, 'vector-store.mjs'), 'query', text, '--top', String(opts.top)], {
231
+ cwd: project.root,
232
+ env: projectEnv(project),
233
+ });
234
+ } catch (error) {
235
+ console.error(chalk.red('❌ 检索失败:'), error.message);
236
+ process.exitCode = 1;
237
+ }
238
+ });
239
+
240
+ program
241
+ .command('graph')
242
+ .description('构建知识图谱并在浏览器中打开')
243
+ .option('--no-open', '构建但不打开浏览器')
244
+ .action(opts => {
245
+ const project = loadProject();
246
+ mkdirSync(dirname(project.graphHtml), { recursive: true });
247
+ console.log(chalk.cyan('🕸️ 构建知识图谱...'));
248
+ try {
249
+ run('python3', [join(SCRIPTS, 'graph_builder.py'), '--wiki-dir', project.wikiDir, '--output', project.graphHtml], {
250
+ cwd: project.root,
251
+ env: projectEnv(project),
252
+ silent: true,
253
+ });
254
+ console.log(chalk.green(`✅ 图谱已生成: ${project.graphHtml}`));
255
+ if (opts.open !== false) {
256
+ const opener = process.platform === 'darwin' ? ['open', [project.graphHtml]]
257
+ : process.platform === 'win32' ? ['explorer.exe', [project.graphHtml]]
258
+ : ['xdg-open', [project.graphHtml]];
259
+ run(opener[0], opener[1], { cwd: project.root, env: projectEnv(project), silent: true });
260
+ }
261
+ } catch (error) {
262
+ console.error(chalk.red('❌ 图谱构建失败:'), error.message);
263
+ process.exitCode = 1;
264
+ }
265
+ });
266
+
267
+ program
268
+ .command('fix')
269
+ .description('自动修复 health check 发现的问题(空链接、缺失文件、过小文件、孤儿页)')
270
+ .option('--dry-run', '只报告,不修改文件')
271
+ .action(opts => {
272
+ const project = loadProject();
273
+ console.log(chalk.magenta('🔧 自动修复 Wiki 问题...'));
274
+ try {
275
+ run('bash', [
276
+ join(SCRIPTS, 'wiki-auto-fix.sh'),
277
+ '--wiki-dir', project.wikiDir,
278
+ '--min-size', String(project.config.health.minFileSize),
279
+ ...(opts.dryRun ? ['--dry-run'] : []),
280
+ ], {
281
+ cwd: project.root,
282
+ env: projectEnv(project),
283
+ });
284
+ console.log(chalk.green('✅ 修复完成!'));
285
+ } catch (error) {
286
+ console.error(chalk.red('❌ 修复失败:'), error.message);
287
+ process.exitCode = 1;
288
+ }
289
+ });
290
+
291
+ program
292
+ .command('tags')
293
+ .description('从 [[tag/<name>]] 链接生成 tag 聚合页(tag/<name>.md)')
294
+ .action(() => {
295
+ const project = loadProject();
296
+ console.log(chalk.blue('🏷 构建 tag 聚合页...'));
297
+ try {
298
+ run(process.execPath, [join(SCRIPTS, 'tags-builder.mjs')], {
299
+ cwd: project.root,
300
+ env: projectEnv(project),
301
+ });
302
+ console.log(chalk.green('✅ tag 聚合页已生成'));
303
+ } catch (error) {
304
+ console.error(chalk.red('❌ 生成失败:'), error.message);
305
+ process.exitCode = 1;
306
+ }
307
+ });
308
+
309
+ program
310
+ .command('health')
311
+ .description('Wiki 健康检查(断链、空文件、孤立页面)')
312
+ .action(() => {
313
+ const project = loadProject();
314
+ console.log(chalk.yellow('🏥 Wiki 健康检查...'));
315
+ try {
316
+ run('bash', [join(SCRIPTS, 'wiki-health.sh'), '--wiki-dir', project.wikiDir, '--min-size', String(project.config.health.minFileSize)], {
317
+ cwd: project.root,
318
+ env: projectEnv(project),
319
+ });
320
+ } catch {
321
+ console.log(chalk.yellow('⚠️ 发现一些问题,建议修复(knowflow fix)'));
322
+ process.exitCode = 1;
323
+ }
324
+ });
325
+
326
+ program
327
+ .command('status')
328
+ .description('显示 Wiki 统计信息(含向量索引状态)')
329
+ .action(() => {
330
+ const project = loadProject();
331
+ const articleCount = countFiles(project.wikiDir);
332
+ const lineCount = countLines(project.wikiDir);
333
+ const rawCount = countFiles(project.rawDir);
334
+ let nodes = '-', edges = '-';
335
+ const graphJson = project.graphHtml.replace(/\.html$/i, '.json');
336
+ if (existsSync(graphJson)) {
337
+ try {
338
+ const graph = JSON.parse(readFileSync(graphJson, 'utf8'));
339
+ nodes = graph.nodes?.length ?? '-';
340
+ edges = graph.edges?.length ?? '-';
341
+ } catch {}
342
+ }
343
+ let vectorStatus = chalk.red('❌ 未构建');
344
+ const vectorFile = join(project.wikiDir, '.vector-index.json');
345
+ if (existsSync(vectorFile)) {
346
+ try {
347
+ const raw = JSON.parse(readFileSync(vectorFile, 'utf8'));
348
+ const pages = Array.isArray(raw) ? raw : raw.pages || [];
349
+ const embedded = pages.filter(item => item.embedding).length;
350
+ vectorStatus = chalk.green(`✅ ${pages.length} 页,${embedded} 页已向量化`);
351
+ } catch { vectorStatus = chalk.yellow('⚠️ 索引损坏'); }
352
+ }
353
+ console.log(`\n${chalk.bold.cyan(' 📊 KnowFlow 状态概览')}`);
354
+ console.log(` Wiki 文章数: ${articleCount} 篇`);
355
+ console.log(` 总行数: ${lineCount} 行`);
356
+ console.log(` 原始素材: ${rawCount} 个`);
357
+ console.log(` 向量索引: ${vectorStatus}`);
358
+ console.log(` 图谱: ${nodes} 个节点 / ${edges} 条关系`);
359
+ console.log(` API Key: ${hasApiKey(project) ? '✅ 已配置' : '❌ 未配置'}`);
360
+ console.log(chalk.dim(` 项目目录: ${project.root}`));
361
+ console.log(chalk.dim(` Wiki 目录: ${project.wikiDir}\n`));
362
+ });
363
+
364
+ program.configureHelp({ sortSubcommands: true, helpWidth: 60 });
365
+
366
+ if (process.argv.length === 2) program.outputHelp();
367
+ else program.parse(process.argv);
@@ -0,0 +1,10 @@
1
+ # KnowFlow 中文文档入口
2
+
3
+ 完整、持续维护的中文项目说明已迁移到仓库根目录的 [README.zh-CN.md](../README.zh-CN.md)。
4
+
5
+ 更多专题文档:
6
+
7
+ - [方法论:LLM Wiki](methodology/llm-wiki-methodology.md)
8
+ - [系统架构](architecture/system-architecture.md)
9
+ - [数据模型](reference/data-model.md)
10
+ - [贡献指南](contributing.md)
@@ -0,0 +1,150 @@
1
+ # KnowFlow 系统架构
2
+
3
+ ## 整体流程
4
+
5
+ ```
6
+ ┌─────────────────────────────────────────────────────────────┐
7
+ │ 输入层 (Input) │
8
+ │ URL │ Tweet │ PDF │ WeChat │ YouTube │ Bookmark │
9
+ └──────────────────────┬──────────────────────────────────────┘
10
+
11
+ ┌─────────────────────────────────────────────────────────────┐
12
+ │ Step 1: Fetch (摄取) │
13
+ │ 自动识别来源类型 → 全文提取 → 存入 raw/ │
14
+ │ scripts/ingest.sh → fetcher 模块 │
15
+ └──────────────────────┬──────────────────────────────────────┘
16
+
17
+ ┌─────────────────────────────────────────────────────────────┐
18
+ │ Step 2: Extract (提取) │
19
+ │ LLM 分析内容 → 提取实体/概念/关系 → JSON Schema 约束输出 │
20
+ │ scripts/batch-ingest.cjs → extractor 模块 │
21
+ └──────────────────────┬──────────────────────────────────────┘
22
+
23
+ ┌─────────────────────────────────────────────────────────────┐
24
+ │ Step 3: Compile (编译) │
25
+ │ 用模板渲染 Wiki 页面 → 自动交叉链接 → 写入 wiki/ │
26
+ │ templates/{entity,concept,comparison,source}.md │
27
+ └──────────────────────┬──────────────────────────────────────┘
28
+
29
+ ┌────────────┴────────────┐
30
+ ▼ ▼
31
+ ┌─────────────────────┐ ┌─────────────────────────────────┐
32
+ │ Step 4: Graph │ │ Step 5: Vector │
33
+ │ 知识图谱构建 │ │ 向量索引构建 │
34
+ │ scripts/graph_ │ │ scripts/vector-store.mjs │
35
+ │ builder.py │ │ scripts/vector_store.py │
36
+ │ → graph.json │ │ → vector-store.mjs-data/ │
37
+ │ → graph.html │ │ │
38
+ └─────────────────────┘ └─────────────────────────────────┘
39
+ ```
40
+
41
+ ## 目录结构详解
42
+
43
+ ```
44
+ knowflow/
45
+ ├── bin/
46
+ │ └── knowflow.js # CLI 入口,命令路由
47
+ ├── scripts/
48
+ │ ├── ingest.sh # 单条 URL/文本采集(fetch→raw)
49
+ │ ├── batch-ingest.cjs # 批量处理原始素材
50
+ │ ├── enrich-wiki.js # Wiki 后处理(链接补全、摘要生成)
51
+ │ ├── graph_builder.py # 从 Wiki 页面构建知识图谱
52
+ │ ├── graph_relation_labeler.py # LLM 分析关系类型
53
+ │ ├── vector-store.mjs # 向量索引(Embedding + 存储)
54
+ │ ├── vector_store.py # 向量检索接口
55
+ │ ├── pipeline.sh # 5 步全自动化管线
56
+ │ ├── bookmark_sync.sh # X/Twitter 书签同步
57
+ │ ├── wechat_sync.sh # 微信公众号文章同步
58
+ │ └── wiki-health.sh # 健康检查(断链、空页面、孤立页)
59
+ ├── templates/
60
+ │ ├── entity.md # 实体页模板(人物/公司/项目)
61
+ │ ├── concept.md # 概念页模板(方法论/技术概念)
62
+ │ ├── comparison.md # 对比页模板(A vs B)
63
+ │ └── source.md # 来源页模板(原始内容摘要)
64
+ ├── docs/
65
+ │ ├── methodology/ # 方法论文档
66
+ │ ├── architecture/ # 架构文档(本文件)
67
+ │ └── reference/ # 参考文档
68
+ ├── articles/ # 博客系列文章
69
+ ├── .knowflowrc.example # 配置示例
70
+ └── package.json
71
+ ```
72
+
73
+ ## 数据流
74
+
75
+ ### 单条 URL 的完整生命周期
76
+
77
+ ```
78
+ 1. 用户执行: knowflow ingest https://example.com/article
79
+
80
+ 2. Fetch 阶段:
81
+ - 检测 URL 类型(普通网页 / Twitter / YouTube / PDF)
82
+ - 选择对应的 fetcher
83
+ - 下载全文,提取标题、作者、日期等元数据
84
+ - 存储为 raw/{timestamp}-{slug}.md
85
+
86
+ 3. Extract 阶段:
87
+ - 读取 raw 文件
88
+ - 调用 LLM(带 JSON Schema 约束)提取:
89
+ {
90
+ "entities": [{name, type, description, mentions}],
91
+ "concepts": [{name, definition, related}],
92
+ "summary": "...",
93
+ "key_points": ["..."]
94
+ }
95
+ - 结果存入临时变量
96
+
97
+ 4. Compile 阶段:
98
+ - 根据提取结果选择模板:
99
+ - 主要实体 → entity.md
100
+ - 新概念 → concept.md
101
+ - 与已有实体对比 → comparison.md
102
+ - 来源记录 → source.md
103
+ - 渲染 Markdown,自动添加 [[WikiLink]] 语法
104
+ - 扫描已有 Wiki 页面,添加反向链接
105
+
106
+ 5. Graph 阶段:
107
+ - 解析所有 Wiki 页面中的 `[[link]]` 语法
108
+ - 构建节点和边
109
+ - 调用 LLM 标注关系类型("created_by"、"uses"、"competes_with" 等)
110
+ - 输出 graph.json + graph.html
111
+
112
+ 6. Vector 阶段:
113
+ - 对每个 Wiki 页面做 Embedding
114
+ - 存入本地向量索引
115
+ - 支持语义搜索查询
116
+ ```
117
+
118
+ ## 关键设计决策
119
+
120
+ ### 为什么用 JSON Schema 约束 LLM 输出?
121
+
122
+ LLM 的自由输出格式不可预测。JSON Schema 就像合同——告诉 AI "我只要你这种格式的输出"。代码里加了 fallback:解析失败重试一次,再失败就当纯文本存入。
123
+
124
+ ### 为什么 Wiki 用 Markdown 而不是数据库?
125
+
126
+ - **人类可读** — 直接用编辑器打开就能看
127
+ - **版本控制友好** — Git 可以追踪每次变更
128
+ - **AI 友好** — LLM 天然擅长生成和理解 Markdown
129
+ - **可移植** — 不依赖任何数据库服务
130
+
131
+ ### 为什么图谱和向量都要?
132
+
133
+ | 能力 | 知识图谱 | 向量检索 |
134
+ |------|---------|---------|
135
+ | 精确查找 | ✅ 按实体名/关系查 | ❌ |
136
+ | 语义搜索 | ❌ | ✅ "类似 XXX 的内容" |
137
+ | 发现关联 | ✅ A→B→C 的路径 | ❌ |
138
+ | 模糊匹配 | ❌ | ✅ 语义相近即可 |
139
+
140
+ 两者互补,缺一不可。
141
+
142
+ ## 技术栈
143
+
144
+ | 组件 | 技术 | 原因 |
145
+ |------|------|------|
146
+ | CLI 运行时 | Node.js | npm 生态,开发者熟悉 |
147
+ | 内容提取 | 智谱 AI (GLM-Flash) | 性价比高,中文优秀 |
148
+ | 知识图谱 | Python + vis.js | 图算法成熟,可视化好 |
149
+ | 向量索引 | 本地 Embedding | 无需外部服务,隐私安全 |
150
+ | 数据存储 | 文件系统 (Markdown) | 零依赖,Git 友好 |
Binary file
@@ -0,0 +1,131 @@
1
+ # KnowFlow 贡献指南
2
+
3
+ [English contribution guide](../CONTRIBUTING.md) | 简体中文
4
+
5
+ > 欢迎贡献!无论是代码、文档、Wiki 模板还是博客文章。
6
+
7
+ ## 给人类贡献者的指南
8
+
9
+ ### 报 Bug
10
+
11
+ 在 GitHub Issues 提交,请包含:
12
+ 1. **复现步骤** — 怎么操作的
13
+ 2. **期望行为** — 你期望发生什么
14
+ 3. **实际行为** — 实际发生了什么
15
+ 4. **环境信息** — Node.js 版本、OS、`.knowflowrc` 关键配置(脱敏)
16
+
17
+ ### 提 PR
18
+
19
+ 1. Fork 本仓库
20
+ 2. 创建特性分支 (`git checkout -b feature/amazing`)
21
+ 3. 提交更改 (`git commit -m 'Add amazing feature'`)
22
+ 4. 推送分支 (`git push origin feature/amazing`)
23
+ 5. 创建 Pull Request
24
+
25
+ ### 代码规范
26
+
27
+ - **Shell 脚本**: `set -euo pipefail`,用 `${var:+"${arr[@]}"}` 处理空数组
28
+ - **Node.js**: ES Modules (`import/export`),不用 CommonJS
29
+ - **Python**: 类型注解,Python 3.10+
30
+ - **Commit message**: 用 Conventional Commits (`feat:`, `fix:`, `docs:` 等)
31
+
32
+ ## 给 AI 助手的指南
33
+
34
+ 如果你是 AI 助手(Claude、GPT、Cursor 等),以下信息帮你快速理解项目:
35
+
36
+ ### 项目本质
37
+
38
+ KnowFlow 是一个 **知识编译器**,不是搜索工具。核心价值链:
39
+
40
+ ```
41
+ 原始内容 → [AI提取] → 结构化Wiki → [关联] → 知识图谱 + 向量索引
42
+ ```
43
+
44
+ ### 关键文件速查
45
+
46
+ | 你想做什么 | 看哪个文件 |
47
+ |-----------|----------|
48
+ | 理解整体架构 | `docs/architecture/system-architecture.md` |
49
+ | 理解方法论 | `docs/methodology/llm-wiki-methodology.md` |
50
+ | 理解数据模型 | `docs/reference/data-model.md` |
51
+ | 修改提取逻辑 | `scripts/batch-ingest.cjs` |
52
+ | 修改模板 | `templates/*.md` |
53
+ | 修改 CLI 命令 | `bin/knowflow.js` |
54
+ | 修改图谱构建 | `scripts/graph_builder.py` |
55
+ | 修改向量检索 | `scripts/vector-store.mjs` |
56
+
57
+ ### 常见贡献场景
58
+
59
+ #### 1. 新增一个 Fetcher(支持新的内容来源)
60
+
61
+ 在 `ingest.sh` 的 URL 类型检测逻辑中添加新分支:
62
+ ```bash
63
+ # 示例:支持 Reddit
64
+ if [[ "$url" == *reddit.com* ]]; then
65
+ fetch_reddit "$url" > "$raw_file"
66
+ fi
67
+ ```
68
+
69
+ #### 2. 改进提取 Prompt
70
+
71
+ 在 `batch-ingest.cjs` 中调整提取逻辑时,注意保持现有 Markdown 数据的向后兼容。
72
+
73
+ #### 3. 新增 Wiki 模板
74
+
75
+ 在 `templates/` 创建新 `.md` 文件,然后在 `enrich-wiki.js` 中注册。
76
+
77
+ #### 4. 优化知识图谱关系标注
78
+
79
+ `graph_relation_labeler.py` 控制 LLM 如何标注边的类型。可以扩展关系类型列表。
80
+
81
+ ### 测试你的改动
82
+
83
+ ```bash
84
+ # 单 URL 原始采集测试(只写入 raw,不生成 Wiki 页面)
85
+ knowflow ingest https://example.com/test-article
86
+
87
+ # 检查输出
88
+ ls raw/web/ # 原始素材是否生成?
89
+
90
+ # 手动或通过 Agent 整理 Wiki 页面后再生成图谱
91
+ knowflow graph --no-open
92
+ cat graph/graph.json
93
+
94
+ # 健康检查
95
+ knowflow health
96
+ ```
97
+
98
+ ## Wiki 页面贡献
99
+
100
+ 除了代码,你也可以通过**写 Wiki 页面**来贡献!
101
+
102
+ ### 方式一:Ingest 高质量内容
103
+
104
+ 最简单的贡献方式 — 找到好的 URL,跑一次 ingest:
105
+
106
+ ```bash
107
+ # 找一篇关于 AI/开发/知识管理的好文章
108
+ knowflow ingest https://awesome-article.example.com/post
109
+ ```
110
+
111
+ ### 方式二:直接写 Wiki 页面
112
+
113
+ 在 `wiki/` 目录下创建 `.md` 文件,遵循[数据模型](./reference/data-model.md)中的格式。记得用 `[[Link]]` 语法链接到其他页面。
114
+
115
+ ## 文档结构
116
+
117
+ ```
118
+ docs/
119
+ ├── methodology/
120
+ │ └── llm-wiki-methodology.md # 核心理念(必读)
121
+ ├── architecture/
122
+ │ └── system-architecture.md # 技术架构(开发者必读)
123
+ └── reference/
124
+ └── data-model.md # 数据格式(贡献者参考)
125
+ ```
126
+
127
+ 这些文档不仅是给人看的——它们也是**项目的自文档化知识库**。任何 LLM 读取这个仓库后,都应该能理解 KnowFlow 是什么、怎么工作、如何参与贡献。
128
+
129
+ ## 许可证
130
+
131
+ MIT License — 随意使用、修改、分发。