@fanchao8609/agent_brain_sync 1.13.1 → 1.13.3

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 CHANGED
@@ -188,8 +188,12 @@ agent_brain_sync/
188
188
  ├── bin/mcp.js MCP server (stdio)
189
189
  ├── src/index.js 图谱定位(只认当前目录的 .brain/)+ 全局技术日志目录
190
190
  ├── src/lock.js 并发写保护(原子锁 + 排队 + SKIP)
191
- ├── src/todo.js todo.md 分区读写
191
+ ├── src/todo.js todo.md 分区读写 + 格式闸门
192
192
  ├── src/store.js CLI 命令实现
193
+ ├── src/lint.js 图谱体检(死链/孤岛/超尺寸/堆积;同类问题折叠计数)
194
+ ├── src/query.js 检索知识页(多词 OR)
195
+ ├── src/relevant.js 相关页推荐(按"当前在干什么"挑该读的页)
196
+ ├── src/codegraph.js 借本机 CodeGraph 取"代码影响面",写回图谱
193
197
  ├── src/hosts.js 四宿主接入定义
194
198
  ├── src/install.js 安装/卸载(分区共存合并)
195
199
  ├── src/userconfig.js 使用者姓名配置(作者标记;占位名如 tester/foo 会被拒)
@@ -197,7 +201,7 @@ agent_brain_sync/
197
201
  ├── hooks/event.sh hook 模板
198
202
  ├── hooks/abs.pi.ts pi 扩展模板(含 todo 面板 / 随机昵称 / 常驻指引)
199
203
  ├── skill/<名称>/SKILL.md 技能(每个子目录 = 一个 skill,装到各智能体)
