@fanchao8609/agent_brain_sync 1.13.0 → 1.13.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
@@ -521,7 +521,7 @@ async function main() {
521
521
  }
522
522
  case 'lint': {
523
523
  rejectExtra(opts._, 'abs lint');
524
- console.log(await cmdLint({ dir: opts.dir }));
524
+ console.log(await cmdLint({ dir: opts.dir, all: opts.all }));
525
525
  break;
526
526
  }
527
527
  // 内部命令(hook 专用,不出现在 help):Stop 时机械快照未完成任务
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fanchao8609/agent_brain_sync",
3
- "version": "1.13.0",
3
+ "version": "1.13.1",
4
4
  "description": "agent-brain-sync: 跨会话 AI 编码记忆 — hook 纯触发 + CLI/MCP 读写 .brain markdown 图谱, 防并发写保护。",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/lint.js CHANGED
@@ -145,7 +145,7 @@ async function checkRootFiles(vault) {
145
145
  `TODO-BREAKPOINT-BLOAT: ${BRAIN_DIR}/${file}:${i + 1} (断点 ${bp.length} 字 > ${LINT_BREAKPOINT_MAX};` +
146
146
  `断点只写「改到哪个文件哪一步」,报告迁 sessions/ 或 sources/)`,
147
147
  );
