@fanchao8609/agent_brain_sync 1.8.2 → 1.8.4

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
@@ -32,7 +32,11 @@ abs install --agent pi --no-mcp # 只装 hook + skill,不要 MCP
32
32
  | claude-code | `~/.claude/settings.json` hooks | `mcpServers.abs` (stdio) | `~/.claude/skills/abs-agent-brain-sync/` |
33
33
  | codex | `~/.codex/hooks.json` | config.toml `[mcp_servers.abs]` | `~/.codex/skills/` |
34
34
  | opencode | `~/.config/opencode/plugins/abs.ts` | opencode.json mcp.abs | skills/ |
35
- | pi | `~/.pi/agent/extensions/abs.ts` | extension 内桥接 | skills/ |
35
+ | pi | `~/.pi/agent/extensions/abs.ts` | `~/.pi/agent/mcp.json` `mcpServers.abs` | `~/.pi/agent/skills/` |
36
+
37
+ > **skill 规则**:`skill/` 下每个含 `SKILL.md` 的子目录 = 一个 skill,
38
+ > **目录名即安装名**(须与 frontmatter `name` 一致,否则 pi 会告警)。
39
+ > 新增 skill 只需建目录,无需改代码。
36
40
 
37
41
  - **幂等**:重复安装 = 更新,写入前自动备份
38
42
  - **共存**:追加式合并,不会顶掉你这个事件上的其它 hook
@@ -110,7 +114,7 @@ npm i -g @fanchao8609/agent_brain_sync@latest
110
114
  abs install # 重新刷 hook/skill