200
- └── test/ 单测(400+,node:test)
204
+ └── test/ 单测(448,node:test,零外部测试依赖)
201
205
  ```
202
206
 
203
207
  MIT
@@ -3,6 +3,10 @@
3
3
  * 纯触发: 生命周期事件 → 技术日志一行 (~/.abs/log/hooks.log, ABS_LOG_DIR 可覆盖)。fire-and-forget。
4
4
  * 纪律: hook 事件只进技术日志, 不进图谱 log.md (log.md 只收工作成果沉淀, 与 event.sh 同纪律)。
5
5
  * Op​encode 事件: session.created / session.idle / session.deleted (idle = 每轮结束, 对应 pi 的 agent_end)。
6
+ * Opencode v2 API: 默认导出 { id, setup }, setup(api) 里用 api.event.subscribe 订阅。
7
+ * (v1 的 { id, server } + return { event } 在 v2 已移除, 会让插件加载失败报
8
+ * "Plugin failed", 见 concept host-plugin-silent-failure。本文件与
9
+ * ~/.config/opencode/plugins/abs.ts 必须同改, 否则下次 abs install 覆盖回去。)
6
10
  * 收尾注入已删除(2026-09-18):曾用 session.idle + promptAsync 推一条收尾提醒, 用户实报
7
11
  * 「每次干活干一会就出来, 任务就中断了」。根因同 pi 侧: 主动往对话里插 turn = 打断,
8
12
  * 不是频率问题(2026-09-15 删登记注入时已得出同一结论)。保留 session.idle:seen 埋点。
@@ -10,32 +14,32 @@
10
14
  import { appendFile, mkdir, stat } from "node:fs/promises"
11
15
  import { homedir } from "node:os"
12
16
  import { join } from "node:path"
13
- const server = async ({ directory }) => {
17
+ // 定位当前项目的 .brain/:只看 cwd 本身,不向上搜索(防爬到家目录图谱)
18
+ async function findBrain(cwd) {
19
+ const d = cwd || process.cwd()
20
+ try { const st = await stat(join(d, ".brain")); return st.isDirectory() ? join(d, ".brain") : null } catch { return null }
21
+ }
22
+
23
+ const setup = async (api) => {
14
24
  // 本会话首次 idle 是否已埋点
15
25
  let idleSeen = false
16
- async function logHook(evt) {
17
- try {
18
- const dir = process.env.ABS_LOG_DIR || join(homedir(), ".abs", "log")
19
- const d = new Date()
20
- const pad = (n) => String(n).padStart(2, "0")
21
- const stamp = d.getFullYear() + "-" + pad(d.getMonth() + 1) + "-" + pad(d.getDate()) + " " + pad(d.getHours()) + ":" + pad(d.getMinutes()) + ":" + pad(d.getSeconds())
22
- await mkdir(dir, { recursive: true })
23
- await appendFile(join(dir, "hooks.log"), "[" + stamp + "] opencode:" + evt + "\n")
24
- } catch {} // fire-and-forget: 永不阻塞宿主
25
- }
26
+ async function logHook(evt) {
27
+ try {
28
+ const dir = process.env.ABS_LOG_DIR || join(homedir(), ".abs", "log")
29
+ const d = new Date()
30
+ const pad = (n) => String(n).padStart(2, "0")
31
+ const stamp = d.getFullYear() + "-" + pad(d.getMonth() + 1) + "-" + pad(d.getDate()) + " " + pad(d.getHours()) + ":" + pad(d.getMinutes()) + ":" + pad(d.getSeconds())
32
+ await mkdir(dir, { recursive: true })
33
+ await appendFile(join(dir, "hooks.log"), "[" + stamp + "] opencode:" + evt + "\n")
34
+ } catch {} // fire-and-forget: 永不阻塞宿主
35
+ }
26
36
 
27
- // 定位当前项目的 .brain/:只看 cwd 本身,不向上搜索(防爬到家目录图谱)
28
- async function findBrain(cwd) {
29
- const d = cwd || process.cwd()
30
- try { const st = await stat(join(d, ".brain")); return st.isDirectory() ? join(d, ".brain") : null } catch { return null }
31
- }
32
-
33
- // 收尾注入已停用(2026-09-18):promptAsync 会在每轮 idle 抢一个 turn,用户实报
34
- // 「每次干活干一会就出来, 任务就中断了」。wroteFiles / nudged / READONLY_CMD /
35
- // tool.execute.after / loggedToday 随注入一起删除(注入没了,它们已无消费方)。
36
- // 保留 session.idle:seen 埋点:区分「事件没触发」与「被守卫拦下」。
37
- return {
38
- event: async ({ event }) => {
37
+ // 收尾注入已停用(2026-09-18):promptAsync 会在每轮 idle 抢一个 turn,用户实报
38
+ // 「每次干活干一会就出来, 任务就中断了」。wroteFiles / nudged / READONLY_CMD /
39
+ // tool.execute.after / loggedToday 随注入一起删除(注入没了,它们已无消费方)。
40
+ // 保留 session.idle:seen 埋点:区分「事件没触发」与「被守卫拦下」。
41
+ api.event.subscribe(async (event) => {
42
+ try {
39
43
  const type = event && event.type
40
44
  if (!type) return
41
45
  if (type === "session.created" || type === "session.deleted") {
@@ -49,19 +53,19 @@ const server = async ({ directory }) => {
49
53
  return
50
54
  }
51
55
  if (type !== "session.idle") return // idle = 每轮结束, 不记日志(太吵), 只做可观测性埋点
52
- const cwd = directory || process.cwd()
56
+ const cwd = api.location?.directory || process.cwd()
53
57
  const brain = await findBrain(cwd)
54
58
  // 可观测性: 首次 idle 留一行痕(否则无法区分“事件没触发”与“被守卫拦下”)
55
59
  if (!idleSeen) {
56
60
  idleSeen = true
57
61
  await logHook(`session.idle:seen cwd=${cwd} brain=${brain || 'none'}`)
58
62
  }
59
- },
60
- }
63
+ } catch {} // 订阅回调抛错会污染宿主, 一律吞掉
64
+ })
61
65
  }
62
66
 
63
67
  export default {
64
68
  id: "abs",
65
- server,
69
+ setup,
66
70
  }
67
71
  @@MARK@@
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fanchao8609/agent_brain_sync",
3
- "version": "1.13.1",
3
+ "version": "1.13.3",
4
4
  "description": "agent-brain-sync: 跨会话 AI 编码记忆 — hook 纯触发 + CLI/MCP 读写 .brain markdown 图谱, 防并发写保护。",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: abs-agent-brain-sync
3
- description: abs (agent-brain-sync) 跨会话记忆与任务续接。适用于「开工续接状态」「沉淀会话收获」「任务登记不丢」「经验落成知识页」「图谱体检」等任务。
3
+ description: "USE FOR abs / agent-brain-sync 跨会话记忆与任务续接。触发词:用户直接输入 abs(单独一个词,或 abs + 命令,如 'abs 开工'、'abs 沉淀');以及「开工续接状态」「沉淀会话收获」「任务登记不丢」「经验落成知识页」「图谱体检」等任务。"
4
4
  ---
5
5
 
6
6
  # abs — 跨会话记忆 (agent-brain-sync)
@@ -9,6 +9,8 @@ description: abs (agent-brain-sync) 跨会话记忆与任务续接。适用于
9
9
 
10
10
  **关键词不是触发器,意图才是。**句子里出现 abs 词(收尾/todo/沉淀/load/log/note)不等于要执行 abs 动作:谈论、提问、吐槽、定规则("太啰嗦""这个设计怎样")**只回应,不执行动作**;明确让我做事才执行。
11
11
 
12
+ **例外(唯一):用户直接输入 `abs`** —— 单独一个词、或 `abs` 后跟命令/意图(如 `abs`、`abs 开工`、`abs 沉淀`、`abs load`)。这是明确的"我要用 abs",不算关键词误触发,**直接按 abs 流程执行**(无子命令时默认走开工续接)。
13
+
12
14
  - 拿不准时**先问**。规则变更("以后简短点")写入本文,不执行动作。
13
15
  - 输出简短:收尾/提示/todo 汇报一两句。本总则适用于所有工具。
14
16
 
package/src/lint.js CHANGED
@@ -170,11 +170,20 @@ function checkPage(pg, names, linkedNames, inbound) {
170
170
  `引用请用 [[${id}]] 或改回文件名)`);
171
171
  }
172
172
  }
173
- for (const ln of pg.links) {
174
- if (/^<.+>$/.test(ln) || /slug|Name|name|Date|页面名$/.test(ln)) {
175
- issues.push(`TEMPLATE-LINK: ${pg.rel} -> [[${ln}]]`);
176
- } else if (!names.has(ln)) {
177
- issues.push(`DEAD-LINK: ${pg.rel} -> [[${ln}]]`);
173
+ // 链接检查
174
+ // ★ sessions/ 豁免 DEAD-LINK(2026-10-05 实测):会话快照正文是「当时的笔记」,
175
+ // 里面的 `[[x]]` 常是在**谈这个记号本身**(如「缺 [[作者]] 这个标记」、
176
+ // 「剥掉 <id>/[[作者]] 记号」),不承担图谱链接职责;而 sessions 是不可改的历史,
177
+ // 改它就是改历史。→ 对快照报断链是规则错,不是数据脏。
178
+ // 注:sessions 的链接职责只体现在 `## 关联连接` 段,那段的断链仍值得管,
179
+ // 但对历史快照而言也已无法修正,故一并豁免。
180
+ if (pg.dir !== 'sessions') {
181
+ for (const ln of pg.links) {
182
+ if (/^<.+>$/.test(ln) || /slug|Name|name|Date|页面名$/.test(ln)) {
183
+ issues.push(`TEMPLATE-LINK: ${pg.rel} -> [[${ln}]]`);
184
+ } else if (!names.has(ln)) {
185
+ issues.push(`DEAD-LINK: ${pg.rel} -> [[${ln}]]`);
186
+ }
178
187
  }
179
188
  }
180
189
  const isTerminal = pg.dir === 'sources'
@@ -270,6 +279,11 @@ async function checkGraph(root, pages) {
270
279
  for (const pg of pages) {
271
280
  if (pg.status !== 'draft') continue;
272
281
  if (pg.dir === 'sources') continue;
282
+ // ★ sessions/ 也不参与(2026-10-05 实测误报):会话快照是**历史事实记录**
283
+ // (「那天发生什么」),不存在「是否仍然有效」—— 它永远不该被标 active,
284
+ // 标了反而是撒谎。对快照报「已 N 天停在 draft」是规则错,不是数据脏。
285
+ // 同理若未来加别的「存档型」目录(归档页等),也该在此豁免。
286
+ if (pg.dir === 'sessions') continue;
273
287
  let ageMs = 0;
274
288
  try { ageMs = Date.now() - (await fs.stat(join(root, pg.rel))).mtimeMs; } catch { continue; }
275
289
  if (ageMs > DRAFT_STALE_DAYS * 86400 * 1000) {