@yolk_vat-y/dsh-project-memory 0.3.0 → 0.3.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/CHANGELOG.md +1 -1
- package/README.md +3 -3
- package/README.zh-CN.md +3 -3
- package/package.json +1 -1
- package/src/doc-pipeline.js +7 -5
- package/src/lazy.js +2 -2
- package/src/symbols.js +15 -13
- package/src/tools/index-doc.js +1 -1
- package/src/tools/index-repo.js +2 -2
- package/src/tools/query-memory.js +9 -4
- package/src/watch.js +2 -2
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[English](README.md) | [简体中文](README.zh-CN.md)
|
|
4
4
|
|
|
5
|
-
[](https://github.com/00080000/dsh-project-memory/actions/workflows/ci.yml) [](LICENSE) [](https://www.npmjs.com/package/@yolk_vat-y/dsh-project-memory) [](https://dsh-plugin.org/plugins/00080000/dsh-project-memory)
|
|
5
|
+
[](https://github.com/00080000/dsh-project-memory/actions/workflows/ci.yml) [](LICENSE) [](https://www.npmjs.com/package/@yolk_vat-y/dsh-project-memory) [](https://dsh-plugin.org/plugins/00080000/dsh-project-memory) [](https://awesome-dsh-plugin.com)
|
|
6
6
|
|
|
7
7
|
Persistent project memory for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(dsh) agents. Indexes documents (PDF / Markdown / txt) and code symbols into a per-workspace store, refreshes them automatically, and recalls them with source citations — documents are cross-linked to the code symbols they reference.
|
|
8
8
|
|
|
@@ -18,6 +18,7 @@ Persistent project memory for [DeepSeek Harness](https://github.com/deepseek-ai/
|
|
|
18
18
|
- **BM25 retrieval** — ranked search over documents, symbols, and experience notes, with optional LLM query expansion to handle vocabulary mismatch. **CJK-optimized**: precise phrase boost (3+ char phrases ×1.5 score on title/keywords match), synonym table (e.g. 数据库连接池 ↔ 连接池 ↔ DB pool), and CJK-aware word boundaries for doc↔symbol linking.
|
|
19
19
|
- **Experience notes** — problems → solutions; similar problems supersede instead of duplicating, and notes are returned only when a search matches. The note store is bounded: capacity scales with project size (clamped to 100–2000), and the oldest notes are pruned when the limit is exceeded. **Supersede tightened to bidirectional 0.7 overlap** (was 0.6); **experience `problem` field now participates in CJK phrase boost** for long-tail query recall.
|
|
20
20
|
- **Minimal dependencies** — pure JavaScript; the only runtime dependency is `pdfjs-dist` (PDF text extraction), no native builds required.
|
|
21
|
+
- **Negligible overhead** — pure in-process operation; cold start <100 ms (5k files), typical project query median 2–3 ms (p99 < 7 ms); bottleneck is LLM summarization and PDF parsing, not the plugin.
|
|
21
22
|
|
|
22
23
|
## How it works
|
|
23
24
|
|
|
@@ -51,7 +52,7 @@ dsh plugin --profile web add @yolk_vat-y/dsh-project-memory -w
|
|
|
51
52
|
A prebuilt tarball is published with each release, installable without a build step:
|
|
52
53
|
|
|
53
54
|
```bash
|
|
54
|
-
dsh plugin --profile web add /path/to/dsh-project-memory-0.
|
|
55
|
+
dsh plugin --profile web add /path/to/dsh-project-memory-0.3.0.tgz
|
|
55
56
|
```
|
|
56
57
|
|
|
57
58
|
Each indexed project has its own store at `<root>/.dsh-project-memory/`. Add it to `.gitignore` if it should not be committed.
|
|
@@ -95,7 +96,6 @@ These are deliberate scope choices.
|
|
|
95
96
|
- **In-process locking** — store writes are serialized per memory directory within one dsh process; two dsh instances sharing a project store is last-writer-wins. A cross-process lock would need a resident daemon, which conflicts with the pure-JS, no-background-service positioning, so multi-instance writes are explicitly unsupported.
|
|
96
97
|
- **Watch poll holds the lock** — while the watcher re-indexes changed docs (LLM summarization), `remember`/`forget` queue behind it. Polling (mtime + content hash) instead of `fs.watch` events keeps behavior consistent across platforms; overlapping polls serialize on the same lock: safe, but they can pile up on very large diffs. Tune via `watchInterval`.
|
|
97
98
|
- **Corrupt files are quarantined** — a store JSON that fails to parse falls back to empty for that file and is rebuilt on the next write; the broken file is renamed to `*.corrupt` with an error logged, but its data cannot be recovered. Auto-repairing partial writes would need a write-ahead journal or an embedded database — out of proportion when quarantining one bad file costs nothing.
|
|
98
|
-
- **Absolute source paths** — entries cite absolute paths; moving a project invalidates citations until the next re-index.
|
|
99
99
|
- **`forget` by query is eager** — keyword deletion matches at ≥0.5 token overlap and may remove several notes at once; prefer deleting by id for precision.
|
|
100
100
|
- **Cross-language recall depends on index time** — with `llmQueryExpansion` off, a Chinese-only query reaches English content through bilingual keywords captured when docs are indexed, plus doc↔symbol links; queries stay LLM-free. Stores indexed before v0.1.1 gain bilingual keywords as files change, or immediately via `index_repo` with `reindex: true`.
|
|
101
101
|
- **CJK retrieval** — phrase boost and synonym expansion are purely query-side; they do not increase index size or LLM usage. Link boundaries use CJK-aware regex only; English symbols keep the original word-boundary behavior. The experience supersede threshold (0.7 bidirectional) is a conservative default; adjust via config if false positives/negatives appear in practice.
|
package/README.zh-CN.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[English](README.md) | [简体中文](README.zh-CN.md)
|
|
4
4
|
|
|
5
|
-
[](https://github.com/00080000/dsh-project-memory/actions/workflows/ci.yml) [](LICENSE) [](https://www.npmjs.com/package/@yolk_vat-y/dsh-project-memory) [](https://dsh-plugin.org/plugins/00080000/dsh-project-memory)
|
|
5
|
+
[](https://github.com/00080000/dsh-project-memory/actions/workflows/ci.yml) [](LICENSE) [](https://www.npmjs.com/package/@yolk_vat-y/dsh-project-memory) [](https://dsh-plugin.org/plugins/00080000/dsh-project-memory) [](https://awesome-dsh-plugin.com)
|
|
6
6
|
|
|
7
7
|
为 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(dsh)agent 提供持久化的项目记忆。将文档(PDF / Markdown / txt)与代码符号索引进每个工作区独立的存储库,自动维护更新,召回时附源文件引用——文档自动交叉链接到其提及的代码符号。
|
|
8
8
|
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
- **BM25 检索** — 对文档、符号与经验笔记进行排序召回,可选 LLM 查询扩展以应对表述不一致。**CJK 增强**:精确短语乘法加分(3+ 字短语在标题/关键词命中 ×1.5)、同义词表(如 数据库连接池 ↔ 连接池 ↔ DB pool)、CJK 感知的文档↔符号链接边界。
|
|
19
19
|
- **经验笔记** — 记录问题 → 方案;相似问题覆盖而非重复;笔记仅在检索命中时返回。笔记数量有界:容量随项目规模伸缩(钳制在 100–2000),超限时淘汰最旧的笔记。**覆盖阈值收紧为双向 0.7 重叠**(原 0.6);**经验 `problem` 字段现参与 CJK 短语加分**,提升长尾问句召回。
|
|
20
20
|
- **依赖极简** — 纯 JavaScript;唯一运行时依赖是 `pdfjs-dist`(PDF 文本提取),无需原生构建。
|
|
21
|
+
- **开销可忽略** — 纯进程内操作;冷启动 <100 ms(5k 文件),典型项目查询中位数 2–3 ms(p99 < 7 ms);瓶颈在 LLM 摘要与 PDF 解析,插件本身不阻塞。
|
|
21
22
|
|
|
22
23
|
## 工作原理
|
|
23
24
|
|
|
@@ -51,7 +52,7 @@ dsh plugin --profile web add @yolk_vat-y/dsh-project-memory -w
|
|
|
51
52
|
每个版本会附带预构建 tarball,无需构建步骤即可安装:
|
|
52
53
|
|
|
53
54
|
```bash
|
|
54
|
-
dsh plugin --profile web add /path/to/dsh-project-memory-0.
|
|
55
|
+
dsh plugin --profile web add /path/to/dsh-project-memory-0.3.0.tgz
|
|
55
56
|
```
|
|
56
57
|
|
|
57
58
|
每个被索引的项目在 `<root>/.dsh-project-memory/` 下有独立存储。如无需入库,可加入 `.gitignore`。
|
|
@@ -95,7 +96,6 @@ v0.2.0 之前创建的库(单文件 `entries.json` / `index.json`)在首次
|
|
|
95
96
|
- **无锁同步事务** — 不采用锁:所有写入(index / watch / remember / forget / watch_repo)统一走同步事务 `store.commit(fn)`,fn 成功后才一次落盘;JS 单线程事件循环保证事务间不交错,`remember`/`forget` 不会被 watch 重索引阻塞排队。两个 dsh 实例共享同一项目存储时后写覆盖先写;跨进程协调需要常驻守护进程,违背纯 JS 插件、无后台服务的定位,故明确不支持多实例共写。
|
|
96
97
|
- **watch 计算与提交分离** — watcher 在事务外完成 mtime/哈希/符号扫描/LLM 摘要等重活,再以单次 `commit` 原子应用全部变更;`applyFileUpdate` 用 CAS 校验(统一 null 处理、删除跳过对比)防并发修改,失败回滚 snapshot 下轮重试,索引失败删除 snapshot 自动重试。轮询(mtime + 内容哈希)而非 `fs.watch` 事件驱动,是为了跨平台行为一致。间隔可用 `watchInterval` 调整。
|
|
97
98
|
- **损坏隔离重建** — 存储 JSON 损坏时该文件回落为空并在下次写入时重建;坏文件会改名备份为 `*.corrupt` 并输出错误日志,但该文件内的数据无法恢复。自动修复半写文件需要预写日志或嵌入式数据库,代价与收益不成比例——而隔离一个坏文件的成本几乎为零。
|
|
98
|
-
- **绝对路径引用** — 条目引用绝对路径;项目搬家后引用失效,重建索引即恢复。
|
|
99
99
|
- **`forget` 按关键词删除偏激进** — 关键词删除按 ≥0.5 token 重叠匹配,可能一次删掉多条;追求精确请用 id 删除。
|
|
100
100
|
- **跨语种召回依赖索引时** — `llmQueryExpansion` 关闭时,纯中文查询靠索引时捕获的双语 keywords 和 doc↔symbol 链接触达英文内容,查询侧保持零 LLM 调用。v0.1.1 之前建立的索引随文件变更逐步获得双语关键词,或用 `index_repo` 的 `reindex: true` 立即重建。
|
|
101
101
|
- **CJK 检索** — 短语加分与同义词展开完全在查询侧,不增加索引体积、不额外消耗 LLM token。链接边界仅对 CJK 使用正则边界,英文符号保持原有词边界行为。经验层 supersede 阈值(双向 0.7)为保守默认;若实测出现误覆盖/误漏报,可通过配置调整。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yolk_vat-y/dsh-project-memory",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Persistent project memory for dsh agents: index docs (PDF/Markdown/text) and code symbols into a searchable per-workspace store, recall them with cited sources, and keep experience entries (problems -> solutions) searchable on demand.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
package/src/doc-pipeline.js
CHANGED
|
@@ -22,10 +22,12 @@ export async function extractTextFromFile(filePath, { maxFileSizeMb = 50, maxPdf
|
|
|
22
22
|
|
|
23
23
|
const DOC_CONCURRENCY = 4
|
|
24
24
|
|
|
25
|
-
export async function buildDocEntries(llm,
|
|
26
|
-
|
|
25
|
+
export async function buildDocEntries(llm, a, b, c) {
|
|
26
|
+
// Backward compatible: old signature (llm, filePath, opts) or new (llm, relPath, filePath, opts)
|
|
27
|
+
const [relPath, filePath, opts] = c === undefined ? [a, a, b] : [a, b, c]
|
|
28
|
+
const text = await extractTextFromFile(filePath, opts)
|
|
27
29
|
if (looksLikeDump(text)) return null
|
|
28
|
-
const chunks = chunkText(text, chunkChars, maxChunks)
|
|
30
|
+
const chunks = chunkText(text, opts.chunkChars, opts.maxChunks)
|
|
29
31
|
const metas = new Array(chunks.length)
|
|
30
32
|
let cursor = 0
|
|
31
33
|
await Promise.all(
|
|
@@ -39,8 +41,8 @@ export async function buildDocEntries(llm, filePath, { chunkChars = 3000, maxChu
|
|
|
39
41
|
),
|
|
40
42
|
)
|
|
41
43
|
return metas.map((meta, i) => ({
|
|
42
|
-
id: `${relativeId(
|
|
43
|
-
sourcePath:
|
|
44
|
+
id: `${relativeId(relPath)}#${i}`,
|
|
45
|
+
sourcePath: relPath,
|
|
44
46
|
sourceLine: chunks[i].line,
|
|
45
47
|
type: 'doc',
|
|
46
48
|
title: meta.title,
|
package/src/lazy.js
CHANGED
|
@@ -104,7 +104,7 @@ export async function indexFile(ctx, config, filePath, watchManager = null) {
|
|
|
104
104
|
|
|
105
105
|
let entries
|
|
106
106
|
if (isSupportedCode(ext)) {
|
|
107
|
-
entries = scanSymbols(filePath, readFileSync(filePath, 'utf8'))
|
|
107
|
+
entries = scanSymbols(rel, filePath, readFileSync(filePath, 'utf8'))
|
|
108
108
|
return store.commit((s) => {
|
|
109
109
|
s.markFile(rel, { sha256: hash, size, type: 'code', indexedAt: new Date().toISOString() })
|
|
110
110
|
s.setEntries(rel, entries)
|
|
@@ -112,7 +112,7 @@ export async function indexFile(ctx, config, filePath, watchManager = null) {
|
|
|
112
112
|
return true
|
|
113
113
|
})
|
|
114
114
|
} else {
|
|
115
|
-
entries = await buildDocEntries(ctx.llm, filePath, {
|
|
115
|
+
entries = await buildDocEntries(ctx.llm, rel, filePath, {
|
|
116
116
|
chunkChars: config.chunkChars,
|
|
117
117
|
maxChunks: config.maxChunksPerFile,
|
|
118
118
|
maxFileSizeMb: config.maxFileSizeMb,
|
package/src/symbols.js
CHANGED
|
@@ -120,7 +120,7 @@ const JS_NON_METHOD = new Set([
|
|
|
120
120
|
'new', 'delete', 'typeof', 'instanceof', 'void', 'await', 'yield', 'with',
|
|
121
121
|
])
|
|
122
122
|
|
|
123
|
-
function scanJsLike(masked,
|
|
123
|
+
function scanJsLike(masked, relPath, rawLines) {
|
|
124
124
|
const symbols = []
|
|
125
125
|
let prevOpensBlock = false
|
|
126
126
|
for (let i = 0; i < masked.length; i++) {
|
|
@@ -148,13 +148,13 @@ function scanJsLike(masked, filePath, rawLines) {
|
|
|
148
148
|
if (/[{};]/.test(tail)) break
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
|
-
if (matched) symbols.push(buildSymbol(matched,
|
|
151
|
+
if (matched) symbols.push(buildSymbol(matched, relPath, rawLines[i], i + 1))
|
|
152
152
|
prevOpensBlock = masked[i].trim().endsWith('{')
|
|
153
153
|
}
|
|
154
154
|
return symbols
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
function scanPython(masked,
|
|
157
|
+
function scanPython(masked, relPath, rawLines) {
|
|
158
158
|
const symbols = []
|
|
159
159
|
let depth = 0
|
|
160
160
|
for (let i = 0; i < masked.length; i++) {
|
|
@@ -169,7 +169,7 @@ function scanPython(masked, filePath, rawLines) {
|
|
|
169
169
|
const cls = fn ? null : text.match(/^class\s+(\w+)\s*[(:]/)
|
|
170
170
|
if (fn) matched = { name: fn[1], kind: 'function' }
|
|
171
171
|
else if (cls) matched = { name: cls[1], kind: 'class' }
|
|
172
|
-
if (matched) symbols.push(buildSymbol(matched,
|
|
172
|
+
if (matched) symbols.push(buildSymbol(matched, relPath, rawLines[i], i + 1))
|
|
173
173
|
depth += delta
|
|
174
174
|
}
|
|
175
175
|
return symbols
|
|
@@ -208,24 +208,26 @@ function matchShell(line) {
|
|
|
208
208
|
return m ? { name: m[1], kind: 'function' } : null
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
-
function buildSymbol(matched,
|
|
211
|
+
function buildSymbol(matched, relPath, rawLine, lineNo) {
|
|
212
212
|
return {
|
|
213
|
-
id: `${String(
|
|
214
|
-
sourcePath:
|
|
213
|
+
id: `${String(relPath).replace(/[\\/:\s]/g, '_')}#${lineNo}`,
|
|
214
|
+
sourcePath: relPath,
|
|
215
215
|
sourceLine: lineNo,
|
|
216
216
|
type: 'symbol',
|
|
217
217
|
title: `${matched.name} (${matched.kind})`,
|
|
218
|
-
summary: `${matched.kind} "${matched.name}" declared at ${
|
|
218
|
+
summary: `${matched.kind} "${matched.name}" declared at ${relPath}:${lineNo}`,
|
|
219
219
|
keywords: [matched.name, matched.kind],
|
|
220
220
|
text: String(rawLine).trim().slice(0, 200),
|
|
221
221
|
}
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
-
export function scanSymbols(
|
|
225
|
-
|
|
224
|
+
export function scanSymbols(a, b, c) {
|
|
225
|
+
// Backward compatible: old signature (filePath, content) or new (relPath, filePath, content)
|
|
226
|
+
const [relPath, filePath, content] = c === undefined ? [a, a, b] : [a, b, c]
|
|
227
|
+
const ext = relPath.slice(relPath.lastIndexOf('.'))
|
|
226
228
|
const lines = content.split(/\r?\n/)
|
|
227
|
-
if (JS_LIKE.has(ext)) return scanJsLike(maskTokens(lines, JS_MASKER),
|
|
228
|
-
if (PYTHON.has(ext)) return scanPython(maskTokens(lines, PY_MASKER),
|
|
229
|
+
if (JS_LIKE.has(ext)) return scanJsLike(maskTokens(lines, JS_MASKER), relPath, lines)
|
|
230
|
+
if (PYTHON.has(ext)) return scanPython(maskTokens(lines, PY_MASKER), relPath, lines)
|
|
229
231
|
|
|
230
232
|
let masker = null
|
|
231
233
|
if (GO.has(ext)) masker = GO_MASKER
|
|
@@ -247,7 +249,7 @@ export function scanSymbols(filePath, content) {
|
|
|
247
249
|
else if (/^(?:def|func|fn|function)\s+(\w+)/.test(line)) {
|
|
248
250
|
matched = { name: line.match(/^(?:def|func|fn|function)\s+(\w+)/)[1], kind: 'function' }
|
|
249
251
|
}
|
|
250
|
-
if (matched) symbols.push(buildSymbol(matched,
|
|
252
|
+
if (matched) symbols.push(buildSymbol(matched, relPath, raw, i + 1))
|
|
251
253
|
}
|
|
252
254
|
return symbols
|
|
253
255
|
}
|
package/src/tools/index-doc.js
CHANGED
|
@@ -42,7 +42,7 @@ export function indexDocTool(ctx, config) {
|
|
|
42
42
|
return `Skipped (unchanged): ${rel}\nAlready indexed with ${(store.entries[rel] || []).length} entry/entries.`
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
const entries = await buildDocEntries(ctx.llm, filePath, {
|
|
45
|
+
const entries = await buildDocEntries(ctx.llm, rel, filePath, {
|
|
46
46
|
chunkChars: config.chunkChars,
|
|
47
47
|
maxChunks: config.maxChunksPerFile,
|
|
48
48
|
maxFileSizeMb: config.maxFileSizeMb,
|
package/src/tools/index-repo.js
CHANGED
|
@@ -47,7 +47,7 @@ export async function indexRepository(ctx, config, root, { reindex = false } = {
|
|
|
47
47
|
let entries
|
|
48
48
|
if (isSupportedCode(ext)) {
|
|
49
49
|
const content = readFileSync(filePath, 'utf8')
|
|
50
|
-
entries = scanSymbols(filePath, content)
|
|
50
|
+
entries = scanSymbols(rel, filePath, content)
|
|
51
51
|
fileUpdates.push({ rel, expectedHash: existing?.sha256, hash, size, entries, type: 'code' })
|
|
52
52
|
updated++
|
|
53
53
|
} else {
|
|
@@ -57,7 +57,7 @@ export async function indexRepository(ctx, config, root, { reindex = false } = {
|
|
|
57
57
|
skipped++
|
|
58
58
|
continue
|
|
59
59
|
}
|
|
60
|
-
entries = await buildDocEntries(ctx.llm, filePath, {
|
|
60
|
+
entries = await buildDocEntries(ctx.llm, rel, filePath, {
|
|
61
61
|
chunkChars: config.chunkChars,
|
|
62
62
|
maxChunks: config.maxChunksPerFile,
|
|
63
63
|
maxFileSizeMb: config.maxFileSizeMb,
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
2
|
+
import path from 'node:path'
|
|
2
3
|
import { memoryRootFor, resolveIndexRoot } from '../util/fs.js'
|
|
3
4
|
import { ProjectMemoryStore, storeOverview } from '../store.js'
|
|
4
5
|
import { expandQuery } from '../llm.js'
|
|
5
6
|
import { rankEntriesMergedScored, rankExperienceScored } from '../util/search.js'
|
|
6
7
|
import { truncate } from '../util/text.js'
|
|
7
8
|
|
|
9
|
+
function toAbs(root, rel) {
|
|
10
|
+
return path.isAbsolute(rel) ? rel : path.join(root, rel)
|
|
11
|
+
}
|
|
12
|
+
|
|
8
13
|
export function queryMemoryTool(ctx, config) {
|
|
9
14
|
return defineTool({
|
|
10
15
|
name: 'query_memory',
|
|
@@ -59,13 +64,13 @@ export function queryMemoryTool(ctx, config) {
|
|
|
59
64
|
const top = scored[0].score || 1
|
|
60
65
|
lines.push(`## Memory (${type === 'all' ? 'docs + symbols' : type})`)
|
|
61
66
|
for (const { entry: e, score } of scored) {
|
|
62
|
-
const
|
|
67
|
+
const absSource = e.sourceLine ? `${toAbs(root, e.sourcePath)}:${e.sourceLine}` : toAbs(root, e.sourcePath)
|
|
63
68
|
const rel = Math.round((score / top) * 100)
|
|
64
|
-
lines.push(`### ${e.title} (score: ${rel})\n- source: ${
|
|
69
|
+
lines.push(`### ${e.title} (score: ${rel})\n- source: ${absSource}\n- ${e.summary}`)
|
|
65
70
|
if (e.type === 'doc' && Array.isArray(e.linkedSymbols) && e.linkedSymbols.length) {
|
|
66
71
|
const refs = e.linkedSymbols.slice(0, 5).map((id) => {
|
|
67
72
|
const s = symbolById.get(id)
|
|
68
|
-
return s ? `${s.title} @ ${s.sourcePath}:${s.sourceLine}` : id
|
|
73
|
+
return s ? `${s.title} @ ${toAbs(root, s.sourcePath)}:${s.sourceLine}` : id
|
|
69
74
|
})
|
|
70
75
|
lines.push(`- references: ${refs.join('; ')}`)
|
|
71
76
|
}
|
|
@@ -78,7 +83,7 @@ export function queryMemoryTool(ctx, config) {
|
|
|
78
83
|
const expTop = scoredExp[0].score || 1
|
|
79
84
|
lines.push(`## Experience (past problems -> solutions)`)
|
|
80
85
|
for (const { item: e, score } of scoredExp) {
|
|
81
|
-
const source = e.sourceFile ? ` (source: ${e.sourceFile})` : ''
|
|
86
|
+
const source = e.sourceFile ? ` (source: ${toAbs(root, e.sourceFile)})` : ''
|
|
82
87
|
lines.push(
|
|
83
88
|
`### Problem: ${e.problem} (score: ${Math.round((score / expTop) * 100)}, id: ${e.id})\n- solution: ${e.solution}${source}\n- updated: ${e.updatedAt}`,
|
|
84
89
|
)
|
package/src/watch.js
CHANGED
|
@@ -95,9 +95,9 @@ export class WatchManager {
|
|
|
95
95
|
try {
|
|
96
96
|
let entries
|
|
97
97
|
if (isSupportedCode(ext)) {
|
|
98
|
-
entries = scanSymbols(filePath, readFileSync(filePath, 'utf8'))
|
|
98
|
+
entries = scanSymbols(rel, filePath, readFileSync(filePath, 'utf8'))
|
|
99
99
|
} else {
|
|
100
|
-
entries = await buildDocEntries(this.ctx.llm, filePath, {
|
|
100
|
+
entries = await buildDocEntries(this.ctx.llm, rel, filePath, {
|
|
101
101
|
chunkChars: this.config.chunkChars,
|
|
102
102
|
maxChunks: this.config.maxChunksPerFile,
|
|
103
103
|
maxFileSizeMb: this.config.maxFileSizeMb,
|