111
115
  ```
112
116
 
113
- > ⚠️ 改完源码(尤其 `skill/SKILL.md`、`src/`)后**必须 `abs install --yes` 重扇出**,否则宿主还在跑旧副本 —— 版本号与代码会脱节。发布用 `npm version patch`(自动打 tag),别手改 `package.json` 的 version;发布后 `npm view` 有缓存延迟,必要时 `npm cache clean --force` 再验。
117
+ > ⚠️ 改完源码(尤其 `skill/*/SKILL.md`、`src/`)后**必须 `abs install --yes` 重扇出**,否则宿主还在跑旧副本 —— 版本号与代码会脱节。发布用 `npm version patch`(自动打 tag),别手改 `package.json` 的 version;发布后 `npm view` 有缓存延迟,必要时 `npm cache clean --force` 再验。
114
118
 
115
119
  ---
116
120
 
@@ -150,7 +154,7 @@ agent_brain_sync/
150
154
  ├── src/userconfig.js 使用者姓名配置(作者标记)
151
155
  ├── src/wrapup.js Stop 收尾快照/归档
152
156
  ├── hooks/event.sh hook 模板
153
- ├── skill/SKILL.md 技能(装到各智能体)
157
+ ├── skill/<名称>/SKILL.md 技能(每个子目录 = 一个 skill,装到各智能体)
154
158
  └── test/ 单测
155
159
  ```
156
160
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fanchao8609/agent_brain_sync",
3
- "version": "1.8.2",
3
+ "version": "1.8.4",
4
4
  "description": "agent-brain-sync: 跨会话 AI 编码记忆 — hook 纯触发 + CLI/MCP 读写 .brain markdown 图谱, 防并发写保护。",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: bug-hunter
2
+ name: abs-bug-hunter
3
3
  description: 按流程论排查 bug,先定位改动 → 日志打印 → 顺藤摸瓜逐层排查
4
4
  ---
5
5
 
package/src/install.js CHANGED
@@ -14,30 +14,78 @@ import { HOSTS, hostByKey } from './hosts.js';
14
14
 
15
15
  const ABS_DIR = resolve(dirname(fileURLToPath(import.meta.url)), '..');
16
16
  const HOOK_TEMPLATE = join(ABS_DIR, 'hooks', 'event.sh');
17
- const SKILL_SOURCE = join(ABS_DIR, 'skill', 'SKILL.md');
18
17
 
19
- // 随 abs 一并安装的附带 skill(agent 排查 bug 时挂载)。
20
- // 必须放在本包内随包发布 —— 不能假设宿主已装:实测 co​dex 只有 abs 自己装的 skill,
21
- // 若引用外部 skill 会成悬空。ponytail: 只加这一个,需要更多时改成扫 skill/ 子目录。
22
- const BUNDLED_SKILLS = [
23
- { name: 'bug-hunter', src: join(ABS_DIR, 'skill', 'bug-hunter', 'SKILL.md') },
24
- ];
18
+ // ---- skill 包规则(单一规则,无特例)----
19
+ // skill/ 下**每个含 SKILL.md 的子目录**就是一个 skill,随包发布。
20
+ // 目录名 = 宿主 skills/ 下的安装目录名 = frontmatter 的 name(三者必须一致:
21
+ // pi 在目录名与 name 不一致时会告警)。
22
+ //
23
+ // 为什么全带 abs- 前缀:
24
+ // 1. 归属可辨 —— 带前缀才敢判「这是 abs 装的、可随 install 更新/卸载」;
25
+ // 不带前缀的副本可能是用户自己放的,卸载时不敢动。
26
+ // 2. 避开同名冲突 —— pi 同时扫 ~/.pi/agent/skills 与 ~/.agents/skills
27
+ // (pi 文档 Locations 一节),后者常有同名副本。两份同名实文件 → pi 报
28
+ // skill 冲突(实测: 曾装成 bug-hunter,与 ~/.agents/skills/bug-hunter 相撞)。
29
+ //
30
+ // 新增 skill: 建 skill/<名称>/SKILL.md 即可,无需改代码(原先写死一条,见旧注释 ponytail)。
31
+ const SKILL_ROOT = join(ABS_DIR, 'skill');
32
+ const ALL_SKILLS = (() => {
33
+ const out = [];
34
+ let entries = [];
35
+ try { entries = fsSync.readdirSync(SKILL_ROOT, { withFileTypes: true }); } catch { /* 无 skill/ 目录: 无 skill 可装 */ }
36
+ for (const e of entries) {
37
+ if (!e.isDirectory()) continue;
38
+ const src = join(SKILL_ROOT, e.name, 'SKILL.md');
39
+ if (!fsSync.existsSync(src)) continue; // 只认含 SKILL.md 的子目录
40
+ out.push({ name: e.name, src });
41
+ }
42
+ if (!out.length) {
43
+ // 包内一个 skill 都没找到 = 安装树与代码版本不一致(半升级 / 旧布局 / 打包漏文件)。
44
+ // 不静默装 0 个: 曾实测新版代码遇扁平旧布局 → out=[] → 一句不说就什么都不装,
45
+ // 而旧版代码遇新布局只报一个当前版本根本不存在的路径(E​NOENT skill/SKILL.md), 把人引去查源码。
46
+ const v = (() => { try { return JSON.parse(fsSync.readFileSync(join(ABS_DIR, 'package.json'), 'utf8')).version; } catch { return 'unknown'; } })();
47
+ const flat = fsSync.existsSync(join(SKILL_ROOT, 'SKILL.md'));
48
+ throw new Error(
49
+ `本包内没有可安装的 skill(${SKILL_ROOT}),安装树与代码版本不一致(当前 ${v})。\n` +
50
+ (flat
51
+ ? ' 检测到旧版扁平布局 skill/SKILL.md,本版要求 skill/<名称>/SKILL.md。\n'
52
+ : ' skill/ 目录缺失或不含任何 <名称>/SKILL.md。\n') +
53
+ ' 修复: npm i -g @fanchao8609/agent_brain_sync@latest && abs install(幂等,可安全重跑)',
54
+ );
55
+ }
56
+ return out;
57
+ })();
58
+ // 主 skill(会话开场加载的那个)。它也只是 ALL_SKILLS 的一员,此处仅用于告警比对。
59
+ const SKILL_SOURCE = join(SKILL_ROOT, 'abs-agent-brain-sync', 'SKILL.md');
25
60
 
