@fanchao8609/agent_brain_sync 1.8.1 → 1.8.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 +7 -3
- package/package.json +1 -1
- package/skill/{SKILL.md → abs-agent-brain-sync/SKILL.md} +4 -1
- package/skill/abs-bug-hunter/SKILL.md +228 -0
- package/src/install.js +65 -22
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` |
|
|
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
|
|
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
|
|
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: abs-agent-brain-sync
|
|
3
|
-
description: abs (agent-brain-sync) 跨会话 AI 编码记忆与任务续接。开场续接状态(abs load/MCP abs_load),干活中任务/经验实时落盘(abs_task/abs_note),每轮结束走收尾循环(读todo→判未登记→沉淀→更新index/log)
|
|
3
|
+
description: abs (agent-brain-sync) 跨会话 AI 编码记忆与任务续接。开场续接状态(abs load/MCP abs_load),干活中任务/经验实时落盘(abs_task/abs_note),每轮结束走收尾循环(读todo→判未登记→沉淀→更新index/log)。解决会话无状态:经验/进度/坑碎片化、重开失忆。遇 bug 排查时配合挂载 bug-hunter skill。
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# abs — 跨会话记忆 (agent-brain-sync)
|
|
@@ -313,3 +313,6 @@ abs supersede <页名> --by <取代它的新页> # 不写 --by 也行 = 单纯
|
|
|
313
313
|
- 只读写 `.brain/` 与目标代码,不动全局配置(一次性接入除外)。
|
|
314
314
|
- 只写真实发生的事实;遵守容量纪律,宁缺毋滥。
|
|
315
315
|
- 双链/frontmatter/index 必须自洽 —— 坏链 = 掰断接力棒。
|
|
316
|
+
- **排查 bug 时挂载 `bug-hunter` skill**(随 abs 一并安装,在 `skills/bug-hunter/`):
|
|
317
|
+
完整排查流程(列假设 → 复现 → 定位改动 → 打印 → 顺藤摸瓜 → 根因 → 验证)
|
|
318
|
+
与「给结论要明确」都在那份里。**排查方法不写在本文件**(两份维护必漂)。
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: abs-bug-hunter
|
|
3
|
+
description: 按流程论排查 bug,先定位改动 → 日志打印 → 顺藤摸瓜逐层排查
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Bug 排查方法论
|
|
7
|
+
|
|
8
|
+
## 核心原则
|
|
9
|
+
|
|
10
|
+
**Bug 多数由新增或修改的功能引起。不凭空猜测,用数据说话。但数据不是终点——先复现,再定位,再理解根因。**
|
|
11
|
+
|
|
12
|
+
常见的失败模式是把"能复现的问题"当成"已定位的 bug",跳过理解直接下结论。定位到错误位置 ≠ 找到根因。改对症状而不改根因,换一批数据又会复发。
|
|
13
|
+
|
|
14
|
+
## 排查流程
|
|
15
|
+
|
|
16
|
+
### 第 -1 步:列假设 —— 方向错了怎么办(不能跳过)
|
|
17
|
+
|
|
18
|
+
**触发**:用户报了症状(“打不开”“报错了”“不对”),但没给一手错误信息。
|
|
19
|
+
|
|
20
|
+
**最大坑(真实案例)**:只有一个假设时,它要么被证实要么思路断掉 —— 于是**自己造一个输入**去测:
|
|
21
|
+
编了个接口名 `app.init` → curl 测出 404 → “我测出来的,是事实” → 推出“nginx 没配 PATH_INFO”
|
|
22
|
+
→ 用户纠正后**重跑同一实验、得到同一 404** → 坚信自己没错。
|
|
23
|
+
|
|
24
|
+
> 假证据最毒之处:**下游全程都是真的**(404 真、命令真跑过)。唯一假的那环
|
|
25
|
+
> (输入名)在最上游,且已消失在过程里 → 所以“再验证一次”救不了,
|
|
26
|
+
> 他会重跑同假输入得同真输出。
|
|
27
|
+
> 下面第 0 步“先复现”在拿不到报错时反而会逼出造假 —— 所以本步在它之前。
|
|
28
|
+
|
|
29
|
+
1. **开局列 ≥2 个假设**(代码层 / 配置层 / 构建产物 / 缓存 / 第三方后台)。
|
|
30
|
+
只列一个 = 逼自己去证实它 = 造证据的动力。
|
|
31
|
+
2. **每个假设配反证条件**:什么现象出现就认它错。
|
|
32
|
+
> 例:“假设 nginx 没配 PATH_INFO;反证条件:用**从代码里读到的真实接口名**
|
|
33
|
+
> 测出也是 404 → 接口本身不存在,与 nginx 无关 → 弃此假设。”
|
|
34
|
+
> 反证条件里写明“真实接口名”,`app.init` 这种编造的就自动不能用了。
|
|
35
|
+
3. **卡住两轮就换假设**,不是在同一假设上加细节。
|
|
36
|
+
4. **领域外的先声明无取证能力**(构建产物在不在 / 缓存 / 微信等第三方后台行为),
|
|
37
|
+
要用户提供事实,**不要脑补**。
|
|
38
|
+
|
|
39
|
+
**结论所依据的每个具体名字(接口/字段/报错文本/路径)必须指得出处(文件:行)。**
|
|
40
|
+
指不出的,只能标“推测”二字,**不得当证据用**。
|
|
41
|
+
|
|
42
|
+
> **为什么多假设能防造假**:单假设必须被证实,否则思路断 → 有动机造。
|
|
43
|
+
> 多假设下,“支持了 A” 不等于 “就是 A”(B/C/D 还没排除)→ **假证据推不出结论,造它就没意义了**。
|
|
44
|
+
|
|
45
|
+
### 第 0 步:复现 — 先让 Bug 稳定出现
|
|
46
|
+
|
|
47
|
+
没有稳定复现,后面全是猜。先确定:
|
|
48
|
+
- 复现的必要条件是什么?哪个输入/操作/环境触发?
|
|
49
|
+
- 是 100% 复现,还是偶发?偶发问题先找触发模式(特定数据、特定时机、特定顺序)。
|
|
50
|
+
- 日志里有没有现成的错误栈?错误信息 + 行号是最便宜的定位入口。
|
|
51
|
+
|
|
52
|
+
**给 Bug 起个名字**(一句话描述 + 触发条件)。排查卡住时,回去核对是否还在同一个 Bug 上。
|
|
53
|
+
|
|
54
|
+
### 第一步:定位范围 — 改动了什么
|
|
55
|
+
|
|
56
|
+
Bug 不会凭空出现。先查最近的代码改动:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
git diff HEAD~3 --stat # 看改了哪些文件
|
|
60
|
+
git log --oneline -5 # 看最近提交
|
|
61
|
+
git diff <commit> -- <file> # 看具体改了什么
|
|
62
|
+
git log -p -S '可疑字段/函数名' # 谁改过这段代码
|
|
63
|
+
git blame <file> -L <行号,行号> # 定位到具体某行是谁写的
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
问自己:
|
|
67
|
+
- 这次新增/修改了什么功能?
|
|
68
|
+
- 改动涉及哪些文件?
|
|
69
|
+
- 哪个改动最可能影响到出问题的地方?
|
|
70
|
+
|
|
71
|
+
**不要排查无关代码,把精力集中在改动范围。**
|
|
72
|
+
|
|
73
|
+
### 第二步:日志打印 — 让数据说话
|
|
74
|
+
|
|
75
|
+
不要猜,打印出来看。根据技术栈选择打印方式:
|
|
76
|
+
|
|
77
|
+
| 技术栈 | 打印方式 | 日志位置 |
|
|
78
|
+
|--------|----------|----------|
|
|
79
|
+
| PHP (ThinkPHP) | `\think\Log::error($data)` 或 `trace($data)` | `runtime/log/` |
|
|
80
|
+
| PHP (Laravel) | `Log::info($data)` 或 `logger($data)` | `storage/logs/` |
|
|
81
|
+
| Vue / JS | `console.log(data)` | 浏览器控制台 |
|
|
82
|
+
| Node.js | `console.log(data)` 或 `logger.debug(data)` | 终端 / 日志文件 |
|
|
83
|
+
| SQL | `\think\Db::getLastSql()` 或开启 SQL 日志 | runtime/log 或控制台 |
|
|
84
|
+
|
|
85
|
+
**打印要打印关键数据,不要只打印 "到了这里",要打印实际的变量值。**
|
|
86
|
+
|
|
87
|
+
```php
|
|
88
|
+
// ❌ 没用
|
|
89
|
+
\Log::error('进入方法');
|
|
90
|
+
|
|
91
|
+
// ✅ 有用 — 打印入参、中间态、出参
|
|
92
|
+
\Log::error('goods detail', ['id' => $id, 'goods' => $goods, 'type' => gettype($goods)]);
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**二分法打印**:在可疑链路上隔几层就插一个日志点,先跑一遍看哪段有数据、哪段没数据,把范围砍半,再往里面加日志。比一次打满所有层更快收敛。
|
|
96
|
+
|
|
97
|
+
**加分技巧**:
|
|
98
|
+
- 给每个日志点带唯一前缀(如 `[BUG-01]`、`[BUG-02]`),日志里 grep 一次就能看到全链路顺序。
|
|
99
|
+
- 打印耗时和调用来源:怀疑并发/时序时,打上时间戳与 `debug_backtrace()` 或请求 ID。
|
|
100
|
+
|
|
101
|
+
### 第三步:顺藤摸瓜 — 按调用链逐层排查
|
|
102
|
+
|
|
103
|
+
从用户操作出发,沿着调用链一层层往下查:
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
用户操作(点击/请求)
|
|
107
|
+
→ 路由(routes / pages.json / url)
|
|
108
|
+
→ 门面/中间件(facade / middleware)
|
|
109
|
+
→ 控制器(controller)
|
|
110
|
+
→ 服务层(service)
|
|
111
|
+
→ 模型(model)
|
|
112
|
+
→ 数据库(SQL)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
每一层都打印关键数据,确认数据在哪一层开始出错:
|
|
116
|
+
|
|
117
|
+
```php
|
|
118
|
+
// 控制器 — 打印接收到的参数
|
|
119
|
+
\Log::error('controller input', ['params' => $params]);
|
|
120
|
+
|
|
121
|
+
// 服务层 — 打印处理中的数据
|
|
122
|
+
\Log::error('service process', ['data' => $data, 'result' => $result]);
|
|
123
|
+
|
|
124
|
+
// 模型 — 打印查询结果
|
|
125
|
+
\Log::error('model query', ['sql' => $this->getLastSql(), 'result' => $result]);
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**找到数据从正确变为错误的那一层,bug 就在那一层。**
|
|
129
|
+
|
|
130
|
+
### 第四步:找到根因,不只修症状
|
|
131
|
+
|
|
132
|
+
改对症状 = 换一批数据又复发。修复前回答:
|
|
133
|
+
- 为什么数据在这一层变错了?是**逻辑错误**(判断写反/字段取错),**类型错误**(null/数组/对象混用),还是**数据本身脏**(上游写入时就错了)?
|
|
134
|
+
- 根因若在上游(写入方/历史数据),这里 patch 只是挡一下,应该去修上游或在入口统一兜底。
|
|
135
|
+
|
|
136
|
+
**修一层,不改所有调用点**:如果多个地方都调用同一函数,只在出问题的那个调用点打补丁,其他调用点照样坏。在共享函数里修一次,是所有调用点的最小修复。
|
|
137
|
+
|
|
138
|
+
### 第五步:验证修复 — 逐行打印确认
|
|
139
|
+
|
|
140
|
+
修复 bug 后,不要凭空验证。**先拿第 0 步的复现条件重跑**,确认错误消失、结果正确,再用打印验证每一步符合预期:
|
|
141
|
+
|
|
142
|
+
```php
|
|
143
|
+
// 修复后验证
|
|
144
|
+
\Log::error('step1: fetch', ['data' => $data]);
|
|
145
|
+
$data = transform($data);
|
|
146
|
+
\Log::error('step2: transform', ['data' => $data]);
|
|
147
|
+
$result = save($data);
|
|
148
|
+
\Log::error('step3: save', ['result' => $result]);
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
验证包括三层:
|
|
152
|
+
- **修好了**:复现路径不再报错,输出正确。
|
|
153
|
+
- **没修坏**:相关正常路径仍正常(回归)。
|
|
154
|
+
- **边界还在**:尝试边缘输入(空值、超大值、并发、重复提交),确认修复没引入新洞。
|
|
155
|
+
|
|
156
|
+
确认无误后,再删除调试日志。
|
|
157
|
+
|
|
158
|
+
## 实战示例
|
|
159
|
+
|
|
160
|
+
**场景:** 商品详情页报 `Call to a member function toArray() on array`
|
|
161
|
+
|
|
162
|
+
**第 0 步:复现**
|
|
163
|
+
- 访问 `/shopro/goods/goods/detail/id/31` 稳定复现
|
|
164
|
+
- 日志:`[error] 致命错误: Call to a member function toArray() on array [/var/www/html/.../GoodsMemberPrice.php:50]`
|
|
165
|
+
|
|
166
|
+
**第一步:定位范围**
|
|
167
|
+
- 最近新增了会员系统,改了 Goods 控制器和新增了 GoodsMemberPrice 模型
|
|
168
|
+
- 怀疑新增代码有问题
|
|
169
|
+
|
|
170
|
+
**第二步:查看日志**
|
|
171
|
+
- 日志直接告诉出错文件和行号
|
|
172
|
+
|
|
173
|
+
**第三步:顺藤摸瓜**
|
|
174
|
+
- 路由:`/shopro/goods/goods/detail/id/31`
|
|
175
|
+
- 控制器:`Goods::detail()` → 调用了 `GoodsMemberPriceModel::getByGoods()`
|
|
176
|
+
- 模型:`getByGoods()` 第50行 `select()->toArray()` → `select()` 返回数组,不是 Collection
|
|
177
|
+
|
|
178
|
+
**第四步:找根因**
|
|
179
|
+
- 为什么 id=31 出错而别的正常?查数据:31 这件商品有会员价记录 → 触发 `select()` 返回多行场景。可能是 `find()`/`select()` 返回值混用的历史问题。若只是个别记录脏,应清理数据或给方法加统一返回类型。
|
|
180
|
+
|
|
181
|
+
**第五步:修复验证**
|
|
182
|
+
```php
|
|
183
|
+
// 修复前
|
|
184
|
+
return self::where('goods_id', $goodsId)->select()->toArray();
|
|
185
|
+
|
|
186
|
+
// 修复后
|
|
187
|
+
$result = self::where('goods_id', $goodsId)->select();
|
|
188
|
+
return $result instanceof \think\Collection ? $result->toArray() : (array)$result;
|
|
189
|
+
```
|
|
190
|
+
- 重跑 id=31(不再报错)+ 跑一个无会员价记录的 id(确认没回归)
|
|
191
|
+
|
|
192
|
+
## 常见陷阱
|
|
193
|
+
|
|
194
|
+
| 陷阱 | 正确做法 |
|
|
195
|
+
|------|----------|
|
|
196
|
+
| 凭经验猜测 bug 位置 | 先复现 + 看日志/打印,用数据定位 |
|
|
197
|
+
| 只看代码不运行 | 打印实际运行时的数据 |
|
|
198
|
+
| 一次改很多地方再测试 | 改一处、验证一处 |
|
|
199
|
+
| 修症状不修根因 | 追问"为什么这层数据变了",确认根因 |
|
|
200
|
+
| 只验证出错的路径 | 重跑复现 + 回归正常路径 + 边界输入 |
|
|
201
|
+
| 忘记删除调试代码 | 修复确认后清理所有 `console.log` / `\Log::error` |
|
|
202
|
+
| 只在本地验证 | 确认服务器代码是否同步部署,数据是否一致 |
|
|
203
|
+
| 排查卡住还硬扛 | 回去核对第 0 步的 Bug 描述,确认是否在同一个问题上;必要时换数据/换触发角度重看 |
|
|
204
|
+
| 偶发当成必然 | 偶发先找触发模式,别用单个样本下结论 |
|
|
205
|
+
|
|
206
|
+
## 兜底:卡住时做什么
|
|
207
|
+
|
|
208
|
+
排查 >20 分钟没进展,不硬扛,回到流程检查:
|
|
209
|
+
1. **Bug 描述还准吗?** 是否有新的观察改变了问题定义。
|
|
210
|
+
2. **复现还稳定吗?** 换一个触发样本还出现吗。
|
|
211
|
+
3. **有没有漏看的日志?** grep 全量日志(不只最近的),可能早期就报过错。
|
|
212
|
+
4. **改动范围查全了吗?** 只看 HEAD~3 可能漏了分支合并、配置文件、部署差异。
|
|
213
|
+
5. **要不要跟同事确认?** 这条功能最近谁改的、改动意图是什么,可能一句话点醒。
|
|
214
|
+
6. **向上游看一层。** 数据/配置来源方是否也变了,不只是消费方的问题。
|
|
215
|
+
|
|
216
|
+
## 收尾:给结论要明确,不要模糊
|
|
217
|
+
|
|
218
|
+
**不许**:"可能是 X,也可能是 Y,建议排查一下" —— 这是把判断推回给用户。
|
|
219
|
+
|
|
220
|
+
**必须**:明确给出
|
|
221
|
+
1. **当前结论**(是什么 / 或者"未定位")
|
|
222
|
+
2. **置信度与依据**(哪来的证据 / 还是只是推测)
|
|
223
|
+
3. **下一步具体动作**(跑什么命令、看什么输出、要用户提供什么)
|
|
224
|
+
|
|
225
|
+
"未定位"是合法输出,但必须配一句"**需要你提供 X**"(具体到要什么都),
|
|
226
|
+
不能只说"无法确定"就结束。
|
|
227
|
+
|
|
228
|
+
> 与第 -1 步同一根因:模糊化是逃避判断。要么给结论 + 依据,要么明确索取证据。
|
package/src/install.js
CHANGED
|
@@ -14,7 +14,63 @@ 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
|
-
|
|
17
|
+
|
|
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
|
+
return out;
|
|
43
|
+
})();
|
|
44
|
+
// 主 skill(会话开场加载的那个)。它也只是 ALL_SKILLS 的一员,此处仅用于告警比对。
|
|
45
|
+
const SKILL_SOURCE = join(SKILL_ROOT, 'abs-agent-brain-sync', 'SKILL.md');
|
|
46
|
+
|
|
47
|
+
/** 安装全部 skill 到该宿主。收口在此:四处安装点共用 —— 新增 skill 只改
|
|
48
|
+
* skill/ 目录,本函数与调用方都不动(避免「改一份不算改」)。返回步骤行。 */
|
|
49
|
+
async function installSkills(agentKey) {
|
|
50
|
+
const steps = [];
|
|
51
|
+
const root = join(hostSkillDir(agentKey), '..');
|
|
52
|
+
for (const s of ALL_SKILLS) {
|
|
53
|
+
const t = join(root, s.name, 'SKILL.md');
|
|
54
|
+
await atomicWrite(t, await fs.readFile(s.src, 'utf8'));
|
|
55
|
+
steps.push(`✓ skill → ${t}`);
|
|
56
|
+
}
|
|
57
|
+
return steps;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** 卸载该宿主的全部 skill(主 + 附带)。与 installSkills 同源同规则。
|
|
61
|
+
* 曾经的坑: 只有 claude-code 删了附带 skill,其余三宿主留下 abs-bug-hunter/ 残留。
|
|
62
|
+
*/
|
|
63
|
+
async function uninstallSkills(agentKey) {
|
|
64
|
+
const steps = [];
|
|
65
|
+
const root = join(hostSkillDir(agentKey), '..');
|
|
66
|
+
for (const s of ALL_SKILLS) {
|
|
67
|
+
const d = join(root, s.name);
|
|
68
|
+
await fs.rm(d, { recursive: true, force: true });
|
|
69
|
+
steps.push(`✓ skill 已删除 → ${d}`);
|
|
70
|
+
}
|
|
71
|
+
return steps;
|
|
72
|
+
}
|
|
73
|
+
|
|
18
74
|
|
|
19
75
|
/**
|
|
20
76
|
* 本包的稳定入口路径解析(mcp.js / abs.js 通用)。
|
|
@@ -455,9 +511,7 @@ async function installClaudeCode({ withMcp, withSkill, log }) {
|
|
|
455
511
|
|
|
456
512
|
// 3) skill → <configRoot>/skills/abs-agent-brain-sync/SKILL.md (config 根 = CLAUDE_CONFIG_DIR)
|
|
457
513
|
if (withSkill) {
|
|
458
|
-
|
|
459
|
-
await atomicWrite(target, await fs.readFile(SKILL_SOURCE, 'utf8'));
|
|
460
|
-
steps.push(`✓ skill → ${target}`);
|
|
514
|
+
steps.push(...await installSkills('claude-code'));
|
|
461
515
|
}
|
|
462
516
|
return steps;
|
|
463
517
|
}
|
|
@@ -492,9 +546,7 @@ async function uninstallClaudeCode() {
|
|
|
492
546
|
await fs.rm(join(homedir(), '.abs', 'hooks', 'claude-code'), { recursive: true, force: true });
|
|
493
547
|
steps.push(`✓ ~/.abs/hooks/claude-code/ (本 agent hook 脚本) 已删除`);
|
|
494
548
|
// skill
|
|
495
|
-
|
|
496
|
-
await fs.rm(skillDir, { recursive: true, force: true });
|
|
497
|
-
steps.push(`✓ skill 已删除`);
|
|
549
|
+
steps.push(...await uninstallSkills('claude-code'));
|
|
498
550
|
return steps;
|
|
499
551
|
}
|
|
500
552
|
|
|
@@ -574,9 +626,7 @@ async function installCodex({ withMcp, withSkill, log }) {
|
|
|
574
626
|
}
|
|
575
627
|
}
|
|
576
628
|
if (withSkill) {
|
|
577
|
-
|
|
578
|
-
await atomicWrite(target, await fs.readFile(SKILL_SOURCE, 'utf8'));
|
|
579
|
-
steps.push(`✓ skill → ${target}`);
|
|
629
|
+
steps.push(...await installSkills('codex'));
|
|
580
630
|
}
|
|
581
631
|
return steps;
|
|
582
632
|
}
|
|
@@ -621,8 +671,7 @@ async function uninstallCodex() {
|
|
|
621
671
|
steps.push(`✓ MCP 已从 ${mcpP} 移除`);
|
|
622
672
|
}
|
|
623
673
|
} catch {}
|
|
624
|
-
await
|
|
625
|
-
steps.push(`✓ skill 已删除`);
|
|
674
|
+
steps.push(...await uninstallSkills('codex'));
|
|
626
675
|
return steps;
|
|
627
676
|
}
|
|
628
677
|
|
|
@@ -726,9 +775,7 @@ async function installOpenCode({ withMcp, withSkill, log }) {
|
|
|
726
775
|
steps.push(`✓ MCP → ${mcpP} (mcp.abs local)`);
|
|
727
776
|
}
|
|
728
777
|
if (withSkill) {
|
|
729
|
-
|
|
730
|
-
await atomicWrite(target, await fs.readFile(SKILL_SOURCE, 'utf8'));
|
|
731
|
-
steps.push(`✓ skill → ${target}`);
|
|
778
|
+
steps.push(...await installSkills('opencode'));
|
|
732
779
|
}
|
|
733
780
|
return steps;
|
|
734
781
|
}
|
|
@@ -751,8 +798,7 @@ async function uninstallOpenCode() {
|
|
|
751
798
|
await atomicWrite(mcpP, JSON.stringify(cfg, null, 2));
|
|
752
799
|
steps.push(`✓ MCP 已从 ${mcpP} 移除`);
|
|
753
800
|
}
|
|
754
|
-
await
|
|
755
|
-
steps.push(`✓ skill 已删除`);
|
|
801
|
+
steps.push(...await uninstallSkills('opencode'));
|
|
756
802
|
return steps;
|
|
757
803
|
}
|
|
758
804
|
|
|
@@ -775,9 +821,7 @@ async function installPi({ withMcp, withSkill, log }) {
|
|
|
775
821
|
steps.push(`✓ MCP → ${mcpP} (mcpServers.abs, stdio)`);
|
|
776
822
|
}
|
|
777
823
|
if (withSkill) {
|
|
778
|
-
|
|
779
|
-
await atomicWrite(target, await fs.readFile(SKILL_SOURCE, 'utf8'));
|
|
780
|
-
steps.push(`✓ skill → ${target}`);
|
|
824
|
+
steps.push(...await installSkills('pi'));
|
|
781
825
|
}
|
|
782
826
|
return steps;
|
|
783
827
|
}
|
|
@@ -786,8 +830,7 @@ async function uninstallPi() {
|
|
|
786
830
|
const steps = [];
|
|
787
831
|
await fs.rm(join(hostConfigRoot('pi'), 'agent', 'extensions', 'abs.ts'), { force: true });
|
|
788
832
|
steps.push(`✓ extension 已删除`);
|
|
789
|
-
await
|
|
790
|
-
steps.push(`✓ skill 已删除`);
|
|
833
|
+
steps.push(...await uninstallSkills('pi'));
|
|
791
834
|
const mcpP = join(hostConfigRoot('pi'), 'agent', 'mcp.json');
|
|
792
835
|
const cfg = await readJson(mcpP, { strict: false });
|
|
793
836
|
if (cfg === null) {
|