@fanchao8609/agent_brain_sync 1.14.0 → 1.15.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/bin/abs.js CHANGED
@@ -284,6 +284,7 @@ const subUsage = {
284
284
  '用法:',
285
285
  ' abs todo 看板',
286
286
  ' abs todo add <id> [--note "做什么"] [--section 讨论中|滞留中]',
287
+ ' abs todo rename <旧id> --note "<新id>" 给自动登记的占位条目起真名',
287
288
  ' abs todo note <id> --note "断点/进度"',
288
289
  ' abs todo state <id> --note 进行中|讨论中|滞留中',
289
290
  ' abs todo done <id> [--as 落地|否决|仅方案] [结语文字]',
@@ -351,6 +352,7 @@ const TODO_ACTIONS = {
351
352
  start: 'start', // add 的别名(老习惯保留)
352
353
  note: 'note',
353
354
  state: 'state', // 改行首状态标记:进行中|讨论中|滞留中(原地,不搬区)
355
+ rename: 'rename', // 改任务 id:hook 只开 auto-TBD-<会话> 占位,真名由此处补
354
356
  done: 'done',
355
357
  };
356
358
 
package/hooks/abs.pi.ts CHANGED
@@ -116,6 +116,10 @@ let autoTaskSeen = false
116
116
  * 只存文件名,不存内容 —— 断点是"记到哪",不是快照。 */
117
117
  let lastTouchedFile = ''
118
118
 
119
+ /** 最近一轮的用户指令原文。只用于「第一次真改文件时」抽任务关键词 ——
120
+ * 登记时机已从 before_agent_start 挪到 tool_call(用户实报:一按回车就登记太早)。 */
121
+ let pendingPrompt = ''
122
+
119
123
  /** 最近一次渲染拿到的 tui 引用。hidePanel 用它请求重绘 —— 面板消失时动画
120
124
  * 定时器已停,没有别的重绘通道(见 hidePanel 的坑注解)。 */
121
125
  let lastTui: any = null
@@ -623,6 +627,7 @@ export default function absPiHook(pi: ExtensionAPI): void {
623
627
  // 新会话 = 重新登记:不重置则同进程的第二个会话永远不再落断点
624
628
  autoTaskSeen = false
625
629
  lastTouchedFile = ''
630
+ pendingPrompt = ''
626
631
  resetThrottle()
627
632
  // 本会话的随机昵称 —— 在这里抽一次(不是每帧抽,否则面板会疯狂闪)。
628
633
  // 每次 session_start(startup/reload/new/resume/fork)重抽 → “每次打开 pi 都是随机的”。
@@ -667,7 +672,13 @@ export default function absPiHook(pi: ExtensionAPI): void {
667
672
  const bp = file ? `改了 ${file.slice(0, 120)}` : '开始改文件'
668
673
  if (file) lastTouchedFile = file.slice(0, 120)
669
674
  const sid = sessionIdFor(ctx)
670
- if (sid) spawnAutoTask(cwd, ['--session', sid, '--note', bp])
675
+ // 首次真改文件 = "真开工了" → 此刻才登记(带用户指令抽关键词)+ 落断点。
676
+ // 这是唯一登记入口:光说/说问/讨论都不登记(2026-10-05 用户实报时机太早)。
677
+ if (sid) {
678
+ const args = ['--session', sid, '--note', bp]
679
+ if (pendingPrompt) args.push('--prompt', pendingPrompt)
680
+ spawnAutoTask(cwd, args)
681
+ }
671
682
  logHook(`tool_call auto_task name=${name} sid=${sid || '-'}`).catch(() => {})
672
683
  }
673
684
  })
