@fanchao8609/agent_brain_sync 1.8.9 → 1.9.1
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/bin/abs.js +138 -63
- package/bin/mcp.js +5 -4
- package/hooks/abs.opencode.ts +14 -4
- package/package.json +3 -2
- package/skill/abs-agent-brain-sync/SKILL.md +2 -2
- package/src/serve.js +2 -1
- package/src/store.js +19 -5
package/bin/abs.js
CHANGED
|
@@ -57,7 +57,7 @@ async function cmdUpdate({ yes }) {
|
|
|
57
57
|
console.log(r);
|
|
58
58
|
console.log('\n刷新四宿主 hook/skill(hook 脚本烧的是绝对路径 + 模板快照,升级后必须重装)…');
|
|
59
59
|
await runInstall({ agent: undefined, mcp: true, skill: true, yes: yes !== false });
|
|
60
|
-
console.log(`\n完成。重启宿主(pi /
|
|
60
|
+
console.log(`\n完成。重启宿主(pi / opencode / claude-code / codex)后新 hook 生效。`);
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
const [,, cmd, ...rest] = process.argv;
|
|
@@ -91,10 +91,13 @@ const FLAG_SPEC = {
|
|
|
91
91
|
'repair': { type: 'boolean' },
|
|
92
92
|
'no-mcp': { type: 'boolean' },
|
|
93
93
|
'no-skill': { type: 'boolean' },
|
|
94
|
+
// serve 用: --no-open 不自动开浏览器(open 命令仅 macOS)。坑: 曾漏声明 → serve 判
|
|
95
|
+
// opts.open !== false 恒真, --no-open 接不上。
|
|
96
|
+
'no-open': { type: 'boolean' },
|
|
94
97
|
};
|
|
95
98
|
|
|
96
99
|
/** 已被"规范键"接管的 raw flag:不再原样漏出(见 parseArgv 返回处的白名单注释)。 */
|
|
97
|
-
const KNOWN_RAW = new Set(['keep-days', 'dry-run', 'no-mcp', 'no-skill']);
|
|
100
|
+
const KNOWN_RAW = new Set(['keep-days', 'dry-run', 'no-mcp', 'no-skill', 'no-open']);
|
|
98
101
|
|
|
99
102
|
function parseArgv(args) {
|
|
100
103
|
// 坑: parseArgs 会把**任何** `-` 开头的 token 当选项,连正文一起吃:
|
|
@@ -167,6 +170,7 @@ function parseArgv(args) {
|
|
|
167
170
|
dryRun: !!values['dry-run'],
|
|
168
171
|
mcp: !values['no-mcp'],
|
|
169
172
|
skill: !values['no-skill'],
|
|
173
|
+
open: !values['no-open'],
|
|
170
174
|
};
|
|
171
175
|
// 未声明的 `--unknown` 静默收下(旧行为:不进位置参数,也不报错)。
|
|
172
176
|
// 这条保留是因为 rejectExtra 只看位置参数 —— 把未知 flag 放进去会改成报错,
|
|
@@ -176,63 +180,56 @@ function parseArgv(args) {
|
|
|
176
180
|
}
|
|
177
181
|
return o;
|
|
178
182
|
}
|
|
179
|
-
const usage = `abs —
|
|
183
|
+
const usage = `abs — 跨会话记忆工具(.brain/ 图谱)
|
|
180
184
|
|
|
181
|
-
|
|
182
|
-
abs
|
|
183
|
-
abs
|
|
184
|
-
abs
|
|
185
|
-
abs
|
|
185
|
+
常用:
|
|
186
|
+
abs load 开工先跑: 读状态、接上次的活
|
|
187
|
+
abs todo add X --note "做什么" 登记任务 (done/state/note 管后续)
|
|
188
|
+
abs note "经验一句话" 随手记经验; abs log "完成 X" 记成果
|
|
189
|
+
abs query <词…> 检索 .brain/; abs todo 看任务板
|
|
186
190
|
|
|
187
|
-
|
|
191
|
+
全部命令 (细节: abs <命令> --help):
|
|
192
|
+
init [--repair] 建图谱 (当前目录)
|
|
193
|
+
load 开机读状态
|
|
194
|
+
todo 任务看板 (add/note/state/done/archive)
|
|
195
|
+
index | log | status 索引 / 流水 / 概要
|
|
196
|
+
note <经验> [--tags a,b] 经验暂存 → sources/
|
|
197
|
+
concept <slug> --title … 建概念页骨架
|
|
198
|
+
query <词…> [--all] 检索知识页 (多词 OR)
|
|
199
|
+
resolve <页名…> 反查页面路径
|
|
200
|
+
supersede <页名> [--by 页] 标经验已失效
|
|
201
|
+
lint 图谱体检 (死链/超限)
|
|
202
|
+
rule [add "一句话"] 硬规则读写
|
|
203
|
+
serve [--port N] [--no-open] 浏览 .brain/ (仅本机; 端口默认自动选)
|
|
204
|
+
install|uninstall [--agent <宿主>] [--no-mcp] [--no-skill]
|
|
205
|
+
update 升级并刷新 hook/skill
|
|
206
|
+
config [set user <名字>] 使用者姓名 (写操作需先设置)
|
|
207
|
+
--version | help 版本 / 本帮助
|
|
188
208
|
|
|
189
|
-
|
|
190
|
-
abs load 开机读状态 (index/todo/log)
|
|
191
|
-
abs todo 任务看板 Todo / Done(未完成的都在这,行首带状态)
|
|
192
|
-
abs index 图谱索引 index.md
|
|
193
|
-
abs log 流水 log.md
|
|
194
|
-
abs status 当前项目 + 图谱概要
|
|
195
|
-
abs serve [--port 7777] 在浏览器里浏览 .brain/(只读,仅本机)
|
|
196
|
-
abs todo add <id> [--note ..] [--section ..] 登记任务 (start 同义)
|
|
197
|
-
abs todo note <id> --note "断点/进度" 实时落 ↳ 断点 行(建议前缀: 验证: / 边界: / 阻塞:)
|
|
198
|
-
abs todo state <id> --note "进行中|讨论中|滞留中" 改状态标记(原地,不搬区)
|
|
199
|
-
abs todo done <id> [--as 落地|否决|仅方案] 完成;结语标明到底"做成了没有"
|
|
200
|
-
默认 落地。否决=评估后不做(含做了又撤);仅方案=只设计过
|
|
201
|
-
不加结语或结语失真会让下一个会话把"想过"当成"做完了"。
|
|
202
|
-
abs log "完成 X:…" 记一行工作成果 (无参=查看)
|
|
203
|
-
abs note "经验" [--tags 坑,docker] [--when "何时该读"] 经验实时暂存 → sources/
|
|
204
|
-
abs concept <slug> --title "标题" [--tags a,b] [--desc "index 描述"]
|
|
205
|
-
建概念页骨架(头/中/尾四段位置)。只给结构不给内容
|
|
206
|
-
|
|
207
|
-
检索与维护:
|
|
208
|
-
abs query <词1> [词2 …] [--all]
|
|
209
|
-
检索 .brain/ 知识页 (多词 OR);superseded 默认隐藏
|
|
210
|
-
abs resolve <id-or-slug> [更多…]
|
|
211
|
-
按 id/页面名反查路径 (页改名后 id 不变,仍能找回)
|
|
212
|
-
abs supersede <页名> [--by <取代它的页>]
|
|
213
|
-
标记一条经验已失效 (不删文件;query/load 默认不再展示)
|
|
214
|
-
abs lint 图谱体检 (死链/孤岛/超尺寸/堆积)
|
|
215
|
-
abs rule 列出 index.md 的 ## Rules 硬规则
|
|
216
|
-
abs rule add "一句话" 追加一条硬规则 (只放违反会丢数据/静默失效级的)
|
|
217
|
-
abs todo archive [--keep-days N] [--dry-run]
|
|
218
|
-
归档 Done 区旧日期组 → sessions/<日期>-todo归档.md
|
|
219
|
-
|
|
220
|
-
安装与配置 (一次性):
|
|
221
|
-
abs install [--agent <宿主>] 安装 MCP+hook+skill (宿主: claude-code/codex/opencode/pi)
|
|
222
|
-
abs uninstall [--agent <...>] 卸载
|
|
223
|
-
abs update 升级到最新版并刷新四宿主 hook/skill
|
|
224
|
-
abs config [show] 查看使用者姓名 (标记作者用)
|
|
225
|
-
abs config set user <名字> 设置使用者姓名 → ~/.abs/config.json
|
|
226
|
-
未设置时写操作会报错要求先设置 (临时: ABS_USER=<名字> abs ...)
|
|
227
|
-
abs init [--repair] 建 .brain/ 图谱; 结构不完整时报明细, --repair 只补缺不覆盖
|
|
228
|
-
abs --version 显示当前版本
|
|
229
|
-
abs help 本帮助
|
|
230
|
-
|
|
231
|
-
注: abs wrapup / abs teardown-check 是 hook 内部命令, 不需手动调用。
|
|
209
|
+
注: wrapup / teardown-check 是 hook 内部命令。
|
|
232
210
|
`;
|
|
233
211
|
|
|
234
212
|
/** 子命令级用法(abs <cmd> --help 时打印)。 */
|
|
235
213
|
const subUsage = {
|
|
214
|
+
load: [
|
|
215
|
+
'abs load — 开机读状态(Rules 全量 + 图谱计数 + todo 看板 + 最近 log)',
|
|
216
|
+
'',
|
|
217
|
+
'用法:',
|
|
218
|
+
' abs load [--dir <项目根>]',
|
|
219
|
+
'',
|
|
220
|
+
'说明:',
|
|
221
|
+
' • 顶部出现「⏳ 上会话滞留」时先收尾再开工',
|
|
222
|
+
' • 输出是折叠过的;全量明细用 abs todo --full / abs index',
|
|
223
|
+
].join('\n'),
|
|
224
|
+
init: [
|
|
225
|
+
'abs init — 在当前目录(或 --dir)建 .brain/ 图谱',
|
|
226
|
+
'',
|
|
227
|
+
'用法:',
|
|
228
|
+
' abs init [--repair] [--dir <项目根>]',
|
|
229
|
+
'',
|
|
230
|
+
'说明:',
|
|
231
|
+
' • 结构不完整时报明细;--repair 只补缺不覆盖,已有内容一律不动',
|
|
232
|
+
].join('\n'),
|
|
236
233
|
install: [
|
|
237
234
|
'abs install — 把 MCP + hook + skill 安装到 AI 编码宿主',
|
|
238
235
|
'',
|
|
@@ -270,6 +267,65 @@ const subUsage = {
|
|
|
270
267
|
' • 只删 abs 自己装的东西,保留你其它的 hook / MCP / 配置字段',
|
|
271
268
|
' • 配置文件无法解析时会跳过该文件(不覆盖)但仍清理 abs 的脚本与 skill',
|
|
272
269
|
].join('\n'),
|
|
270
|
+
serve: [
|
|
271
|
+
'abs serve — 把 .brain/ 挂成只读网页(左目录树 + 右正文 + [[双链]]跳转)',
|
|
272
|
+
'',
|
|
273
|
+
'用法:',
|
|
274
|
+
' abs serve [--port N] [--no-open] [--dir <项目根>]',
|
|
275
|
+
'',
|
|
276
|
+
'参数:',
|
|
277
|
+
' --port N 端口。默认 0 = 自动选空闲端口(多项目可同时开各的)',
|
|
278
|
+
' --no-open 不自动打开浏览器(open 仅 macOS)',
|
|
279
|
+
' --dir <路径> 服务别的项目的 .brain/',
|
|
280
|
+
'',
|
|
281
|
+
'说明:',
|
|
282
|
+
' • 只读、仅绑定 127.0.0.1,外部访问不了',
|
|
283
|
+
' • 主题/明暗切换在网页右上角;最后一页会记住',
|
|
284
|
+
].join('\n'),
|
|
285
|
+
todo: [
|
|
286
|
+
'abs todo — 任务看板(Todo / Done 两区;未完成都带行首状态标记)',
|
|
287
|
+
'',
|
|
288
|
+
'用法:',
|
|
289
|
+
' abs todo 看板',
|
|
290
|
+
' abs todo add <id> [--note "做什么"] [--section 讨论中|滞留中]',
|
|
291
|
+
' abs todo note <id> --note "断点/进度"',
|
|
292
|
+
' abs todo state <id> --note 进行中|讨论中|滞留中',
|
|
293
|
+
' abs todo done <id> [--as 落地|否决|仅方案] [结语文字]',
|
|
294
|
+
' abs todo archive [--keep-days N] [--dry-run]',
|
|
295
|
+
'',
|
|
296
|
+
'done 的结语:',
|
|
297
|
+
' 落地(默认) = 真做成且有验证; 否决 = 评估后不做(含做了又撤); 仅方案 = 只设计过',
|
|
298
|
+
' 结语失真会让下个会话把"想过"当成"做完了" —— 不确定就写清楚',
|
|
299
|
+
].join('\n'),
|
|
300
|
+
note: [
|
|
301
|
+
'abs note — 经验实时暂存 → sources/(幂等去重;先记后提炼)',
|
|
302
|
+
'',
|
|
303
|
+
'用法:',
|
|
304
|
+
' abs note "一句话经验" [--tags 坑,docker] [--when "什么时候该读这条"]',
|
|
305
|
+
'',
|
|
306
|
+
'说明:',
|
|
307
|
+
' • --tags 首个标签建议带类别(坑/技巧/决策…),检索按词 OR 命中',
|
|
308
|
+
' • --when 供 load 的相关页推荐匹配「何时该读」',
|
|
309
|
+
' • sources 是暂存区: 提炼成 concept 后应清理',
|
|
310
|
+
].join('\n'),
|
|
311
|
+
query: [
|
|
312
|
+
'abs query — 全文检索 .brain/ 知识页',
|
|
313
|
+
'',
|
|
314
|
+
'用法:',
|
|
315
|
+
' abs query <词1> [词2 …] [--all] 多词 OR;命中页按命中数排序',
|
|
316
|
+
'',
|
|
317
|
+
'参数:',
|
|
318
|
+
' --all 连 superseded(已失效)页一起返回',
|
|
319
|
+
].join('\n'),
|
|
320
|
+
config: [
|
|
321
|
+
'abs config — 使用者姓名(写操作的作者标记)',
|
|
322
|
+
'',
|
|
323
|
+
'用法:',
|
|
324
|
+
' abs config 查看当前姓名',
|
|
325
|
+
' abs config set user <名字> 设置(未设置时写操作报错要求先设置)',
|
|
326
|
+
'',
|
|
327
|
+
'临时用法: ABS_USER=<名字> abs …',
|
|
328
|
+
].join('\n'),
|
|
273
329
|
};
|
|
274
330
|
|
|
275
331
|
/**
|
|
@@ -336,7 +392,12 @@ async function main() {
|
|
|
336
392
|
try {
|
|
337
393
|
const opts = parseArgv(rest);
|
|
338
394
|
if (RENAMED[cmd]) throw new Error(RENAMED[cmd]());
|
|
339
|
-
|
|
395
|
+
// 子命令级 --help: 有用法页的命令在此一处拦截(此前只在 install/uninstall 分支里判,
|
|
396
|
+
// 其余命令的 --help 被忽略 —— serve --help 曾直接把服务起起来挂住终端)。
|
|
397
|
+
if (opts.help && subUsage[cmd] && !(cmd === 'todo' && opts._.length)) {
|
|
398
|
+
console.log(subUsage[cmd]);
|
|
399
|
+
} else {
|
|
400
|
+
switch (cmd) {
|
|
340
401
|
case 'init': {
|
|
341
402
|
rejectExtra(opts._, 'abs init [--repair]');
|
|
342
403
|
const msg = opts.repair
|
|
@@ -374,15 +435,22 @@ async function main() {
|
|
|
374
435
|
// 坑(2026-09-16 实测): requireBrain 返回的是【项目根】, .brain/ 在它下面 ——
|
|
375
436
|
// 直接把项目根当服务根会扫到 node_modules。必须走 brainPath()。
|
|
376
437
|
const root = brainPath(await requireBrain(opts.dir || process.cwd()));
|
|
377
|
-
|
|
438
|
+
// 默认 0 = 让系统挑空闲端口:固定 7777 第二个项目就起不来了。
|
|
439
|
+
// --port abc 曾静默变 NaN → server listen 报怪错;在此拦下。
|
|
440
|
+
const port = opts.port ? Number(opts.port) : 0;
|
|
441
|
+
if (!Number.isInteger(port) || port < 0 || port > 65535) {
|
|
442
|
+
throw new Error(`✗ --port 需为 0-65535 的整数(收到 "${opts.port}")。0 = 自动选空闲端口`);
|
|
443
|
+
}
|
|
444
|
+
// --no-open 显式声明在 FLAG_SPEC(type:boolean),parseArgv 归一化为 open:false。
|
|
445
|
+
// 坑(2026-09-16 审计#6): 曾判 opts.open !== false 但 FLAG_SPEC 无 open →
|
|
446
|
+
// 条件恒真且 --no-open 接不上(open 命令也仅 macOS)。
|
|
378
447
|
const { url, server } = await serve({ root, port });
|
|
379
448
|
console.log(`📖 abs serve → ${url}`);
|
|
380
449
|
console.log(` 根: ${root}`);
|
|
381
450
|
console.log(' (只读,仅本机可访;Ctrl+C 停止)');
|
|
382
|
-
if (opts.open
|
|
451
|
+
if (opts.open) runCmd('open', [url]);
|
|
383
452
|
// 不断开进程:服务要活着才有用
|
|
384
453
|
await new Promise(() => {});
|
|
385
|
-
server.unref();
|
|
386
454
|
break;
|
|
387
455
|
}
|
|
388
456
|
// abs todo —— 无子命令=看板;带子命令=任务写操作
|
|
@@ -409,8 +477,18 @@ async function main() {
|
|
|
409
477
|
` 登记任务: abs todo add <id> --note "做什么"`,
|
|
410
478
|
);
|
|
411
479
|
}
|
|
412
|
-
|
|
413
|
-
|
|
480
|
+
// done 的结语:--note "结语" 或位置参数(`abs todo done X 验证: 全绿`)都收。
|
|
481
|
+
// 坑(2026-09-16 实测): --help 里写了 [结语文字] 但实现只读 --note,
|
|
482
|
+
// 位置参数被 rejectExtra 当多余参数拒掉 —— 文档与实现不一致。
|
|
483
|
+
const isDone = action === 'done';
|
|
484
|
+
const usage = `abs todo ${sub} <id>${isDone ? ' [--as 落地|否决|仅方案] [--note "结语"]' : ' --note "…"'}`;
|
|
485
|
+
if (isDone) {
|
|
486
|
+
const note = opts.note || (rest2.length ? rest2.join(' ') : undefined);
|
|
487
|
+
console.log(await cmdTask({ dir: opts.dir, action, id, section: opts.section, note, as: opts.as }));
|
|
488
|
+
break;
|
|
489
|
+
}
|
|
490
|
+
rejectExtra(rest2, usage);
|
|
491
|
+
if (!id) throw new Error(`✗ 缺 <id>\n 用法: ${usage}`);
|
|
414
492
|
console.log(await cmdTask({ dir: opts.dir, action, id, section: opts.section, note: opts.note, as: opts.as }));
|
|
415
493
|
break;
|
|
416
494
|
}
|
|
@@ -431,11 +509,7 @@ async function main() {
|
|
|
431
509
|
}
|
|
432
510
|
case 'install':
|
|
433
511
|
case 'uninstall': {
|
|
434
|
-
//
|
|
435
|
-
// 坑(INSTALL-HELP-FOOTGUN): 以前 --help 只在顶层命令被识别,跟在 install 后面时
|
|
436
|
-
// 落到 parseArgv 的通用分支,而 install 分支根本不读它 → 用户想看帮助,
|
|
437
|
-
// 实际执行了全量安装(幂等不炸,但确实改了四宿主配置,写了 15 个文件)。
|
|
438
|
-
if (opts.help) { console.log(subUsage[cmd]); break; }
|
|
512
|
+
// --help 已在 switch 前统一拦截(subUsage)。
|
|
439
513
|
if (cmd === 'install') {
|
|
440
514
|
await runInstall({ agent: opts.agent, mcp: opts.mcp !== false, skill: opts.skill !== false, yes: opts.yes });
|
|
441
515
|
} else {
|
|
@@ -488,6 +562,7 @@ async function main() {
|
|
|
488
562
|
case '--version': case '-v': case 'version': console.log(pkgVersion()); break;
|
|
489
563
|
case 'help': case undefined: case '--help': console.log(usage); break;
|
|
490
564
|
default: throw new Error(`未知命令: ${cmd}\n\n${usage}`);
|
|
565
|
+
}
|
|
491
566
|
}
|
|
492
567
|
} catch (e) {
|
|
493
568
|
// 带码的错(AbsError):首行印 [CODE],fallback 另起一行。
|
package/bin/mcp.js
CHANGED
|
@@ -112,19 +112,20 @@ tool(
|
|
|
112
112
|
|
|
113
113
|
tool(
|
|
114
114
|
'abs_task',
|
|
115
|
-
'任务实时落盘(幂等键 = id)。start 登记进 Todo / note 补断点(改到哪文件哪行) / state 改状态 / done 完成归位 Done
|
|
115
|
+
'任务实时落盘(幂等键 = id)。start 登记进 Todo / note 补断点(改到哪文件哪行) / state 改状态 / done 完成归位 Done(done 时 note=结语文字,as=结语类型:落地|否决|仅方案)。',
|
|
116
116
|
{
|
|
117
117
|
action: z.enum(['add', 'start', 'done', 'note', 'state']),
|
|
118
118
|
id: z.string().describe('任务幂等键,如 TASK-xxx 或子任务名'),
|
|
119
119
|
cwd: z.string().describe('项目根目录(.brain/ 所在处)'),
|
|
120
|
-
note: z.string().optional().describe('add/start=做什么; note=断点(文件/到哪步); state
|
|
120
|
+
note: z.string().optional().describe('add/start=做什么; note=断点(文件/到哪步); state=进行中|讨论中|滞留中; done=结语文字'),
|
|
121
|
+
as: z.enum(['落地', '否决', '仅方案']).optional().describe('仅 done:结语类型。默认 落地。做了又撤/评估后不做用 否决,只设计过用 仅方案 —— 别让假【落地】污染看板'),
|
|
121
122
|
},
|
|
122
|
-
async ({ action, id, cwd, note }) => {
|
|
123
|
+
async ({ action, id, cwd, note, as }) => {
|
|
123
124
|
const root = await findBrainRoot(cwd || process.cwd());
|
|
124
125
|
if (!root) return errNoBrain(cwd || process.cwd());
|
|
125
126
|
// add 是 start 的别名(与 CLI `abs todo add` 对齐)
|
|
126
127
|
const act = action === 'add' ? 'start' : action;
|
|
127
|
-
return { content: [{ type: 'text', text: await cmdTask({ dir: root, action: act, id, note }) }] };
|
|
128
|
+
return { content: [{ type: 'text', text: await cmdTask({ dir: root, action: act, id, note, as }) }] };
|
|
128
129
|
}
|
|
129
130
|
);
|
|
130
131
|
|
package/hooks/abs.opencode.ts
CHANGED
|
@@ -39,16 +39,18 @@ const server = async ({ client, directory }) => {
|
|
|
39
39
|
try { const st = await stat(join(d, ".brain")); return st.isDirectory() ? join(d, ".brain") : null } catch { return null }
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
// 判定"今天是否收尾过":
|
|
43
|
-
//
|
|
44
|
-
//
|
|
42
|
+
// 判定"今天是否收尾过": 只认【工作成果】条目(kind=dev),行头 '## [YYYY-MM-DD HH:MM] [[name]] dev |'。
|
|
43
|
+
// 坑1: 曾用裸日期 substring, 正文/任务行里任何一处提到今天就把 nudge 永久压掉(守卫形同虚设)。
|
|
44
|
+
// 坑2(2026-09-13 实测, pi 侧先修): `abs note` 也写 log.md(kind=note),
|
|
45
|
+
// 旧判据只看「今天有没有行」会把「沉淀了一条经验」误判成「已收尾」→ 整天不再提醒。
|
|
46
|
+
// 三宿主(pi/CC/opencode)判据必须一致 —— 见 [[hook-throttle-alignment]]。
|
|
45
47
|
async function loggedToday(brain) {
|
|
46
48
|
try {
|
|
47
49
|
const txt = await readFile(join(brain, "log.md"), "utf8")
|
|
48
50
|
const d = new Date()
|
|
49
51
|
const pad = (n) => String(n).padStart(2, "0")
|
|
50
52
|
const today = d.getFullYear() + "-" + pad(d.getMonth() + 1) + "-" + pad(d.getDate())
|
|
51
|
-
return new RegExp("^## \\[" + today + " \\d{2}:\\d{2}\\]", "m").test(txt)
|
|
53
|
+
return new RegExp("^## \\[" + today + " \\d{2}:\\d{2}\\] (?:\\[[^\\]]+\\]\\] )?dev \\|", "m").test(txt)
|
|
52
54
|
} catch { return false }
|
|
53
55
|
}
|
|
54
56
|
|
|
@@ -90,6 +92,14 @@ const server = async ({ client, directory }) => {
|
|
|
90
92
|
// session.created / idle 等事件带 sessionID —— 存下来供 tool.execute.after 用
|
|
91
93
|
const sid = event.properties && event.properties.sessionID
|
|
92
94
|
if (sid) currentSessionID = String(sid)
|
|
95
|
+
// 新会话 = 重置节流状态。坑(2026-09-13 pi 侧同构实测): 状态声明在 server 工厂闭包里,
|
|
96
|
+
// 同一进程的第二个会话会继承上个会话的 true → 永久不再提醒(后半场全部静默)。
|
|
97
|
+
// pi 侧在 session_start 里重置; opencode 的对应事件就是 session.created。
|
|
98
|
+
if (type === "session.created") {
|
|
99
|
+
wroteFiles = false
|
|
100
|
+
nudged = false
|
|
101
|
+
idleSeen = false
|
|
102
|
+
}
|
|
93
103
|
return
|
|
94
104
|
}
|
|
95
105
|
if (type !== "session.idle") return // idle = 每轮结束, 不记日志(太吵), 只做收尾判定
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fanchao8609/agent_brain_sync",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.1",
|
|
4
4
|
"description": "agent-brain-sync: 跨会话 AI 编码记忆 — hook 纯触发 + CLI/MCP 读写 .brain markdown 图谱, 防并发写保护。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
"node": ">=18"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@modelcontextprotocol/sdk": "^1.30.0"
|
|
32
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
33
|
+
"zod": "^3.25 || ^4.0"
|
|
33
34
|
},
|
|
34
35
|
"scripts": {
|
|
35
36
|
"abs": "node bin/abs.js",
|
|
@@ -259,7 +259,7 @@ abs todo archive # 一条命令搞定,无需手工搬
|
|
|
259
259
|
| 只在讨论、还没动手 | `abs_task {action:state, id:"T-1", note:"讨论中"}` | 原地改标记 |
|
|
260
260
|
| 卡住了/等人等数据 | `abs_task {action:state, id:"T-1", note:"滞留中"}` | 原地改标记 |
|
|
261
261
|
| 被打断/干到一半 | `abs_task {action:note, id:"T-1", note:"改到哪个文件/到哪步"}` | 原地 ↳断点 |
|
|
262
|
-
| 子任务做完 | `abs_task {action:done, id:"T-1", note:"
|
|
262
|
+
| 子任务做完 | `abs_task {action:done, id:"T-1", note:"结语文字", as:"落地|否决|仅方案"}` | Done(as 默认 落地;做了又撤用否决,只设计过用仅方案,别让假【落地】污染看板) |
|
|
263
263
|
| 总结出经验/坑/规律 | `abs_note {text:"一句话", tags:"坑,docker"}` | sources/ |
|
|
264
264
|
|
|
265
265
|
> **为什么只有两区**(2026-09-13 实测):跨 4 个项目,Backlog/Today 常年 **0 条**,而 log.md
|
|
@@ -389,6 +389,6 @@ abs supersede <页名> --by <取代它的新页> # 不写 --by 也行 = 单纯
|
|
|
389
389
|
- 只读写 `.brain/` 与目标代码,不动全局配置(一次性接入除外)。
|
|
390
390
|
- 只写真实发生的事实;遵守容量纪律,宁缺毋滥。
|
|
391
391
|
- 双链/frontmatter/index 必须自洽 —— 坏链 = 掰断接力棒。
|
|
392
|
-
- **排查 bug 时挂载 `bug-hunter` skill**(随 abs
|
|
392
|
+
- **排查 bug 时挂载 `bug-hunter` skill**(随 abs 一并安装,安装名为 `abs-bug-hunter`,位于 ~/.pi/agent/skills/abs-bug-hunter/ 等宿主对应目录):
|
|
393
393
|
完整排查流程(列假设 → 复现 → 定位改动 → 打印 → 顺藤摸瓜 → 根因 → 验证)
|
|
394
394
|
与「给结论要明确」都在那份里。**排查方法不写在本文件**(两份维护必漂)。
|
package/src/serve.js
CHANGED
|
@@ -461,8 +461,9 @@ async function load(path, el) {
|
|
|
461
461
|
/**
|
|
462
462
|
* 起服务。
|
|
463
463
|
* @param {{root:string, port?:number, host?:string}} cfg root = .brain/ 绝对路径
|
|
464
|
+
* port = 0(默认)让系统挑空闲端口,多个项目可同时开。
|
|
464
465
|
*/
|
|
465
|
-
export async function serve({ root, port =
|
|
466
|
+
export async function serve({ root, port = 0, host = '127.0.0.1' }) {
|
|
466
467
|
const server = http.createServer(async (req, res) => {
|
|
467
468
|
try {
|
|
468
469
|
const url = req.url || '/';
|
package/src/store.js
CHANGED
|
@@ -831,11 +831,14 @@ export async function cmdTeardownCheck({ dir, payload }) {
|
|
|
831
831
|
let root;
|
|
832
832
|
try { root = await requireBrain(dir0); } catch { return '{}'; }
|
|
833
833
|
|
|
834
|
-
// 条件③: log.md
|
|
834
|
+
// 条件③: log.md 今日尚无【工作成果】条目(条目头 '## [YYYY-MM-DD HH:MM] [[name]] dev |')。
|
|
835
|
+
// 坑(2026-09-13 实测, pi 侧先修): `abs note` 也写 log.md(kind=note),
|
|
836
|
+
// 旧判据只看「今天有没有行」会把「沉淀了一条经验」误判成「今天已收尾」→ 整天不再提醒。
|
|
837
|
+
// 三宿主(pi/CC/opencode)判据必须一致 —— 见 [[hook-throttle-alignment]]。
|
|
835
838
|
const logTxt = await readIfExists(brainPath(root, 'log.md'));
|
|
836
839
|
const stamp = localStamp(); // 'YYYY-MM-DD HH:MM'
|
|
837
840
|
const day = stamp.slice(0, 10);
|
|
838
|
-
if (new RegExp('^## \\[' + day + ' \\d{2}:\\d{2}\\]', 'm').test(logTxt)) return '{}';
|
|
841
|
+
if (new RegExp('^## \\[' + day + ' \\d{2}:\\d{2}\\] (?:\\[\\[[^\\]]+\\]\\] )?dev \\|', 'm').test(logTxt)) return '{}';
|
|
839
842
|
|
|
840
843
|
// 条件④: 每会话一次。
|
|
841
844
|
// 双保险, 因为官方 stop_hook_active 有已知
|
|
@@ -1003,13 +1006,22 @@ export async function cmdTask({ dir, action, id, section, note, as }) {
|
|
|
1003
1006
|
if (as && !DONE_KINDS.includes(as)) {
|
|
1004
1007
|
throw new Error(`✗ --as 只接受: ${DONE_KINDS.join(' | ')}(收到 "${as}")`);
|
|
1005
1008
|
}
|
|
1006
|
-
|
|
1009
|
+
// 结语 = note(MCP 路径)或位置参数(CLI 路径)。坑(2026-09-16 实测):
|
|
1010
|
+
// MCP abs_task schema 无 as,AI 按旧 SKILL.md 传 note 会被静默丢弃、行上默认盖【落地】
|
|
1011
|
+
// —— 状态失真且无声。现在 note 也当结语;note 里自带【落地/否决/仅方案】时它就是 kind。
|
|
1012
|
+
// as 与 note 里的 kind 矛盾 → 报错不猜(猜一侧就是静默篡改另一侧)。
|
|
1013
|
+
const conclusion = note ? String(note).replace(/\u200b/g, '') : undefined;
|
|
1014
|
+
const inlineKind = conclusion ? (conclusion.match(/【(落地|否决|仅方案)】/) || [])[1] : undefined;
|
|
1015
|
+
if (inlineKind && as && inlineKind !== as) {
|
|
1016
|
+
throw new Error(`✗ as="${as}" 与 note 里的【${inlineKind}】矛盾 —— 只留一个(改 as,或改 note 里的【】)`);
|
|
1017
|
+
}
|
|
1018
|
+
const res = await markDone(brainPath(root, 'todo.md'), id, as || inlineKind || '落地', conclusion);
|
|
1007
1019
|
return res;
|
|
1008
1020
|
}
|
|
1009
1021
|
throw new Error(`unknown task action: ${action}`);
|
|
1010
1022
|
}
|
|
1011
1023
|
|
|
1012
|
-
async function markDone(file, id, kind = '落地') {
|
|
1024
|
+
async function markDone(file, id, kind = '落地', conclusion) {
|
|
1013
1025
|
const res = await editFile(file, (text) => {
|
|
1014
1026
|
const lines = text.split('\n');
|
|
1015
1027
|
let changed = false;
|
|
@@ -1020,8 +1032,10 @@ async function markDone(file, id, kind = '落地') {
|
|
|
1020
1032
|
const l = lines[i];
|
|
1021
1033
|
if (!moved && idOfTaskLine(l) === wantId) {
|
|
1022
1034
|
changed = true;
|
|
1035
|
+
// 结语(MCP note / CLI 位置参数)拼在日期前 —— 与 CLI `abs todo done <id> [--as ..] <结语>` 同一落点。
|
|
1036
|
+
const tail = conclusion ? ` — ${conclusion}` : '';
|
|
1023
1037
|
const head = withDoneKind(
|
|
1024
|
-
stripStateMark(l).replace('- [ ]', '- [x]').replace(/\(认领[^)]*\)/, '') +
|
|
1038
|
+
stripStateMark(l).replace('- [ ]', '- [x]').replace(/\(认领[^)]*\)/, '') + `${tail} (完成 ${today()})`,
|
|
1025
1039
|
kind,
|
|
1026
1040
|
);
|
|
1027
1041
|
const bp = [];
|