@fanchao8609/agent_brain_sync 1.7.0 → 1.7.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/bin/abs.js CHANGED
@@ -76,6 +76,7 @@ const FLAG_SPEC = {
76
76
  'keep-days': { type: 'string' },
77
77
  'help': { type: 'boolean' },
78
78
  'dry-run': { type: 'boolean' },
79
+ 'full': { type: 'boolean' },
79
80
  'yes': { type: 'boolean' },
80
81
  'repair': { type: 'boolean' },
81
82
  'no-mcp': { type: 'boolean' },
@@ -313,8 +314,8 @@ async function main() {
313
314
  case 'todo': {
314
315
  const [sub, id, ...rest2] = opts._;
315
316
  if (!sub) {
316
- rejectExtra([id, ...rest2].filter(Boolean), 'abs todo');
317
- console.log(await cmdShow({ dir: opts.dir, view: 'todo' }));
317
+ rejectExtra([id, ...rest2].filter(Boolean), 'abs todo [--full]');
318
+ console.log(await cmdShow({ dir: opts.dir, view: 'todo', full: opts.full }));
318
319
  break;
319
320
  }
320
321
  const action = TODO_ACTIONS[sub];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fanchao8609/agent_brain_sync",
3
- "version": "1.7.0",
3
+ "version": "1.7.1",
4
4
  "description": "agent-brain-sync: 跨会话 AI 编码记忆 — hook 纯触发 + CLI/MCP 读写 .brain markdown 图谱, 防并发写保护。",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/skill/SKILL.md CHANGED
@@ -145,6 +145,11 @@ monorepo 若多个子包各自独立交付,可各建一份 `.brain/`。`abs st
145
145
  图谱已存在;收到第一个核心开发指令**之前**走这条链载入上下文:
146
146
 
147
147
  1. **读状态**:`abs load`(或 MCP `abs_load`)读 index 路线 + todo 看板 + 最近 log。
148
+ > **Done 区是折叠的(按日期计数)**,不是全量 —— Done 是无上限增长的归档区,
149
+ > 全量打印会随项目历史线性膨胀,把上下文塞满(曾占 load 输出 68.8%,
150
+ > 长历史项目上单次 load 吃掉 40% 上下文)。
151
+ > 要 Done 明细:`abs todo --full`,或看 `.brain/sessions/<日期>-todo归档.md`。
152
+ > 「最近动作」每条也按语义边界收口 —— 续接只需线索,不需要日志全文。
148
153
  2. **对账滞留(强制,别跳过)**:若 `abs load` 顶部出现 `⏳ 上会话滞留`,说明上会话有任务做完/做到一半就断了。**先收尾再开工**:
149
154
  - 快照里的任务现在真做完了 → `abs todo done <id>`(done 后下次 load 滞留自动消失);
150
155
  - 还没做完 → `abs todo note <id> --note "接到哪/改到哪个文件"` 补断点(别空手续接)。
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 { requireBrain, brainPath, absLogDir, BRAIN_DIR } from './index.js';
6
6
  import { requireUser, atTag, getUser } from './userconfig.js';
7
- import { addTask, upsertTask, boardText, readTodo, ensureTodo, todoTemplate, today, localStamp, setBreakpoint, moveBlocked, insertDoneGrouped, idOfTaskLine, archiveDoneInText, renderArchivePage, renderArchiveBody, DONE_KINDS, withDoneKind, doneKindOf } from './todo.js';
7
+ import { addTask, upsertTask, boardText, readTodo, ensureTodo, todoTemplate, today, localStamp, setBreakpoint, moveBlocked, insertDoneGrouped, idOfTaskLine, archiveDoneInText, renderArchivePage, renderArchiveBody, DONE_KINDS, withDoneKind, doneKindOf, collapseDone } from './todo.js';
8
8
  import { editFile, SKIP } from './lock.js';
9
9
  import { appendWrapup, strandedFor } from './wrapup.js';
10
10
 
@@ -128,9 +128,9 @@ export function logTemplate() {
128
128
  }
129
129
 
130
130
  // ---------- board: 看板 ----------
131
- export async function cmdBoard({ dir }) {
131
+ export async function cmdBoard({ dir, full }) {
132
132
  const root = await requireBrain(dir || process.cwd());
133
- return boardText(root);
133
+ return boardText(root, undefined, { full: !!full });
134
134
  }
135
135
 
136
136
  // ---------- status: 定位报告 + 图谱概要 ----------
@@ -182,7 +182,8 @@ export async function cmdLoad({ dir }) {
182
182
  index || '(index.md 为空)',
183
183
  '',
184
184
  '--- Todo 看板 (todo.md) ---',
185
- todo.trim() || '(todo.md 为空)',
185
+ collapseDone(todo).text || '(todo.md 为空)',
186
+ '\n(Done 已按日期折叠计数;明细: abs todo --full)',
186
187
  '',
187
188
  '--- 最近动作 (log.md, 最新 5 条) ---',
188
189
  recentLogLines(log, 5) || '(log.md 为空)',
@@ -366,6 +367,9 @@ function recentLogLines(text, n) {
366
367
  .split('\n')
367
368
  .filter((l) => /^##\s*\[/.test(l))
368
369
  .slice(0, n)
370
+ // 每条按语义边界收口:load 是开机读状态,只求"上会话做到哪"的线索,不须全文。
371
+ // 坑: 不收口时 5 条能占 2.9KB(单条 800B),又是一处随日志变长而膨胀的上下文。
372
+ .map((l) => clip(l, 220))
369
373
  .join('\n');
370
374
  }
371
375
 
@@ -493,7 +497,7 @@ async function markDone(file, id, kind = '落地') {
493
497
  }
494
498
 
495
499
  // ---------- show: 查看 index/todo/log(只读面) ----------
496
- export async function cmdShow({ dir, view }) {
500
+ export async function cmdShow({ dir, view, full }) {
497
501
  const v = String(view || '').toLowerCase();
498
502
  if (!['todo', 'index', 'log'].includes(v)) {
499
503
  return '用法: abs <todo|index|log> — todo=看板(原 board), index=图谱索引, log=操作流水';
@@ -510,7 +514,7 @@ export async function cmdShow({ dir, view }) {
510
514
  return `${v}.md 不存在于 ${brainPath(root)}。初始化/补齐: abs init --repair`;
511
515
  }
512
516
  if (!text) return `(${v}.md 为空)`;
513
- return v === 'todo' ? boardText(root, text) : text;
517
+ return v === 'todo' ? boardText(root, text, { full: !!full }) : text;
514
518
  }
515
519
  // ---------- query: 检索知识图谱(多词 OR,扫全 .md 页) ----------
516
520
  const KNOWN_SLUG_HINT = /模板残留|\[\[slug\]\]/;
package/src/todo.js CHANGED
@@ -160,6 +160,51 @@ function renderDoneGroups(units) {
160
160
  return out;
161
161
  }
162
162
 
163
+ /** 把 Done 区从「全量行」折叠为「按日期计数」,供 `abs load` / `abs todo` 这类
164
+ * 给人(和 AI 上下文)看的视图用。
165
+ *
166
+ * 坑: 这两个命令此前直接 `todo.trim()` 全量打印,而 Done 区**无上限增长** ——
167
+ * 本仓库一处就占 load 输出的 68.8%(19.6KB/28.4KB)。长历史机器上直接把
168
+ * 上下文塞满(实报:「另一台机器 abs load 塞了 40%」)。
169
+ * 这与 hooks.log / wrapup.log 同类问题(那两处有轮转),故这里只给计数,
170
+ * 要看明细用 `abs todo` 加 `--full`,或直接看 todo.md / 归档页。
171
+ *
172
+ * `### 归档` 区(历史的「归档 N 条」标记行)体积小且是长期引用,原样保留。
173
+ * 返回 { text, doneCount };text 不含 `## Done` 标题行。 */
174
+ export function collapseDone(text) {
175
+ const lines = String(text).split('\n');
176
+ const di = lines.findIndex((l) => l.startsWith('## Done'));
177
+ if (di === -1) return { text: String(text).trim(), doneCount: 0 };
178
+ const head = lines.slice(0, di);
179
+ const rest = lines.slice(di + 1);
180
+ const { groupLines, archiveLines } = splitDoneBody(rest);
181
+ const units = parseDoneUnits(groupLines);
182
+ // 按日期归组计数(新日期在前),未标日期的归尾
183
+ const byDate = new Map();
184
+ let undated = 0;
185
+ for (const u of units) {
186
+ if (!u.date) undated++;
187
+ else byDate.set(u.date, (byDate.get(u.date) || 0) + 1);
188
+ }
189
+ const counts = [...byDate.entries()]
190
+ .sort((a, b) => (a[0] < b[0] ? 1 : -1))
191
+ .map(([d, n]) => `${d} ${n} 条`);
192
+ if (undated) counts.push(`未标日期 ${undated} 条`);
193
+ // 只列最近 MAX_DONE_DATE_LINES 个日期组:日期组本身也随历史增长(实测 300 条跨 28 天
194
+ // 会把 load 输出从 660B 拉到 1203B)。载入视图只需"最近做了多少",早期历史看归档页。
195
+ const MAX_DONE_DATE_LINES = 7;
196
+ const shown = counts.slice(0, MAX_DONE_DATE_LINES);
197
+ if (counts.length > MAX_DONE_DATE_LINES) {
198
+ const rest = counts.length - MAX_DONE_DATE_LINES;
199
+ shown.push(`… 另有 ${rest} 个更早日期组(abs todo --full 看全量)`);
200
+ }
201
+ const doneLines = units.length
202
+ ? [`## Done(${units.length} 条,按日期折叠)`, ...shown.map((c) => `- ${c}`)]
203
+ : ['## Done(0 条)'];
204
+ const out = [...head, ...doneLines, '', ...archiveLines].join('\n').replace(/\n{3,}/g, '\n\n').trim();
205
+ return { text: out, doneCount: units.length };
206
+ }
207
+
163
208
  /** 归档标记区标题。它在 Done 区内部、日期分组之后,形如:
164
209
  * ### 归档
165
210
  * - [[2026-09-10-todo归档]] 完成任务 10 条
@@ -513,9 +558,12 @@ export async function moveBlocked(brainRoot, { id, reason }) {
513
558
  }
514
559
 
515
560
  // ---------- 看板输出 ----------
516
- export async function boardText(brainRoot, textOverride) {
561
+ export async function boardText(brainRoot, textOverride, { full = false } = {}) {
517
562
  const text = textOverride !== undefined ? textOverride : await readTodo(brainRoot);
518
563
  const head = `📂 abs → 项目: ${brainRoot}`;
519
564
  if (!text.trim()) return `${head}\n\n(todo.md 为空,先 abs todo add 登记任务)`;
520
- return `${head}\n\n${text.trim()}`;
565
+ // Done 区折叠:它无上限增长,全量打印会把上下文塞满(见 collapseDone 注释)
566
+ const body = full ? text.trim() : collapseDone(text).text;
567
+ const hint = full ? '' : '\n\n(Done 只给计数;看明细: abs todo --full)';
568
+ return `${head}\n\n${body}${hint}`;
521
569
  }