@meitouai/mem-note-cli 0.0.3 → 0.1.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.
@@ -1,346 +1,101 @@
1
1
  # mem-note 数据约定
2
2
 
3
- > 这份文档是 mem-note 数据目录的**真源约定**。所有写入该目录的代理(人、CLI、AI agent、外部脚本)都必须遵守。
3
+ > 本文件由插件管理,`mem-note init` 和插件升级时会覆盖写入,**请勿手动修改**。
4
+ > 这里定义的数据格式是硬约束,CLI 和 AI agent 都必须遵守。
4
5
  >
5
- > AI agent 在任何 mem-note skill 开始时应首先 `Read` 本文件,作为判断的依据。
6
+ > 如何 groom inbox 数据(triage 策略、合并判断、洞察提取等)见 [GROOM_RULES.md](GROOM_RULES.md),该文件归用户所有,可自由迭代。
6
7
 
7
- ## 1. 核心哲学
8
-
9
- 1. **文件即数据库**。所有数据是 markdown 文件,无隐藏索引 / 数据库 / 二进制状态。
10
- 2. **CLI 最小化**。CLI 只覆盖 5 个场景:(a) hook 时 AI 不在场、(b) 人需要快捷入口、(c) 多步易错操作的封装、(d) 跨会话的一致性检查、(e) 初始化。其他所有读写由 AI agent 用原生工具(`Read` / `Write` / `Edit` / `Glob` / `Grep` / `Bash`)完成。
11
- 3. **AI agent 负责智能**。语义判断、矛盾识别、重复检测、groom 决策全部由 skill 里的 AI agent 完成,CLI 不包含任何智能逻辑。
12
- 4. **约定优于配置**。本文档定义的格式是硬约束;漂移由 `mem-note doctor` 发现。
13
-
14
- ## 2. 目录布局
8
+ ## 目录布局
15
9
 
16
10
  ```
17
11
  ~/.aibox-mem-note/ # 数据根(可由 $MEM_NOTE_DATA_DIR 覆盖)
18
- ├── CONVENTIONS.md # 本文件(mem-note init 写入;已存在则跳过)
12
+ ├── CONVENTIONS.md # 本文件(插件管理,自动同步)
13
+ ├── GROOM_RULES.md # groom 策略(用户所有,可自由修改)
19
14
  ├── notes/ # 永久 note
20
15
  │ ├── NOTES.md # 根索引
21
16
  │ ├── <topic>/
22
17
  │ │ ├── NOTES.md # 子目录索引(必维护)
23
- │ │ └── <slug>.md # 具体 note
24
- │ ├── skills/ # 用户自定义 skill(保留子目录,不是 note
25
- └── <skill-name>/
26
- │ │ ├── SKILL.md # skill 本体(frontmatter: name, description)
27
- │ │ └── evals/ # 可选:该 skill 的 eval workspace
28
- │ └── _archived/ # 归档的 note
18
+ │ │ └── <slug>.md
19
+ │ ├── skills/ # 用户自定义 skill(非 note,groom 跳过)
20
+ │ └── _archived/ # 归档 note(groom 排除)
29
21
  │ └── <YYYY-MM-DD>_<slug>.md
30
- ├── inbox/ # 待 groom 的原始输入池
22
+ ├── inbox/ # 待 groom 的原始输入
31
23
  │ ├── <YYYY-MM-DD-HHMMSS>_<slug>.md
32
- │ └── .processed/ # 已处理的 entry(merged / discarded)
33
- ├── insights/ # groom 过程产出的洞察
24
+ │ └── .processed/ # 已处理的 entry
25
+ ├── insights/ # groom 产出的洞察
34
26
  │ └── <id>.md
35
- ├── .context/ # SessionStart / PreCompact 注入的上下文
36
- │ └── index.md # groom skill 维护
37
- ├── .reports/ # 操作审计日志(append-only)
38
- │ └── <YYYY-MM-DD>.md
39
- └── .snapshots/ # 可选:groom 前的快照
40
- └── <snapshot-id>/
27
+ ├── .context/
28
+ │ └── index.md # groom 编译的会话注入上下文(< 2KB)
29
+ └── .reports/ # 审计日志(append-only)
30
+ ├── <YYYY-MM-DD>.md
31
+ └── graph.html # 知识图谱可视化(groom 生成)
41
32
  ```
42
33
 