26
- /** 安装主 skill + 全部附带 skill 到该宿主。收口在此:四处安装点共用,
27
- * 新增附带 skill 时只改本函数(避免「改一份不算改」)。返回步骤行。 */
61
+ /** 安装全部 skill 到该宿主。收口在此:四处安装点共用 —— 新增 skill 只改
62
+ * skill/ 目录,本函数与调用方都不动(避免「改一份不算改」)。返回步骤行。 */
28
63
  async function installSkills(agentKey) {
29
64
  const steps = [];
30
- const base = hostSkillDir(agentKey);
31
- await atomicWrite(join(base, 'SKILL.md'), await fs.readFile(SKILL_SOURCE, 'utf8'));
32
- steps.push(`✓ skill → ${join(base, 'SKILL.md')}`);
33
- for (const b of BUNDLED_SKILLS) {
34
- const t = join(base, '..', b.name, 'SKILL.md');
35
- await atomicWrite(t, await fs.readFile(b.src, 'utf8'));
65
+ const root = join(hostSkillDir(agentKey), '..');
66
+ for (const s of ALL_SKILLS) {
67
+ const t = join(root, s.name, 'SKILL.md');
68
+ await atomicWrite(t, await fs.readFile(s.src, 'utf8'));
36
69
  steps.push(`✓ skill → ${t}`);
37
70
  }
38
71
  return steps;
39
72
  }
40
73
 
74
+ /** 卸载该宿主的全部 skill(主 + 附带)。与 installSkills 同源同规则。
75
+ * 曾经的坑: 只有 claude-code 删了附带 skill,其余三宿主留下 abs-bug-hunter/ 残留。
76
+ */
77
+ async function uninstallSkills(agentKey) {
78
+ const steps = [];
79
+ const root = join(hostSkillDir(agentKey), '..');
80
+ for (const s of ALL_SKILLS) {
81
+ const d = join(root, s.name);
82
+ await fs.rm(d, { recursive: true, force: true });
83
+ steps.push(`✓ skill 已删除 → ${d}`);
84
+ }
85
+ return steps;
86
+ }
87
+
88
+
41
89
  /**
42
90
  * 本包的稳定入口路径解析(mcp.js / abs.js 通用)。
43
91
  *
@@ -512,10 +560,7 @@ async function uninstallClaudeCode() {
512
560
  await fs.rm(join(homedir(), '.abs', 'hooks', 'claude-code'), { recursive: true, force: true });
513
561
  steps.push(`✓ ~/.abs/hooks/claude-code/ (本 agent hook 脚本) 已删除`);
514
562
  // skill
515
- const skillDir = hostSkillDir('claude-code');
516
- await fs.rm(skillDir, { recursive: true, force: true });
517
- for (const b of BUNDLED_SKILLS) await fs.rm(join(skillDir, '..', b.name), { recursive: true, force: true });
518
- steps.push(`✓ skill 已删除(含附带: ${BUNDLED_SKILLS.map((x) => x.name).join(", ")})`);
563
+ steps.push(...await uninstallSkills('claude-code'));
519
564
  return steps;
520
565
  }
521
566
 
@@ -640,8 +685,7 @@ async function uninstallCodex() {
640
685
  steps.push(`✓ MCP 已从 ${mcpP} 移除`);
641
686
  }
642
687
  } catch {}
643
- await fs.rm(hostSkillDir('codex'), { recursive: true, force: true });
644
- steps.push(`✓ skill 已删除`);
688
+ steps.push(...await uninstallSkills('codex'));
645
689
  return steps;
646
690
  }
647
691
 
@@ -768,8 +812,7 @@ async function uninstallOpenCode() {
768
812
  await atomicWrite(mcpP, JSON.stringify(cfg, null, 2));
769
813
  steps.push(`✓ MCP 已从 ${mcpP} 移除`);
770
814
  }
771
- await fs.rm(hostSkillDir('opencode'), { recursive: true, force: true });
772
- steps.push(`✓ skill 已删除`);
815
+ steps.push(...await uninstallSkills('opencode'));
773
816
  return steps;
774
817
  }
775
818
 
@@ -801,8 +844,7 @@ async function uninstallPi() {
801
844
  const steps = [];
802
845
  await fs.rm(join(hostConfigRoot('pi'), 'agent', 'extensions', 'abs.ts'), { force: true });
803
846
  steps.push(`✓ extension 已删除`);
804
- await fs.rm(hostSkillDir('pi'), { recursive: true, force: true });
805
- steps.push(`✓ skill 已删除`);
847
+ steps.push(...await uninstallSkills('pi'));
806
848
  const mcpP = join(hostConfigRoot('pi'), 'agent', 'mcp.json');
807
849
  const cfg = await readJson(mcpP, { strict: false });
808
850
  if (cfg === null) {