@yolk_vat-y/dsh-project-memory 0.1.6 → 0.2.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/CHANGELOG.md +10 -0
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/package.json +1 -1
- package/src/link.js +6 -2
- package/src/store.js +4 -2
- package/src/util/search.js +51 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.0 (2026-08-27)
|
|
4
|
+
|
|
5
|
+
### CJK 检索增强
|
|
6
|
+
- 链接侧:非拉丁符号名改用 CJK 边界正则 `(?<![CJK])名(?![CJK])`,解决 `用户服务` 误链 `用户服务管理器`;英文分支零改动
|
|
7
|
+
- 查询侧:BM25 增加精确短语乘法加分 —— 3+ 字 CJK 短语命中 `title`/`keywords` 时 `score *= 1.5` 并重排,自适应不压过高相关结果
|
|
8
|
+
- 查询侧:同义词表(`数据库连接池` ↔ `连接池` ↔ `DB pool`),查询展开后再走短语加分
|
|
9
|
+
|
|
10
|
+
### 经验笔记 supersede 阈值收紧
|
|
11
|
+
- 双向重叠判定:`overlap / query_tokens ≥ 0.7` 且 `overlap / item_tokens ≥ 0.7`(原单向 0.6),减少短问题误吞长笔记
|
|
12
|
+
|
|
3
13
|
## 0.1.6 (2026-08-25)
|
|
4
14
|
|
|
5
15
|
### 存储
|
package/README.md
CHANGED
|
@@ -149,7 +149,7 @@ These commands are for **maintaining the plugin code** — regular users do not
|
|
|
149
149
|
|
|
150
150
|
```bash
|
|
151
151
|
npm install
|
|
152
|
-
npm test #
|
|
152
|
+
npm test # chunker / symbols / store / tools / BM25 / links / watch / lazy / config / dump / concurrency / restore / size limit
|
|
153
153
|
```
|
|
154
154
|
|
|
155
155
|
## License
|
package/README.zh-CN.md
CHANGED
|
@@ -149,7 +149,7 @@ dsh web --patch ./config.yml
|
|
|
149
149
|
|
|
150
150
|
```bash
|
|
151
151
|
npm install
|
|
152
|
-
npm test #
|
|
152
|
+
npm test # 检查项:chunker / symbols / store / tools / BM25 / links / watch / lazy / config / dump / concurrency / restore / size limit
|
|
153
153
|
```
|
|
154
154
|
|
|
155
155
|
## 许可证
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yolk_vat-y/dsh-project-memory",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
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/link.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
const LATIN_NAME = /^[A-Za-z0-9_]+$/
|
|
2
|
+
const CJK_CHAR = /[\u3400-\u9fff\uf900-\ufaff\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af]/
|
|
2
3
|
|
|
3
4
|
function buildMatcher(name) {
|
|
4
5
|
const lower = name.toLowerCase()
|
|
5
|
-
if (
|
|
6
|
-
|
|
6
|
+
if (LATIN_NAME.test(name)) {
|
|
7
|
+
return { lower, re: new RegExp(`(?<![a-z0-9_$])${lower}(?![a-z0-9_$])`) }
|
|
8
|
+
}
|
|
9
|
+
const escaped = lower.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
10
|
+
return { lower, re: new RegExp(`(?<!${CJK_CHAR.source})${escaped}(?!${CJK_CHAR.source})`) }
|
|
7
11
|
}
|
|
8
12
|
|
|
9
13
|
export function linkEntries(store) {
|
package/src/store.js
CHANGED
|
@@ -303,8 +303,10 @@ export class ProjectMemoryStore {
|
|
|
303
303
|
for (const item of this.experience) {
|
|
304
304
|
const itemTokens = tokenize(item.problem)
|
|
305
305
|
const overlap = itemTokens.filter((t) => tokens.includes(t)).length
|
|
306
|
-
|
|
307
|
-
|
|
306
|
+
if (overlap === 0) continue
|
|
307
|
+
const ratioQuery = overlap / tokens.length
|
|
308
|
+
const ratioItem = overlap / itemTokens.length
|
|
309
|
+
if (ratioQuery >= 0.7 && ratioItem >= 0.7 && overlap > bestOverlap) {
|
|
308
310
|
best = item
|
|
309
311
|
bestOverlap = overlap
|
|
310
312
|
}
|
package/src/util/search.js
CHANGED
|
@@ -1,6 +1,42 @@
|
|
|
1
1
|
export const CJK_RANGE =
|
|
2
2
|
/[\u3400-\u9fff\uf900-\ufaff\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af]/
|
|
3
3
|
|
|
4
|
+
const SYNONYMS = new Map([
|
|
5
|
+
['数据库连接池', ['连接池', 'DB pool', 'db pool']],
|
|
6
|
+
['连接池', ['数据库连接池', 'DB pool', 'db pool']],
|
|
7
|
+
['DB pool', ['数据库连接池', '连接池']],
|
|
8
|
+
['db pool', ['数据库连接池', '连接池']],
|
|
9
|
+
])
|
|
10
|
+
|
|
11
|
+
function extractCjkPhrases(text) {
|
|
12
|
+
const phrases = []
|
|
13
|
+
let run = ''
|
|
14
|
+
for (const ch of text) {
|
|
15
|
+
if (CJK_RANGE.test(ch)) {
|
|
16
|
+
run += ch
|
|
17
|
+
} else if (run.length >= 3) {
|
|
18
|
+
phrases.push(run)
|
|
19
|
+
run = ''
|
|
20
|
+
} else {
|
|
21
|
+
run = ''
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
if (run.length >= 3) phrases.push(run)
|
|
25
|
+
return phrases
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function expandQuery(query) {
|
|
29
|
+
const lower = query.toLowerCase()
|
|
30
|
+
const expanded = new Set([lower])
|
|
31
|
+
for (const [key, vals] of SYNONYMS) {
|
|
32
|
+
if (lower.includes(key.toLowerCase())) {
|
|
33
|
+
for (const v of vals) expanded.add(v.toLowerCase())
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const cjkPhrases = extractCjkPhrases(query)
|
|
37
|
+
return { original: lower, expanded: [...expanded], cjkPhrases }
|
|
38
|
+
}
|
|
39
|
+
|
|
4
40
|
export function tokenizeRaw(text) {
|
|
5
41
|
if (!text) return []
|
|
6
42
|
const lower = text.toLowerCase()
|
|
@@ -43,7 +79,9 @@ export function buildBm25(docs, getFieldText) {
|
|
|
43
79
|
const terms = tokenizeRaw(text)
|
|
44
80
|
const tf = {}
|
|
45
81
|
for (const t of terms) tf[t] = (tf[t] || 0) + 1
|
|
46
|
-
|
|
82
|
+
const title = (doc.title || '').toLowerCase()
|
|
83
|
+
const keywords = (doc.keywords || []).join(' ').toLowerCase()
|
|
84
|
+
return { doc, length: terms.length, tf, title, keywords }
|
|
47
85
|
})
|
|
48
86
|
const df = {}
|
|
49
87
|
for (const d of documents) {
|
|
@@ -55,7 +93,12 @@ export function buildBm25(docs, getFieldText) {
|
|
|
55
93
|
return {
|
|
56
94
|
idf,
|
|
57
95
|
score(query) {
|
|
58
|
-
const
|
|
96
|
+
const { expanded, cjkPhrases } = expandQuery(query)
|
|
97
|
+
const qTokens = new Set()
|
|
98
|
+
for (const term of expanded) {
|
|
99
|
+
for (const tok of tokenizeRaw(term)) qTokens.add(tok)
|
|
100
|
+
}
|
|
101
|
+
const q = [...qTokens]
|
|
59
102
|
if (!q.length) return []
|
|
60
103
|
const avgLen = avgdl
|
|
61
104
|
return documents
|
|
@@ -67,6 +110,12 @@ export function buildBm25(docs, getFieldText) {
|
|
|
67
110
|
if (!tf) continue
|
|
68
111
|
score += idf(t) * ((tf * (K1 + 1)) / (tf + K1 * (1 - B + (B * len) / avgLen)))
|
|
69
112
|
}
|
|
113
|
+
for (const phrase of cjkPhrases) {
|
|
114
|
+
const lowerPhrase = phrase.toLowerCase()
|
|
115
|
+
if (d.title.includes(lowerPhrase) || d.keywords.includes(lowerPhrase)) {
|
|
116
|
+
score *= 1.5
|
|
117
|
+
}
|
|
118
|
+
}
|
|
70
119
|
return { doc: d.doc, score }
|
|
71
120
|
})
|
|
72
121
|
.filter((r) => r.score > 0)
|