148
- } else if (bp.split(/[。!?;!?;]+|\s*[\u2460-\u2473]\s*/).map((x) => x.trim()).filter(Boolean).length > 2) {
148
+ } else if (bp.split(/[。!?!?]+|\s*[\u2460-\u2473]\s*/).map((x) => x.trim()).filter(Boolean).length > 2) {
149
149
  issues.push(
150
150
  `TODO-BREAKPOINT-PROSE: ${BRAIN_DIR}/${file}:${i + 1} (断点写了多句 = 报告体;` +
151
151
  `todo 只放待办,长文迁 sessions/ 或 sources/)`,
@@ -345,7 +345,7 @@ async function readFileOrNull(p) {
345
345
 
346
346
  // ---------- 入口 ----------
347
347
 
348
- export async function cmdLint({ dir }) {
348
+ export async function cmdLint({ dir, all = false }) {
349
349
  let root;
350
350
  try {
351
351
  root = await requireBrain(dir || process.cwd());
@@ -374,10 +374,44 @@ export async function cmdLint({ dir }) {
374
374
  issues.push(...await checkRootFiles(vault));
375
375
 
376
376
  const n = issues.length;
377
+ // ★ 同类折叠(2026-10-05):体检输出必须按问题**类型**折叠计数。
378
+ // 为什么:给根文件加形状校验后,abs 自带的 log.md 有 102 条历史行缺 `[[作者]]`,
379
+ // 102 条 LOG-ENTRY 淹没了其它 12 条真问题(DRAFT-STALE/DEAD-LINK/NO-INBOUND)——
380
+ // lint 的价值是「让人看见该修的」,被噪声淹没就退化成不可读。
381
+ // 同族约束:既有 Rule `读取侧输出不得随规模增长`、Done 区已按日期折叠计数。
382
+ // 形态:同类 > THRESHOLD 时只报「N 条同类 + 前 SAMPLE 条样例」。
383
+ // 注:全量明细仍可用 `abs lint --all` 拿到(不静默丢信息)。
384
+ const folded = all ? issues : foldSameKind(issues);
385
+ const n2 = folded.length;
377
386
  return [
378
- ...(issues.length ? issues : []),
387
+ ...folded,
379
388
  '',
380
- `lint: ${n} problem(s).`,
389
+ `lint: ${n2} problem(s).${n2 !== n ? ` (共 ${n} 条,同类已折叠;全量: abs lint --all)` : ''}`,
381
390
  n === 0 ? '✓ 图谱健康' : '',
382
391
  ].filter(Boolean).join('\n');
392
+ }
393
+
394
+ /** 同类问题折叠:按错误码分组,超过阈值只报计数 + 前几条样例。
395
+ * 错误码 = 首行的 `CODE:` 前缀(checkPage/checkGraph/checkRootFiles 都是这个形态)。
396
+ * 保序:按「首次出现顺序」输出,不因折叠打乱阅读节奏。 */
397
+ const FOLD_THRESHOLD = 10;
398
+ const FOLD_SAMPLE = 3;
399
+ export function foldSameKind(issues) {
400
+ const groups = new Map(); // code -> { items, order }
401
+ for (const it of issues) {
402
+ const m = String(it).match(/^([A-Z][A-Z0-9-]+):/);
403
+ const code = m ? m[1] : '(其它)';
404
+ if (!groups.has(code)) groups.set(code, { items: [] });
405
+ groups.get(code).items.push(it);
406
+ }
407
+ const out = [];
408
+ for (const [code, g] of groups) {
409
+ if (g.items.length <= FOLD_THRESHOLD) {
410
+ out.push(...g.items);
411
+ } else {
412
+ out.push(...g.items.slice(0, FOLD_SAMPLE));
413
+ out.push(` … 还有 ${g.items.length - FOLD_SAMPLE} 条同类的 ${code}(共 ${g.items.length} 条)`);
414
+ }
415
+ }
416
+ return out;
383
417
  }
package/src/store.js CHANGED
@@ -11,7 +11,7 @@ import { keywords, pickRelevant, renderRelevant, recentFiles, rankPage, topicStr
11
11
  import { impactOf } from './codegraph.js';
12
12
 
13
13
  // Re-export lint.js symbols so external importers (e.g. bin/mcp.js, bin/abs.js) still work.
14
- export { cmdLint, listPages, hasTail, PAGE_DIRS } from './lint.js';
14
+ export { cmdLint, listPages, hasTail, PAGE_DIRS, foldSameKind } from './lint.js';
15
15
  import { SOURCES_MAX, PAGE_DIRS, listPages } from './lint.js';
16
16
 
17
17
  // ---------- init: 建 .brain/ 骨架 ----------
package/src/todo.js CHANGED
@@ -206,7 +206,11 @@ export const ENTRY_SHAPES = {
206
206
  test: (l) => {
207
207
  if (/^#{1,3}\s/.test(l)) return null; // 标题行(## Done / ### 日期)不是条目
208
208
  if (!/^-\s/.test(l)) return null; // 非条目行(如缩进的 ↳ 断点)由下面单独判
209
- // Done 区的归档行是 `- [[log-日期]] 完成任务 N 条`,不是任务行
209
+ // Done 区的归档行:`- [[log-日期]] 完成任务 N 条`。
210
+ // ★ 历史命名不统一:还有 `[[2026-09-12-todo归档]]` 这种旧写法(实测),
211
+ // 只认 `log-` 前缀会把它误报为「缺状态标记」。状归档行与任务行的
212
+ // 区别不是命名而是**形态**:没有 `[ ]`/`[x]` 复选框的 [[链接]] 行。
213
+ if (/^-\s*\[\[[^\]]+\]\]\s+完成任务/.test(l)) return null;
210
214
  if (/^-\s*\[\[log-\d{4}-\d{2}-\d{2}\]\]/.test(l)) return null;
211
215
  const m = l.match(/^-\s*\[([ x])\]\s*(.*)$/);
212
216
  if (!m) return '应以 `- [ ] ` 或 `- [x] ` 开头';
@@ -504,12 +508,15 @@ const REPORT_MARKERS = [
504
508
  ];
505
509
 
506
510
  /** 数一个断点里有几个「句子」—— 断点是一件事,多句 = 在塞报告。
507
- * 中文按 。!?;/ 英文按 .!?; 切;带圈编号 ①②③ 也算分隔符。
511
+ * 分隔符只用**句末标点**(。!?!?)与带圈编号 ①②③。
512
+ * ★ 分号(;/;)**不算**分隔符:中文技术写作里它常用来连接同一个意思的两部分
513
+ * (「改到哪个文件;同类约束见 xx」),算成两句会误伤正常断点(实测:
514
+ * 一条正常的「改到哪 + 同类约束 + 待确认」被判 3 句而拒绝写入)。
508
515
  * 为什么不用关键词表:关键词命中不稳(「功能/方案」正常描述也会出现),
509
516
  * 而「一句还是一段」是结构判据 —— 结构判据才可靠。 */
510
517
  function sentenceCount(s) {
511
518
  return String(s)
512
- .split(/[。!?;!?;]+|\s*[\u2460-\u2473]\s*/)
519
+ .split(/[。!?!?]+|\s*[\u2460-\u2473]\s*/)
513
520
  .map((x) => x.trim())
514
521
  .filter(Boolean).length;
515
522
  }