@thincoder/core 0.9.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.
Files changed (203) hide show
  1. package/abort-provenance.mjs +116 -0
  2. package/advisor/citations.mjs +139 -0
  3. package/advisor/compaction.mjs +174 -0
  4. package/advisor/convergence.mjs +80 -0
  5. package/advisor/history.mjs +77 -0
  6. package/advisor/loop.mjs +288 -0
  7. package/advisor/messages.mjs +299 -0
  8. package/advisor/notice.mjs +141 -0
  9. package/advisor/project-context.mjs +197 -0
  10. package/advisor/repos.mjs +150 -0
  11. package/advisor/run.mjs +190 -0
  12. package/advisor/truncate.mjs +57 -0
  13. package/advisor.mjs +281 -0
  14. package/agent/child-marks.mjs +24 -0
  15. package/agent/completion.mjs +145 -0
  16. package/agent/dispatch.mjs +493 -0
  17. package/agent/family-tools.mjs +174 -0
  18. package/agent/helpers.mjs +412 -0
  19. package/agent/post-turn.mjs +70 -0
  20. package/agent/record-results.mjs +174 -0
  21. package/agent/relay-prefix.mjs +39 -0
  22. package/agent/run-stages.mjs +244 -0
  23. package/agent/setup-reminders.mjs +199 -0
  24. package/agent/setup.mjs +234 -0
  25. package/agent/spawn-child.mjs +258 -0
  26. package/agent/suspension.mjs +240 -0
  27. package/agent/write-gate.mjs +87 -0
  28. package/agent-tools/advisor-async.mjs +481 -0
  29. package/agent-tools/advisor-settle.mjs +240 -0
  30. package/agent-tools/advisor.mjs +280 -0
  31. package/agent-tools/async-discard.mjs +143 -0
  32. package/agent-tools/async-settle.mjs +299 -0
  33. package/agent-tools/batch-segment.mjs +265 -0
  34. package/agent-tools/child-permission.mjs +45 -0
  35. package/agent-tools/consult.mjs +471 -0
  36. package/agent-tools/design-token.mjs +117 -0
  37. package/agent-tools/digest-budget.mjs +76 -0
  38. package/agent-tools/eng.mjs +102 -0
  39. package/agent-tools/escalate-async.mjs +302 -0
  40. package/agent-tools/goal.mjs +119 -0
  41. package/agent-tools/panel-blocks.mjs +24 -0
  42. package/agent-tools/parent-channel.mjs +231 -0
  43. package/agent-tools/plan.mjs +86 -0
  44. package/agent-tools/read-history.mjs +309 -0
  45. package/agent-tools/recent-changes.mjs +24 -0
  46. package/agent-tools/review-facts.mjs +31 -0
  47. package/agent-tools/settings.mjs +268 -0
  48. package/agent-tools/skill.mjs +63 -0
  49. package/agent-tools/spawn-gates.mjs +109 -0
  50. package/agent-tools/subagent-actions.mjs +496 -0
  51. package/agent-tools/subagent-async.mjs +456 -0
  52. package/agent-tools/subagent-panel.mjs +160 -0
  53. package/agent-tools/subagent-run.mjs +208 -0
  54. package/agent-tools/subagent-scheduler.mjs +446 -0
  55. package/agent-tools/subagent-spawn.mjs +478 -0
  56. package/agent-tools/subagent.mjs +419 -0
  57. package/agent-tools/task.mjs +87 -0
  58. package/agent-tools/timer.mjs +46 -0
  59. package/agent-tools/verify.mjs +295 -0
  60. package/agent-tools.mjs +23 -0
  61. package/agent.mjs +430 -0
  62. package/auto-think.mjs +115 -0
  63. package/compress-form.mjs +24 -0
  64. package/config-io.mjs +277 -0
  65. package/config-migrate.mjs +178 -0
  66. package/config-presets.mjs +49 -0
  67. package/config.mjs +419 -0
  68. package/context.mjs +495 -0
  69. package/conventions.mjs +223 -0
  70. package/embedding.mjs +120 -0
  71. package/escape.mjs +152 -0
  72. package/expand-home.mjs +16 -0
  73. package/explore-distill.mjs +152 -0
  74. package/generate-title.mjs +123 -0
  75. package/git/checkpoint.mjs +448 -0
  76. package/git/gitmem.mjs +100 -0
  77. package/history-window.mjs +179 -0
  78. package/hooks.mjs +108 -0
  79. package/i18n.mjs +106 -0
  80. package/index-bin.mjs +48 -0
  81. package/index-discover.mjs +176 -0
  82. package/ledger-cmd.mjs +209 -0
  83. package/ledger-db.mjs +86 -0
  84. package/ledger-surface.mjs +76 -0
  85. package/ledger.mjs +202 -0
  86. package/log.mjs +195 -0
  87. package/manifest.mjs +338 -0
  88. package/markdown.mjs +106 -0
  89. package/mcp/helpers.mjs +51 -0
  90. package/mcp/transport-http.mjs +248 -0
  91. package/mcp/transport-stdio.mjs +140 -0
  92. package/mcp/transport-ws.mjs +122 -0
  93. package/mcp.mjs +295 -0
  94. package/memory/code-index.mjs +219 -0
  95. package/memory/code-sync.mjs +427 -0
  96. package/memory/core.mjs +318 -0
  97. package/memory/delete.mjs +242 -0
  98. package/memory/docs.mjs +431 -0
  99. package/memory/file-walk.mjs +109 -0
  100. package/memory/origin.mjs +24 -0
  101. package/memory/scan.mjs +177 -0
  102. package/memory/schema.mjs +460 -0
  103. package/memory.mjs +21 -0
  104. package/model-ref.mjs +66 -0
  105. package/model-specs.mjs +277 -0
  106. package/package.json +32 -0
  107. package/peer-domains.mjs +265 -0
  108. package/peer-instances.mjs +178 -0
  109. package/permission.mjs +79 -0
  110. package/process-probe.mjs +315 -0
  111. package/prompt-files.mjs +113 -0
  112. package/prompt-overlays.mjs +78 -0
  113. package/prompts/advisor-design.md +43 -0
  114. package/prompts/advisor-round1.md +41 -0
  115. package/prompts/advisor-round2.md +46 -0
  116. package/prompts/advisor-round3.md +42 -0
  117. package/prompts/common.md +158 -0
  118. package/prompts/consult-base.md +19 -0
  119. package/prompts/discipline-engineering.md +123 -0
  120. package/prompts/discipline-normal.md +206 -0
  121. package/prompts/persona-coder.md +21 -0
  122. package/prompts/persona-eng-coder.md +41 -0
  123. package/prompts/persona-eng-designer.md +80 -0
  124. package/prompts/persona-engineering.md +160 -0
  125. package/prompts/persona-explore.md +15 -0
  126. package/prompts/persona-normal.md +35 -0
  127. package/prompts/persona-plan.md +27 -0
  128. package/provider/anthropic.mjs +225 -0
  129. package/provider/core.mjs +491 -0
  130. package/provider/errors.mjs +101 -0
  131. package/provider/google.mjs +257 -0
  132. package/provider/index.mjs +7 -0
  133. package/provider/list-models.mjs +163 -0
  134. package/provider/normalize.mjs +81 -0
  135. package/provider/rate.mjs +168 -0
  136. package/provider/responses.mjs +495 -0
  137. package/provider/retry.mjs +88 -0
  138. package/provider/sse.mjs +264 -0
  139. package/provider/wait-status.mjs +59 -0
  140. package/proxy.mjs +274 -0
  141. package/rules.mjs +53 -0
  142. package/session-gc.mjs +248 -0
  143. package/session-guard.mjs +59 -0
  144. package/session-lifecycle.mjs +305 -0
  145. package/session-migrate.mjs +48 -0
  146. package/session-rename.mjs +38 -0
  147. package/session-segments.mjs +99 -0
  148. package/session-slot-write.mjs +168 -0
  149. package/session-slots-manifest.mjs +264 -0
  150. package/session-slots.mjs +298 -0
  151. package/session-store.mjs +441 -0
  152. package/session.mjs +244 -0
  153. package/skills.mjs +234 -0
  154. package/text-budget.mjs +79 -0
  155. package/token-ttl.mjs +285 -0
  156. package/tool-docs/apply_patch.md +15 -0
  157. package/tool-docs/bash.md +38 -0
  158. package/tool-docs/delete.md +13 -0
  159. package/tool-docs/edit.md +30 -0
  160. package/tool-docs/execute.md +21 -0
  161. package/tool-docs/fetch.md +12 -0
  162. package/tool-docs/file_ops.md +17 -0
  163. package/tool-docs/get_current_time.md +8 -0
  164. package/tool-docs/git.md +54 -0
  165. package/tool-docs/glob.md +11 -0
  166. package/tool-docs/grep.md +19 -0
  167. package/tool-docs/hashline_edit.md +14 -0
  168. package/tool-docs/insert_after.md +15 -0
  169. package/tool-docs/lint.md +10 -0
  170. package/tool-docs/ls.md +12 -0
  171. package/tool-docs/lsp.md +10 -0
  172. package/tool-docs/process.md +10 -0
  173. package/tool-docs/question.md +16 -0
  174. package/tool-docs/read.md +20 -0
  175. package/tool-docs/read_image.md +8 -0
  176. package/tool-docs/tree.md +14 -0
  177. package/tool-docs/wait_for.md +22 -0
  178. package/tool-docs/websearch.md +16 -0
  179. package/tool-docs/write.md +11 -0
  180. package/tools/bash.mjs +276 -0
  181. package/tools/edit-batch.mjs +204 -0
  182. package/tools/edit-diff.mjs +388 -0
  183. package/tools/exec-run.mjs +43 -0
  184. package/tools/execute.mjs +243 -0
  185. package/tools/file.mjs +464 -0
  186. package/tools/git-checkpoint.mjs +143 -0
  187. package/tools/git-ext.mjs +173 -0
  188. package/tools/git.mjs +415 -0
  189. package/tools/glob-dialect.mjs +130 -0
  190. package/tools/index.mjs +76 -0
  191. package/tools/linter.mjs +120 -0
  192. package/tools/lsp.mjs +335 -0
  193. package/tools/ops.mjs +293 -0
  194. package/tools/patch.mjs +290 -0
  195. package/tools/question.mjs +26 -0
  196. package/tools/repomap.mjs +314 -0
  197. package/tools/search.mjs +248 -0
  198. package/tools/shared.mjs +467 -0
  199. package/tools/tree.mjs +81 -0
  200. package/tools/web.mjs +224 -0
  201. package/tools/write-path.mjs +191 -0
  202. package/traces/trace-store.mjs +303 -0
  203. package/undo-stack.mjs +47 -0