43
- **特殊目录约定**:
44
-
45
- - `notes/_archived/` **不**参与任何 Glob scanning;AI agent 应显式排除
46
- - `notes/**/NOTES.md` 不是 note,AI agent 应显式排除
47
- - `notes/skills/` 是**保留子目录**,里面存的是用户自定义 skill 而不是 note:
48
- - groom 不要 triage 它(不当作 inbox 输出目标,不更新 NOTES.md 条目)
49
- - 任何 `Glob notes/**/*.md` 应显式排除 `notes/skills/**` 以免混入 SKILL.md
50
- - 发现 user skill 时用 `Glob notes/skills/*/SKILL.md`,读 frontmatter 的 `name` + `description` 即可
51
- - `inbox/.processed/` 是软删除区,不参与 pending 统计
34
+ **排除规则**:`_archived/`、`skills/`、`NOTES.md` 不是 note,Glob 时排除。
52
35
 
53
- ## 3. Frontmatter Schema
36
+ ## Frontmatter Schema
54
37
 
55
- `CONVENTIONS.md`、`NOTES.md`、`.context/index.md`、`.reports/*.md`、
56
- `notes/skills/**/SKILL.md`(遵循 Claude Code skill 自有格式)外,
57
- 所有 `.md` 文件必须有 YAML frontmatter。
58
-
59
- ### 3.1 Note (`notes/**/*.md`,除 NOTES.md)
38
+ ### Note (`notes/<topic>/<slug>.md`)
60
39
 
61
40
  ```yaml
62
41
  ---
63
- type: preference # 必填:preference | fact | lesson | reminder | note
64
- title: Dark mode preference # 必填:人类可读,用于 NOTES.md 索引展示
65
- description: > # 必填:一句话摘要,用于 NOTES.md 索引描述
66
- Prefers dark mode across all editors, Monokai Pro theme.
67
- tags: [ui, editor] # 必填(可为空数组):小写 kebab-case
42
+ type: preference # 必填:preference | fact | lesson | reminder | note
43
+ title: Dark mode # 必填:人类可读标题
44
+ description: > # 必填:一句话摘要
45
+ Prefers dark mode across all editors.
46
+ tags: [ui, editor] # 必填(可空数组):小写 kebab-case
68
47
  updated: 2026-04-10 # 必填:ISO 日期
69
- related: # 可选:相关 note 路径
70
- - notes/user/editor-theme.md
71
- source: # 可选:原始来源 inbox entry 路径
72
- - inbox/2026-04-09-142030_dark-mode.md
48
+ related: [] # 可选:相关 note 路径
49
+ source: [] # 可选:来源 inbox entry 路径
73
50
  ---
74
51
  ```
75
52
 
76
- ### 3.2 Inbox entry (`inbox/**/*.md`)
53
+ ### Inbox entry (`inbox/<YYYY-MM-DD-HHMMSS>_<slug>.md`)
77
54
 
78
55
  ```yaml
79
56
  ---
80
- type: note # 可选:保存者的初步判断;groom 时可被 AI agent 修改
81
- tags: [] # 可选:groom 时补充
82
- created: 2026-04-10-142030 # 必填:ISO 时间戳
83
- source:
84
- terminal # 可选但推荐:去重 key
85
- # 取值:terminal | hook:stop | hook:sync | auto-memory:<path>
57
+ created: 2026-04-10-142030 # 必填
58
+ type: note # 可选:初步判断
59
+ tags: [] # 可选
60
+ source: terminal # 可选:去重 key(terminal | hook:stop | hook:sync | auto-memory:<path>)
86
61
  ---
87
62
  ```
88
63
 
89
- - `type` / `tags` **可为空**。inbox 的语义是"未分类的原始输入",分类是 groom 的职责。
90
- - `source`:`mem-note save --source X` 会拒绝 source 已存在的导入(去重)。
91
- - 顶层 `inbox/*.md` 保持原始输入语义;当条目被移动到 `inbox/.processed/` 后,
92
- 处理后的副本**可以**追加一个 `processed` 对象记录处理结果,例如:
93
-
94
- ```yaml
95
- processed:
96
- status: user-ignored
97
- at: 2026-04-11-090807
98
- reason: Ignored by user from the mem-note inbox UI.
99
- ```
100
-
101
- ### 3.3 Insight (`insights/*.md`)
64
+ ### Insight (`insights/<id>.md`)
102
65
 
