@yolk_vat-y/dsh-project-memory 0.5.0 → 0.5.2
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 +46 -1
- package/README.md +10 -10
- package/README.zh-CN.md +10 -10
- package/client/client.js +164 -96
- package/client/client.js.map +1 -1
- package/package.json +2 -2
- package/src/auto-inject.js +77 -75
- package/src/client/TaskComponents.tsx +123 -20
- package/src/client/TaskPanel.module.css +29 -1
- package/src/index.js +16 -8
- package/src/lazy.js +5 -4
- package/src/setup/taskbridge.js +64 -8
- package/src/store.js +7 -0
- package/src/tools/index-repo.js +6 -7
- package/src/util/fs.js +13 -1
- package/src/watch.js +26 -12
package/src/tools/index-repo.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
2
2
|
import path from 'node:path'
|
|
3
|
-
import {
|
|
4
|
-
import { isSupportedCode, isSupportedDoc, looksLikeDump, memoryRootFor,
|
|
3
|
+
import { statSync } from 'node:fs'
|
|
4
|
+
import { isSupportedCode, isSupportedDoc, looksLikeDump, memoryRootFor, readFileForIndex, relativePath, storeKey, walkDir } from '../util/fs.js'
|
|
5
5
|
import { buildDocEntries } from '../doc-pipeline.js'
|
|
6
6
|
import { scanSymbols } from '../symbols.js'
|
|
7
7
|
import { linkEntries } from '../link.js'
|
|
@@ -38,8 +38,8 @@ export async function indexRepository(ctx, config, root, { reindex = false } = {
|
|
|
38
38
|
continue
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
//
|
|
42
|
-
const { hash } =
|
|
41
|
+
// 单次读盘:同一 buffer 供哈希与正文使用(不再 sha256OfFile + readFileSync 读两遍)
|
|
42
|
+
const { hash, buffer } = readFileForIndex(filePath)
|
|
43
43
|
if (!reindex && existing && existing.sha256 === hash) {
|
|
44
44
|
skipped++
|
|
45
45
|
continue
|
|
@@ -47,12 +47,11 @@ export async function indexRepository(ctx, config, root, { reindex = false } = {
|
|
|
47
47
|
|
|
48
48
|
let entries
|
|
49
49
|
if (isSupportedCode(ext)) {
|
|
50
|
-
|
|
51
|
-
entries = scanSymbols(rel, filePath, content)
|
|
50
|
+
entries = scanSymbols(rel, filePath, buffer.toString('utf8'))
|
|
52
51
|
fileUpdates.push({ rel, expectedHash: existing?.sha256, hash, size, entries, type: 'code' })
|
|
53
52
|
updated++
|
|
54
53
|
} else {
|
|
55
|
-
const content =
|
|
54
|
+
const content = buffer.toString('utf8')
|
|
56
55
|
if (looksLikeDump(content)) {
|
|
57
56
|
fileUpdates.push({ rel, deleted: true })
|
|
58
57
|
skipped++
|
package/src/util/fs.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { accessSync, constants, readdirSync, statSync } from 'node:fs'
|
|
1
|
+
import { accessSync, constants, readdirSync, readFileSync, statSync } from 'node:fs'
|
|
2
2
|
import { readFile } from 'node:fs/promises'
|
|
3
3
|
import { createHash } from 'node:crypto'
|
|
4
4
|
import path from 'node:path'
|
|
@@ -32,6 +32,18 @@ export async function sha256OfFile(filePath) {
|
|
|
32
32
|
return { hash: sha256OfBuffer(buf), size: buf.length }
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
/**
|
|
36
|
+
* 单次同步读:同一 buffer 同时供内容哈希与正文解码使用。
|
|
37
|
+
*
|
|
38
|
+
* 替代 “await sha256OfFile() + readFileSync()” 的二次读盘。对大量小文件的批量
|
|
39
|
+
* 索引,逐文件 `async readFile` 的线程池往返是主要开销(实测 2000 文件 260ms →
|
|
40
|
+
* 单次同步读 ~6ms);调用方拿到 buffer 后按需 toString('utf8'),未变更文件无需解码。
|
|
41
|
+
*/
|
|
42
|
+
export function readFileForIndex(filePath) {
|
|
43
|
+
const buffer = readFileSync(filePath)
|
|
44
|
+
return { hash: sha256OfBuffer(buffer), size: buffer.length, buffer }
|
|
45
|
+
}
|
|
46
|
+
|
|
35
47
|
export async function readTextFile(filePath, maxBytes = 2 * 1024 * 1024) {
|
|
36
48
|
const buf = await readFile(filePath)
|
|
37
49
|
if (buf.length > maxBytes) {
|
package/src/watch.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { existsSync,
|
|
1
|
+
import { existsSync, statSync } from 'node:fs'
|
|
2
2
|
import path from 'node:path'
|
|
3
|
-
import { isSupportedCode, isSupportedDoc, memoryRootFor,
|
|
3
|
+
import { isSupportedCode, isSupportedDoc, memoryRootFor, readFileForIndex, relativePath, storeKey, walkDir } from './util/fs.js'
|
|
4
4
|
import { buildDocEntries } from './doc-pipeline.js'
|
|
5
5
|
import { scanSymbols } from './symbols.js'
|
|
6
6
|
import { linkEntries } from './link.js'
|
|
@@ -13,13 +13,14 @@ export class WatchManager {
|
|
|
13
13
|
this.config = config
|
|
14
14
|
this.roots = new Map()
|
|
15
15
|
this.timer = null
|
|
16
|
+
this._polling = false
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
restorePersisted() {
|
|
19
20
|
const cwd = process.cwd()
|
|
20
|
-
|
|
21
|
+
// 用 load() 的返回值:storeCache 命中时 load() 返回的是缓存实例,忽略返回值会拿到空 store
|
|
22
|
+
const store = new ProjectMemoryStore(memoryRootFor(cwd, this.config.memoryDir)).load()
|
|
21
23
|
if (existsSync(store.dir)) {
|
|
22
|
-
store.load()
|
|
23
24
|
for (const root of store.watchlist) {
|
|
24
25
|
if (typeof root === 'string' && root) this.addRoot(root)
|
|
25
26
|
}
|
|
@@ -43,7 +44,11 @@ export class WatchManager {
|
|
|
43
44
|
|
|
44
45
|
start(intervalMs = 15000) {
|
|
45
46
|
if (this.timer) return
|
|
46
|
-
|
|
47
|
+
// NaN/undefined 会让 setInterval 退化成 1ms 轮询(Node 只发一条 TimeoutNaNWarning),
|
|
48
|
+
// 足以打满事件循环让 agent 无法响应;非法值回退到 15s。
|
|
49
|
+
const raw = Number(intervalMs)
|
|
50
|
+
const ms = Number.isFinite(raw) ? Math.max(raw, 1000) : 15000
|
|
51
|
+
this.timer = setInterval(() => this.poll(), ms)
|
|
47
52
|
if (this.timer.unref) this.timer.unref()
|
|
48
53
|
}
|
|
49
54
|
|
|
@@ -53,12 +58,20 @@ export class WatchManager {
|
|
|
53
58
|
}
|
|
54
59
|
|
|
55
60
|
async poll() {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
+
// setInterval 不等待上一轮:大仓库/文档 LLM 摘要让一轮 >interval 时,
|
|
62
|
+
// 轮询会叠加成并发索引,最终打满事件循环。用重入锁让慢轮询自然跳过。
|
|
63
|
+
if (this._polling) return
|
|
64
|
+
this._polling = true
|
|
65
|
+
try {
|
|
66
|
+
for (const [root, state] of this.roots) {
|
|
67
|
+
try {
|
|
68
|
+
await this.pollRoot(root, state)
|
|
69
|
+
} catch (err) {
|
|
70
|
+
console.error(`[dsh-project-memory] watch poll failed for ${root}: ${err.message}`)
|
|
71
|
+
}
|
|
61
72
|
}
|
|
73
|
+
} finally {
|
|
74
|
+
this._polling = false
|
|
62
75
|
}
|
|
63
76
|
}
|
|
64
77
|
|
|
@@ -89,14 +102,15 @@ export class WatchManager {
|
|
|
89
102
|
continue
|
|
90
103
|
}
|
|
91
104
|
|
|
92
|
-
|
|
105
|
+
// 单次读盘:同一 buffer 供哈希与正文使用
|
|
106
|
+
const { hash, buffer } = readFileForIndex(filePath)
|
|
93
107
|
const existing = state.store.fileRecord(rel)
|
|
94
108
|
if (existing && existing.sha256 === hash) continue
|
|
95
109
|
|
|
96
110
|
try {
|
|
97
111
|
let entries
|
|
98
112
|
if (isSupportedCode(ext)) {
|
|
99
|
-
entries = scanSymbols(rel, filePath,
|
|
113
|
+
entries = scanSymbols(rel, filePath, buffer.toString('utf8'))
|
|
100
114
|
} else {
|
|
101
115
|
entries = await buildDocEntries(this.ctx.llm, rel, filePath, {
|
|
102
116
|
chunkChars: this.config.chunkChars,
|