@fanchao8609/agent_brain_sync 1.29.0 → 1.31.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/README.md +4 -9
- package/bin/abs.js +9 -64
- package/bin/mcp.js +2 -34
- package/hooks/abs-rounded-editor/format.ts +26 -37
- package/hooks/abs-rounded-editor/index.ts +34 -55
- package/hooks/abs.omp.ts +8 -50
- package/hooks/abs.pi.ts +67 -106
- package/package.json +1 -1
- package/skill/abs-agent-brain-sync/SKILL.md +11 -9
- package/src/brain/capture.js +0 -6
- package/src/brain/load.js +11 -244
- package/src/note.js +0 -14
- package/src/store.js +1 -1
- package/src/todo/common.js +19 -4
- package/src/todo/spec.js +3 -1
- package/src/todo/task-cmd.js +11 -5
- package/src/todo/tasks.js +32 -2
- package/src/todo.js +1 -1
- package/src/brain/db-cmd.js +0 -387
- package/src/brain/db.js +0 -667
- package/src/brain/evolve.js +0 -135
package/hooks/abs.pi.ts
CHANGED
|
@@ -780,13 +780,13 @@ export function pickNickname(rnd: () => number = Math.random): string {
|
|
|
780
780
|
* 子项行(缩进的 `- [ ]`)归属到它上一条任务,不单独成条。 */
|
|
781
781
|
export function parseOpenTasks(md: string, max = PANEL_MAX_ROWS, who = ''): {
|
|
782
782
|
total: number
|
|
783
|
-
rows: { state: string; id: string; desc: string; kids: { done: boolean; text: string }[]; sid: string }[]
|
|
783
|
+
rows: { state: string; id: string; desc: string; kids: { done: boolean; text: string; state: string | null }[]; sid: string }[]
|
|
784
784
|
hidden: number
|
|
785
785
|
} {
|
|
786
786
|
// 两阶段:先无过滤地按序解析(子项归属需要“上一条”是原文里真正的前一条),
|
|
787
787
|
// 再按作者过滤。若边解析边过滤,被滤掉的条目下方的子项会挂到它上面那一条
|
|
788
788
|
// —— 实测:别人任务的子项会显示在我的任务下面(2026-10-03 审查发现)。
|
|
789
|
-
type Row = { state: string; id: string; desc: string; kids: { done: boolean; text: string }[]; authors: string[]; sid: string }
|
|
789
|
+
type Row = { state: string; id: string; desc: string; kids: { done: boolean; text: string; state: string | null }[]; authors: string[]; sid: string }
|
|
790
790
|
const parsed: Row[] = []
|
|
791
791
|
let inTodo = false
|
|
792
792
|
for (const raw of String(md || '').split('\n')) {
|
|
@@ -861,12 +861,20 @@ async function currentUser(): Promise<string> {
|
|
|
861
861
|
* 为何不 import src:面板是宿主扩展,单独装到 ~/.pi/extensions/,与包内 src/ 不同目录
|
|
862
862
|
* (装完就断了)。故两端各一份 —— 规则简单(两步正则),且两边测试都盖。
|
|
863
863
|
* 标准 md:缩进的 `- [ ] 内容`。 */
|
|
864
|
-
|
|
864
|
+
/** 行首状态标记的四个词(任务行与子项共用;与 CLI 侧 common.js 的 TASK_STATES 同值)。
|
|
865
|
+
* 两个扩展各自独立安装,所以这里再拄一份,不跨包 import。 */
|
|
866
|
+
const STATE_WORDS = ['进行中', '讨论中', '滞留中', '搁置']
|
|
867
|
+
|
|
868
|
+
function parseChildLine(l: string): { done: boolean; text: string; indent: number; state: string | null } | null {
|
|
865
869
|
// 只认标准 md 缩进子项(2026-10-20 旧箭头写法已废弃)。
|
|
866
870
|
// 旧行由 CLI 侧 migrateLegacyChild 在读写时迁移掉,面板拿到的已是新格式。
|
|
867
871
|
const m = String(l || '').match(/^(\s+)-\s*\[([ x])\]\s*(.*)$/)
|
|
868
872
|
if (!m) return null
|
|
869
|
-
|
|
873
|
+
// 子项也能带状态标记(2026-11-09 用户要求,与任务行同形):只认四个词,
|
|
874
|
+
// 别的方括号(如 `[WIP]`)是正文的一部分,原样留在 text 里(同 CLI 侧 parseChild)。
|
|
875
|
+
const s = m[3].match(/^\[([^\]]+)\]\s*(.*)$/)
|
|
876
|
+
const state = s && STATE_WORDS.includes(s[1]) ? s[1] : null
|
|
877
|
+
return { done: m[2] === 'x', text: (state ? s![2] : m[3]).trim(), indent: m[1].length, state }
|
|
870
878
|
}
|
|
871
879
|
|
|
872
880
|
function dispWidth(s: string): number {
|
|
@@ -935,110 +943,69 @@ function clipToWidth(s: string, width: number): string {
|
|
|
935
943
|
//
|
|
936
944
|
// 为何用盲文点阵而不是 ⠋⠙⠹ 之外的 ASCII 旋转符:
|
|
937
945
|
// ⠿ 系符号在等宽字体里都是 1 列宽,且视觉上是个转动的方框,比 | / - \ 均匀。
|
|
938
|
-
// 为何帧宽必须恒定:面板每 250ms 重绘,宽度一变整行就会左右抖。盲文恰好全是 1 列。
|
|
939
|
-
const TODO_ANIM_FRAMES = ['◐', '◓', '◑', '◒']
|
|
940
|
-
const TODO_ANIM_MS = 200
|
|
941
|
-
|
|
942
|
-
/** 动画相位(每 tick +1)。模块级 —— 同 agentEndSeen,闭包 flag 在重复注册时不共享。 */
|
|
943
|
-
let animPhase = 0
|
|
944
|
-
let animTimer: ReturnType<typeof setInterval> | undefined
|
|
945
|
-
|
|
946
946
|
/** 当前推理强度(off/minimal/low/…/max)。模块级,理由同上:widget factory 每帧重新调,
|
|
947
947
|
* 拿不到 ctx,只能由事件回调先存好。2026-11-06 用户需求:面板主色跟推理强度走。 */
|
|
948
948
|
let effortLevel = ''
|
|
949
949
|
|
|
950
|
-
/**
|
|
951
|
-
* getTui 是为了每 tick 拿**当前**的 tui 引用 —— widget 可能因刷新被重建,
|
|
952
|
-
* 持旧的引用会调到一个已被丢弃的 renderer。 */
|
|
953
|
-
function startAnimTimer(getTui: () => any): void {
|
|
954
|
-
if (animTimer) return
|
|
955
|
-
animTimer = setInterval(() => {
|
|
956
|
-
animPhase++
|
|
957
|
-
try { getTui()?.requestRender?.() } catch {}
|
|
958
|
-
}, TODO_ANIM_MS)
|
|
959
|
-
// 别阻止进程退出(扩展是宿主进程的一分子,定时器不该吊住事件循环)
|
|
960
|
-
;(animTimer as any)?.unref?.()
|
|
961
|
-
}
|
|
962
|
-
|
|
963
|
-
function stopAnimTimer(): void {
|
|
964
|
-
if (animTimer) { clearInterval(animTimer); animTimer = undefined }
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
/** 取当前动画帧(phase 每 tick +1,由定时器驱动)。非进行中的行不传 phase(不显示)。 */
|
|
968
|
-
export function todoAnimFrame(phase: number): string {
|
|
969
|
-
const n = TODO_ANIM_FRAMES.length
|
|
970
|
-
const i = ((Math.floor(phase) % n) + n) % n
|
|
971
|
-
return TODO_ANIM_FRAMES[i]
|
|
972
|
-
}
|
|
973
|
-
|
|
974
|
-
/**
|
|
975
|
-
* 推理强度 → 主题色 token。空串表示「不上推理色,用调用方给的兼底色」。
|
|
950
|
+
/** 档位 → pi 主题里那档 thinking 色(与输入框边框同一个来源)。
|
|
976
951
|
*
|
|
977
|
-
*
|
|
978
|
-
*
|
|
979
|
-
*
|
|
980
|
-
*
|
|
981
|
-
*
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
minimal: { rgb: '70;82;106', ansi256: 240 },
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
export function effortAnsi(level: string | null | undefined, colorMode = 'truecolor'): string {
|
|
996
|
-
const g = EFFORT_COLOR[String(level || '').trim().toLowerCase()]
|
|
997
|
-
if (!g) return ''
|
|
998
|
-
return colorMode === 'truecolor' ? `38;2;${g.rgb}` : `38;5;${g.ansi256}`
|
|
952
|
+
* 为何要向主题要(2026-11-09 用户要求「todo 上的颜色也跟着 pi 输入框的颜色」):
|
|
953
|
+
* 那七档色写在主题里(dark.json 的 okhsl),自己拄一份就会跟着主题改而跑偏。
|
|
954
|
+
* 为何要抠一层正则:getFgAnsi() 返回的是整段 `\x1b[38;2;…m`,而面板内部用的是
|
|
955
|
+
* 「色码串 + paintAnsi 自己包转义」那套,只能取里面的 `38;2;…`。
|
|
956
|
+
* 空档位或主题不认这个 token → 返回空串,调用处回落到 C.desc。 */
|
|
957
|
+
export function thinkingAnsiOf(theme: any, level: string | null | undefined): string {
|
|
958
|
+
const lvl = String(level || '').trim().toLowerCase()
|
|
959
|
+
if (!lvl) return ''
|
|
960
|
+
const token = `thinking${lvl[0].toUpperCase()}${lvl.slice(1)}`
|
|
961
|
+
try {
|
|
962
|
+
const m = String(theme?.getFgAnsi?.(token) || '').match(/\x1b\[([0-9;]+)m/)
|
|
963
|
+
return m ? m[1] : ''
|
|
964
|
+
} catch {
|
|
965
|
+
return ''
|
|
966
|
+
}
|
|
999
967
|
}
|
|
1000
968
|
|
|
1001
|
-
/**
|
|
969
|
+
/** 用色码串包一段文字(如 `paint('38;2;16;185;129', 'x')`);空串原样返回。 */
|
|
1002
970
|
export function paintAnsi(ansi: string, text: string): string {
|
|
1003
971
|
return ansi ? `\x1b[${ansi}m${text}\x1b[39m` : text
|
|
1004
972
|
}
|
|
1005
973
|
|
|
1006
974
|
export function renderPanelLines(
|
|
1007
|
-
data: { total: number; rows: { state: string; id: string; desc: string; kids?: { done: boolean; text: string }[] }[]; hidden: number },
|
|
975
|
+
data: { total: number; rows: { state: string; id: string; desc: string; kids?: { done: boolean; text: string; state?: string | null }[] }[]; hidden: number },
|
|
1008
976
|
who: string,
|
|
1009
977
|
width: number,
|
|
1010
978
|
fg: (color: string, s: string) => string,
|
|
1011
|
-
animPhase = -1,
|
|
1012
979
|
nickname = '',
|
|
1013
|
-
|
|
980
|
+
effortColor = '',
|
|
1014
981
|
): string[] {
|
|
1015
982
|
// ★ 空看板不再直接返回空数组(2026-10-06 用户要求:「即便 todo 没有列表,也要在 pi 上显示」)。
|
|
1016
983
|
// 理由与看板段一致:面板是「机制在不在」的常驻指示,进去就看不见 = 不知道机制活着。
|
|
1017
984
|
// 下面仍要画框和标题,所以这里只是不再提前 return。
|
|
1018
985
|
//
|
|
1019
|
-
// ★
|
|
1020
|
-
//
|
|
1021
|
-
//
|
|
1022
|
-
//
|
|
1023
|
-
//
|
|
1024
|
-
//
|
|
1025
|
-
// 标题色(2026-11-08 用户实测「太亮了」):从纯金 #FFD700 换成与「搁置」同一个橙 #F59E0B
|
|
1026
|
-
// —— 纯金在暗底上抢眼,标题只是分区标签,不该比当天要做的事更跳。
|
|
1027
|
-
// 代价:标题与 `[搁置]` 标签同色;两者一个在最左(标题)一个在行中(状态),位置分得开。
|
|
986
|
+
// ★ 文字色跟推理强度走(2026-11-09 用户要求「面板上的文字颜色要与输入框推理强度一致」):
|
|
987
|
+
// 标题、树线、任务 id、描述、子项、`+N more` 全用档位色(effortColor,由调用处从主题取)。
|
|
988
|
+
// ★ 四个状态标签(进行中/讨论中/滞留中/搁置)**保留各自语义色**,不跟档位走 ——
|
|
989
|
+
// 用户选的方案:否则四态只能靠文字看,一眼分不出。别再顺手把它们也统一了。
|
|
990
|
+
// 描述色 C.desc 降为「取不到档位色时的兼底」(主题没这个 token / 模型不支持推理)。
|
|
991
|
+
// 历史(勿重走):2026-11-08 曾定过「现代暗色方案:标题橙、树线蓝灰、描述次灰」,已作废。
|
|
1028
992
|
const C = {
|
|
993
|
+
// 面板大标题回到擱置橙 #F59E0B(2026-11-09 用户试过灰白,看完要回橙;沿用 2026-11-08 的定)。
|
|
994
|
+
// 任务行的 id/描述**不用**这个色 —— 它们跟档位色跑。
|
|
1029
995
|
title: '38;2;245;158;11',
|
|
1030
996
|
task: '38;2;226;232;240',
|
|
1031
997
|
desc: '38;2;148;163;184',
|
|
1032
|
-
tree: '38;2;150;170;198',
|
|
1033
998
|
doing: '38;2;16;185;129',
|
|
1034
999
|
talk: '38;2;59;130;246',
|
|
1035
1000
|
hold: '38;2;245;158;11',
|
|
1036
1001
|
stuck: '38;2;139;92;246',
|
|
1037
|
-
done: '38;2;100;116;139',
|
|
1038
1002
|
}
|
|
1039
1003
|
const stateAnsi = (st: string): string =>
|
|
1040
1004
|
st === '进行中' ? C.doing : st === '讨论中' ? C.talk : st === '搁置' ? C.hold : st === '滞留中' ? C.stuck : C.task
|
|
1041
1005
|
const paint = (ansi: string, t: string) => paintAnsi(ansi, t)
|
|
1006
|
+
// 面板文字色 = 调用处从主题取的档位色;取不到(主题没这个 token / 模型不支持推理)回落描述灰。
|
|
1007
|
+
// 提到循环外:标题与 `+N more` 也在用它。
|
|
1008
|
+
const eff = effortColor || C.desc
|
|
1042
1009
|
const lines: string[] = []
|
|
1043
1010
|
// 上描边行已删除(2026-11-04 用户要求「去掉上边框」)。
|
|
1044
1011
|
// 历史:那行曾用 `thinkingOff` 对齐 pi 输入框描边色(排查三轮才定色)。
|
|
@@ -1059,51 +1026,52 @@ export function renderPanelLines(
|
|
|
1059
1026
|
// 全篇没有 └─ 收尾,子项行的 │ 又延伸到视觉底部,看起来像列表被截断。)
|
|
1060
1027
|
const isLast = i === lastIdx
|
|
1061
1028
|
const branch = isLast ? '└─' : '├─'
|
|
1062
|
-
//
|
|
1063
|
-
//
|
|
1064
|
-
|
|
1065
|
-
const hasState = ['进行中', '讨论中', '搁置', '滞留中'].includes(t.state)
|
|
1066
|
-
const anim = t.state === '进行中' && animPhase >= 0 ? ' ' + paint(C.doing, todoAnimFrame(animPhase)) : ''
|
|
1029
|
+
// 进行中的行曾带圆圈动画(2026-11-08 用户定:「这个动画没啥用」,整个去掉)。
|
|
1030
|
+
// 去掉后 [进行中] 靠状态色区分,不再额外消耗一次 200ms 重绘。
|
|
1031
|
+
const hasState = STATE_WORDS.includes(t.state)
|
|
1067
1032
|
// 复选框 `[ ]`(2026-10-16 用户要求):面板是 todo.md 的只读视图,
|
|
1068
1033
|
// 就该长成 todo 原貌 —— 摆成与文件里 `- [ ]` 同形,读到就认得出对应哪一行。
|
|
1069
|
-
const eff = effortAnsi(effort, 'truecolor') || C.desc
|
|
1070
1034
|
const box = hasState ? '' : paint(eff, '[ ] ')
|
|
1071
1035
|
const stPart = hasState
|
|
1072
1036
|
? paint(stateAnsi(t.state), `[${t.state}]`)
|
|
1073
1037
|
: paint(eff, '[待办]')
|
|
1074
|
-
const head = `${paint(
|
|
1075
|
-
const desc = t.desc ? ' ' + paint(
|
|
1038
|
+
const head = `${paint(eff, branch)} ${box}${stPart} ${paint(eff, t.id)}`
|
|
1039
|
+
const desc = t.desc ? ' ' + paint(eff, `— ${t.desc}`) : ''
|
|
1076
1040
|
lines.push(clipToWidth(head + desc, width))
|
|
1077
1041
|
if (t.kids && t.kids.length) {
|
|
1078
1042
|
// 子项:标准 md 嵌套列表 → 画成树
|
|
1079
1043
|
// ├─ [ ] 子项一
|
|
1080
1044
|
// └─ [x] 子项二
|
|
1081
1045
|
// 树线前缀 = 父任务的 │(非末条)或空格(末条),与父任务分支对齐。
|
|
1082
|
-
//
|
|
1083
|
-
const stem = isLast ? ' ' : paint(
|
|
1046
|
+
// 勾过的用删除线区分(颜色已统一为档位色),未勾不着修饰。
|
|
1047
|
+
const stem = isLast ? ' ' : paint(eff, '│ ')
|
|
1084
1048
|
const n = t.kids.length
|
|
1085
1049
|
t.kids.forEach((k, ki) => {
|
|
1086
1050
|
const kb = ki === n - 1 ? '└─' : '├─'
|
|
1087
|
-
|
|
1088
|
-
const
|
|
1089
|
-
|
|
1051
|
+
// 有状态的子项跟任务同形:不画 `[ ]`,改画状态标签(2026-11-09 用户要求子项也能标四态)。
|
|
1052
|
+
const kbox = k.state
|
|
1053
|
+
? paint(stateAnsi(k.state), `[${k.state}]`)
|
|
1054
|
+
: paint(eff, k.done ? '[x]' : '[ ]')
|
|
1055
|
+
// 勾过的子项靠删除线区分(颜色已统一为档位色,不再降灰)
|
|
1056
|
+
const ktext = k.done ? `\x1b[9m${paint(eff, k.text)}\x1b[29m` : paint(eff, k.text)
|
|
1057
|
+
lines.push(clipToWidth(`${stem} ${paint(eff, kb)} ${kbox} ${ktext}`, width))
|
|
1090
1058
|
})
|
|
1091
1059
|
}
|
|
1092
1060
|
})
|
|
1093
1061
|
|
|
1094
|
-
if (data.hidden > 0) lines.push(clipToWidth(paint(
|
|
1062
|
+
if (data.hidden > 0) lines.push(clipToWidth(paint(eff, ` +${data.hidden} more`), width))
|
|
1095
1063
|
return lines
|
|
1096
1064
|
}
|
|
1097
1065
|
/** 隐藏面板。**必须主动 requestRender** —— 否则数据清了、屏幕还留着旧画面。
|
|
1098
1066
|
*
|
|
1099
1067
|
* 坑(2026-10-05 用户实报): 任务全标完后面板不消失, 按 ESC 或重启才正常。
|
|
1100
|
-
* 根因:
|
|
1101
|
-
* needAnim=false →
|
|
1102
|
-
*
|
|
1103
|
-
*
|
|
1068
|
+
* 根因: 当时唯一的重绘入口是动画定时器里的 requestRender,而"任务全完成"恰好让
|
|
1069
|
+
* needAnim=false → 停定时器 → 从此没有任何人请求重绘 → 屏幕冻结在旧帧。
|
|
1070
|
+
* 动画已拆(2026-11-08),定时器不再存在,但这个坑改头换面仍然成立:
|
|
1071
|
+
* 重绘只能由事件主动请求,少一处就静默冻结。
|
|
1072
|
+
*/
|
|
1104
1073
|
function hidePanel(ui: any): void {
|
|
1105
1074
|
try { ui.setWidget(PANEL_KEY, undefined) } catch {}
|
|
1106
|
-
stopAnimTimer()
|
|
1107
1075
|
try { lastTui?.requestRender?.() } catch {}
|
|
1108
1076
|
}
|
|
1109
1077
|
|
|
@@ -1137,24 +1105,18 @@ async function refreshTodoPanel(ui: any, cwd: string): Promise<void> {
|
|
|
1137
1105
|
who,
|
|
1138
1106
|
width,
|
|
1139
1107
|
(c: string, s: string) => theme.fg(c, s),
|
|
1140
|
-
animPhase,
|
|
1141
1108
|
sessionNickname,
|
|
1142
|
-
|
|
1109
|
+
// 面板主色跟 pi 输入框一致:从主题现取当前档位的 thinking 色(2026-11-09 用户要求)。
|
|
1110
|
+
thinkingAnsiOf(theme, effortLevel),
|
|
1143
1111
|
),
|
|
1144
1112
|
invalidate: () => {},
|
|
1145
1113
|
}
|
|
1146
1114
|
},
|
|
1147
1115
|
{ placement: 'aboveEditor' },
|
|
1148
1116
|
)
|
|
1149
|
-
//
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
else stopAnimTimer()
|
|
1153
|
-
// ★ 无动画时得自己刷一帧。
|
|
1154
|
-
// 若这里不主动刷,从“有任务”变成“无任务”时屏幕会冻结在旧帧
|
|
1155
|
-
// (标题还写着旧计数)—— 与 2026-10-05 那个静默 bug 同一族。
|
|
1156
|
-
// 仍在 needAnim 时不用管:定时器每帧会刷。
|
|
1157
|
-
if (!needAnim) try { lastTui?.requestRender?.() } catch {}
|
|
1117
|
+
// 动画去掉后(2026-11-08),每次刷新都得自己请求重绘 ——
|
|
1118
|
+
// 没有定时器兜底了,漏这一句屏幕会冻结在旧帧(标题还写着旧计数)。
|
|
1119
|
+
try { lastTui?.requestRender?.() } catch {}
|
|
1158
1120
|
} catch {
|
|
1159
1121
|
hidePanel(ui)
|
|
1160
1122
|
}
|
|
@@ -1337,7 +1299,6 @@ export default function absPiHook(pi: ExtensionAPI): void {
|
|
|
1337
1299
|
})
|
|
1338
1300
|
|
|
1339
1301
|
pi.on("session_shutdown", (_e: any, ctx: any) => {
|
|
1340
|
-
stopAnimTimer() // 面板没了,动画定时器必须一起停(否则残留定时器白刷已丢弃的 tui)
|
|
1341
1302
|
snapshotWrapup((ctx && ctx.cwd) || process.cwd())
|
|
1342
1303
|
snapshotSessionPage((ctx && ctx.cwd) || process.cwd(), sessionIdFor(ctx))
|
|
1343
1304
|
logHook("session_shutdown").catch(() => {})
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@ description: "USE FOR abs / agent-brain-sync cross-session memory and task hando
|
|
|
9
9
|
|
|
10
10
|
**Querying it is your decision, not something you wait to be reminded of.** The moment a
|
|
11
11
|
keyword forms or a question appears ("was this bug solved before?", "who decided this?"),
|
|
12
|
-
|
|
12
|
+
run `abs query <词>` — do not wait for an error or for the user to prompt you.
|
|
13
13
|
|
|
14
14
|
## Startup: one command
|
|
15
15
|
|
|
@@ -31,16 +31,17 @@ unfinished ones get `abs todo note <id> --note "where it stopped"`.
|
|
|
31
31
|
- index rows without a real description (date-template only) → rejected by lint.
|
|
32
32
|
- Sub-items are one sentence each. Reports go to `abs log` / `sources/`, not the board.
|
|
33
33
|
|
|
34
|
-
## Find knowledge:
|
|
34
|
+
## Find knowledge: one entry
|
|
35
35
|
|
|
36
36
|
| Situation | Command |
|
|
37
37
|
|---|---|
|
|
38
|
-
|
|
|
39
|
-
|
|
|
40
|
-
| Want exact literal, short output | `abs query <词…>` — multi-word OR |
|
|
38
|
+
| Have keywords | `abs query <词…>` — multi-word OR over `.brain/`, page name + summary |
|
|
39
|
+
| Got a page name | `abs resolve <页名>` → read only that page |
|
|
41
40
|
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
One entry, no tiers: `query` is the only search (2026-11-08 — the SQLite graph库 with
|
|
42
|
+
`abs search` / `abs recall` / `abs db` was deleted; it was write-only in practice:
|
|
43
|
+
881 sessions logged `search` 3 times, `recall` 9 times, while the todo board ran 1897).
|
|
44
|
+
Cite sources as `[[页面名]]`.
|
|
44
45
|
|
|
45
46
|
## Write knowledge: three sinks, do not mix
|
|
46
47
|
|
|
@@ -60,7 +61,8 @@ Then run `abs lint` (dead links, over-size, NO-TAIL).
|
|
|
60
61
|
abs todo start <id> --note "做什么" # register (note required)
|
|
61
62
|
abs todo note <id> --note "到哪一步" # append child (one sentence)
|
|
62
63
|
abs todo done <id> --as 落地|否决|仅方案 "结语"
|
|
63
|
-
abs todo state <id> --note 进行中|讨论中|滞留中|搁置
|
|
64
|
+
abs todo state <id> --note 进行中|讨论中|滞留中|搁置 # 任务本身
|
|
65
|
+
abs todo state <id> --note 滞留中 --n 2 # 第 2 条子项(子项也带四态)
|
|
64
66
|
abs todo remove <id> # delete incl. children, no undo
|
|
65
67
|
```
|
|
66
68
|
|
|
@@ -88,7 +90,7 @@ Exception: the user types `abs` alone or `abs` + command → execute; no subcomm
|
|
|
88
90
|
## ⛔ Never
|
|
89
91
|
|
|
90
92
|
1. Hand-build skeletons or hand-register tasks — `abs init` / `abs todo start`.
|
|
91
|
-
2. Read `.brain/` end-to-end — `abs load` for state,
|
|
93
|
+
2. Read `.brain/` end-to-end — `abs load` for state, `abs query` for topics, `abs resolve` for one page.
|
|
92
94
|
3. Delete a knowledge page to mark it obsolete — `abs supersede <页> --by <新页>`.
|
|
93
95
|
4. Write Rules directly — propose `[Rules 提议]`, wait for the user, then `abs rule add`.
|
|
94
96
|
5. Host-native todo for cross-session work — use `abs todo`.
|
package/src/brain/capture.js
CHANGED
|
@@ -18,7 +18,6 @@ import { registerInIndex } from '../note.js';
|
|
|
18
18
|
import { cmdLog } from '../note.js';
|
|
19
19
|
import { clip, slugOf } from '../text.js';
|
|
20
20
|
import { LAYERS, layerOf, DEFAULT_LAYER } from './layers.js';
|
|
21
|
-
import { autoIndex } from './db-cmd.js';
|
|
22
21
|
import { summonTail } from './summon.js';
|
|
23
22
|
|
|
24
23
|
/** 层 → 落点目录。L1/L6 不立页,由下面的分支单独处理。 */
|
|
@@ -197,10 +196,6 @@ export async function cmdCapture({ dir, text, layer, tags, title, kind }) {
|
|
|
197
196
|
await registerInIndex(root, section, slug, head);
|
|
198
197
|
await cmdLog({ dir: root, title: clean, kind: 'note', summon: false });
|
|
199
198
|
|
|
200
|
-
// 落盘即入库 —— 否则用户以为记下了,检索却空手而归(静默失效)。
|
|
201
|
-
// autoIndex 永不抛错也不阻塞:库坏了只是检索旧一点,不能让记笔记失败。
|
|
202
|
-
const idx = await autoIndex();
|
|
203
|
-
|
|
204
199
|
// 每次总结都顺手查一遍会话历史,避免漏下没总结的会话(2026-11-02 用户要求)。
|
|
205
200
|
// 只给线索不写盘 —— 落哪一条仍是人的判断。
|
|
206
201
|
const tail = await summonTail(root);
|
|
@@ -209,7 +204,6 @@ export async function cmdCapture({ dir, text, layer, tags, title, kind }) {
|
|
|
209
204
|
`✓ 已落入 L${L} ${meta.name}层 → ${dirName}/${file}`,
|
|
210
205
|
` ${clip(clean, 60)} ${atTag(who)}`,
|
|
211
206
|
` 该层衰减 ${meta.decay}(${meta.note})`,
|
|
212
|
-
idx.ok ? ' 已同步入库,abs db search 可查到' : ` (未入库:${idx.reason})`,
|
|
213
207
|
].join('\n') + tail;
|
|
214
208
|
}
|
|
215
209
|
|