103
66
  ```yaml
104
67
  ---
105
- id: insight-v1K2p7 # 必填:短 nanoid
106
- kind: clarification # 必填:clarification | pattern | skill-candidate | contradiction | <custom>
107
- status: open # 必填:open | resolved | dismissed
68
+ id: insight-v1K2p7 # 必填:短 nanoid
69
+ kind: clarification # 必填:clarification | pattern | skill-candidate | contradiction
70
+ status: open # 必填:open | resolved | dismissed
108
71
  title: Testing framework contradiction
109
72
  created: 2026-04-10
110
- related:
111
- - notes/user/testing-jest.md
112
- - notes/user/testing-vitest.md
113
- ---
114
- <自由 markdown 正文:evidence、reasoning、建议行动>
115
- ```
116
-
117
- ### 3.4 Archived note (`notes/_archived/**/*.md`)
118
-
119
- 与原 note frontmatter 相同,追加一个 `archived` 字段:
120
-
121
- ```yaml
122
- ---
123
- type: fact
124
- title: Old deployment tool
125
- ---
126
- archived: 2026-04-10 # 归档日期
73
+ related: []
127
74
  ---
128
75
  ```
129
76
 
130
- ## 4. 文件命名
77
+ ### Archived note (`notes/_archived/<YYYY-MM-DD>_<slug>.md`)
131
78
 
132
- | 类型 | 路径模板 | 示例 |
133
- | ------------- | ---------------------------------------------------------------------------------------------------------- | ----------------------------------------- |
134
- | Note | `notes/<topic>/<slug>.md` | `notes/user/dark-mode.md` |
135
- | Inbox entry | `inbox/<YYYY-MM-DD-HHMMSS>_<slug>.md` (collisions within the same second get a `-<n>` suffix before `.md`) | `inbox/2026-04-10-142030_dark-mode.md` |
136
- | Archived note | `notes/_archived/<YYYY-MM-DD>_<original-slug>.md` | `notes/_archived/2026-04-10_dark-mode.md` |
137
- | Insight | `insights/<id>.md` | `insights/insight-v1K2p7.md` |
79
+ note frontmatter,追加 `archived: 2026-04-10`。
138
80
 
139
- **slug 约定**:小写 kebab-case,只含 `[a-z0-9-]`,最长 60 字符。`mem-note save` 自动从内容前若干字生成。
81
+ ## 文件命名
140
82
 
141
- ## 5. NOTES.md 索引规则
83
+ Slug:小写 kebab-case,只含 `[a-z0-9-]`,最长 60 字符。
142
84
 
143
- 每个 `notes/` 子目录(包括 `notes/` 本身)必须维护一份 `NOTES.md` 作为该目录的人类可读索引。
85
+ ## NOTES.md 索引
144
86
 
145
- ### 5.1 格式
87
+ 每个 `notes/` 子目录维护 `NOTES.md`:
146
88
 
147
89
  ```markdown
148
90
  # notes/user
149
91
 
150
- User-scope preferences, facts, and lessons.
151
-
152
92
  ## Notes
153
93
 
154
94
  - [Dark mode preference](dark-mode.md) — Prefers dark mode, Monokai Pro theme.
155
- - [Deploy lesson](deploy-lesson.md) — Always run DB migrations before deploying.
156
95
 
157
96
  ## Subdirectories
158
97
 
159
98
  - [editor/](editor/NOTES.md) — Editor-specific preferences
160
99
  ```
161
100
 