@@ -700,14 +711,10 @@ export default function absPiHook(pi: ExtensionAPI): void {
700
711
  const ok = injectTodoGuidelines(event?.systemPromptOptions)
701
712
  logHook(`before_agent_start todo_guide=${ok ? 'on' : 'off'} cwd=${cwd}`).catch(() => {})
702
713
 
703
- // 自动登记(2026-10-05):有图谱 + 指令有实质内容 → 先把任务落到看板上。
704
- // 抽不出关键词("在吗"/"继续")时 abs 侧会静默跳过 —— 宁可不登记也不脏看板。
705
- // 一会话一条:同会话后续的 before_agent_start 会命中已有条目,只更新断点。
706
- // ★ 位置必须在 no_brain 分支【之后】—— 放到里面等于"只有没图谱时才登记",逻辑全反。
707
- const sid = sessionIdFor(ctx)
708
- if (sid && event?.prompt) {
709
- spawnAutoTask(cwd, ['--session', sid, '--prompt', String(event.prompt).slice(0, 200)])
710
- }
714
+ // 只【记住】本轮用户指令,不在这里登记(2026-10-05 用户实报时机太早)。
715
+ // 旧行为: 一按回车就登记 → 用户还在说/聊、需求没成型,看板上就已经开出了任务。
716
+ // 现在: 留到第一次真改文件时(tool_call)才登记,那时才算"真开工了"。
717
+ if (event?.prompt) pendingPrompt = String(event.prompt).slice(0, 200)
711
718
  } catch (e: any) {
712
719
  logHook(`before_agent_start error=${e?.message || 'unknown'}`).catch(() => {})
713
720
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fanchao8609/agent_brain_sync",
3
- "version": "1.14.0",
3
+ "version": "1.15.0",
4
4
  "description": "agent-brain-sync: 跨会话 AI 编码记忆 — hook 纯触发 + CLI/MCP 读写 .brain markdown 图谱, 防并发写保护。",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/store.js CHANGED
@@ -1120,6 +1120,31 @@ export async function cmdTask({ dir, action, id, section, note, as }) {
1120
1120
  });
1121
1121
  return `✓ 任务${r.updated ? '更新(幂等)' : '登记'} → ${brainPath(root, 'todo.md')}\n [${st}] ${id} ${atTag(who)}${note ? ' — ' + note : ''}`;
1122
1122
  }
