@ohos-cpf/3rdloop 0.0.10 → 0.0.11
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 +5 -7
- package/lib/cli.js +3 -3
- package/lib/knowledge.js +97 -174
- package/package.json +1 -1
- package/vendor/VERSION +2 -2
package/README.md
CHANGED
|
@@ -192,21 +192,19 @@ npm 包内置 MCP Gateway(`vendor/MCP`)、知识库检索引擎(`vendor/Ar
|
|
|
192
192
|
|
|
193
193
|
### 场景七:知识回收(把沉淀的知识打包发给维护者)
|
|
194
194
|
|
|
195
|
-
|
|
195
|
+
任务执行会在本机沉淀知识 JSON(`<dataDir>/Knowledge/` 下的 `index.json` 登记表 + `kn_*.json` 条目)。想贡献给社区/团队,打包发给维护者即可:
|
|
196
196
|
|
|
197
197
|
```bash
|
|
198
198
|
# 用户侧:打包(纯本地操作,不上传任何数据)
|
|
199
199
|
3rdloop knowledge export # → ./3rdloop-knowledge-<日期>-<主机名>.zip
|
|
200
|
-
3rdloop knowledge export --since 2026-09-01 # 只打包该日期后新增的
|
|
201
200
|
3rdloop knowledge export --note "mp3agic 批次" # 附备注
|
|
202
201
|
|
|
203
|
-
# 维护者侧:收到 zip
|
|
204
|
-
3rdloop knowledge import <zip> --dry-run #
|
|
205
|
-
3rdloop knowledge import <zip> #
|
|
206
|
-
3rdloop knowledge import <zip> --allow-overwrite # 覆盖同名文件
|
|
202
|
+
# 维护者侧:收到 zip 后解包到收件箱
|
|
203
|
+
3rdloop knowledge import <zip> --dry-run # 预览
|
|
204
|
+
3rdloop knowledge import <zip> # → ~/.3lib/3rdloop/knowledge-inbox/<来源主机>-<日期>/
|
|
207
205
|
```
|
|
208
206
|
|
|
209
|
-
|
|
207
|
+
包内容只有知识 JSON(不含 md / kb 文档 / BadCase)。zip 为标准格式,可先解压自查;多个用户的包在收件箱按 `主机名-日期` 隔离,互不冲突,也不覆盖维护者本地知识库——人工评估后再入库。
|
|
210
208
|
|
|
211
209
|
更多进阶用法(任务管理、`step` 单步执行、`orch` 自定义编排、工作流注册等),见仓库 `cli/README_DEVELOP.md` 或 `3rdloop <子命令> --help`。
|
|
212
210
|
|
package/lib/cli.js
CHANGED
|
@@ -697,9 +697,9 @@ function printHelp() {
|
|
|
697
697
|
3rdloop mcp install [--cli opencode|deveco|all] 装配 MCP Gateway(注册到 AI CLI +
|
|
698
698
|
种子复制知识库数据/BadCase 到 ~/.3lib/3rdloop/,npm 安装态必需)
|
|
699
699
|
3rdloop mcp uninstall|status 移除注册 / 查看装配状态
|
|
700
|
-
3rdloop knowledge export [--
|
|
701
|
-
|
|
702
|
-
3rdloop knowledge import <zip> [--dry-run]
|
|
700
|
+
3rdloop knowledge export [--note <text>]
|
|
701
|
+
打包本地知识沉淀 JSON(db/Knowledge)为 zip 发给维护者回收
|
|
702
|
+
3rdloop knowledge import <zip> [--dry-run] 维护者解包收到的知识包到收件箱
|
|
703
703
|
3rdloop serve [--port N] [--web-port N] 启动 Server 后端 + Web 前端(同时启动)
|
|
704
704
|
[--web-root <dir>] [--ext-root <dir>] [--ext <name,...>] [--no-ext] [--open]
|
|
705
705
|
3rdloop serve install-ext <dir> 安装扩展(tag/issue/prcheck 等二级功能)
|
package/lib/knowledge.js
CHANGED
|
@@ -1,27 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* knowledge.js —— 3rdloop knowledge
|
|
2
|
+
* knowledge.js —— 3rdloop knowledge:知识沉淀 JSON 打包回收(export)与收件(import)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* 最简人工中转通道:知识沉淀系统的数据全部是 JSON(<dataDir>/Knowledge/ 下的
|
|
5
|
+
* index.json 登记表 + kn_*.json 条目),export 打包这些 JSON 为 zip 发给维护者;
|
|
6
|
+
* 维护者 import 解包到收件箱目录人工评估,不直接覆盖本地知识库。
|
|
6
7
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* 打包 ~/.3lib/3rdloop/kb-data/docs 新增文档 + BadCase 新规则/案例 +
|
|
10
|
-
* db/Knowledge 索引清单 → zip(含 manifest.json:来源/时间/清单/统计)
|
|
11
|
-
* 默认输出 ./3rdloop-knowledge-<日期>-<主机名>.zip
|
|
12
|
-
*
|
|
13
|
-
* 维护者侧(import,收到 zip 后一键预检 + 合入):
|
|
14
|
-
* 3rdloop knowledge import <zip> [--dry-run] [--allow-overwrite]
|
|
15
|
-
* 解包校验 manifest → 逐文件落位(docs → kb-data/docs、BadCase → BadCase、
|
|
16
|
-
* 索引清单仅汇总展示不入库)→ 重建由 kb 引擎首次检索时自动完成
|
|
17
|
-
* --dry-run 只预览将发生的变更,不写任何文件
|
|
8
|
+
* 用户侧: 3rdloop knowledge export [--output <file>] [--note <text>]
|
|
9
|
+
* 维护者侧: 3rdloop knowledge import <zip> [--dry-run]
|
|
18
10
|
*
|
|
19
11
|
* 设计约束:
|
|
20
|
-
* -
|
|
21
|
-
* -
|
|
22
|
-
* -
|
|
23
|
-
*
|
|
24
|
-
* 不自动导入(指针含本机绝对路径,对他机无意义)
|
|
12
|
+
* - 只打 JSON,不碰任何 md / kb-data / BadCase
|
|
13
|
+
* - export 纯本地打包,不上传
|
|
14
|
+
* - import 落位到收件箱 ~/.3lib/3rdloop/knowledge-inbox/<来源>-<日期>/,
|
|
15
|
+
* 多个用户的包互不冲突,也不污染维护者自己的 Knowledge 库(人工评估后自行入库)
|
|
25
16
|
*/
|
|
26
17
|
|
|
27
18
|
import fs from 'node:fs';
|
|
@@ -30,31 +21,26 @@ import os from 'node:os';
|
|
|
30
21
|
import zlib from 'node:zlib';
|
|
31
22
|
import process from 'node:process';
|
|
32
23
|
|
|
33
|
-
// ───
|
|
24
|
+
// ─── 路径 ─────────────────────────────────────────────────────────────
|
|
34
25
|
|
|
35
26
|
/**
|
|
36
|
-
*
|
|
37
|
-
* @param {string}
|
|
38
|
-
* @param {string} [dataDir] - CLI 解析的数据目录(kb-data/BadCase 固定在 loopHome,
|
|
39
|
-
* 仅 Knowledge 索引跟随 dataDir——npm 安装态两者相同,仓库开发态 dataDir 可能被覆盖)
|
|
27
|
+
* 知识目录。
|
|
28
|
+
* @param {string} dataDir - CLI 解析的数据目录(Knowledge = <dataDir>/Knowledge)
|
|
40
29
|
*/
|
|
41
|
-
export function knowledgePaths(
|
|
42
|
-
const loopHome = path.join(user3libHome, '3rdloop');
|
|
30
|
+
export function knowledgePaths(dataDir) {
|
|
43
31
|
return {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
badcaseDir: path.join(loopHome, 'BadCase'),
|
|
47
|
-
knowledgeIndex: dataDir
|
|
48
|
-
? path.join(dataDir, 'Knowledge', 'index.json')
|
|
49
|
-
: path.join(loopHome, 'db', 'Knowledge', 'index.json'),
|
|
32
|
+
knowledgeDir: path.join(dataDir, 'Knowledge'),
|
|
33
|
+
inboxRoot: null, // import 侧单独解析(基于 user3libHome)
|
|
50
34
|
};
|
|
51
35
|
}
|
|
52
36
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
37
|
+
/** 维护者收件箱根目录。 */
|
|
38
|
+
function inboxRoot(user3libHome) {
|
|
39
|
+
return path.join(user3libHome, '3rdloop', 'knowledge-inbox');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ─── zip 读写(单成员 DEFLATE zip,纯 Node 无第三方依赖)──────────────
|
|
43
|
+
// 一个 zip 只含一个被 deflate 压缩的 JSON 载荷文件 payload.json。
|
|
58
44
|
|
|
59
45
|
const PAYLOAD_NAME = 'payload.json';
|
|
60
46
|
|
|
@@ -153,133 +139,101 @@ function crc32(buf) {
|
|
|
153
139
|
// ─── export ───────────────────────────────────────────────────────────
|
|
154
140
|
|
|
155
141
|
/**
|
|
156
|
-
*
|
|
157
|
-
*/
|
|
158
|
-
function walkFiles(root, rel = '', out = []) {
|
|
159
|
-
if (!fs.existsSync(root)) return out;
|
|
160
|
-
for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
|
|
161
|
-
if (entry.name === '.index' || entry.name === '.DS_Store' || entry.name === 'node_modules') continue;
|
|
162
|
-
const relPath = rel ? `${rel}/${entry.name}` : entry.name;
|
|
163
|
-
if (entry.isDirectory()) walkFiles(path.join(root, entry.name), relPath, out);
|
|
164
|
-
else out.push(relPath);
|
|
165
|
-
}
|
|
166
|
-
return out;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* 3rdloop knowledge export 实现。
|
|
142
|
+
* 3rdloop knowledge export 实现:打包 <dataDir>/Knowledge/ 下全部 JSON。
|
|
171
143
|
* @param {object} opts
|
|
172
|
-
* @param {string} opts.
|
|
173
|
-
* @param {string[]} opts.rest - 命令参数(--output/--
|
|
144
|
+
* @param {string} opts.dataDir
|
|
145
|
+
* @param {string[]} opts.rest - 命令参数(--output/--note)
|
|
174
146
|
* @param {boolean} opts.jsonMode
|
|
175
147
|
* @returns {Promise<number>} 退出码
|
|
176
148
|
*/
|
|
177
|
-
export async function knowledgeExport({
|
|
149
|
+
export async function knowledgeExport({ dataDir, rest = [], jsonMode = false }) {
|
|
178
150
|
const out = (data) => { if (jsonMode) process.stdout.write(JSON.stringify(data, null, 2) + '\n'); };
|
|
179
151
|
const log = (msg) => process.stderr.write(`${msg}\n`);
|
|
180
152
|
|
|
181
153
|
const output = _flagValue(rest, '--output');
|
|
182
|
-
const since = _flagValue(rest, '--since'); // ISO 日期:仅收该日期后新增的文件(按 mtime)
|
|
183
154
|
const note = _flagValue(rest, '--note') || '';
|
|
184
|
-
const sinceMs = since ? Date.parse(since) : null;
|
|
185
|
-
if (since && Number.isNaN(sinceMs)) {
|
|
186
|
-
log(`错误: 非法 --since 日期: ${since}(示例: 2026-09-01)`);
|
|
187
|
-
return 1;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
const up = knowledgePaths(user3libHome, dataDir);
|
|
191
|
-
|
|
192
|
-
// ── 收集三类知识 ──
|
|
193
|
-
const docsFiles = walkFiles(up.kbDocsDir);
|
|
194
|
-
const badcaseFiles = walkFiles(up.badcaseDir);
|
|
195
|
-
const knowledgeIndex = fs.existsSync(up.knowledgeIndex)
|
|
196
|
-
? JSON.parse(fs.readFileSync(up.knowledgeIndex, 'utf8'))
|
|
197
|
-
: [];
|
|
198
155
|
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
for (const rel of docsFiles) {
|
|
205
|
-
const abs = path.join(up.kbDocsDir, rel);
|
|
206
|
-
if (!sinceFilter(abs)) { docsSkipped++; continue; }
|
|
207
|
-
docs[rel] = fs.readFileSync(abs, 'utf8');
|
|
156
|
+
const knowledgeDir = path.join(dataDir, 'Knowledge');
|
|
157
|
+
if (!fs.existsSync(knowledgeDir)) {
|
|
158
|
+
log(`未找到知识目录: ${knowledgeDir}`);
|
|
159
|
+
log('提示: 知识在任务执行后自动沉淀(db/Knowledge),先跑几个任务再看。');
|
|
160
|
+
return 0;
|
|
208
161
|
}
|
|
209
162
|
|
|
210
|
-
//
|
|
211
|
-
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
const abs = path.join(up.badcaseDir, rel);
|
|
216
|
-
if (!sinceFilter(abs)) { badcaseSkipped++; continue; }
|
|
217
|
-
badcase[rel] = fs.readFileSync(abs, 'utf8');
|
|
163
|
+
// 收集全部 .json(index.json 登记表 + kn_*.json 知识条目)
|
|
164
|
+
const files = {};
|
|
165
|
+
for (const entry of fs.readdirSync(knowledgeDir, { withFileTypes: true })) {
|
|
166
|
+
if (!entry.isFile() || !entry.name.endsWith('.json')) continue;
|
|
167
|
+
files[entry.name] = fs.readFileSync(path.join(knowledgeDir, entry.name), 'utf8');
|
|
218
168
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
log('未发现可导出的知识(kb-data/docs、BadCase 均为空)。');
|
|
222
|
-
log('提示: 知识在任务执行后由 knowledge-import SKILL 沉淀,先跑几个任务再看。');
|
|
169
|
+
if (Object.keys(files).length === 0) {
|
|
170
|
+
log(`知识目录为空: ${knowledgeDir}`);
|
|
223
171
|
return 0;
|
|
224
172
|
}
|
|
225
173
|
|
|
226
|
-
// ── 载荷与 manifest ──
|
|
227
174
|
const manifest = {
|
|
228
175
|
format: '3rdloop-knowledge/1',
|
|
229
176
|
exportedAt: new Date().toISOString(),
|
|
230
177
|
host: os.hostname(),
|
|
231
178
|
platform: process.platform,
|
|
232
179
|
note,
|
|
233
|
-
since: since || null,
|
|
234
180
|
stats: {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
181
|
+
files: Object.keys(files).length,
|
|
182
|
+
entries: files['index.json']
|
|
183
|
+
? _countIndexEntries(files['index.json'])
|
|
184
|
+
: Object.keys(files).filter(n => n.startsWith('kn_')).length,
|
|
238
185
|
},
|
|
239
186
|
};
|
|
240
|
-
const payload = JSON.stringify({ manifest,
|
|
187
|
+
const payload = JSON.stringify({ manifest, knowledgeFiles: files }, null, 2);
|
|
241
188
|
|
|
242
|
-
// 默认文件名:./3rdloop-knowledge-<YYYYMMDD
|
|
189
|
+
// 默认文件名:./3rdloop-knowledge-<YYYYMMDD>-<主机名>.zip
|
|
243
190
|
const date = new Date().toISOString().slice(0, 10).replace(/-/g, '');
|
|
244
191
|
const defaultName = `3rdloop-knowledge-${date}-${os.hostname().split('.')[0]}.zip`;
|
|
245
|
-
const target = output
|
|
246
|
-
? path.resolve(output)
|
|
247
|
-
: path.join(process.cwd(), defaultName);
|
|
192
|
+
const target = output ? path.resolve(output) : path.join(process.cwd(), defaultName);
|
|
248
193
|
|
|
249
194
|
writeSingleFileZip(target, payload);
|
|
250
195
|
|
|
251
196
|
if (jsonMode) {
|
|
252
|
-
out({ ok: true, file: target, ...manifest.stats
|
|
197
|
+
out({ ok: true, file: target, ...manifest.stats });
|
|
253
198
|
} else {
|
|
254
199
|
log(`[3rdloop knowledge] 知识包已生成: ${target}`);
|
|
255
|
-
log(`
|
|
256
|
-
log(`
|
|
257
|
-
log(` 知识索引条目: ${manifest.stats.knowledgeIndexEntries} 条(仅作人工评估清单)`);
|
|
200
|
+
log(` JSON 文件: ${manifest.stats.files} 个(index.json + kn_*.json)`);
|
|
201
|
+
log(` 知识条目: ${manifest.stats.entries} 条`);
|
|
258
202
|
if (note) log(` 备注: ${note}`);
|
|
259
203
|
log(` 大小: ${(fs.statSync(target).size / 1024).toFixed(1)} KB`);
|
|
260
|
-
log(`请将 zip
|
|
204
|
+
log(`请将 zip 发送给维护者(微信/邮件均可);内容由你本机知识 JSON 打包而成,可先解压自查。`);
|
|
261
205
|
}
|
|
262
206
|
return 0;
|
|
263
207
|
}
|
|
264
208
|
|
|
209
|
+
/** 解析 index.json 统计条目数(容错:解析失败返回 0)。 */
|
|
210
|
+
function _countIndexEntries(indexRaw) {
|
|
211
|
+
try {
|
|
212
|
+
const arr = JSON.parse(indexRaw);
|
|
213
|
+
return Array.isArray(arr) ? arr.length : 0;
|
|
214
|
+
} catch {
|
|
215
|
+
return 0;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
265
219
|
// ─── import ───────────────────────────────────────────────────────────
|
|
266
220
|
|
|
267
221
|
/**
|
|
268
|
-
* 3rdloop knowledge import 实现(维护者侧:收 zip →
|
|
222
|
+
* 3rdloop knowledge import 实现(维护者侧:收 zip → 解到收件箱目录)。
|
|
223
|
+
* 收件箱按 <来源主机>-<导出时间> 隔离,多包互不冲突;不覆盖本地 Knowledge 库。
|
|
269
224
|
* @param {object} opts
|
|
270
225
|
* @param {string} opts.user3libHome
|
|
271
|
-
* @param {string[]} opts.rest - <zip> --dry-run
|
|
226
|
+
* @param {string[]} opts.rest - <zip> --dry-run
|
|
272
227
|
* @param {boolean} opts.jsonMode
|
|
273
228
|
*/
|
|
274
|
-
export async function knowledgeImport({ user3libHome,
|
|
229
|
+
export async function knowledgeImport({ user3libHome, rest = [], jsonMode = false }) {
|
|
275
230
|
const out = (data) => { if (jsonMode) process.stdout.write(JSON.stringify(data, null, 2) + '\n'); };
|
|
276
231
|
const log = (msg) => process.stderr.write(`${msg}\n`);
|
|
277
232
|
|
|
278
233
|
const dryRun = rest.includes('--dry-run');
|
|
279
|
-
const allowOverwrite = rest.includes('--allow-overwrite');
|
|
280
234
|
const zipPath = rest.find(a => !a.startsWith('--'));
|
|
281
235
|
if (!zipPath) {
|
|
282
|
-
log('缺少 zip 路径。用法: 3rdloop knowledge import <zip> [--dry-run]
|
|
236
|
+
log('缺少 zip 路径。用法: 3rdloop knowledge import <zip> [--dry-run]');
|
|
283
237
|
return 1;
|
|
284
238
|
}
|
|
285
239
|
const absZip = path.resolve(zipPath);
|
|
@@ -288,7 +242,6 @@ export async function knowledgeImport({ user3libHome, dataDir = '', rest = [], j
|
|
|
288
242
|
return 1;
|
|
289
243
|
}
|
|
290
244
|
|
|
291
|
-
// ── 解包 ──
|
|
292
245
|
let payload;
|
|
293
246
|
try {
|
|
294
247
|
payload = JSON.parse(readSingleFileZip(absZip));
|
|
@@ -296,67 +249,43 @@ export async function knowledgeImport({ user3libHome, dataDir = '', rest = [], j
|
|
|
296
249
|
log(`错误: 解析知识包失败: ${err.message}`);
|
|
297
250
|
return 1;
|
|
298
251
|
}
|
|
299
|
-
const { manifest,
|
|
252
|
+
const { manifest, knowledgeFiles } = payload;
|
|
300
253
|
if (!manifest || manifest.format !== '3rdloop-knowledge/1') {
|
|
301
254
|
log(`错误: 未知知识包格式: ${manifest?.format || '(缺失 manifest)'}`);
|
|
302
255
|
return 1;
|
|
303
256
|
}
|
|
304
257
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
const
|
|
309
|
-
for (const rel of Object.keys(docs || {})) {
|
|
310
|
-
(fs.existsSync(path.join(up.kbDocsDir, rel)) ? plan.docsSkip : plan.docsAdd).push(rel);
|
|
311
|
-
}
|
|
312
|
-
for (const rel of Object.keys(badcase || {})) {
|
|
313
|
-
(fs.existsSync(path.join(up.badcaseDir, rel)) ? plan.badcaseSkip : plan.badcaseAdd).push(rel);
|
|
314
|
-
}
|
|
258
|
+
// 收件箱:~/.3lib/3rdloop/knowledge-inbox/<主机名>-<导出日期>/
|
|
259
|
+
const date = String(manifest.exportedAt).slice(0, 10);
|
|
260
|
+
const host = String(manifest.host || 'unknown').split('.')[0];
|
|
261
|
+
const inboxDir = path.join(inboxRoot(user3libHome), `${host}-${date}`);
|
|
315
262
|
|
|
316
263
|
if (dryRun) {
|
|
317
|
-
if (jsonMode) out({ ok: true, dryRun: true, manifest,
|
|
264
|
+
if (jsonMode) out({ ok: true, dryRun: true, manifest, inboxDir, files: Object.keys(knowledgeFiles || {}) });
|
|
318
265
|
else {
|
|
319
266
|
log(`[3rdloop knowledge] 预演(不写入任何文件)`);
|
|
320
267
|
log(` 知识包: ${absZip}`);
|
|
321
268
|
log(` 导出时间: ${manifest.exportedAt} 来源: ${manifest.host}(${manifest.platform})`);
|
|
322
269
|
if (manifest.note) log(` 备注: ${manifest.note}`);
|
|
323
|
-
log(`
|
|
324
|
-
log(` 将新增 BadCase: ${plan.badcaseAdd.length} 个${plan.badcaseAdd.length ? `(如 ${plan.badcaseAdd.slice(0, 3).join(', ')}${plan.badcaseAdd.length > 3 ? ' ...' : ''})` : ''}`);
|
|
325
|
-
log(` 跳过同名冲突: docs ${plan.docsSkip.length} / BadCase ${plan.badcaseSkip.length}(--allow-overwrite 可覆盖)`);
|
|
326
|
-
log(` 知识索引条目: ${(knowledgeIndex || []).length} 条(仅展示,不入库)`);
|
|
270
|
+
log(` JSON 文件: ${Object.keys(knowledgeFiles || {}).length} 个 → 收件箱 ${inboxDir}`);
|
|
327
271
|
}
|
|
328
272
|
return 0;
|
|
329
273
|
}
|
|
330
274
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
337
|
-
fs.writeFileSync(target, docs[rel]);
|
|
338
|
-
added++;
|
|
339
|
-
}
|
|
340
|
-
for (const rel of Object.keys(badcase || {})) {
|
|
341
|
-
const target = path.join(up.badcaseDir, rel);
|
|
342
|
-
if (fs.existsSync(target) && !allowOverwrite) { skipped++; continue; }
|
|
343
|
-
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
344
|
-
fs.writeFileSync(target, badcase[rel]);
|
|
345
|
-
added++;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
// 知识索引:打印摘要供人工评估(不入库:含他机绝对路径指针)
|
|
349
|
-
if (knowledgeIndex && knowledgeIndex.length > 0 && !jsonMode) {
|
|
350
|
-
log(`[3rdloop knowledge] 携带知识索引 ${knowledgeIndex.length} 条(未入库;含导出者机器的本地路径,仅供参考)`);
|
|
275
|
+
let written = 0;
|
|
276
|
+
for (const [name, content] of Object.entries(knowledgeFiles || {})) {
|
|
277
|
+
fs.mkdirSync(inboxDir, { recursive: true });
|
|
278
|
+
fs.writeFileSync(path.join(inboxDir, name), content);
|
|
279
|
+
written++;
|
|
351
280
|
}
|
|
352
281
|
|
|
353
282
|
if (jsonMode) {
|
|
354
|
-
out({ ok: true,
|
|
283
|
+
out({ ok: true, inboxDir, written, manifest });
|
|
355
284
|
} else {
|
|
356
|
-
log(`[3rdloop knowledge]
|
|
357
|
-
log(`
|
|
358
|
-
log(`
|
|
359
|
-
log(
|
|
285
|
+
log(`[3rdloop knowledge] 已解包到收件箱: ${inboxDir}`);
|
|
286
|
+
log(` JSON 文件: ${written} 个(含 index.json + kn_*.json)`);
|
|
287
|
+
log(` 来源: ${manifest.host} 导出时间: ${manifest.exportedAt}${manifest.note ? ` 备注: ${manifest.note}` : ''}`);
|
|
288
|
+
log(`人工评估后可将有价值条目拷入仓库或本地 <dataDir>/Knowledge/。`);
|
|
360
289
|
}
|
|
361
290
|
return 0;
|
|
362
291
|
}
|
|
@@ -368,31 +297,30 @@ function _flagValue(rest, name) {
|
|
|
368
297
|
return idx !== -1 ? rest[idx + 1] : undefined;
|
|
369
298
|
}
|
|
370
299
|
|
|
300
|
+
/** 该 flag 是否携带值。 */
|
|
301
|
+
function _hasValue(flag) {
|
|
302
|
+
return flag === '--output' || flag === '--note';
|
|
303
|
+
}
|
|
304
|
+
|
|
371
305
|
export function printKnowledgeHelp() {
|
|
372
|
-
process.stdout.write(`3rdloop knowledge ——
|
|
306
|
+
process.stdout.write(`3rdloop knowledge —— 知识沉淀 JSON 打包回收(人工中转通道)
|
|
373
307
|
|
|
374
308
|
用法(用户侧:打包发给维护者):
|
|
375
309
|
3rdloop knowledge export [选项]
|
|
376
|
-
打包
|
|
377
|
-
- kb-data/docs/ 结构化知识文档(knowledge-import SKILL 的沉淀)
|
|
378
|
-
- BadCase/ 已知问题规则/案例(泛化 SKILL 的沉淀)
|
|
379
|
-
- db/Knowledge/ 索引清单(仅作人工评估参考,不入库)
|
|
310
|
+
打包 <dataDir>/Knowledge/ 下的全部 JSON(index.json 登记表 + kn_*.json 知识条目)
|
|
380
311
|
选项:
|
|
381
312
|
--output <file> 输出路径(默认 ./3rdloop-knowledge-<日期>-<主机名>.zip)
|
|
382
|
-
--since <date> 仅打包该日期后新增的文件(示例: 2026-09-01)
|
|
383
313
|
--note <text> 备注(写入 manifest,如 "mp3agic 检视批次")
|
|
384
314
|
--json 结构化输出
|
|
385
315
|
|
|
386
|
-
用法(维护者侧:收到 zip
|
|
387
|
-
3rdloop knowledge import <zip> [--dry-run]
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
解包后 docs → ~/.3lib/3rdloop/kb-data/docs、BadCase → ~/.3lib/3rdloop/BadCase
|
|
316
|
+
用法(维护者侧:收到 zip 后解包到收件箱):
|
|
317
|
+
3rdloop knowledge import <zip> [--dry-run]
|
|
318
|
+
解包到 ~/.3lib/3rdloop/knowledge-inbox/<来源主机>-<日期>/
|
|
319
|
+
多个用户的包互不冲突;不覆盖本地知识库,人工评估后自行入库
|
|
391
320
|
|
|
392
321
|
说明:
|
|
393
322
|
- export 纯本地打包,不上传任何数据;可先解压自查内容再发送
|
|
394
|
-
-
|
|
395
|
-
- 维护者审核后可把精选内容合入仓库 Archive/data 与 BadCase/,随版本分发
|
|
323
|
+
- 只含知识沉淀 JSON,不含 md / kb 文档 / BadCase
|
|
396
324
|
`);
|
|
397
325
|
}
|
|
398
326
|
|
|
@@ -401,6 +329,7 @@ export function printKnowledgeHelp() {
|
|
|
401
329
|
* @param {object} opts
|
|
402
330
|
* @param {string[]} opts.rest
|
|
403
331
|
* @param {string} opts.user3libHome
|
|
332
|
+
* @param {string} opts.dataDir
|
|
404
333
|
* @param {boolean} opts.jsonMode
|
|
405
334
|
* @returns {Promise<number>}
|
|
406
335
|
*/
|
|
@@ -413,7 +342,6 @@ export async function cmdKnowledge({ rest = [], user3libHome = '', dataDir = '',
|
|
|
413
342
|
let subIdx = -1;
|
|
414
343
|
for (let i = 0; i < rest.length; i++) {
|
|
415
344
|
if (rest[i].startsWith('--')) {
|
|
416
|
-
// flag 值(如 --note 后面的文本)不是子命令,跳过
|
|
417
345
|
if (_hasValue(rest[i]) && rest[i + 1] !== undefined && !rest[i + 1].startsWith('--')) i++;
|
|
418
346
|
continue;
|
|
419
347
|
}
|
|
@@ -427,14 +355,9 @@ export async function cmdKnowledge({ rest = [], user3libHome = '', dataDir = '',
|
|
|
427
355
|
return rest.length === 0 ? 0 : 1;
|
|
428
356
|
}
|
|
429
357
|
const sub = rest[subIdx];
|
|
430
|
-
if (sub === 'export') return knowledgeExport({
|
|
431
|
-
if (sub === 'import') return knowledgeImport({ user3libHome,
|
|
358
|
+
if (sub === 'export') return knowledgeExport({ dataDir, rest: args, jsonMode });
|
|
359
|
+
if (sub === 'import') return knowledgeImport({ user3libHome, rest: args, jsonMode });
|
|
432
360
|
process.stderr.write(`未知子命令: ${sub}\n`);
|
|
433
361
|
printKnowledgeHelp();
|
|
434
362
|
return 1;
|
|
435
363
|
}
|
|
436
|
-
|
|
437
|
-
/** 该 flag 是否携带值(--output/--since/--note)。 */
|
|
438
|
-
function _hasValue(flag) {
|
|
439
|
-
return flag === '--output' || flag === '--since' || flag === '--note';
|
|
440
|
-
}
|
package/package.json
CHANGED
package/vendor/VERSION
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
0.0.
|
|
2
|
-
built=2026-09-04T07:
|
|
1
|
+
0.0.11
|
|
2
|
+
built=2026-09-04T07:50:10.913Z
|
|
3
3
|
sha=84d14394dbb68bd4
|