@fanchao8609/agent_brain_sync 1.13.0 → 1.13.2
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 +1 -1
- package/package.json +1 -1
- package/src/lint.js +57 -9
- package/src/store.js +1 -1
- package/src/todo.js +10 -3
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
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(/[
|
|
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/)`,
|
|
@@ -170,11 +170,20 @@ function checkPage(pg, names, linkedNames, inbound) {
|
|
|
170
170
|
`引用请用 [[${id}]] 或改回文件名)`);
|
|
171
171
|
}
|
|
172
172
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
173
|
+
// 链接检查
|
|
174
|
+
// ★ sessions/ 豁免 DEAD-LINK(2026-10-05 实测):会话快照正文是「当时的笔记」,
|
|
175
|
+
// 里面的 `[[x]]` 常是在**谈这个记号本身**(如「缺 [[作者]] 这个标记」、
|
|
176
|
+
// 「剥掉 <id>/[[作者]] 记号」),不承担图谱链接职责;而 sessions 是不可改的历史,
|
|
177
|
+
// 改它就是改历史。→ 对快照报断链是规则错,不是数据脏。
|
|
178
|
+
// 注:sessions 的链接职责只体现在 `## 关联连接` 段,那段的断链仍值得管,
|
|
179
|
+
// 但对历史快照而言也已无法修正,故一并豁免。
|
|
180
|
+
if (pg.dir !== 'sessions') {
|
|
181
|
+
for (const ln of pg.links) {
|
|
182
|
+
if (/^<.+>$/.test(ln) || /slug|Name|name|Date|页面名$/.test(ln)) {
|
|
183
|
+
issues.push(`TEMPLATE-LINK: ${pg.rel} -> [[${ln}]]`);
|
|
184
|
+
} else if (!names.has(ln)) {
|
|
185
|
+
issues.push(`DEAD-LINK: ${pg.rel} -> [[${ln}]]`);
|
|
186
|
+
}
|
|
178
187
|
}
|
|
179
188
|
}
|
|
180
189
|
const isTerminal = pg.dir === 'sources'
|
|
@@ -270,6 +279,11 @@ async function checkGraph(root, pages) {
|
|
|
270
279
|
for (const pg of pages) {
|
|
271
280
|
if (pg.status !== 'draft') continue;
|
|
272
281
|
if (pg.dir === 'sources') continue;
|
|
282
|
+
// ★ sessions/ 也不参与(2026-10-05 实测误报):会话快照是**历史事实记录**
|
|
283
|
+
// (「那天发生什么」),不存在「是否仍然有效」—— 它永远不该被标 active,
|
|
284
|
+
// 标了反而是撒谎。对快照报「已 N 天停在 draft」是规则错,不是数据脏。
|
|
285
|
+
// 同理若未来加别的「存档型」目录(归档页等),也该在此豁免。
|
|
286
|
+
if (pg.dir === 'sessions') continue;
|
|
273
287
|
let ageMs = 0;
|
|
274
288
|
try { ageMs = Date.now() - (await fs.stat(join(root, pg.rel))).mtimeMs; } catch { continue; }
|
|
275
289
|
if (ageMs > DRAFT_STALE_DAYS * 86400 * 1000) {
|
|
@@ -345,7 +359,7 @@ async function readFileOrNull(p) {
|
|
|
345
359
|
|
|
346
360
|
// ---------- 入口 ----------
|
|
347
361
|
|
|
348
|
-
export async function cmdLint({ dir }) {
|
|
362
|
+
export async function cmdLint({ dir, all = false }) {
|
|
349
363
|
let root;
|
|
350
364
|
try {
|
|
351
365
|
root = await requireBrain(dir || process.cwd());
|
|
@@ -374,10 +388,44 @@ export async function cmdLint({ dir }) {
|
|
|
374
388
|
issues.push(...await checkRootFiles(vault));
|
|
375
389
|
|
|
376
390
|
const n = issues.length;
|
|
391
|
+
// ★ 同类折叠(2026-10-05):体检输出必须按问题**类型**折叠计数。
|
|
392
|
+
// 为什么:给根文件加形状校验后,abs 自带的 log.md 有 102 条历史行缺 `[[作者]]`,
|
|
393
|
+
// 102 条 LOG-ENTRY 淹没了其它 12 条真问题(DRAFT-STALE/DEAD-LINK/NO-INBOUND)——
|
|
394
|
+
// lint 的价值是「让人看见该修的」,被噪声淹没就退化成不可读。
|
|
395
|
+
// 同族约束:既有 Rule `读取侧输出不得随规模增长`、Done 区已按日期折叠计数。
|
|
396
|
+
// 形态:同类 > THRESHOLD 时只报「N 条同类 + 前 SAMPLE 条样例」。
|
|
397
|
+
// 注:全量明细仍可用 `abs lint --all` 拿到(不静默丢信息)。
|
|
398
|
+
const folded = all ? issues : foldSameKind(issues);
|
|
399
|
+
const n2 = folded.length;
|
|
377
400
|
return [
|
|
378
|
-
...
|
|
401
|
+
...folded,
|
|
379
402
|
'',
|
|
380
|
-
`lint: ${
|
|
403
|
+
`lint: ${n2} problem(s).${n2 !== n ? ` (共 ${n} 条,同类已折叠;全量: abs lint --all)` : ''}`,
|
|
381
404
|
n === 0 ? '✓ 图谱健康' : '',
|
|
382
405
|
].filter(Boolean).join('\n');
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/** 同类问题折叠:按错误码分组,超过阈值只报计数 + 前几条样例。
|
|
409
|
+
* 错误码 = 首行的 `CODE:` 前缀(checkPage/checkGraph/checkRootFiles 都是这个形态)。
|
|
410
|
+
* 保序:按「首次出现顺序」输出,不因折叠打乱阅读节奏。 */
|
|
411
|
+
const FOLD_THRESHOLD = 10;
|
|
412
|
+
const FOLD_SAMPLE = 3;
|
|
413
|
+
export function foldSameKind(issues) {
|
|
414
|
+
const groups = new Map(); // code -> { items, order }
|
|
415
|
+
for (const it of issues) {
|
|
416
|
+
const m = String(it).match(/^([A-Z][A-Z0-9-]+):/);
|
|
417
|
+
const code = m ? m[1] : '(其它)';
|
|
418
|
+
if (!groups.has(code)) groups.set(code, { items: [] });
|
|
419
|
+
groups.get(code).items.push(it);
|
|
420
|
+
}
|
|
421
|
+
const out = [];
|
|
422
|
+
for (const [code, g] of groups) {
|
|
423
|
+
if (g.items.length <= FOLD_THRESHOLD) {
|
|
424
|
+
out.push(...g.items);
|
|
425
|
+
} else {
|
|
426
|
+
out.push(...g.items.slice(0, FOLD_SAMPLE));
|
|
427
|
+
out.push(` … 还有 ${g.items.length - FOLD_SAMPLE} 条同类的 ${code}(共 ${g.items.length} 条)`);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
return out;
|
|
383
431
|
}
|
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
|
|
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(/[
|
|
519
|
+
.split(/[。!?!?]+|\s*[\u2460-\u2473]\s*/)
|
|
513
520
|
.map((x) => x.trim())
|
|
514
521
|
.filter(Boolean).length;
|
|
515
522
|
}
|