1123
+ if (action === 'rename') {
1124
+ // 给任务改名(2026-10-05):hook 只开 `auto-TBD-<会话>` 占位,真名由理解任务的一方起
1125
+ // (`fix-skill-trigger` 这种不是从用户字面抽出来的,是读懂在干什么之后起的)。
1126
+ // --note 传新 id(沿用既有 CLI 习惯,免得再加一个 flag)。
1127
+ if (!note) throw new Error('✗ 用法: abs todo rename <旧id> --note "<新id>"');
1128
+ const newId = String(note).replace(/[\u200b\u200c\u200d\ufeff]/g, '').trim();
1129
+ if (!newId || /\s/.test(newId)) throw new Error(`✗ 新 id 不能为空或含空白: "${newId}"`);
1130
+ const f = brainPath(root, 'todo.md');
1131
+ let done = false;
1132
+ await editFile(f, (cur) => {
1133
+ if (cur === null) return SKIP;
1134
+ const lines = cur.split('\n');
1135
+ const want = String(id).replace(/[\u200b\u200c\u200d\ufeff]/g, '');
1136
+ for (let i = 0; i < lines.length; i++) {
1137
+ if (idOfTaskLine(lines[i]) !== want) continue;
1138
+ lines[i] = lines[i].replace(want, newId);
1139
+ // 占位描述「待命名」随之换掉 —— 改完名还写着"待命名"会让人以为没改。
1140
+ lines[i] = lines[i].replace(/—\s*待命名\s*/, `— ${newId.replace(/^(auto-|fix-|feat-)/, '')} `);
1141
+ done = true;
1142
+ break;
1143
+ }
1144
+ return done ? { text: lines.join('\n') } : SKIP;
1145
+ });
1146
+ return done ? `✓ 任务改名 ${id} → ${newId}` : `[NO_MATCH] 未找到含 "${id}" 的未完成任务行`;
1147
+ }
1123
1148
  if (action === 'state') {
1124
1149
  // 改状态标记(原地,不搬区):进行中 / 讨论中 / 滞留中
1125
1150
  if (!note) throw new Error(`✗ 用法: abs todo state <id> --note "${TASK_STATES.join('|')}"`);
@@ -1198,6 +1223,23 @@ export function autoTaskId(text) {
1198
1223
  return slug.length >= 2 ? slug : null;
1199
1224
  }
1200
1225
 
1226
+ /** 从断点文本提一个像样的 slug。
1227
+ *
1228
+ * 断点形如 `改了 src/store.js` / `本轮到 hooks/abs.pi.ts`;直接当 slug 会得到
1229
+ * `改了`(首词)这种废名。这里取【文件名】当 slug —— 「改了哪个文件」比「改了」有意义。
1230
+ */
1231
+ export function autoTaskSlugFromNote(note) {
1232
+ const s = String(note || '').replace(/[\u200b\u200c\u200d\ufeff]/g, '');
1233
+ // 文件路径优先(含 / 或 . 扩展名)
1234
+ const m = s.match(/[\w./-]*[\w-]+\.[A-Za-z0-9]{1,8}/);
1235
+ if (m) {
1236
+ const base = m[0].split('/').pop().replace(/\.[A-Za-z0-9]{1,8}$/, '');
1237
+ if (base && base.length >= 2) return `改 ${base}`;
1238
+ }
1239
+ // 退而求其次:抽到的关键词(可能为空)
1240
+ return autoTaskId(s);
1241
+ }
1242
+
1201
1243
  /** hook 自动登记: 一会话一条 + 断点更新。
1202
1244
  *
1203
1245
  * @param {object} o
@@ -1235,26 +1277,30 @@ export async function cmdAutoTask({ dir, session, prompt, note }) {
1235
1277
  return r?.changed ? `↳ ${existingId} 断点更新` : null;
1236
1278
  }
1237
1279
 
1238
- // 首次: 抽不出关键词就不登记
1239
- const slug = autoTaskId(prompt);
1240
- if (!slug) return null;
1241
- // id = 关键词(可读);同 slug 已存在则加序号,避免两条不同活撞同一个 id。
1242
- let id = `${AUTO_TASK_PREFIX}${slug}`;
1243
- let n = 1;
1244
- await editFile(todoPath, (cur) => {
1245
- if (cur === null) return SKIP;
1246
- const ids = new Set(cur.split('\n').map((l) => idOfTaskLine(l)).filter(Boolean));
1247
- while (ids.has(id)) id = `${AUTO_TASK_PREFIX}${slug}-${++n}`;
1248
- return SKIP;
1249
- });
1280
+ // 占位登记(2026-10-05 用户实报后重设计)。
1281
+ //
1282
+ // 为何不再抽用户话当名字:机器抽词天生不可靠 —— 实测抽出过
1283
+ // `我已经重启测试一下`(用户的开场白)、`本轮有改动`(更糟:那是 hook 自己
1284
+ // 生成的断点文本,等于把机器的话当成用户意图)。
1285
+ // 而用户想要的名字长这样:`fix-skill-trigger` / `fix-opencode-v2-plugin` ——
1286
+ // 那**不是从用户的字面上抽出来的**,是理解"在干什么"之后起的名字。
1287
+ //
1288
+ // 故分工改为:hook 只开一个 `TBD-<会话>` 占位(明确标记"未命名"),
1289
+ // 名字留给理解任务的一方改(`abs todo rename` 或直接编辑 todo.md)。
1290
+ // 占位名机器化、一眼可辨,不冒充真任务名。
1291
+ // 开占位的前置条件:**真的动过文件**(hook 传了 --note)。
1292
+ // 只有 prompt 而无 note = 用户说了句话但没动手 → 不开(这是"时机"那条坑的守门,
1293
+ // 改成固定占位后一度丢掉此判断,导致纯寒暄也开条目 —— 单测抓到的)。
1294
+ if (!note) return null;
1295
+ const id = `${AUTO_TASK_PREFIX}TBD-${sid}`;
1250
1296
  const who = await requireUser();
1251
1297
  await ensurePersonPage(root, who);
1252
1298
  await upsertTask(root, {
1253
1299
  section: SEC.todo,
1254
- text: `[进行中] ${id} ${atTag(who)} — ${slug} (${MARK})`,
1300
+ text: `[进行中] ${id} ${atTag(who)} — 待命名 (${MARK})`,
1255
1301
  });
1256
1302
  if (note) await setBreakpoint(root, { id, text: note });
1257
- return `✓ 自动登记 ${id} — ${slug}`;
1303
+ return `✓ 自动登记占位 ${id}(待命名,稍后改名)`;
1258
1304
  }
1259
1305
 
1260
1306
  /** 收尾: 把自动登记的未完成任务标 done(用户没显式接管它们)。