@fanchao8609/agent_brain_sync 1.2.0 → 1.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fanchao8609/agent_brain_sync",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
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
@@ -4,7 +4,7 @@ import { promises as fs } from 'node:fs';
4
4
  import { join, resolve, dirname } from 'node:path';
5
5
  import { homedir } from 'node:os';
6
6
  import { requireBrain, findBrainRoot, brainPath } from './index.js';
7
- import { addTask, upsertTask, boardText, readTodo, ensureTodo, todoTemplate, today, localStamp, setBreakpoint, moveBlocked, insertDoneGrouped } from './todo.js';
7
+ import { addTask, upsertTask, boardText, readTodo, ensureTodo, todoTemplate, today, localStamp, setBreakpoint, moveBlocked, insertDoneGrouped, idOfTaskLine } from './todo.js';
8
8
  import { editFile, SKIP } from './lock.js';
9
9
  import { appendWrapup, strandedFor, wrapupLogPath } from './wrapup.js';
10
10
 
@@ -320,9 +320,10 @@ async function markDone(file, id) {
320
320
  let changed = false;
321
321
  let moved = null;
322
322
  const kept = [];
323
+ const wantId = String(id).replace(/\u200b/g, '');
323
324
  for (let i = 0; i < lines.length; i++) {
324
325
  const l = lines[i];
325
- if (!moved && l.startsWith('- [ ]') && l.includes(id)) {
326
+ if (!moved && idOfTaskLine(l) === wantId) {
326
327
  changed = true;
327
328
  const head = l.replace('- [ ]', '- [x]').replace(/\(认领[^)]*\)/, '') + ` (完成 ${today()})`;
328
329
  const bp = [];
package/src/todo.js CHANGED
@@ -215,8 +215,8 @@ export async function upsertTask(brainRoot, { section, text }) {
215
215
  const out = await editFile(p, (orig) => {
216
216
  const base = normalizeTodo(orig);
217
217
  const lines = base.split('\n');
218
- // 在所有分区中找含该 id 的未完成任务行(Done 的已完成行不重复动)
219
- const idx = lines.findIndex((l) => l.startsWith('- [ ]') && id && l.includes(id));
218
+ // 在所有分区中找该 id 对应的未完成任务行(Done 的已完成行不重复动);全等比对
219
+ const idx = findTaskLine(lines, id);
220
220
  if (idx !== -1) {
221
221
  // 原位更新:保留断点附属行,只换任务行本体
222
222
  const oldNote = extractNote(lines[idx].replace(/^- \[ \] /, ''));
@@ -244,10 +244,25 @@ export function extractNote(text) {
244
244
  return i === -1 ? '' : String(text).slice(i + 3).replace(/\s*\(认领[^)]*\)\s*$/, '');
245
245
  }
246
246
 
247
- /** 定位含 id 的未完成任务行下标;附属断点行(↳ 开头)不算任务行。 */
247
+ /**
248
+ * 从任务行取出行首 id token(`- [ ] <id> …` 里的 <id>)。
249
+ * 坑: 不能用 l.includes(id) 定位任务 —— 那是子串匹配,`T1` 会命中 `T11`(同理
250
+ * TASK-1/TASK-10、fix-hook/fix-hook-2),导致 upsert 误判"已存在"而原地改写别的任务、
251
+ * 新任务静默消失。必须取出 id token 做全等比对。
252
+ * id 里可能混入零宽字符(历史瑕疵),比对前先剥掉。
253
+ */
254
+ export function idOfTaskLine(line) {
255
+ if (!line || !line.startsWith('- [ ]')) return null;
256
+ const m = line.match(/^- \[ \] (\S+)/);
257
+ return m ? m[1].replace(/\u200b/g, '') : null;
258
+ }
259
+
260
+ /** 定位 id 对应的未完成任务行下标;附属断点行(↳ 开头)不算任务行。
261
+ * 全等比对,不用 includes(否则 T1 会误命中 T11)。 */
248
262
  export function findTaskLine(lines, id) {
249
263
  if (!id) return -1;
250
- return lines.findIndex((l) => l.startsWith('- [ ]') && l.includes(id));
264
+ const want = String(id).replace(/\u200b/g, '');
265
+ return lines.findIndex((l) => idOfTaskLine(l) === want);
251
266
  }
252
267
 
253
268
  /** 实时断点: 在 id 任务行下原位补/换 `↳ 断点:` 附属行(不挪任务位置)。 */