@a9i5k4/dsh-auto-memory 2.5.3 → 3.0.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/README.md +171 -1
- package/README.zh-CN.md +171 -1
- package/docs/CONTRIBUTORS.html +471 -0
- package/docs/HANDOFF-CRITERIA.md +92 -0
- package/docs/INTEGRATION-ANALYSIS.md +350 -348
- package/docs/USER-GUIDE.en.md +56 -1
- package/docs/USER-GUIDE.zh-CN.md +57 -2
- package/docs/internal/ACCEPT-35-LIVE.md +143 -0
- package/docs/internal/ACCEPTANCE-20260914.md +90 -0
- package/docs/internal/ARCH-REVIEW-BRIEF.md +411 -0
- package/docs/internal/ARCH-REVIEW-REQUEST.md +201 -0
- package/docs/internal/ARCH-REVIEW-ROUND2.md +169 -0
- package/docs/internal/ARCH-REVIEW-ROUND3.md +206 -0
- package/docs/internal/AUDIT-WB-GRAPH-FULL-20260916.md +314 -0
- package/docs/internal/CONCURRENCY-INVESTIGATION-20260917.md +192 -0
- package/docs/internal/CROSS-SESSION-SEARCH-PATH-DECISION.md +72 -0
- package/docs/internal/CROSS-SESSION-SEARCH-RESEARCH.md +131 -0
- package/docs/internal/DECISIONS-20260914-SESSION.md +269 -0
- package/docs/internal/DESIGN-P1-STATE-COMMIT-20260915.md +219 -0
- package/docs/internal/DIRECTION-CHECK-WB-GRAPH-20260916.md +132 -0
- package/docs/internal/FEEDBACK-TO-DSHAPI-RELAY.md +13 -0
- package/docs/internal/GH-DISCUSSION-5732-COMMENT.md +74 -0
- package/docs/internal/GPT-ACCEPTANCE-PROMPT-20260916.md +352 -0
- package/docs/internal/GPT-REVIEW-PROMPT.md +216 -0
- package/docs/internal/GROUP-WEBHOOK-SETUP.md +33 -0
- package/docs/internal/KICKOFF-P0.md +254 -0
- package/docs/internal/MASTER-PLAN-3.0.md +411 -0
- package/docs/internal/MEMORY-MUTATION-AND-INDEX-DESIGN.md +85 -0
- package/docs/internal/MERGE-CONFLICT-SCAN-20260914.md +222 -0
- package/docs/internal/PENDING-FIXES-20260916.md +289 -0
- package/docs/internal/RAG-KARPATHY-PROGRAM.md +229 -0
- package/docs/internal/REPORT-P0-NIGHTLY.md +212 -0
- package/docs/internal/REPORT-P5-ACCEPTANCE.md +31 -0
- package/docs/internal/REPORT-WB-GRAPH-NIGHTLY.md +153 -0
- package/docs/internal/REVIEW-WB-GRAPH-SELF.md +81 -0
- package/docs/internal/ROADMAP-20260917-WEEK.md +305 -0
- package/docs/internal/ROADMAP.md +106 -0
- package/docs/internal/RUN-P0-NIGHTLY.md +227 -0
- package/docs/internal/S10-CONSTRUCTION-HANDOFF-20260917.md +175 -0
- package/docs/internal/S10-GAPS-PLAIN-20260917.md +125 -0
- package/docs/internal/SEMANTIC-ARCHITECTURE-SPEC.md +360 -0
- package/docs/internal/SESSION-FILE-REPAIR-PROTOCOL.md +90 -0
- package/docs/internal/THREE-LAYER-CONTRACT.md +210 -0
- package/docs/internal/TODO-BACKLOG.md +263 -142
- package/docs/internal/TODO-GRAPH.html +715 -0
- package/docs/internal/TODO-GRAPH.html.bak-20260914-v2 +493 -0
- package/docs/internal/TODO-GRAPH.html.bak-20260915-alsfix +710 -0
- package/docs/internal/TODO-GRAPH.html.bak-20260915-p1 +710 -0
- package/docs/internal/TODO-GRAPH.html.bak-20260915-p6a-rev +703 -0
- package/docs/internal/TODO-GRAPH.html.bak-20260915-wshint +710 -0
- package/docs/internal/TODO-GRAPH.html.bak-20260916-batch +715 -0
- package/docs/internal/WB-FORMAT-CONVENTION.md +112 -0
- package/docs/internal/WB-GRAPH-DECISIONS-20260914.md +71 -0
- package/docs/internal/reviews/CLAIM-VERIFICATION-20260914.md +56 -0
- package/docs/internal/reviews/PLAN-gpt6astra-round2-20260914.md +787 -0
- package/docs/internal/reviews/REVIEW-gpt6astra-20260914.md +112 -0
- package/docs/internal/reviews/ROUND3-REVIEW-INTEGRATION-20260914.md +230 -0
- package/docs/prompts/M8-3-enable-verify.md +49 -49
- package/lib/acceptance.js +71 -0
- package/lib/activation-host.js +90 -9
- package/lib/activation-inbox.js +25 -7
- package/lib/board-mode.js +30 -0
- package/lib/client.js +878 -75
- package/lib/context-bridge.js +2 -2
- package/lib/context-host.js +70 -6
- package/lib/engine-identity.js +149 -0
- package/lib/engine-switch.js +247 -0
- package/lib/episodic-store.js +11 -10
- package/lib/evidence-store.js +2 -2
- package/lib/fact-store.js +1 -1
- package/lib/fs-retry.js +46 -0
- package/lib/index.js +1987 -153
- package/lib/intent-clean-safe.js +40 -0
- package/lib/intent-clean.js +12 -16
- package/lib/l0-extract.js +263 -149
- package/lib/l0-index-sync.js +195 -0
- package/lib/l0-index.js +349 -239
- package/lib/ledger-criteria.js +142 -0
- package/lib/m7-index-sync-host.js +65 -4
- package/lib/m7-wire.js +3 -3
- package/lib/memory-anchor.js +56 -1
- package/lib/memory-envelope.js +252 -0
- package/lib/memory-hub.js +14 -4
- package/lib/memory-mutation.js +246 -0
- package/lib/memory-writer.js +204 -24
- package/lib/procedure-observation.js +48 -0
- package/lib/procedure-store.js +34 -17
- package/lib/python-setup.js +1 -1
- package/lib/rerank-host.js +160 -0
- package/lib/rules-layer.js +261 -0
- package/lib/semantic-js.js +15 -0
- package/lib/shadow-retrieval.js +3 -3
- package/lib/state-commit.js +245 -0
- package/lib/subagent-gc.js +4 -8
- package/lib/tier-layer-inject.js +650 -0
- package/lib/tier0-catalog.js +693 -0
- package/lib/water-window.js +263 -186
- package/lib/wb-contract.js +495 -0
- package/lib/wb-sidecar.js +839 -0
- package/lib/ws-overview-rank.js +2 -2
- package/package.json +1 -1
- package/python/m7_embedding_v1.py +5 -5
- package/python/worker_semantic_v1.py +17 -6
- package/python/worker_v1.py +38 -4
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* L0 索引接线(l0_index_sync_v1)—— 三层检索契约 C3
|
|
3
|
+
* (`docs/internal/THREE-LAYER-CONTRACT.md` §7 C3;接口规范 `SEMANTIC-ARCHITECTURE-SPEC.md` S1/S9)。
|
|
4
|
+
*
|
|
5
|
+
* 背景:`lib/l0-index.js`(L0 自己的向量索引,增量)长期**零引用**——文件在、能力在、
|
|
6
|
+
* 就是没人调用它。本模块是它的**接线口**:把「工作区语料里的 L0 摘要」按层增量写成索引文件,
|
|
7
|
+
* 供后续语义检索读回向量,而不必每次现场重嵌(S1.3 的增量精神:只重算变化条)。
|
|
8
|
+
*
|
|
9
|
+
* ── 文件布局与命名(契约 C3)────────────────────────────────────────────
|
|
10
|
+
* <dir>/l0-index-<workspaceKey 短哈希>-<layer>.json
|
|
11
|
+
* 例:~/.dsh/memory/semantic/l0/l0-index-9f2c1ab34de5-project.json
|
|
12
|
+
*
|
|
13
|
+
* 为什么**按层各一份文件**、而不是「一个工作区一份合并文件」(与任务书的字面表述有偏差,
|
|
14
|
+
* 这是**能力边界**而非偷懒,见下):
|
|
15
|
+
* `l0-index` 的 `buildFull`/`update` 每次调用只接受**单一 `layer`**
|
|
16
|
+
* (内部是 `buildL0IndexPre(text, { layer })` → 该次调用产出的**全部**条目同层)。
|
|
17
|
+
* 工作区 L0 语料天然多层(project / user / log / reflection / whiteboard 五种来源同批),
|
|
18
|
+
* 因此「一次调用产出一份含真实层归属的单文件」在该 API 下**无法表达**:
|
|
19
|
+
* - 传合并文本 + 任选一个 layer → 其余层的条目**全部落成错的层**(契约 I4 直接违反,
|
|
20
|
+
* 这正是契约 C3 行里点名的「`:147/172` 未传层 → 会全落默认 log」同类错误);
|
|
21
|
+
* - 沿用模块原样不动(任务书要求)时,唯一能保住层归属的实现就是**按层分文件**。
|
|
22
|
+
* 若日后要「一份合并文件」,需要二选一:①给 `l0-index` 增一个多来源入口(改模块);
|
|
23
|
+
* ②在接线侧自己合并条目(等于把 `assemble()` 复制一份 → 同一文件格式两个写者,风险更高)。
|
|
24
|
+
* 两条都未做,交由契约方裁定(见交付报告「未完成/存疑」)。
|
|
25
|
+
*
|
|
26
|
+
* ── 边界与纪律 ─────────────────────────────────────────────────────────
|
|
27
|
+
* - **默认开启**(宿主开关 `l0IndexEnabled` 默认 true,用户 2026-09-14 裁定):`enabled !== true` 时零 IO、零嵌入、零目录创建(设 false 即回到零 IO);
|
|
28
|
+
* - **fail-soft**:任何异常(读文件失败 / 嵌入抛错 / 写盘失败 / 单层失败)只记一行 `diag`,
|
|
29
|
+
* 返回结构化 `{ok:false, reason}` 或逐层 `files[].error`,**绝不抛**、绝不阻塞调用方;
|
|
30
|
+
* - **S9(检索路径零 LLM)**:嵌入通道由调用方注入,本模块自身零网络、零模型、零 fetch——
|
|
31
|
+
* 宿主注入的是端侧 JS 引擎(e5-small q8)的 `embedPassages`;
|
|
32
|
+
* - 只读调用方给出的来源路径,不自行发现工作区(禁用参数注入,不猜路径)。
|
|
33
|
+
* 零第三方依赖(仅 node 内建)。UTF-8 无 BOM。
|
|
34
|
+
*/
|
|
35
|
+
import path from 'node:path'
|
|
36
|
+
import { createHash } from 'node:crypto'
|
|
37
|
+
import { L0_LAYERS } from './l0-extract.js'
|
|
38
|
+
import { createL0IndexPre } from './l0-index.js'
|
|
39
|
+
|
|
40
|
+
export const L0_INDEX_SYNC_VERSION = 'l0_index_sync_v1'
|
|
41
|
+
|
|
42
|
+
/** 索引文件名前缀(契约 C3 命名:`l0-index-<workspaceKey 短哈希>`)。 */
|
|
43
|
+
export const L0_INDEX_FILE_PREFIX = 'l0-index-'
|
|
44
|
+
|
|
45
|
+
/** 短哈希长度(hex 字符):12 hex = 48 bit,够工作区级唯一,文件名仍短。 */
|
|
46
|
+
export const L0_INDEX_WS_HASH_CHARS = 12
|
|
47
|
+
|
|
48
|
+
const LAYER_SET = new Set(L0_LAYERS)
|
|
49
|
+
|
|
50
|
+
/** workspaceKey → 短哈希(确定性;空键也稳定,不抛)。 */
|
|
51
|
+
export function workspaceKeyShortHashPre(workspaceKey, chars = L0_INDEX_WS_HASH_CHARS) {
|
|
52
|
+
const n = Number(chars) > 0 ? Math.min(64, Math.floor(Number(chars))) : L0_INDEX_WS_HASH_CHARS
|
|
53
|
+
return createHash('sha256').update(String(workspaceKey == null ? '' : workspaceKey), 'utf8').digest('hex').slice(0, n)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** 索引文件名:`l0-index-<wsHash 12>-<layer>.json`(非法 layer 回落 log,绝不生成脏文件名)。 */
|
|
57
|
+
export function l0IndexFileNamePre(workspaceKey, layer, chars = L0_INDEX_WS_HASH_CHARS) {
|
|
58
|
+
const l = LAYER_SET.has(String(layer)) ? String(layer) : 'log'
|
|
59
|
+
return L0_INDEX_FILE_PREFIX + workspaceKeyShortHashPre(workspaceKey, chars) + '-' + l + '.json'
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 工厂:L0 索引同步器(IO / 嵌入 / 读文本 全注入)。
|
|
64
|
+
*
|
|
65
|
+
* @param {object} opts
|
|
66
|
+
* @param {{readJson(path):any, writeJson(path,obj):void}} opts.io 落盘 IO(同 l0-index 契约;
|
|
67
|
+
* readJson 对缺失文件返回 null 或抛 ENOENT 均可被模块层容错)。
|
|
68
|
+
* @param {{embedPassages(texts:string[]):Promise<Float32Array[]|number[][]>}} opts.embedder
|
|
69
|
+
* 端侧嵌入通道(宿主机注入 `_jsSemantic.embedPassages`;测试注入假 embedder)。
|
|
70
|
+
* @param {(filePath:string)=>Promise<string>} [opts.readText] 读来源文本(fail-soft;返回空串=跳过)。
|
|
71
|
+
* @param {(msg:string)=>void} [opts.diag] 诊断输出(默认静默)。
|
|
72
|
+
*/
|
|
73
|
+
export function createL0IndexSyncPre(opts = {}) {
|
|
74
|
+
const io = opts.io
|
|
75
|
+
const embedder = opts.embedder
|
|
76
|
+
const readText = typeof opts.readText === 'function' ? opts.readText : null
|
|
77
|
+
const diagFn = typeof opts.diag === 'function' ? opts.diag : () => {}
|
|
78
|
+
// ★P2(T2-9):透传引擎身份与两个开关到索引层(身份门 + 跨 id 复用)。
|
|
79
|
+
// 不传身份 ⇒ 索引层不启用身份门(旧行为);宿主接线时传真实宽身份。
|
|
80
|
+
const engineIdentity = typeof opts.engineIdentity === 'string' ? opts.engineIdentity : ''
|
|
81
|
+
const engineIdentityGate = opts.engineIdentityGate
|
|
82
|
+
const crossIdReuse = opts.crossIdReuse
|
|
83
|
+
// 工厂级配置错误直接抛(调用方组装错,不属运行期 fail-soft 范畴)——与 l0-index 同口径。
|
|
84
|
+
if (!io || typeof io.readJson !== 'function' || typeof io.writeJson !== 'function') {
|
|
85
|
+
throw new Error('l0-index-sync-pre: io.readJson/io.writeJson required')
|
|
86
|
+
}
|
|
87
|
+
if (!embedder || typeof embedder.embedPassages !== 'function') {
|
|
88
|
+
throw new Error('l0-index-sync-pre: embedder.embedPassages required')
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const safeDiag = (msg) => { try { diagFn(String(msg).slice(0, 200)) } catch (_) {} }
|
|
92
|
+
|
|
93
|
+
/** 路径拼接(统一走 node:path,Windows 反斜杠不影响)。 */
|
|
94
|
+
function indexPathOf(dir, workspaceKey, layer) {
|
|
95
|
+
return path.join(String(dir || ''), l0IndexFileNamePre(workspaceKey, layer))
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* 同步一次(幂等、可重复调用)。
|
|
100
|
+
*
|
|
101
|
+
* @param {object} o
|
|
102
|
+
* @param {boolean} o.enabled 总开关 —— 非 true 直接返回 disabled,**零 IO**(宿主 `l0IndexEnabled`,默认 true)。
|
|
103
|
+
* @param {string} o.workspaceKey 工作区键(进文件名短哈希)。
|
|
104
|
+
* @param {string} o.dir 索引目录(<dshHome>/memory/semantic/l0)。
|
|
105
|
+
* @param {Array<{layer:string, path:string, text?:string}>} o.sources 来源(text 已给则不读盘)。
|
|
106
|
+
* @param {number} [o.maxChars] / @param {number} [o.minChars] 透传 l0-extract-pre 的抽取参数。
|
|
107
|
+
* @param {()=>number} [o.now] 时间注入(测试确定性)。
|
|
108
|
+
* @returns {Promise<{ok:boolean, reason:string, enabled:boolean, written:number, count:number,
|
|
109
|
+
* files:Array<{layer:string, path:string, ok:boolean, count:number, added:number, recomputed:number,
|
|
110
|
+
* removed:number, skipped:number, l0IndexVersion:string, error:string}>}>}
|
|
111
|
+
*/
|
|
112
|
+
async function sync(o = {}) {
|
|
113
|
+
const files = []
|
|
114
|
+
const empty = (reason, enabled) => ({ ok: false, reason, enabled: !!enabled, written: 0, count: 0, files })
|
|
115
|
+
try {
|
|
116
|
+
const enabled = o && o.enabled === true
|
|
117
|
+
if (!enabled) return empty('disabled', false)
|
|
118
|
+
const dir = String((o && o.dir) || '')
|
|
119
|
+
if (!dir) return empty('no-dir', true)
|
|
120
|
+
const workspaceKey = String((o && o.workspaceKey) || '')
|
|
121
|
+
const maxChars = o && o.maxChars
|
|
122
|
+
const minChars = o && o.minChars
|
|
123
|
+
const now = o && typeof o.now === 'function' ? o.now : undefined
|
|
124
|
+
|
|
125
|
+
// ① 来源分组:只认合法 layer + 非空路径;同路径去重(今日日志可能同时在 logs 与 logPath 里)。
|
|
126
|
+
const groups = new Map()
|
|
127
|
+
const seenPaths = new Set()
|
|
128
|
+
let droppedSources = 0
|
|
129
|
+
for (const s of Array.isArray(o && o.sources) ? o.sources : []) {
|
|
130
|
+
const layer = String((s && s.layer) || '')
|
|
131
|
+
const p = String((s && s.path) || '')
|
|
132
|
+
if (!LAYER_SET.has(layer) || !p) { droppedSources++; continue }
|
|
133
|
+
let key = p
|
|
134
|
+
try { key = path.resolve(p) } catch (_) { key = p }
|
|
135
|
+
const keyWithLayer = layer + '|' + key
|
|
136
|
+
if (seenPaths.has(keyWithLayer)) { droppedSources++; continue }
|
|
137
|
+
seenPaths.add(keyWithLayer)
|
|
138
|
+
if (!groups.has(layer)) groups.set(layer, [])
|
|
139
|
+
groups.get(layer).push({ path: p, text: s && typeof s.text === 'string' ? s.text : null })
|
|
140
|
+
}
|
|
141
|
+
if (!groups.size) return { ok: false, reason: 'no-sources', enabled: true, written: 0, count: 0, droppedSources, files }
|
|
142
|
+
|
|
143
|
+
// ② 逐层同步(层名升序 = 确定性;每层一次 update,只重算该层变化的条)。
|
|
144
|
+
let written = 0
|
|
145
|
+
let count = 0
|
|
146
|
+
const idx = createL0IndexPre({
|
|
147
|
+
io, embedder, ...(now ? { now } : {}),
|
|
148
|
+
...(engineIdentity ? { engineIdentity } : {}),
|
|
149
|
+
...(engineIdentityGate !== undefined ? { engineIdentityGate } : {}),
|
|
150
|
+
...(crossIdReuse !== undefined ? { crossIdReuse } : {}),
|
|
151
|
+
})
|
|
152
|
+
for (const layer of [...groups.keys()].sort()) {
|
|
153
|
+
const parts = []
|
|
154
|
+
for (const src of groups.get(layer)) {
|
|
155
|
+
let text = src.text
|
|
156
|
+
if (text == null) {
|
|
157
|
+
if (!readText) continue
|
|
158
|
+
try { text = await readText(src.path) } catch (e) { safeDiag('l0-index-sync: read fail ' + layer + ' ' + src.path + ': ' + ((e && e.message) || e)); text = '' }
|
|
159
|
+
}
|
|
160
|
+
if (text) parts.push(String(text))
|
|
161
|
+
}
|
|
162
|
+
const text = parts.join('\n')
|
|
163
|
+
const filePath = indexPathOf(dir, workspaceKey, layer)
|
|
164
|
+
let r
|
|
165
|
+
try {
|
|
166
|
+
// layer 必须显式传入:不传 → buildL0IndexPre 回退默认 log,整层归属丢失(契约 C3 点名的坑)。
|
|
167
|
+
r = await idx.update({ path: filePath, text, layer, ...(maxChars ? { maxChars } : {}), ...(minChars ? { minChars } : {}) })
|
|
168
|
+
} catch (e) {
|
|
169
|
+
r = { ok: false, error: String((e && e.message) || e) }
|
|
170
|
+
}
|
|
171
|
+
if (!r || !r.ok) {
|
|
172
|
+
safeDiag('l0-index-sync: update fail layer=' + layer + ': ' + String((r && r.error) || 'unknown'))
|
|
173
|
+
files.push({ layer, path: filePath, ok: false, count: 0, added: 0, recomputed: 0, removed: 0, skipped: 0, l0IndexVersion: '', error: String((r && r.error) || 'unknown') })
|
|
174
|
+
continue
|
|
175
|
+
}
|
|
176
|
+
written++
|
|
177
|
+
count += Number(r.count) || 0
|
|
178
|
+
files.push({
|
|
179
|
+
layer, path: filePath, ok: true,
|
|
180
|
+
count: Number(r.count) || 0, added: Number(r.added) || 0, recomputed: Number(r.recomputed) || 0,
|
|
181
|
+
removed: Number(r.removed) || 0, skipped: Number(r.skipped) || 0,
|
|
182
|
+
embedded: Number(r.embedded) || 0, // ★P2(T2-1):真实 embedder 输入数(逐层)
|
|
183
|
+
l0IndexVersion: String(r.l0IndexVersion || ''), error: '',
|
|
184
|
+
})
|
|
185
|
+
}
|
|
186
|
+
if (!written) return { ok: false, reason: 'all-failed', enabled: true, written: 0, count: 0, droppedSources, files }
|
|
187
|
+
return { ok: true, reason: '', enabled: true, written, count, droppedSources, files }
|
|
188
|
+
} catch (e) {
|
|
189
|
+
safeDiag('l0-index-sync 降级: ' + String((e && e.message) || e))
|
|
190
|
+
return empty('error', true)
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return { version: L0_INDEX_SYNC_VERSION, sync, indexPathOf, _l0IndexFileNamePre: l0IndexFileNamePre }
|
|
195
|
+
}
|