@fanchao8609/agent_brain_sync 1.21.0 → 1.23.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.
- package/README.md +9 -5
- package/bin/abs.js +8 -6
- package/package.json +1 -1
- package/skill/abs-agent-brain-sync/SKILL.md +13 -1
- package/src/brain/capture.js +20 -4
- package/src/brain/db-cmd.js +138 -19
- package/src/brain/db.js +261 -33
- package/src/brain/evolve.js +135 -0
- package/src/brain/layers.js +16 -1
- package/src/brain/load.js +96 -14
- package/src/brain/summon.js +73 -6
- package/src/install.js +20 -6
- package/src/note.js +16 -5
package/README.md
CHANGED
|
@@ -17,14 +17,17 @@ npm install -g @fanchao8609/agent_brain_sync
|
|
|
17
17
|
abs install
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
**默认只装 pi**(其余宿主 opt-in)——装到不用的宿主上会往那边配置文件塞 hook/MCP 条目。
|
|
21
21
|
|
|
22
22
|
```bash
|
|
23
|
-
abs install
|
|
24
|
-
abs install
|
|
23
|
+
abs install # 默认: 只装 pi
|
|
24
|
+
abs install --all # 全装: claude-code / codex / opencode / pi / omp
|
|
25
|
+
abs install --agent claude-code # 只装指定宿主
|
|
25
26
|
abs install --agent pi --no-mcp # 只装 hook + skill,不要 MCP
|
|
26
27
|
```
|
|
27
28
|
|
|
29
|
+
交互模式下回车 = 只装 pi;输入 `all` 才全装。
|
|
30
|
+
|
|
28
31
|
装了什么:
|
|
29
32
|
|
|
30
33
|
| 宿主 | hook | MCP | skill |
|
|
@@ -117,11 +120,12 @@ abs todo done TASK-1
|
|
|
117
120
|
abs note "一句话经验" --tags 坑,docker # 经验暂存 → sources/
|
|
118
121
|
abs log "完成 X" # 记一行流水;abs log 无参 = 查看
|
|
119
122
|
abs query <词> # 检索图谱(多词 OR);已被推翻的经验默认隐藏(--all 可看)
|
|
120
|
-
abs capture [--layer 1-7] "一句话"
|
|
123
|
+
abs capture [--layer 1-7] [--kind …] "一句话" # 按七层判层后落到位(--kind: gotcha|decision|tradeoff|howto)
|
|
121
124
|
abs db rebuild # 扫所有登记项目 → 建图数据库(~/.abs/graph.db)
|
|
122
|
-
abs db search <词>
|
|
125
|
+
abs db search <词> [--full] # 走库检索:默认只给摘要+token 成本,--full 才附正文
|
|
123
126
|
abs db status # 库体检:七层分布 / 悬挂链接 / 已入库项目
|
|
124
127
|
abs db add # 把当前项目登记进 ~/.abs/projects.json
|
|
128
|
+
abs db evolve # 知识进化体检:被推翻/断链/主题堆积/僵化页
|
|
125
129
|
abs summon [--mark|--status] # 会话总结托底:列出未总结 / 有更新的会话(不写盘)
|
|
126
130
|
abs resolve <页名或id> # 反查页面路径(页改名后 id 不变)
|
|
127
131
|
abs supersede <页名> [--by <新页>] # 标记经验已失效(不删文件,保留历史;query 默认不再返回)
|
package/bin/abs.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// abs <cmd> [args]
|
|
4
4
|
// 命令: init / board / status / load / task / install / uninstall / help
|
|
5
5
|
import { cmdInit, cmdStatus, cmdLoad, cmdTask, cmdLog, cmdQuery, cmdLint, cmdNote, cmdConcept, cmdShow, cmdRepair, cmdWrapup, cmdRule, cmdTeardownCheck, cmdTodoArchive, cmdResolve, cmdSupersede, cmdReview } from '../src/store.js';
|
|
6
|
-
import { cmdDbRebuild, cmdDbSearch, cmdDbStatus, addProject } from '../src/brain/db-cmd.js';
|
|
6
|
+
import { cmdDbRebuild, cmdDbSearch, cmdDbStatus, cmdDbEvolve, addProject } from '../src/brain/db-cmd.js';
|
|
7
7
|
import { cmdCapture } from '../src/brain/capture.js';
|
|
8
8
|
import { cmdSummon } from '../src/brain/summon-cmd.js';
|
|
9
9
|
import { setUser, getUser, userConfigPath } from '../src/userconfig.js';
|
|
@@ -210,6 +210,7 @@ function parseArgv(args) {
|
|
|
210
210
|
tags: values.tags,
|
|
211
211
|
limit: values.limit === undefined ? undefined : Number(values.limit),
|
|
212
212
|
layer: values.layer,
|
|
213
|
+
kind: values.kind,
|
|
213
214
|
mark: !!values.mark,
|
|
214
215
|
status: !!values.status,
|
|
215
216
|
title: values.title,
|
|
@@ -249,7 +250,7 @@ const usage = `abs — 跨会话记忆工具(.brain/ 图谱)
|
|
|
249
250
|
summon [--mark|--status] 会话总结托底: 列出未总结/有更新的会话 (不写盘)
|
|
250
251
|
concept <slug> --title … 建概念页骨架
|
|
251
252
|
query <词…> [--all] 检索知识页 (多词 OR)
|
|
252
|
-
db rebuild|search|status
|
|
253
|
+
db rebuild|search|status|evolve 图数据库: 建库 / 亮度搜索 / 体检 / 进化体检
|
|
253
254
|
resolve <页名…> 反查页面路径
|
|
254
255
|
supersede <页名> [--by 页] 标经验已失效
|
|
255
256
|
review [--accept 页…|--reject 页…] 待确认页队列 (draft 升 active/否决)
|
|
@@ -534,7 +535,7 @@ async function main() {
|
|
|
534
535
|
break;
|
|
535
536
|
}
|
|
536
537
|
case 'capture': {
|
|
537
|
-
console.log(await cmdCapture({ dir: opts.dir, text: opts._.join(' '), layer: opts.layer, tags: opts.tags, title: opts.title }));
|
|
538
|
+
console.log(await cmdCapture({ dir: opts.dir, text: opts._.join(' '), layer: opts.layer, tags: opts.tags, title: opts.title, kind: opts.kind }));
|
|
538
539
|
break;
|
|
539
540
|
}
|
|
540
541
|
case 'note': {
|
|
@@ -556,7 +557,7 @@ async function main() {
|
|
|
556
557
|
case 'uninstall': {
|
|
557
558
|
// --help 已在 switch 前统一拦截(subUsage)。
|
|
558
559
|
if (cmd === 'install') {
|
|
559
|
-
await runInstall({ agent: opts.agent, mcp: opts.mcp !== false, skill: opts.skill !== false, yes: opts.yes });
|
|
560
|
+
await runInstall({ agent: opts.agent, all: !!opts.all, mcp: opts.mcp !== false, skill: opts.skill !== false, yes: opts.yes });
|
|
560
561
|
} else {
|
|
561
562
|
await runUninstall({ agent: opts.agent, yes: opts.yes });
|
|
562
563
|
}
|
|
@@ -579,14 +580,15 @@ async function main() {
|
|
|
579
580
|
const sub = opts._[0];
|
|
580
581
|
const rest = opts._.slice(1);
|
|
581
582
|
if (sub === 'rebuild') { console.log(await cmdDbRebuild({ dir: opts.dir })); break; }
|
|
582
|
-
if (sub === 'search') { console.log(await cmdDbSearch({ query: rest.join(' '), limit: Number(opts.limit) || 8 })); break; }
|
|
583
|
+
if (sub === 'search') { console.log(await cmdDbSearch({ query: rest.join(' '), limit: Number(opts.limit) || 8, full: !!opts.full })); break; }
|
|
583
584
|
if (sub === 'status') { console.log(await cmdDbStatus()); break; }
|
|
585
|
+
if (sub === 'evolve') { console.log(await cmdDbEvolve({ staleDays: Number(opts['stale-days']) || 90 })); break; }
|
|
584
586
|
if (sub === 'add') {
|
|
585
587
|
const r = await addProject(opts.dir || process.cwd());
|
|
586
588
|
console.log(`✓ 已登记项目: ${r}\n 建库: abs db rebuild`);
|
|
587
589
|
break;
|
|
588
590
|
}
|
|
589
|
-
console.log('用法: abs db rebuild | search <词> | status | add [--dir <项目根>]');
|
|
591
|
+
console.log('用法: abs db rebuild | search <词> | status | evolve | add [--dir <项目根>]');
|
|
590
592
|
break;
|
|
591
593
|
}
|
|
592
594
|
case 'query': {
|
package/package.json
CHANGED
|
@@ -19,7 +19,7 @@ description: "USE FOR abs / agent-brain-sync 跨会话记忆与任务续接。
|
|
|
19
19
|
abs load # 开机读状态
|
|
20
20
|
abs query <词> # 检索;abs resolve <页名> 反查路径
|
|
21
21
|
abs todo add <id> --note "做什么" # 登记;note/memo/state/done 见「进行中」
|
|
22
|
-
abs capture [--layer 1-7] "…"
|
|
22
|
+
abs capture [--layer 1-7] [--kind …] "…" # 按七层判定落点后写入(--kind 分作用)
|
|
23
23
|
abs note "经验" [--tags a,b] # 暂存经验(一律 L7 混沌层)
|
|
24
24
|
abs concept <slug> --title "…" # 建概念页骨架
|
|
25
25
|
abs rule add "一句话" # 加 Rules;abs lint 体检;abs init 建图谱
|
|
@@ -110,6 +110,18 @@ abs rule add "一句话" # 加 Rules;abs lint 体检;abs init
|
|
|
110
110
|
|
|
111
111
|
判据不明时用 L4 —— 但**知道是工具经验就写 L3**:实测算过,本仓 52 页 concepts 里 23 页其实是 L3。
|
|
112
112
|
|
|
113
|
+
**`--kind` 是另一条正交轴**(借 claude-mem 的 observation 类型):`layer` 说「什么性质」,`kind` 说「起什么作用」。
|
|
114
|
+
|
|
115
|
+
| kind | 含义 | 何时用 |
|
|
116
|
+
|---|---|---|
|
|
117
|
+
| `gotcha` | 关键坑 | 踩了会白干、想不到的那种 |
|
|
118
|
+
| `decision` | 决策 | 记「当时为什么这么选」 |
|
|
119
|
+
| `tradeoff` | 权衡 | A 好但 B 也有理,最后选了哪个 |
|
|
120
|
+
| `howto` | 做法 | 这类事该怎么做 |
|
|
121
|
+
|
|
122
|
+
不传就不写(旧页留空,不强制补)。实测只有 `gotcha` 能靠 tags/标题机械识别,
|
|
123
|
+
另三类判据在内容里 —— 别拿关键词瞎猜。
|
|
124
|
+
|
|
113
125
|
`abs capture` 之后跑 `abs db rebuild` 让图数据库收录,否则检索拿不到它。
|
|
114
126
|
|
|
115
127
|
## 进行中:什么时候动它
|
package/src/brain/capture.js
CHANGED
|
@@ -19,6 +19,7 @@ import { cmdLog } from '../note.js';
|
|
|
19
19
|
import { clip, slugOf } from '../text.js';
|
|
20
20
|
import { LAYERS, layerOf, DEFAULT_LAYER } from './layers.js';
|
|
21
21
|
import { autoIndex } from './db-cmd.js';
|
|
22
|
+
import { summonTail } from './summon.js';
|
|
22
23
|
|
|
23
24
|
/** 层 → 落点目录。L1/L6 不立页,由下面的分支单独处理。 */
|
|
24
25
|
const LAYER_DIR = {
|
|
@@ -51,13 +52,13 @@ export function layerGuide() {
|
|
|
51
52
|
].join('\n');
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
export async function cmdCapture({ dir, text, layer, tags, title }) {
|
|
55
|
+
export async function cmdCapture({ dir, text, layer, tags, title, kind }) {
|
|
55
56
|
const clean = String(text || '').trim();
|
|
56
57
|
const layerArg = String(layer || '').trim();
|
|
57
58
|
|
|
58
59
|
if (!clean) {
|
|
59
60
|
return [
|
|
60
|
-
'用法: abs capture [--layer 1-7] "一句话" [--tags a,b] [--title "页标题"]',
|
|
61
|
+
'用法: abs capture [--layer 1-7] [--kind gotcha|decision|tradeoff|howto] "一句话" [--tags a,b] [--title "页标题"]',
|
|
61
62
|
'',
|
|
62
63
|
layerGuide(),
|
|
63
64
|
].join('\n');
|
|
@@ -85,6 +86,16 @@ export async function cmdCapture({ dir, text, layer, tags, title }) {
|
|
|
85
86
|
return r;
|
|
86
87
|
}
|
|
87
88
|
|
|
89
|
+
// 作用分档(2026-11-02 借 cloude-mem):gotcha 坑 / decision 决策 / tradeoff 权衡 / howto 做法。
|
|
90
|
+
// 与 layer 正交:layer 说「这是什么性质的知识」,kind 说「它起什么作用」。
|
|
91
|
+
const KIND = {
|
|
92
|
+
gotcha: '坑', decision: '决策', tradeoff: '权衡', howto: '做法',
|
|
93
|
+
};
|
|
94
|
+
const kindArg = String(kind || '').trim().toLowerCase();
|
|
95
|
+
if (kindArg && !KIND[kindArg]) {
|
|
96
|
+
return `✗ --kind 只认 ${Object.keys(KIND).join(' / ')}(收到 "${kind}")`;
|
|
97
|
+
}
|
|
98
|
+
|
|
88
99
|
let root;
|
|
89
100
|
try {
|
|
90
101
|
root = await requireBrain(dir || process.cwd());
|
|
@@ -120,6 +131,7 @@ export async function cmdCapture({ dir, text, layer, tags, title }) {
|
|
|
120
131
|
'---',
|
|
121
132
|
`tags: [${['concept', ...tagList].join(', ')}]`,
|
|
122
133
|
`layer: ${L}`,
|
|
134
|
+
...(kindArg ? [`kind: ${kindArg}`] : []),
|
|
123
135
|
`author: ${who}`,
|
|
124
136
|
`updated: ${today()}`,
|
|
125
137
|
'status: draft',
|
|
@@ -153,18 +165,22 @@ export async function cmdCapture({ dir, text, layer, tags, title }) {
|
|
|
153
165
|
const slug = file.replace(/\.md$/, '');
|
|
154
166
|
const section = dirName === 'concepts' ? 'Concepts' : dirName === 'sessions' ? 'Sessions' : 'Sources';
|
|
155
167
|
await registerInIndex(root, section, slug, head);
|
|
156
|
-
await cmdLog({ dir: root, title: clean, kind: 'note' });
|
|
168
|
+
await cmdLog({ dir: root, title: clean, kind: 'note', summon: false });
|
|
157
169
|
|
|
158
170
|
// 落盘即入库 —— 否则用户以为记下了,检索却空手而归(静默失效)。
|
|
159
171
|
// autoIndex 永不抛错也不阻塞:库坏了只是检索旧一点,不能让记笔记失败。
|
|
160
172
|
const idx = await autoIndex();
|
|
161
173
|
|
|
174
|
+
// 每次总结都顺手查一遍会话历史,避免漏下没总结的会话(2026-11-02 用户要求)。
|
|
175
|
+
// 只给线索不写盘 —— 落哪一条仍是人的判断。
|
|
176
|
+
const tail = await summonTail(root);
|
|
177
|
+
|
|
162
178
|
return [
|
|
163
179
|
`✓ 已落入 L${L} ${meta.name}层 → ${dirName}/${file}`,
|
|
164
180
|
` ${clip(clean, 60)} ${atTag(who)}`,
|
|
165
181
|
` 该层衰减 ${meta.decay}(${meta.note})`,
|
|
166
182
|
idx.ok ? ' 已同步入库,abs db search 可查到' : ` (未入库:${idx.reason})`,
|
|
167
|
-
].join('\n');
|
|
183
|
+
].join('\n') + tail;
|
|
168
184
|
}
|
|
169
185
|
|
|
170
186
|
export { LAYERS };
|
package/src/brain/db-cmd.js
CHANGED
|
@@ -6,12 +6,72 @@
|
|
|
6
6
|
import { promises as fs } from 'node:fs';
|
|
7
7
|
import { join, resolve, dirname } from 'node:path';
|
|
8
8
|
import { findBrainRoot, brainPath } from '../index.js';
|
|
9
|
-
import { openDb, rebuild, search, illuminate, rulesOf, stats, ensureDbDir, dbPath } from './db.js';
|
|
9
|
+
import { openDb, rebuild, search, illuminate, rulesOf, stats, ensureDbDir, dbPath, lastOpenRebuilt } from './db.js';
|
|
10
10
|
import { LAYERS } from './layers.js';
|
|
11
11
|
|
|
12
12
|
const PAGE_DIRS = ['concepts', 'entities', 'sources', 'syntheses', 'sessions'];
|
|
13
13
|
const FLAT_FILES = ['index.md', 'todo.md', 'log.md'];
|
|
14
14
|
|
|
15
|
+
/** 项目根路径 → 项目名(入库时的 project 字段)。
|
|
16
|
+
*
|
|
17
|
+
* 取路径末段。为何不引入 UUID(2026-11-02 评估):项目名是给人看的标识,
|
|
18
|
+
* 出现在每条检索结果里;UUID 会让输出不可读。末段同名的风险用 `db status` 暴露,
|
|
19
|
+
* 不做静默处理。
|
|
20
|
+
*/
|
|
21
|
+
function projectNameOf(root) {
|
|
22
|
+
return String(root).split('/').filter(Boolean).pop() || 'unknown';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** 给一批项目根算出**互不重名**的项目名。
|
|
26
|
+
*
|
|
27
|
+
* 为何需要(2026-11-02 实测):项目名默认取路径末段,而机器上同时存在
|
|
28
|
+
* ~/.agents 与 ~/Documents/软件/.agents,两者末段都是 `.agents` →
|
|
29
|
+
* 入库时撞 UNIQUE(project, dir, slug) 约束,整个 rebuild 失败。
|
|
30
|
+
*
|
|
31
|
+
* 消歧规则:重名的那些改用「父目录-末段」,仍重名就继续往上加,直到唯一。
|
|
32
|
+
* 只给重名的项目加前缀 —— 没重名的保持短名(检索结果里读起来更清楚)。
|
|
33
|
+
*/
|
|
34
|
+
export function uniqueProjectNames(roots) {
|
|
35
|
+
const seg = (p) => String(p).split('/').filter(Boolean);
|
|
36
|
+
const base = new Map(roots.map((r) => [r, seg(r).pop() || 'unknown']));
|
|
37
|
+
const groups = new Map();
|
|
38
|
+
for (const [r, n] of base) {
|
|
39
|
+
if (!groups.has(n)) groups.set(n, []);
|
|
40
|
+
groups.get(n).push(r);
|
|
41
|
+
}
|
|
42
|
+
const out = new Map();
|
|
43
|
+
for (const [name, list] of groups) {
|
|
44
|
+
if (list.length === 1) { out.set(list[0], name); continue; }
|
|
45
|
+
// 重名 → 逐层加父目录前缀,直到组内唯一
|
|
46
|
+
for (let depth = 2; depth <= 4; depth++) {
|
|
47
|
+
const cand = new Map();
|
|
48
|
+
for (const r of list) {
|
|
49
|
+
const s = seg(r);
|
|
50
|
+
cand.set(r, s.slice(Math.max(0, s.length - depth)).join('-'));
|
|
51
|
+
}
|
|
52
|
+
const vals = [...cand.values()];
|
|
53
|
+
if (new Set(vals).size === vals.length) {
|
|
54
|
+
for (const [r, v] of cand) out.set(r, v);
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
if (depth === 4) for (const [r, v] of cand) out.set(r, v); // 兜底:认了
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** 当前所在项目名。查询时用它决定「读哪几条铁律」。
|
|
64
|
+
*
|
|
65
|
+
* 为何要单独一个函数(2026-11-02):原来只在 cmdDbSearch 里内联算,
|
|
66
|
+
* 与 rebuild 用的算法是两份代码 —— 任一边改了项目名规则,铁律就会读错项目的。
|
|
67
|
+
* 判据:项目名的算法只能有一处。 */
|
|
68
|
+
async function currentProjectName(dir) {
|
|
69
|
+
if (process.env.ABS_PROJECT) return process.env.ABS_PROJECT;
|
|
70
|
+
const from = dir || process.cwd();
|
|
71
|
+
const root = await findBrainRoot(from);
|
|
72
|
+
return root ? projectNameOf(root) : '';
|
|
73
|
+
}
|
|
74
|
+
|
|
15
75
|
/** 扫一个项目的 .brain/ → 页面数组(含 index/todo/log 三份平铺文件)。 */
|
|
16
76
|
async function scanProject(root, project) {
|
|
17
77
|
const out = [];
|
|
@@ -80,14 +140,19 @@ export async function cmdDbRebuild({ dir } = {}) {
|
|
|
80
140
|
return '✗ 没有可入的项目。先在项目根跑: abs db add 或 abs db rebuild';
|
|
81
141
|
}
|
|
82
142
|
|
|
143
|
+
// 先收集所有有效根,再统一算项目名 —— 名字要去重,必须看到全局才知道谁重名
|
|
144
|
+
const found = [];
|
|
145
|
+
for (const root of roots) {
|
|
146
|
+
const r = await findBrainRoot(root);
|
|
147
|
+
if (r) found.push(r); // 登记了但目录没了 → 跳过,不报错
|
|
148
|
+
}
|
|
149
|
+
const names = uniqueProjectNames([...new Set(found)]);
|
|
83
150
|
const pages = [];
|
|
84
151
|
const used = [];
|
|
85
|
-
for (const
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
pages.push(...await scanProject(found, project));
|
|
90
|
-
used.push(`${project} (${found})`);
|
|
152
|
+
for (const f of [...new Set(found)]) {
|
|
153
|
+
const project = names.get(f);
|
|
154
|
+
pages.push(...await scanProject(f, project));
|
|
155
|
+
used.push(`${project} (${f})`);
|
|
91
156
|
}
|
|
92
157
|
if (!pages.length) return '✗ 登记的项目都没有 .brain/ 图谱,无可入库内容。';
|
|
93
158
|
|
|
@@ -111,7 +176,7 @@ export async function cmdDbRebuild({ dir } = {}) {
|
|
|
111
176
|
}
|
|
112
177
|
|
|
113
178
|
/** `abs db search <词>` —— 走库搜索:FTS 种子 → 亮度扩散。 */
|
|
114
|
-
export async function cmdDbSearch({ query, limit = 8 } = {}) {
|
|
179
|
+
export async function cmdDbSearch({ query, limit = 8, full = false } = {}) {
|
|
115
180
|
const q = String(query || '').trim();
|
|
116
181
|
if (!q) return '用法: abs db search <词>';
|
|
117
182
|
|
|
@@ -122,9 +187,7 @@ export async function cmdDbSearch({ query, limit = 8 } = {}) {
|
|
|
122
187
|
const h = openDb(path);
|
|
123
188
|
try {
|
|
124
189
|
// L1 铁律永远先出 —— 「不可逾越」的代码保证,不靠读到文本才遵守
|
|
125
|
-
const project =
|
|
126
|
-
|| (await findBrainRoot(process.cwd()) || '').split('/').filter(Boolean).pop()
|
|
127
|
-
|| '';
|
|
190
|
+
const project = await currentProjectName();
|
|
128
191
|
const rules = rulesOf(h, project);
|
|
129
192
|
|
|
130
193
|
const seeds = search(h, q, { limit: 12 });
|
|
@@ -136,17 +199,54 @@ export async function cmdDbSearch({ query, limit = 8 } = {}) {
|
|
|
136
199
|
for (const r of rules) out.push(`- ${r}`);
|
|
137
200
|
}
|
|
138
201
|
if (!lit.length) {
|
|
139
|
-
out.push('',
|
|
202
|
+
out.push('', lastOpenRebuilt()
|
|
203
|
+
? '(库文件曾损坏被清空,现在没有内容:跑 abs db rebuild 重建)'
|
|
204
|
+
: '(无命中。库可能是旧的:abs db rebuild 重扫)');
|
|
140
205
|
return out.join('\n');
|
|
141
206
|
}
|
|
207
|
+
// --full:显式要求时才读正文。默认不给 —— 这是索引优先的核心。
|
|
208
|
+
// 实测(2026-11-02):ABS 单页平均 824 token,8 页全带就是 6600 token,
|
|
209
|
+
// 而真正被用上的往往只有 1-2 页。让「看全文」成为一个显式动作。
|
|
210
|
+
const bodies = new Map();
|
|
211
|
+
if (full) {
|
|
212
|
+
const root = await findBrainRoot(process.cwd());
|
|
213
|
+
if (root) {
|
|
214
|
+
for (const p of lit) {
|
|
215
|
+
const body = await fs.readFile(brainPath(root, p.dir, `${p.slug}.md`), 'utf8').catch(() => '');
|
|
216
|
+
if (body) bodies.set(p.id, body);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
142
221
|
out.push('', '--- 按亮度排序 ---');
|
|
143
222
|
for (const p of lit) {
|
|
144
|
-
|
|
223
|
+
// 亮度现在是 0.3~1.0(不再是早期可能到 165 万的异常值),
|
|
224
|
+
// 条长按 1.0 满格算:0.3 → 4 格,1.0 → 12 格。
|
|
225
|
+
const bar = '█'.repeat(Math.max(1, Math.round(Math.min(p.bright, 1) * 12)));
|
|
145
226
|
const where = [p.project, p.dir, p.slug].filter(Boolean).join('/');
|
|
146
227
|
const tag = p.from === 'spread' ? ' ↝' : '';
|
|
147
|
-
|
|
228
|
+
// 跨项目的结果要显式标出来 —— 用户得知道「这条是别的项目的经验」,
|
|
229
|
+
// 否则会以为是自己项目的结论(判据、上下文都可能不同)。
|
|
230
|
+
const other = project && p.project && p.project !== project ? ' [跨项目]' : '';
|
|
231
|
+
// 索引优先:给摘要 + 取全文成本,让「要不要看全文」成为一个可算的决定。
|
|
232
|
+
const about = p.digest ? `\n ${p.digest}` : '';
|
|
233
|
+
const cost = p.tokens ? ` ~${p.tokens}tok` : '';
|
|
234
|
+
// 作用分档(gotcha/decision/tradeoff/howto)—— 同层里「哪种更该先看」的线索
|
|
235
|
+
const KIND_ICON = { gotcha: '🔴坑', decision: '🟤决策', tradeoff: '⚖️权衡', howto: '🔵做法' };
|
|
236
|
+
const kind = p.kind && KIND_ICON[p.kind] ? ` ${KIND_ICON[p.kind]}` : '';
|
|
237
|
+
out.push(`${bar} ${p.bright.toFixed(2)} L${p.layer} ${where}${tag}${other}${kind}${cost}${about}`);
|
|
238
|
+
if (full && bodies.has(p.id)) {
|
|
239
|
+
// 正文缩进显示,便于与索引行区分
|
|
240
|
+
const body = bodies.get(p.id).replace(/^---[\s\S]*?---\n/, ''); // 去掉 frontmatter
|
|
241
|
+
out.push(...body.split('\n').slice(0, 60).map((l) => ` │ ${l}`));
|
|
242
|
+
if (body.split('\n').length > 60) out.push(' │ …(已截断,用 abs resolve 看全文)');
|
|
243
|
+
}
|
|
148
244
|
}
|
|
149
245
|
out.push('', '(L1 铁律恒在最前;↝ 表示由邻居扩散点亮,非直接命中)');
|
|
246
|
+
out.push('(只给摘要与成本;要看全文: abs db search <词> --full 或 abs resolve <页名>)');
|
|
247
|
+
if (project) {
|
|
248
|
+
out.push(`(铁律只显示本项目 "${project}" 的 —— 别的项目的铁律不外借)`);
|
|
249
|
+
}
|
|
150
250
|
return out.join('\n');
|
|
151
251
|
} finally {
|
|
152
252
|
h.close();
|
|
@@ -161,9 +261,11 @@ export async function cmdDbStatus() {
|
|
|
161
261
|
|
|
162
262
|
const h = openDb(path);
|
|
163
263
|
try {
|
|
264
|
+
const rebuilt = lastOpenRebuilt();
|
|
164
265
|
const s = stats(h);
|
|
165
266
|
const out = [
|
|
166
267
|
`📀 库: ${path}`,
|
|
268
|
+
...(rebuilt ? ['⚠ 库文件曾损坏,已自动清空重建 —— 跑 abs db rebuild 恢复内容'] : []),
|
|
167
269
|
`节点 ${s.nodes} | 边 ${s.edges} | 悬挂链接 ${s.dangling} | 断点词 ${s.anchors} | 铁律 ${s.rules}`,
|
|
168
270
|
'',
|
|
169
271
|
'--- 七层分布 ---',
|
|
@@ -231,14 +333,31 @@ async function cmdDbRebuildQuiet() {
|
|
|
231
333
|
const here = await findBrainRoot(process.cwd());
|
|
232
334
|
const roots = [...new Set([...registered, ...(here ? [here] : [])])];
|
|
233
335
|
if (!roots.length) return;
|
|
234
|
-
const
|
|
336
|
+
const found = [];
|
|
235
337
|
for (const root of roots) {
|
|
236
|
-
const
|
|
237
|
-
if (
|
|
238
|
-
const project = found.split('/').filter(Boolean).pop() || 'unknown';
|
|
239
|
-
pages.push(...await scanProject(found, project));
|
|
338
|
+
const r = await findBrainRoot(root);
|
|
339
|
+
if (r) found.push(r);
|
|
240
340
|
}
|
|
341
|
+
const uniq = [...new Set(found)];
|
|
342
|
+
const names = uniqueProjectNames(uniq);
|
|
343
|
+
const pages = [];
|
|
344
|
+
for (const f of uniq) pages.push(...await scanProject(f, names.get(f)));
|
|
241
345
|
if (!pages.length) return;
|
|
242
346
|
const h = openDb(dbPath());
|
|
243
347
|
try { rebuild(h, pages); } finally { h.close(); }
|
|
244
348
|
}
|
|
349
|
+
|
|
350
|
+
/** `abs db evolve` — 知识自我进化的体检:哪些被推翻、哪些堆积、哪些该合并。 */
|
|
351
|
+
export async function cmdDbEvolve({ staleDays = 90 } = {}) {
|
|
352
|
+
const path = dbPath();
|
|
353
|
+
const exists = await fs.stat(path).then(() => true).catch(() => false);
|
|
354
|
+
if (!exists) return `库不存在: ${path}\n 先跑: abs db rebuild`;
|
|
355
|
+
|
|
356
|
+
const h = openDb(path);
|
|
357
|
+
try {
|
|
358
|
+
const { evolveSignals, renderEvolve } = await import('./evolve.js');
|
|
359
|
+
return renderEvolve(evolveSignals(h, { staleDays }), { staleDays });
|
|
360
|
+
} finally {
|
|
361
|
+
h.close();
|
|
362
|
+
}
|
|
363
|
+
}
|