@@ -0,0 +1,177 @@
1
+ /**
2
+ * memory/scan.mjs — 检索向量通道的分块扫描 + 有界 top-K。
3
+ * (TUI-OOM-ROOTCAUSE 批——MEMORY.md §10;扫描面修复批——§6.10 修法 A1/A2,2026-09-18。)
4
+ *
5
+ * 病灶一(调度面 · §6.10):块循环**零 `await`** ⇒ 独占事件循环(TUI 假死:滚轮 / 键盘 /
6
+ * 渲染与 agent 同一条事件循环)。
7
+ * 病灶二(访问序面 · §6.10):游标 = rowid 序,而两张大表索引序 = PK `(origin, path,
8
+ * line_start)` ⇒ 每块 `USE TEMP B-TREE FOR ORDER BY`(重排**整个过滤集**,SELECT 含
9
+ * `embedding` ⇒ 排序物化带 blob;真库实测单表 29.4 s)。
10
+ *
11
+ * 修法 A2(让出 · 只改调度):块内每 `SCAN_YIELD_CHECK_ROWS` 行读一次钟,自上次让出累计
12
+ * ≥ `SCAN_YIELD_MS` 即 `await yieldFn()`;**块边界同判**(同一预算——预算未耗不白让出)。
13
+ * 访问序 / 评分 / top-K 候选集 / 并列稳定规则**零变**。
14
+ * 修法 A1(PK 游标 · 只改访问序):游标列由调用点声明(`cursorKey`,缺省 `["rowid"]` =
15
+ * 现行为);键 = **表 PK 去掉「被等值过滤的前缀列」**——SQLite 只把与索引列序对齐的元组
16
+ * 比较用作 seek;被等值约束的前导列留在元组里则整条谓词退化为**残余过滤**(每块从头扫起 =
17
+ * 静默二次方,且无 `TEMP B-TREE`、无报错——§6.10 否决形)。
18
+ *
19
+ * 对外结构不变:调用方拿到的仍是 `[{id, score}]` 降序候选(RRF 融合输入)——并列分数按
20
+ * 既有稳定规则(先到先留——与「全量稳定 sort + slice(0,K)」等价)。
21
+ *
22
+ * 可测缝(N-M1 · §6.10):`cursorKey` · `runChunkedQuery(sql, params)`(传入**构建后的**
23
+ * SQL 与参数——计划面与分页行为皆可机判)· `yieldFn`(缺省 `yieldTick`)· `nowFn`
24
+ * (缺省 `Date.now`)· `yieldMs`(缺省常量)——让出行为可**确定性**断言,不依赖真实时钟。
25
+ */
26
+
27
+ import { yieldTick } from "./code-index.mjs"
28
+
29
+ /** 单块行数(单源——三处共用;D-M2:块内存量级 KB~MB,随维度有界)。 */
30
+ export const SCAN_CHUNK_ROWS = 2_000
31
+
32
+ /** 让出检查间隔(行)——每处理这么多行读一次时钟(§6.10 修法 A2)。 */
33
+ export const SCAN_YIELD_CHECK_ROWS = 64
34
+
35
+ /** 让出预算(毫秒)——自上次让出累计达此值即让出(§6.10 修法 A2;D-MEM17)。 */
36
+ export const SCAN_YIELD_MS = 50
37
+
38
+ /** 缺省游标键 = rowid(**即现行为**——`entries` / `files` 两调用点零改,§6.10 逐调用点表)。 */
39
+ const DEFAULT_CURSOR_KEY = ["rowid"]
40
+
41
+ /**
42
+ * 有界 top-K(升序小顶堆):`push({id, score})` 摊销 O(log K);`list()` 返回降序
43
+ * `[{id, score}]`。并列分数先到先留(与全量稳定排序一致)。
44
+ */
45
+ export function createTopK(k) {
46
+ const cap = Math.max(1, k)
47
+ const heap = [] // 升序小顶堆(堆顶 = 当前最差)
48
+ let seq = 0
49
+
50
+ /** a 比 b 更差?(分低者差;同分 → 迟到者差(seq 大)——堆顶恒为最差) */
51
+ const worse = (a, b) => a.score < b.score || (a.score === b.score && a.seq > b.seq)
52
+ /** a 严格优于 b?(同分不互优——先到先留) */
53
+ const strictlyBetter = (a, b) => a.score > b.score || (a.score === b.score && a.seq < b.seq)
54
+ const siftUp = (i) => {
55
+ while (i > 0) {
56
+ const p = (i - 1) >> 1
57
+ if (worse(heap[i], heap[p])) { const t = heap[i]; heap[i] = heap[p]; heap[p] = t; i = p; continue }
58
+ break
59
+ }
60
+ }
61
+ const siftDown = (i) => {
62
+ for (;;) {
63
+ const l = i * 2 + 1
64
+ const r = l + 1
65
+ let worst = i
66
+ if (l < heap.length && worse(heap[l], heap[worst])) worst = l
67
+ if (r < heap.length && worse(heap[r], heap[worst])) worst = r
68
+ if (worst === i) break
69
+ const t = heap[i]; heap[i] = heap[worst]; heap[worst] = t
70
+ i = worst
71
+ }
72
+ }
73
+
74
+ return {
75
+ get size() { return heap.length },
76
+ push(item) {
77
+ const node = { id: item.id, score: item.score, seq: seq++ }
78
+ if (heap.length < cap) { heap.push(node); siftUp(heap.length - 1); return true }
79
+ if (!strictlyBetter(node, heap[0])) return false // 不优于当前最差(含同分迟到)→ 丢弃(先到先留)
80
+ heap[0] = node
81
+ siftDown(0)
82
+ return true
83
+ },
84
+ /** 降序 [{id, score}](K 上限——与「全量 sort desc + slice(0,K)」逐条等价)。 */
85
+ list() {
86
+ return [...heap]
87
+ .sort((a, b) => (b.score - a.score) || (a.seq - b.seq))
88
+ .map((n) => ({ id: n.id, score: n.score }))
89
+ },
90
+ }
91
+ }
92
+
93
+ /** 末行键值元组:任一键列缺失(`undefined` / `null`)⇒ `null`(防呆①:等价现 `:90`)。 */
94
+ function cursorValues(keys, row) {
95
+ if (!row) return null
96
+ const out = []
97
+ for (const k of keys) {
98
+ const v = row[k]
99
+ if (v === undefined || v === null) return null
100
+ out.push(v)
101
+ }
102
+ return out
103
+ }
104
+
105
+ /** 文本键比较(`-1` / `0` / `1`):**与 SQLite BINARY 同序**(UTF-8 字节序 = 码点序)。
106
+ * JS 字符串 `<`/`>` 是 UTF-16 码元序——键含增补平面字符(emoji 等 U+10000+)且相邻处有
107
+ * U+E000–U+FFFF 字符时与 BINARY 相抵 ⇒ 防呆①会误判「游标未前进」而**提前止**(静默少覆盖)。 */
108
+ function compareText(x, y) {
109
+ const c = Buffer.compare(Buffer.from(x, "utf8"), Buffer.from(y, "utf8"))
110
+ return c === 0 ? 0 : c > 0 ? 1 : -1
111
+ }
112
+
113
+ /** 键元组序比较:数值按数值、其余按 BINARY 同序文本(`-1` / `0` / `1`)。 */
114
+ function compareKeyTuples(a, b) {
115
+ for (let i = 0; i < a.length; i++) {
116
+ const x = a[i], y = b[i]
117
+ if (x === y) continue
118
+ if (typeof x === "number" && typeof y === "number") return x > y ? 1 : -1
119
+ return compareText(String(x), String(y))
120
+ }
121
+ return 0
122
+ }
123
+
124
+ /**
125
+ * 分块扫描(键序游标——§6.10 修法 A1)+ 时间片让出(修法 A2):逐块物化 → 逐行 `onRow`
126
+ * → 块内存随迭代释放。
127
+ * @param {object} db sqlite 句柄(`.prepare(sql).all(...params)`)
128
+ * @param {string} sql 单表查询(须含键列【见 `cursorKey`】;不含 ORDER BY / LIMIT)
129
+ * @param {Array} params 绑定参数(`?` 占位——顺序与 SQL 一致)
130
+ * @param {object} opts `{ chunk, onRow, runChunkedQuery, cursorKey, yieldFn, nowFn, yieldMs }`
131
+ * @returns {Promise<number>} 扫描到的行数
132
+ */
133
+ export async function scanVectors(db, sql, params = [], {
134
+ chunk = SCAN_CHUNK_ROWS,
135
+ onRow = null,
136
+ runChunkedQuery = null,
137
+ cursorKey = DEFAULT_CURSOR_KEY,
138
+ yieldFn = yieldTick,
139
+ nowFn = Date.now,
140
+ yieldMs = SCAN_YIELD_MS,
141
+ } = {}) {
142
+ const run = runChunkedQuery ?? ((q, p) => db.prepare(q).all(...p))
143
+ const keys = Array.isArray(cursorKey) && cursorKey.length > 0 ? cursorKey : DEFAULT_CURSOR_KEY
144
+ const keyList = keys.join(", ")
145
+ const orderBy = `ORDER BY ${keyList}`
146
+ // 首块不带游标谓词(否决哨兵值——哨兵须假设列域下界);中块 = 与索引列序对齐的元组比较
147
+ const firstSql = `${sql} ${orderBy} LIMIT ?`
148
+ const midSql = `${sql} AND (${keyList}) > (${keys.map(() => "?").join(", ")}) ${orderBy} LIMIT ?`
149
+
150
+ let cursor = null
151
+ let total = 0
152
+ let checked = 0
153
+ let lastYieldAt = nowFn()
154
+ /** 时间片预算:自上次让出累计 ≥ `yieldMs` ⇒ 让出(`yieldMs: Infinity` = 不让出)。 */
155
+ const mightYield = async () => {
156
+ if (nowFn() - lastYieldAt >= yieldMs) { await yieldFn(); lastYieldAt = nowFn() }
157
+ }
158
+
159
+ for (;;) {
160
+ const rows = (cursor === null
161
+ ? run(firstSql, [...params, chunk])
162
+ : run(midSql, [...params, ...cursor, chunk])) ?? []
163
+ if (rows.length === 0) break
164
+ for (const r of rows) {
165
+ total++
166
+ onRow?.(r)
167
+ if (++checked >= SCAN_YIELD_CHECK_ROWS) { checked = 0; await mightYield() }
168
+ }
169
+ const next = cursorValues(keys, rows[rows.length - 1])
170
+ // 防呆①:键值缺失 ∨ 末键元组未严格大于上一游标键 ⇒ 止(游标不前进即止——防异常行序空转)
171
+ if (!next || (cursor !== null && compareKeyTuples(next, cursor) <= 0)) break
172
+ cursor = next
173
+ if (rows.length < chunk) break // 防呆②:尾块(末块读尽——不再多打一次空查询)
174
+ await mightYield() // 块边界同判(同一预算)
175
+ }
176
+ return total
177
+ }
@@ -0,0 +1,460 @@
1
+ /**
2
+ * memory/schema.mjs — database schema definition, migration, CJK segmentation
3
+ * v1: node:sqlite + FTS5 standalone implementation, zero dependencies.
4
+ *
5
+ * Chinese search strategy: FTS5 unicode61 tokenizer + CJK character-by-character spacing (applied to both write and query sides).
6
+ * Effect: Chinese indexed by character; two-character words like "分号" still match; ASCII stays whole-word.
7
+ */
8
+
9
+ import { DatabaseSync } from "node:sqlite"
10
+ import { mkdirSync } from "node:fs"
11
+ import { dirname } from "node:path"
12
+
13
+ export const VALID_TYPES = new Set(["rule", "knowledge", "decision", "pattern"])
14
+ export const SCHEMA_VERSION = 9
15
+ export const SQLITE_BUSY_TIMEOUT = 3000
16
+ /** WAL 回收后的文件截断上界(§6.12 修法①——防复胀;实测曾达 587 MB)。 */
17
+ export const WAL_SIZE_LIMIT_BYTES = 64 * 1024 * 1024
18
+
19
+ // Code index: source file extensions. Curated DEFAULTS — a project can declare
20
+ // more (union) through .thincoder/conventions.json → index.codeExtensions
21
+ // (PORTABILITY PO-9: an unlisted extension used to be invisible AND undeclarable).
22
+ export const CODE_EXTS = new Set([
23
+ ".mjs", ".js", ".cjs", ".mts", ".cts", ".ts", ".tsx", ".jsx",
24
+ ".py", ".rs", ".go", ".java", ".c", ".h", ".cpp", ".hpp",
25
+ ".rb", ".swift", ".kt", ".dart", ".lua", ".cs", ".fs", ".fsx",
26
+ ".clj", ".cljs", ".ex", ".exs", ".erl", ".hrl", ".scala", ".groovy",
27
+ ".pl", ".pm", ".r", ".jl", ".zig", ".ps1",
28
+ ".sh", ".bash", ".sql", ".yaml", ".yml", ".toml", ".json",
29
+ ".proto", ".graphql", ".tf", ".hcl",
30
+ ".css", ".html", ".vue", ".svelte",
31
+ ])
32
+ // Doc index: markdown / plain text (separate index makes it easier for LLM to distinguish "design specs" from "existing code")
33
+ // Same declaration rule as CODE_EXTS (index.docExtensions).
34
+ export const DOC_EXTS = new Set([".md", ".mdc", ".mdx", ".txt", ".rst", ".adoc", ".org", ".wiki", ".tex"])
35
+ // Directory names always skipped during code/doc indexing
36
+ // NOTE: these are case-sensitive basename matches; add common platform-specific dirs
37
+ export const SKIP_DIRS = new Set([
38
+ "node_modules", ".git", "dist", "build", ".turbo", "coverage",
39
+ "__pycache__", ".venv", "venv", "target", ".next", ".nuxt", ".svelte-kit",
40
+ // Windows user profile directories (never contain project code)
41
+ "AppData", "Application Data", "Desktop", "Documents", "Downloads",
42
+ "Music", "Pictures", "Videos", "OneDrive", "Contacts", "Favorites",
43
+ "Links", "Saved Games", "Searches",
44
+ // Other common non-code directories
45
+ "Program Files", "Program Files (x86)", "Windows", "$Recycle.Bin",
46
+ ])
47
+ // Files larger than these limits are skipped during bulk indexing
48
+ // (minified bundles, test fixtures, generated code, etc.)
49
+ export const MAX_CODE_FILE_BYTES = 1024 * 1024 // 1 MB
50
+ export const MAX_DOC_FILE_BYTES = 512 * 1024 // 512 KB
51
+ export const BIG_FILE_LINES = 2000
52
+
53
+ /**
54
+ * CJK character-by-character spacing: makes unicode61 treat each Han/Kana/Hangul character as an independent token.
55
+ * Both write and query must use the same processing for retrieval to match.
56
+ */
57
+ export function segmentCJK(text) {
58
+ return text.replace(
59
+ /[぀-ヿ㐀-䶿一-鿿豈-﫿가-힯]+/g,
60
+ (run) => [...run].join(" "),
61
+ )
62
+ }
63
+
64
+ /**
65
+ * Open/initialize the memory store. dbPath is auto-created if missing.
66
+ * The returned memory object is the interface; all subsequent functions take it as their first argument.
67
+ */
68
+ export function createMemory({ dbPath }) {
69
+ mkdirSync(dirname(dbPath), { recursive: true })
70
+ const db = new DatabaseSync(dbPath)
71
+ // WAL: reads and writes don't block each other (TUI search and background indexing can run concurrently); busy_timeout prevents SQLITE_BUSY from multi-process same-db access
72
+ db.exec(`PRAGMA journal_mode = WAL`)
73
+ db.exec(`PRAGMA busy_timeout = ${SQLITE_BUSY_TIMEOUT}`)
74
+ // §6.12 WAL 卫生(TUI 假死批):① `journal_size_limit` = 回收后 WAL 文件截断上界(防复胀);
75
+ // ② 开库**一次性** `wal_checkpoint(TRUNCATE)`——失败容忍(另一实例持读事务 ⇒ busy:静默跳过,
76
+ // 不重试不报错)。边界写实:最坏等待上界 = 连接上的 `busy_timeout`(**不是**「不阻塞」;
77
+ // `wal_checkpoint` 是否走 busy handler = unverified——判据面 = 批档 §2.5 T-W2)。
78
+ db.exec(`PRAGMA journal_size_limit = ${WAL_SIZE_LIMIT_BYTES}`)
79
+ try { db.exec(`PRAGMA wal_checkpoint(TRUNCATE)`) } catch { /* busy / 非 WAL(内存库)⇒ 保持现状 */ }
80
+
81
+ db.exec(`
82
+ CREATE TABLE IF NOT EXISTS entries (
83
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
84
+ type TEXT NOT NULL CHECK(type IN ('rule','knowledge','decision','pattern')),
85
+ title TEXT NOT NULL,
86
+ content TEXT NOT NULL,
87
+ tags TEXT NOT NULL DEFAULT '',
88
+ seg_title TEXT NOT NULL DEFAULT '',
89
+ seg_content TEXT NOT NULL DEFAULT '',
90
+ seg_tags TEXT NOT NULL DEFAULT '',
91
+ created_at INTEGER NOT NULL,
92
+ updated_at INTEGER NOT NULL
93
+ )
94
+ `)
95
+
96
+ migrate(db)
97
+ return { db }
98
+ }
99
+
100
+ /** Step-by-step migration by user_version. Single transaction — any step failure rolls back, no half-finished schema left behind. */
101
+ export function migrate(db) {
102
+ const { user_version: version } = db.prepare(`PRAGMA user_version`).get()
103
+ if (version >= SCHEMA_VERSION) return
104
+
105
+ db.exec("BEGIN IMMEDIATE")
106
+ try {
107
+ if (version < 2) {
108
+ // v1 (trigram) or empty DB → v2 (unicode61 + CJK char-by-char): rebuild FTS and triggers
109
+ db.exec(`
110
+ DROP TRIGGER IF EXISTS entries_ai;
111
+ DROP TRIGGER IF EXISTS entries_ad;
112
+ DROP TRIGGER IF EXISTS entries_au;
113
+ DROP TABLE IF EXISTS entries_fts;
114
+ `)
115
+ // Old DB (v1) has no seg columns — add them
116
+ const columns = db.prepare(`PRAGMA table_info(entries)`).all().map((c) => c.name)
117
+ for (const col of ["seg_title", "seg_content", "seg_tags"]) {
118
+ if (!columns.includes(col)) {
119
+ db.exec(`ALTER TABLE entries ADD COLUMN ${col} TEXT NOT NULL DEFAULT ''`)
120
+ }
121
+ }
122
+ // Backfill seg columns (segmentation done in JS; SQL can't do it)
123
+ const rows = db.prepare(`SELECT id, title, content, tags FROM entries`).all()
124
+ const update = db.prepare(`UPDATE entries SET seg_title = ?, seg_content = ?, seg_tags = ? WHERE id = ?`)
125
+ for (const r of rows) {
126
+ update.run(segmentCJK(r.title), segmentCJK(r.content), segmentCJK(r.tags), r.id)
127
+ }
128
+
129
+ db.exec(`
130
+ CREATE VIRTUAL TABLE entries_fts USING fts5(
131
+ seg_title, seg_content, seg_tags,
132
+ content='entries', content_rowid='id',
133
+ tokenize='unicode61'
134
+ )
135
+ `)
136
+ db.exec(`
137
+ CREATE TRIGGER entries_ai AFTER INSERT ON entries BEGIN
138
+ INSERT INTO entries_fts(rowid, seg_title, seg_content, seg_tags)
139
+ VALUES (new.id, new.seg_title, new.seg_content, new.seg_tags);
140
+ END;
141
+ CREATE TRIGGER entries_ad AFTER DELETE ON entries BEGIN
142
+ INSERT INTO entries_fts(entries_fts, rowid, seg_title, seg_content, seg_tags)
143
+ VALUES ('delete', old.id, old.seg_title, old.seg_content, old.seg_tags);
144
+ END;
145
+ CREATE TRIGGER entries_au AFTER UPDATE ON entries BEGIN
146
+ INSERT INTO entries_fts(entries_fts, rowid, seg_title, seg_content, seg_tags)
147
+ VALUES ('delete', old.id, old.seg_title, old.seg_content, old.seg_tags);
148
+ INSERT INTO entries_fts(rowid, seg_title, seg_content, seg_tags)
149
+ VALUES (new.id, new.seg_title, new.seg_content, new.seg_tags);
150
+ END;
151
+ `)
152
+ db.exec(`INSERT INTO entries_fts(entries_fts) VALUES('rebuild')`)
153
+ db.exec(`PRAGMA user_version = 2`)
154
+ }
155
+
156
+ if (version < 3) {
157
+ // v3: markdown layer (project/team) files table + FTS + triggers
158
+ db.exec(`
159
+ CREATE TABLE IF NOT EXISTS files (
160
+ layer TEXT NOT NULL CHECK(layer IN ('project','team')),
161
+ path TEXT NOT NULL,
162
+ type TEXT NOT NULL CHECK(type IN ('rule','knowledge','decision','pattern')),
163
+ title TEXT NOT NULL,
164
+ content TEXT NOT NULL,
165
+ tags TEXT NOT NULL DEFAULT '',
166
+ author TEXT NOT NULL DEFAULT '',
167
+ embedding BLOB,
168
+ mtime_ms INTEGER NOT NULL DEFAULT 0,
169
+ seg_title TEXT NOT NULL DEFAULT '',
170
+ seg_content TEXT NOT NULL DEFAULT '',
171
+ seg_tags TEXT NOT NULL DEFAULT '',
172
+ updated_at INTEGER NOT NULL,
173
+ PRIMARY KEY (layer, path)
174
+ )
175
+ `)
176
+ db.exec(`
177
+ CREATE VIRTUAL TABLE IF NOT EXISTS files_fts USING fts5(
178
+ seg_title, seg_content, seg_tags,
179
+ content='files', content_rowid='rowid',
180
+ tokenize='unicode61'
181
+ )
182
+ `)
183
+ db.exec(`
184
+ CREATE TRIGGER files_ai AFTER INSERT ON files BEGIN
185
+ INSERT INTO files_fts(rowid, seg_title, seg_content, seg_tags)
186
+ VALUES (new.rowid, new.seg_title, new.seg_content, new.seg_tags);
187
+ END;
188
+ CREATE TRIGGER files_ad AFTER DELETE ON files BEGIN
189
+ INSERT INTO files_fts(files_fts, rowid, seg_title, seg_content, seg_tags)
190
+ VALUES ('delete', old.rowid, old.seg_title, old.seg_content, old.seg_tags);
191
+ END;
192
+ CREATE TRIGGER files_au AFTER UPDATE ON files BEGIN
193
+ INSERT INTO files_fts(files_fts, rowid, seg_title, seg_content, seg_tags)
194
+ VALUES ('delete', old.rowid, old.seg_title, old.seg_content, old.seg_tags);
195
+ INSERT INTO files_fts(rowid, seg_title, seg_content, seg_tags)
196
+ VALUES (new.rowid, new.seg_title, new.seg_content, new.seg_tags);
197
+ END;
198
+ `)
199
+ db.exec(`PRAGMA user_version = 3`)
200
+ }
201
+
202
+ if (version < 4) {
203
+ // v4: add vector column to personal entries table (files table already had it since v3); meta table stores embedding model name
204
+ const columns = db.prepare(`PRAGMA table_info(entries)`).all().map((c) => c.name)
205
+ if (!columns.includes("embedding")) {
206
+ db.exec(`ALTER TABLE entries ADD COLUMN embedding BLOB`)
207
+ }
208
+ db.exec(`CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT)`)
209
+ db.exec(`PRAGMA user_version = 4`)
210
+ }
211
+
212
+ if (version < 5) {
213
+ // v5: add origin column to files table (absolute project path), prevent cross-project memory collision
214
+ const columns = db.prepare(`PRAGMA table_info(files)`).all().map((c) => c.name)
215
+ if (!columns.includes("origin")) {
216
+ db.exec(`ALTER TABLE files ADD COLUMN origin TEXT NOT NULL DEFAULT ''`)
217
+ }
218
+ db.exec(`PRAGMA user_version = 5`)
219
+ }
220
+
221
+ if (version < 6) {
222
+ // v6: code index — code_chunks table + FTS5 (same pattern as files table)
223
+ db.exec(`
224
+ CREATE TABLE IF NOT EXISTS code_chunks (
225
+ path TEXT NOT NULL,
226
+ language TEXT NOT NULL,
227
+ chunk_type TEXT NOT NULL CHECK(chunk_type IN ('file','symbol')),
228
+ symbol_name TEXT NOT NULL DEFAULT '',
229
+ content TEXT NOT NULL,
230
+ line_start INTEGER NOT NULL DEFAULT 0,
231
+ line_end INTEGER NOT NULL DEFAULT 0,
232
+ mtime_ms INTEGER NOT NULL DEFAULT 0,
233
+ embedding BLOB,
234
+ seg_content TEXT NOT NULL DEFAULT '',
235
+ PRIMARY KEY (path, line_start)
236
+ )
237
+ `)
238
+ db.exec(`
239
+ CREATE VIRTUAL TABLE IF NOT EXISTS code_chunks_fts USING fts5(
240
+ path, symbol_name, seg_content,
241
+ content='code_chunks', content_rowid='rowid',
242
+ tokenize='unicode61'
243
+ )
244
+ `)
245
+ db.exec(`
246
+ CREATE TRIGGER code_chunks_ai AFTER INSERT ON code_chunks BEGIN
247
+ INSERT INTO code_chunks_fts(rowid, path, symbol_name, seg_content)
248
+ VALUES (new.rowid, new.path, new.symbol_name, new.seg_content);
249
+ END;
250
+ CREATE TRIGGER code_chunks_ad AFTER DELETE ON code_chunks BEGIN
251
+ INSERT INTO code_chunks_fts(code_chunks_fts, rowid, path, symbol_name, seg_content)
252
+ VALUES ('delete', old.rowid, old.path, old.symbol_name, old.seg_content);
253
+ END;
254
+ CREATE TRIGGER code_chunks_au AFTER UPDATE ON code_chunks BEGIN
255
+ INSERT INTO code_chunks_fts(code_chunks_fts, rowid, path, symbol_name, seg_content)
256
+ VALUES ('delete', old.rowid, old.path, old.symbol_name, old.seg_content);
257
+ INSERT INTO code_chunks_fts(rowid, path, symbol_name, seg_content)
258
+ VALUES (new.rowid, new.path, new.symbol_name, new.seg_content);
259
+ END;
260
+ `)
261
+ db.exec(`PRAGMA user_version = 6`)
262
+ }
263
+
264
+ if (version < 7) {
265
+ // v7: doc index — doc_chunks table + FTS5 (same pattern as code_chunks), markdown chunked by ## headings
266
+ db.exec(`
267
+ CREATE TABLE IF NOT EXISTS doc_chunks (
268
+ path TEXT NOT NULL,
269
+ language TEXT NOT NULL DEFAULT 'markdown',
270
+ heading TEXT NOT NULL DEFAULT '',
271
+ content TEXT NOT NULL,
272
+ line_start INTEGER NOT NULL DEFAULT 0,
273
+ line_end INTEGER NOT NULL DEFAULT 0,
274
+ mtime_ms INTEGER NOT NULL DEFAULT 0,
275
+ embedding BLOB,
276
+ seg_content TEXT NOT NULL DEFAULT '',
277
+ PRIMARY KEY (path, line_start)
278
+ )
279
+ `)
280
+ db.exec(`
281
+ CREATE VIRTUAL TABLE IF NOT EXISTS doc_chunks_fts USING fts5(
282
+ path, heading, seg_content,
283
+ content='doc_chunks', content_rowid='rowid',
284
+ tokenize='unicode61'
285
+ )
286
+ `)
287
+ db.exec(`
288
+ CREATE TRIGGER doc_chunks_ai AFTER INSERT ON doc_chunks BEGIN
289
+ INSERT INTO doc_chunks_fts(rowid, path, heading, seg_content)
290
+ VALUES (new.rowid, new.path, new.heading, new.seg_content);
291
+ END;
292
+ CREATE TRIGGER doc_chunks_ad AFTER DELETE ON doc_chunks BEGIN
293
+ INSERT INTO doc_chunks_fts(doc_chunks_fts, rowid, path, heading, seg_content)
294
+ VALUES ('delete', old.rowid, old.path, old.heading, old.seg_content);
295
+ END;
296
+ CREATE TRIGGER doc_chunks_au AFTER UPDATE ON doc_chunks BEGIN
297
+ INSERT INTO doc_chunks_fts(doc_chunks_fts, rowid, path, heading, seg_content)
298
+ VALUES ('delete', old.rowid, old.path, old.heading, old.seg_content);
299
+ INSERT INTO doc_chunks_fts(rowid, path, heading, seg_content)
300
+ VALUES (new.rowid, new.path, new.heading, new.seg_content);
301
+ END;
302
+ `)
303
+ db.exec(`PRAGMA user_version = 7`)
304
+ }
305
+
306
+ if (version < 8) {
307
+ // v8: add origin column to code_chunks/doc_chunks (absolute project root), PK changed to (origin, path, line_start).
308
+ // SQLite can't ALTER primary key → drop and recreate table (auto-reindexed by next codeSync/docSync).
309
+ db.exec(`
310
+ DROP TRIGGER IF EXISTS code_chunks_ai;
311
+ DROP TRIGGER IF EXISTS code_chunks_ad;
312
+ DROP TRIGGER IF EXISTS code_chunks_au;
313
+ DROP TABLE IF EXISTS code_chunks_fts;
314
+ DROP TABLE IF EXISTS code_chunks;
315
+ DROP TRIGGER IF EXISTS doc_chunks_ai;
316
+ DROP TRIGGER IF EXISTS doc_chunks_ad;
317
+ DROP TRIGGER IF EXISTS doc_chunks_au;
318
+ DROP TABLE IF EXISTS doc_chunks_fts;
319
+ DROP TABLE IF EXISTS doc_chunks;
320
+ `)
321
+ db.exec(`
322
+ CREATE TABLE code_chunks (
323
+ origin TEXT NOT NULL DEFAULT '',
324
+ path TEXT NOT NULL,
325
+ language TEXT NOT NULL,
326
+ chunk_type TEXT NOT NULL CHECK(chunk_type IN ('file','symbol')),
327
+ symbol_name TEXT NOT NULL DEFAULT '',
328
+ content TEXT NOT NULL,
329
+ line_start INTEGER NOT NULL DEFAULT 0,
330
+ line_end INTEGER NOT NULL DEFAULT 0,
331
+ mtime_ms INTEGER NOT NULL DEFAULT 0,
332
+ embedding BLOB,
333
+ seg_content TEXT NOT NULL DEFAULT '',
334
+ PRIMARY KEY (origin, path, line_start)
335
+ )
336
+ `)
337
+ db.exec(`
338
+ CREATE VIRTUAL TABLE code_chunks_fts USING fts5(
339
+ path, symbol_name, seg_content,
340
+ content='code_chunks', content_rowid='rowid',
341
+ tokenize='unicode61'
342
+ )
343
+ `)
344
+ db.exec(`
345
+ CREATE TRIGGER code_chunks_ai AFTER INSERT ON code_chunks BEGIN
346
+ INSERT INTO code_chunks_fts(rowid, path, symbol_name, seg_content)
347
+ VALUES (new.rowid, new.path, new.symbol_name, new.seg_content);
348
+ END;
349
+ CREATE TRIGGER code_chunks_ad AFTER DELETE ON code_chunks BEGIN
350
+ INSERT INTO code_chunks_fts(code_chunks_fts, rowid, path, symbol_name, seg_content)
351
+ VALUES ('delete', old.rowid, old.path, old.symbol_name, old.seg_content);
352
+ END;
353
+ CREATE TRIGGER code_chunks_au AFTER UPDATE ON code_chunks BEGIN
354
+ INSERT INTO code_chunks_fts(code_chunks_fts, rowid, path, symbol_name, seg_content)
355
+ VALUES ('delete', old.rowid, old.path, old.symbol_name, old.seg_content);
356
+ INSERT INTO code_chunks_fts(rowid, path, symbol_name, seg_content)
357
+ VALUES (new.rowid, new.path, new.symbol_name, new.seg_content);
358
+ END;
359
+ `)
360
+ db.exec(`
361
+ CREATE TABLE doc_chunks (
362
+ origin TEXT NOT NULL DEFAULT '',
363
+ path TEXT NOT NULL,
364
+ language TEXT NOT NULL DEFAULT 'markdown',
365
+ heading TEXT NOT NULL DEFAULT '',
366
+ content TEXT NOT NULL,
367
+ line_start INTEGER NOT NULL DEFAULT 0,
368
+ line_end INTEGER NOT NULL DEFAULT 0,
369
+ mtime_ms INTEGER NOT NULL DEFAULT 0,
370
+ embedding BLOB,
371
+ seg_content TEXT NOT NULL DEFAULT '',
372
+ PRIMARY KEY (origin, path, line_start)
373
+ )
374
+ `)
375
+ db.exec(`
376
+ CREATE VIRTUAL TABLE doc_chunks_fts USING fts5(
377
+ path, heading, seg_content,
378
+ content='doc_chunks', content_rowid='rowid',
379
+ tokenize='unicode61'
380
+ )
381
+ `)
382
+ db.exec(`
383
+ CREATE TRIGGER doc_chunks_ai AFTER INSERT ON doc_chunks BEGIN
384
+ INSERT INTO doc_chunks_fts(rowid, path, heading, seg_content)
385
+ VALUES (new.rowid, new.path, new.heading, new.seg_content);
386
+ END;
387
+ CREATE TRIGGER doc_chunks_ad AFTER DELETE ON doc_chunks BEGIN
388
+ INSERT INTO doc_chunks_fts(doc_chunks_fts, rowid, path, heading, seg_content)
389
+ VALUES ('delete', old.rowid, old.path, old.heading, old.seg_content);
390
+ END;
391
+ CREATE TRIGGER doc_chunks_au AFTER UPDATE ON doc_chunks BEGIN
392
+ INSERT INTO doc_chunks_fts(doc_chunks_fts, rowid, path, heading, seg_content)
393
+ VALUES ('delete', old.rowid, old.path, old.heading, old.seg_content);
394
+ INSERT INTO doc_chunks_fts(rowid, path, heading, seg_content)
395
+ VALUES (new.rowid, new.path, new.heading, new.seg_content);
396
+ END;
397
+ `)
398
+ db.exec(`PRAGMA user_version = 8`)
399
+ }
400
+
401
+ if (version < 9) {
402
+ // v9: files table PK gains origin, prevents cross-project project-layer memory overwrite
403
+ // SQLite can't ALTER primary key → drop and recreate table (auto-reindexed by next syncDir)
404
+ db.exec(`
405
+ DROP TRIGGER IF EXISTS files_ai;
406
+ DROP TRIGGER IF EXISTS files_ad;
407
+ DROP TRIGGER IF EXISTS files_au;
408
+ DROP TABLE IF EXISTS files_fts;
409
+ DROP TABLE IF EXISTS files;
410
+ `)
411
+ db.exec(`
412
+ CREATE TABLE files (
413
+ layer TEXT NOT NULL CHECK(layer IN ('project','team')),
414
+ origin TEXT NOT NULL DEFAULT '',
415
+ path TEXT NOT NULL,
416
+ type TEXT NOT NULL CHECK(type IN ('rule','knowledge','decision','pattern')),
417
+ title TEXT NOT NULL,
418
+ content TEXT NOT NULL,
419
+ tags TEXT NOT NULL DEFAULT '',
420
+ author TEXT NOT NULL DEFAULT '',
421
+ embedding BLOB,
422
+ mtime_ms INTEGER NOT NULL DEFAULT 0,
423
+ seg_title TEXT NOT NULL DEFAULT '',
424
+ seg_content TEXT NOT NULL DEFAULT '',
425
+ seg_tags TEXT NOT NULL DEFAULT '',
426
+ updated_at INTEGER NOT NULL,
427
+ PRIMARY KEY (layer, origin, path)
428
+ )
429
+ `)
430
+ db.exec(`
431
+ CREATE VIRTUAL TABLE files_fts USING fts5(
432
+ seg_title, seg_content, seg_tags,
433
+ content='files', content_rowid='rowid',
434
+ tokenize='unicode61'
435
+ )
436
+ `)
437
+ db.exec(`
438
+ CREATE TRIGGER files_ai AFTER INSERT ON files BEGIN
439
+ INSERT INTO files_fts(rowid, seg_title, seg_content, seg_tags)
440
+ VALUES (new.rowid, new.seg_title, new.seg_content, new.seg_tags);
441
+ END;
442
+ CREATE TRIGGER files_ad AFTER DELETE ON files BEGIN
443
+ INSERT INTO files_fts(files_fts, rowid, seg_title, seg_content, seg_tags)
444
+ VALUES ('delete', old.rowid, old.seg_title, old.seg_content, old.seg_tags);
445
+ END;
446
+ CREATE TRIGGER files_au AFTER UPDATE ON files BEGIN
447
+ INSERT INTO files_fts(files_fts, rowid, seg_title, seg_content, seg_tags)
448
+ VALUES ('delete', old.rowid, old.seg_title, old.seg_content, old.seg_tags);
449
+ INSERT INTO files_fts(rowid, seg_title, seg_content, seg_tags)
450
+ VALUES (new.rowid, new.seg_title, new.seg_content, new.seg_tags);
451
+ END;
452
+ `)
453
+ db.exec(`PRAGMA user_version = 9`)
454
+ }
455
+ db.exec("COMMIT")
456
+ } catch (err) {
457
+ db.exec("ROLLBACK")
458
+ throw err
459
+ }
460
+ }
package/memory.mjs ADDED
@@ -0,0 +1,21 @@
1
+ /**
2
+ * memory.mjs — memory system re-export hub
3
+ * Submodules in src/memory/ directory, split by responsibility.
4
+ */
5
+
6
+ // schema + constants
7
+ export { createMemory, migrate, segmentCJK, VALID_TYPES, SCHEMA_VERSION, CODE_EXTS, DOC_EXTS, SKIP_DIRS, BIG_FILE_LINES } from "./memory/schema.mjs"
8
+
9
+ // CRUD + search + ensureEmbeddings
10
+ // (delete.mjs owns the disk-truth row match + deletion family — see its header)
11
+ export { put, search, ftsSearch, fetchEntry, ensureEmbeddings, putMarkdown, syncDir, indexMarkdownFile, list, clearPersonal, buildFtsQuery } from "./memory/core.mjs"
12
+ export { matchMemoryRows, deleteWhere, deleteByUid, remove } from "./memory/delete.mjs"
13
+
14
+ // code chunking + markdown chunking
15
+ export { detectLanguage, extractSymbols, extractPySymbols, chunkCode, extractLeadingDoc, yieldTick, _upsertCodeFile, chunkMarkdown, _upsertDocFile } from "./memory/code-index.mjs"
16
+
17
+ // code sync + search + reindex
18
+ export { gitSync, codeSync, markIndexedCommit, codeSearch, ensureCodeEmbeddings, codeSearchTool, reindexFile } from "./memory/code-sync.mjs"
19
+
20
+ // doc sync + search + memoryTools
21
+ export { docSync, docSearch, ensureDocEmbeddings, docSearchTool, memoryTools } from "./memory/docs.mjs"