162
- ### 5.2 维护契约
163
-
164
- **任何创建、重命名、归档 note 的代理都必须同步更新对应目录的 `NOTES.md`**:
165
-
166
- - 创建新 note → `## Notes` 追加 `- [<title>](<file>.md) — <description>`
167
- - 更新 note 的 title 或 description → 更新对应行
168
- - 归档 note → 从 `## Notes` 删除该行
169
- - 创建子目录 → `## Subdirectories` 追加条目
170
- - 删除空子目录 → 从 `## Subdirectories` 删除条目
171
-
172
- `<title>` 和 `<description>` 直接来自 note 的 frontmatter 同名字段。
173
-
174
- ### 5.3 为什么不自动维护
175
-
176
- - NOTES.md 就地可读(人 / AI agent / git diff 都一目了然)
177
- - 避免"CLI 自动维护 + AI agent 手动维护"的 race
178
- - `mem-note doctor` 负责定期检查漂移,不靠运行时
179
-
180
- ## 6. `.context/index.md` 约定
181
-
182
- `.context/index.md` 是 **groom skill 编译**出的"本会话 AI agent 应记住的核心信息"。
183
- SessionStart 和 PreCompact hook 都会注入它。
184
-
185
- ### 6.1 结构
186
-
187
- ```markdown
188
- # Context — 2026-04-10
189
-
190
- ## Preferences
191
-
192
- - Dark mode, Monokai Pro, JetBrains Mono
193
- - Functional style, named exports, files under 200 lines
194
-
195
- ## Active Reminders
196
-
197
- - API design review deadline: April 15
198
- - Tuesday 10am standup — prepare notes
199
-
200
- ## Key Facts
201
-
202
- - Uses PostgreSQL 15 with PgBouncer
203
- - Tests: Jest (unit) + Playwright (e2e), 80% coverage
204
- - Planning REST → GraphQL migration (Apollo Server) in Q3
205
- ```
206
-
207
- ### 6.2 约束
208
-
209
- - `## Active Reminders` 是**必须**段落(可以为空)。groom skill 必须在每次
210
- 编译时扫描所有 `type: reminder` 的 note 重新生成这一段 —— 因为 PreCompact
211
- hook 依赖它。
212
- - 整体文件大小建议 **< 2KB**。SessionStart 和 PreCompact 每次都注入,太大
213
- 会吃掉宝贵的 session context。
214
- - 由 groom skill 的最后一步 `Write` 生成,**不**由 CLI 维护。
215
-
216
- ## 7. Inbox / Archive 生命周期
217
-
218
- ```
219
- mem-note save
220
-
221
-
222
- inbox/<ts>_<slug>.md ────── groom skill reads ──┐
223
-
224
- ┌────────────────────┤
225
- │ │
226
- merge discard
227
- │ │
228
- ▼ ▼
229
- notes/<topic>/<slug>.md inbox/.processed/<original>.md
230
- (+ update NOTES.md) (软删除)
231
-
232
- mem-note archive notes/<topic>/<slug>.md
233
-
234
-
235
- notes/_archived/<date>_<slug>.md (+ remove from topic's NOTES.md)
236
- ```
237
-
238
- `.processed/` 保留所有历史 inbox entry,便于回溯;不再在 listing 中出现。
239
-
240
- ## 8. AI agent 的工具使用优先级
241
-
242
- AI agent 在 skill 会话中处理数据时,**优先使用原生文件工具**。
243
- CLI 只在下表标注的少数场景调用。
244
-
245
- | 操作 | 首选 | 不要用 |
246
- | ------------------------ | ------------------------------------------------------------------------ | ---------------------------------- |
247
- | 列出所有 note | `Glob notes/**/*.md`(记得排除 `_archived/**`、`skills/**`、`NOTES.md`) | ❌ `mem-note list-notes`(不存在) |
248
- | 发现用户 skill | `Glob notes/skills/*/SKILL.md` + 读 frontmatter | ❌ 任何 CLI |
249
- | 读一个 note | `Read notes/path/foo.md` | ❌ CLI |
250
- | 读某目录的索引视图 | `Read notes/<topic>/NOTES.md` | ❌ CLI |
251
- | 写 / 更新 note | `Write` / `Edit` + 记得同步 `NOTES.md` | ❌ CLI |
252
- | 搜索 note 内容 | `Grep "query" notes/**/*.md` | ❌ CLI |
253
- | 处理 inbox entry | `Bash mv inbox/x.md inbox/.processed/` | ❌ CLI |
254
- | 写 insight | `Write insights/<id>.md` | ❌ CLI |
255
- | 编译 `.context/index.md` | `Write .context/index.md` | ❌ CLI |
256
- | **归档 note** | ✅ `mem-note archive <path>`(封装多步操作) | `mv` + 手动更新 NOTES.md(易漏) |
257
- | **检查一致性** | ✅ `mem-note doctor`(每次 groom 末尾) | 手动检查(不现实) |
258
- | 追加 audit log | `Bash` 追加 `.reports/<date>.md` 或最后一次 `Write` | — |
259
-
260
- ## 9. CLI 命令清单(5 个)
261
-
262
- | # | 命令 | 职责 | 典型调用方 |
263
- | --- | ------------------------- | ------------------------------------------------------ | ---------------------------------- |
264
- | 1 | `mem-note init` | 幂等创建目录 + 写入 `CONVENTIONS.md`(已存在则跳过) | 人(首次安装) |
265
- | 2 | `mem-note save <content>` | 存一条 inbox entry。支持 `--type`、`--source`、stdin | 人 / hook / 外部脚本 |
266
- | 3 | `mem-note archive <path>` | 封装 "加日期前缀 + mv 到 `_archived/` + 更新 NOTES.md" | 人 / AI agent |
267
- | 4 | `mem-note doctor` | 检查 frontmatter schema、NOTES.md 漂移、归档命名一致性 | 人 / AI agent(每次 groom 结束时) |
268
- | 5 | `mem-note quick-context` | 输出 `.context/index.md` 内容(封装路径解析) | hook (SessionStart / PreCompact) |
269
-
270
- 其他一切都是 AI agent / 人用原生工具直接做。
271
-
272
- ## 10. Groom Skill 默认工作流
273
-
274
- 作为参考,groom skill 按以下顺序处理:
275
-
276
- 1. `Read CONVENTIONS.md` —— 加载约定(若当前会话已加载可跳过)
277
- 2. `Read notes/NOTES.md` + 感兴趣子目录的 `NOTES.md` —— 拿目录视图
278
- 3. `Glob inbox/*.md` —— 列 pending inbox entries
279
- 4. `Read` 每个 inbox entry
280
- 5. 对每条 entry 决策 **create** / **merge** / **discard**:
281
- - **create**:`Write notes/<topic>/<slug>.md` + `Edit notes/<topic>/NOTES.md`
282
- - **merge**:`Edit notes/<topic>/existing.md` 合入新信息 + 更新 NOTES.md 的 description
283
- - **discard**:无动作
284
- 6. 所有情况都 `Bash mv inbox/<entry> inbox/.processed/`
285
- 7. 如有冲突 / 过期 note,`mem-note archive <path>` 归档
286
- 8. 如发现矛盾 / 模式,`Write insights/<id>.md`
287
- 9. 重新编译 `.context/index.md`:`Write .context/index.md` 包含 Preferences / **Active Reminders** / Key Facts
288
- 10. `Bash mem-note doctor` 检查一致性;有警告则修正
289
- 11. `Bash` 追加一段 audit 到 `.reports/<YYYY-MM-DD>.md`
290
-
291
- ## 11. Git 集成(可选但推荐)
292
-
293
- `mem-note init` 会检测 git 是否可用,可用则在数据根执行 `git init`、写入
294
- `.gitignore`、并做一次初始 commit。之后:
295
-
296
- - `mem-note save` / `mem-note archive` 每次成功写入后自 commit,message 形如
297
- `save: <slug>` / `archive: <slug>`。
298
- - 插件的 Stop hook (`session-commit.js`) 在每次 Claude Code 会话结束时兜底
299
- commit 剩余未提交的改动,message 形如 `session: <ISO timestamp>`,
300
- 用于捕获 skill 通过 `Write`/`Edit`/`Bash mv` 直接做的写入。
301
-
302
- git 不可用时 mem-note 完全回退:所有功能照常工作,只是没有版本历史。
303
-
304
- **`.gitignore` 默认内容**:忽略 `.snapshots/` 和 OS 垃圾(`.DS_Store` 等)。
305
- 其余所有内容(包括 `notes/`、`inbox/`、`inbox/.processed/`、`insights/`、
306
- `.context/index.md`、`.reports/`、`notes/skills/`)都进入 git。
307
-
308
- **为什么 `.processed/` 也 track**:虽然和 git 历史有重合,但它保留了"已处理
309
- inbox entry"的语义维度,方便后续 groom 或人类直接 `Read` 回溯;grep 友好。
310
-
311
- ## 12. FAQ
312
-
313
- **Q: 为什么 inbox entry 的 frontmatter 可以为空?**
314
- A: inbox 是原始输入池。人 / hook / 外部脚本 save 时不应该被分类 schema 绑架。分类是 groom 的职责。
315
-
316
- **Q: 为什么 `archive` 有 CLI 但 `create-note` 没有?**
317
- A: archive 涉及"加日期前缀 + mv + 更新 NOTES.md"多步操作,手动容易漏;create 只是一次 `Write` + 一次 `Edit NOTES.md`,原生工具更透明。
318
-
319
- **Q: AI agent 怎么在会话里搜 note?**
320
- A: 用 `Grep "query" notes/**/*.md` 或 `Grep --glob '!**/NOTES.md' --glob '!**/_archived/**' "query" notes/`。
321
-
322
- **Q: 人想在 terminal 快速搜 note?**
323
- A: 用 ripgrep:`rg -i "query" ~/.aibox-mem-note/notes/`。懒的话 shell rc 加:
324
-
325
- ```sh
326
- alias mns='rg -i ~/.aibox-mem-note/notes/'
327
- ```
328
-
329
- **Q: 插件升级时 `CONVENTIONS.md` 会被覆盖吗?**
330
- A: 不会。`mem-note init` 检测到已存在的 `CONVENTIONS.md` 会跳过写入。如果 schema 升级需要迁移,由 `mem-note doctor --migrate` 处理(未来扩展)。
331
-
332
- **Q: `doctor` 检查哪些东西?**
333
- A:
334
-
335
- 1. 每个 note 的 frontmatter 是否有必填字段(`type`、`title`、`description`、`tags`、`updated`)
336
- 2. 每个 note 是否在对应目录的 `NOTES.md` 里有对应条目
337
- 3. 每个 `NOTES.md` 条目是否指向存在的文件
338
- 4. `notes/_archived/` 下的文件名是否符合 `<YYYY-MM-DD>_<slug>.md` 格式
339
- 5. Inbox entry 的 `created` 字段是否存在
340
- 6. `.context/index.md` 是否存在 `## Active Reminders` 段落
341
-
342
- **Q: 多个 AI agent 会话并发写怎么办?**
343
- A: 不支持真正的并发(没有锁)。groom 是单会话操作,假设没有并发 writer。
344
-
345
- **Q: 归档后能恢复吗?**
346
- A: 能。`Bash mv notes/_archived/<date>_<slug>.md notes/<topic>/<slug>.md` + `Edit NOTES.md`。没有专门 CLI 命令,因为是罕见操作。
101
+ **维护契约**:创建 / 重命名 / 归档 note 时必须同步更新对应 `NOTES.md`。`title` 和 `description` 来自 note frontmatter。
@@ -0,0 +1,43 @@
1
+ # Groom Rules
2
+
3
+ > 本文件归用户所有,插件升级不会覆盖(仅首次 init 时写入种子版本)。
4
+ > 你可以自由修改以调教 AI agent 的 groom 行为。
5
+
6
+ ## Triage 策略
7
+
8
+ 对每条 inbox entry,决策 **create** / **merge** / **discard**:
9
+
10
+ - **create**:全新知识,与现有 notes 无重叠
11
+ - **merge**:补充已有 note 的信息
12
+ - **discard**:噪声、调试碎片、已被捕获的重复内容
13
+
14
+ 拿不准时优先 **create** — 后续 groom 可以再合并去重。
15
+
16
+ ## 并行 Triage
17
+
18
+ 可为每条 inbox entry 派发一个 sub-agent(便宜模型即可),并行处理:
19
+ - 每个 sub-agent `Read` 一条 entry + notes 索引快照,输出结构化决策
20
+ - 主 agent 收集结果后顺序执行写入(避免 NOTES.md 冲突)
21
+
22
+ ## 合并判断
23
+
24
+ - 语义重叠 > 50% 即应 merge 而非 create
25
+ - merge 时保留原 note 的结构,追加新信息,bump `updated`
26
+
27
+ ## Discard 标准
28
+
29
+ - 纯 tool 输出、命令行日志、无实质内容的对话片段
30
+ - 与已有 note 完全重复(无新信息)
31
+ - 临时性调试上下文(除非包含可复用的 lesson)
32
+
33
+ ## 洞察提取
34
+
35
+ 发现以下情况时写入 `insights/`:
36
+ - 两条 note 之间存在矛盾
37
+ - ≥ 3 条 note 呈现同一模式
38
+ - 某个重复操作适合抽成 skill
39
+
40
+ ## .context/index.md 编译
41
+
42
+ groom 结束时重新编译,< 2KB,必须包含 `## Active Reminders`(可为空)。
43
+ 扫描所有 `type: reminder` 的 note 生成该段落。
package/bin/mem-note.mjs CHANGED
@@ -7084,13 +7084,7 @@ var require_gray_matter = __commonJS({
7084
7084
  import { homedir } from "node:os";
7085
7085
  import { basename as basename3, dirname as dirname3, join as join22, resolve } from "node:path";
7086
7086
  import { fileURLToPath } from "node:url";
7087
- import {
7088
- access as access2,
7089
- copyFile,
7090
- readFile as readFile4,
7091
- readdir as readdir3,
7092
- writeFile as writeFile22
7093
- } from "node:fs/promises";
7087
+ import { access as access2, copyFile, readFile as readFile4, readdir as readdir3 } from "node:fs/promises";
7094
7088
 
7095
7089
  // apps/mem-note-cli/node_modules/commander/esm.mjs
7096
7090
  var import_index = __toESM(require_commander(), 1);
@@ -7172,6 +7166,10 @@ var DataDir = class {
7172
7166
  conventionsPath() {
7173
7167
  return join(this.root, "CONVENTIONS.md");
7174
7168
  }
7169
+ /** `<root>/GROOM_RULES.md` — user-owned groom strategy, seeded by init, never overwritten. */
7170
+ groomRulesPath() {
7171
+ return join(this.root, "GROOM_RULES.md");
7172
+ }
7175
7173
  /** The absolute root path. */
7176
7174
  get rootDir() {
7177
7175
  return this.root;
@@ -7731,19 +7729,33 @@ function printInstallGuidance() {
7731
7729
  function resolveDataRoot() {
7732
7730
  return process.env["MEM_NOTE_DATA_DIR"] ?? join22(homedir(), ".aibox-mem-note");
7733
7731
  }
7734
- async function getDataDir() {
7735
- const dd = new DataDir(resolveDataRoot());
7736
- await dd.ensureInitialized();
7737
- return dd;
7732
+ function resolveResourceDir() {
7733
+ try {
7734
+ return dirname3(fileURLToPath(import.meta.url));
7735
+ } catch {
7736
+ return __dirname;
7737
+ }
7738
7738
  }
7739
- function resolveConventionsResource() {
7740
- let here;
7739
+ async function syncResources(dd) {
7740
+ const resDir = resolveResourceDir();
7741
+ try {
7742
+ await copyFile(resolve(resDir, "conventions.md"), dd.conventionsPath());
7743
+ } catch {
7744
+ }
7741
7745
  try {
7742
- here = dirname3(fileURLToPath(import.meta.url));
7746
+ await access2(dd.groomRulesPath());
7743
7747
  } catch {
7744
- here = __dirname;
7748
+ try {
7749
+ await copyFile(resolve(resDir, "groom-rules.md"), dd.groomRulesPath());
7750
+ } catch {
7751
+ }
7745
7752
  }
7746
- return resolve(here, "conventions.md");
7753
+ }
7754
+ async function getDataDir() {
7755
+ const dd = new DataDir(resolveDataRoot());
7756
+ await dd.ensureInitialized();
7757
+ await syncResources(dd);
7758
+ return dd;
7747
7759
  }
7748
7760
  async function readStdin() {
7749
7761
  if (process.stdin.isTTY)
@@ -7757,28 +7769,9 @@ async function readStdin() {
7757
7769
  var program2 = new Command();
7758
7770
  program2.name("mem-note").description("AI-enhanced personal knowledge base \u2014 minimal CLI surface").version("0.2.0");
7759
7771
  program2.command("init").description(
7760
- "Create the data directory tree and write CONVENTIONS.md (idempotent \u2014 does not overwrite an existing CONVENTIONS.md)"
7772
+ "Create data directory, sync plugin resources, and initialize git (idempotent)"
7761
7773
  ).action(async () => {
7762
7774
  const dd = await getDataDir();
7763
- const conventionsDest = dd.conventionsPath();
7764
- let conventionsExisted = false;
7765
- try {
7766
- await access2(conventionsDest);
7767
- conventionsExisted = true;
7768
- } catch {
7769
- }
7770
- if (!conventionsExisted) {
7771
- const source = resolveConventionsResource();
7772
- try {
7773
- await copyFile(source, conventionsDest);
7774
- } catch (err) {
7775
- const fallback = "# mem-note CONVENTIONS\n\nFull conventions document missing from this installation. See apps/mem-note-cli/src/conventions.md in the source repo.\n";
7776
- await writeFile22(conventionsDest, fallback, "utf-8");
7777
- console.warn(
7778
- `[mem-note init] Warning: could not read ${source}: ${String(err)}`
7779
- );
7780
- }
7781
- }
7782
7775
  let gitStatus = "skipped-no-git";
7783
7776
  if (await isGitAvailable()) {
7784
7777
  if (await isGitRepo(dd.rootDir)) {
@@ -7794,12 +7787,14 @@ program2.command("init").description(
7794
7787
  JSON.stringify({
7795
7788
  ok: true,
7796
7789
  root: dd.rootDir,
7797
- conventions: conventionsExisted ? "already-present" : conventionsDest,
7798
7790
  git: gitStatus
7799
7791
  })
7800
7792
  );
7801
7793
  });
7802
- program2.command("save").description("Save a new inbox entry. Accepts content as argument or stdin.").argument("[content]", "Content to save (omit to read from stdin)").option("--type <type>", "Preliminary type hint (optional; groom may rewrite)").option(
7794
+ program2.command("save").description("Save a new inbox entry. Accepts content as argument or stdin.").argument("[content]", "Content to save (omit to read from stdin)").option(
7795
+ "--type <type>",
7796
+ "Preliminary type hint (optional; groom may rewrite)"
7797
+ ).option(
7803
7798
  "--tags <tags>",
7804
7799
  "Comma-separated tags (optional)",
7805
7800
  (val) => val.split(",").map((t) => t.trim()).filter(Boolean)
@@ -7918,9 +7913,7 @@ async function walkNotes(dir) {
7918
7913
  }
7919
7914
  return results;
7920
7915
  }
7921
- program2.command("ui-exec", { hidden: true }).description(
7922
- "Internal-only UI bridge command"
7923
- ).action(async () => {
7916
+ program2.command("ui-exec", { hidden: true }).description("Internal-only UI bridge command").action(async () => {
7924
7917
  const raw = await readStdin();
7925
7918
  if (!raw) {
7926
7919
  process.stderr.write("ui-exec: no input received on stdin\n");
@@ -7959,7 +7952,9 @@ program2.command("ui-exec", { hidden: true }).description(
7959
7952
  }
7960
7953
  case "list-inbox": {
7961
7954
  const inboxDir = dd.inboxDir();
7962
- const entries = await readdir3(inboxDir, { withFileTypes: true }).catch(() => []);
7955
+ const entries = await readdir3(inboxDir, {
7956
+ withFileTypes: true
7957
+ }).catch(() => []);
7963
7958
  const items = await Promise.all(
7964
7959
  entries.filter((e) => e.isFile() && e.name.endsWith(".md")).map(async (e) => {
7965
7960
  const absPath = join22(inboxDir, e.name);
@@ -7990,10 +7985,31 @@ program2.command("ui-exec", { hidden: true }).description(
7990
7985
  const date = typeof input["date"] === "string" ? input["date"] : (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
7991
7986
  let content = "";
7992
7987
  try {
7993
- content = await readFile4(join22(dd.reportsDir(), `${date}.md`), "utf-8");
7988
+ content = await readFile4(
7989
+ join22(dd.reportsDir(), `${date}.md`),
7990
+ "utf-8"
7991
+ );
7994
7992
  } catch {
7995
7993
  }
7996
- process.stdout.write(JSON.stringify({ ok: true, data: { content, date } }));
7994
+ process.stdout.write(
7995
+ JSON.stringify({ ok: true, data: { content, date } })
7996
+ );
7997
+ break;
7998
+ }
7999
+ case "get-graph-path": {
8000
+ const graphPath = join22(dd.reportsDir(), "graph.html");
8001
+ let exists = false;
8002
+ try {
8003
+ await access2(graphPath);
8004
+ exists = true;
8005
+ } catch {
8006
+ }
8007
+ process.stdout.write(
8008
+ JSON.stringify({
8009
+ ok: true,
8010
+ data: { path: exists ? graphPath : null }
8011
+ })
8012
+ );
7997
8013
  break;
7998
8014
  }
7999
8015
  case "process-inbox": {
@@ -8002,7 +8018,9 @@ program2.command("ui-exec", { hidden: true }).description(
8002
8018
  const reason = typeof input["reason"] === "string" ? input["reason"] : void 0;
8003
8019
  const filename = basename3(rawFilename);
8004
8020
  if (!filename || !filename.endsWith(".md") || filename !== rawFilename) {
8005
- process.stderr.write("ui-exec: process-inbox requires a plain .md filename\n");
8021
+ process.stderr.write(
8022
+ "ui-exec: process-inbox requires a plain .md filename\n"
8023
+ );
8006
8024
  process.exit(1);
8007
8025
  }
8008
8026
  if (rawStatus !== "processed" && rawStatus !== "user-ignored") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meitouai/mem-note-cli",
3
- "version": "0.0.3",
3
+ "version": "0.1.0",
4
4
  "description": "Personal knowledge base CLI for use with the mem-note Claude Code plugin.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -9,7 +9,8 @@
9
9
  },
10
10
  "files": [
11
11
  "bin/mem-note.mjs",
12
- "bin/conventions.md"
12
+ "bin/conventions.md",
13
+ "bin/groom-rules.md"
13
14
  ],
14
15
  "engines": {
15
16
  "node": ">=20"