@a9i5k4/dsh-auto-memory 3.1.5 → 3.1.7
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/docs/CONTINUITY-FLOW.md +6 -6
- package/docs/HANDBOOK.md +54 -53
- package/docs/M-CM-PLAN.md +1 -1
- package/docs/M-CM-STATE.md +1 -1
- package/docs/M-CM7-HANDOFF-LAYERED-RETRIEVAL.md +1 -1
- package/docs/M3B-CONTRACT.md +7 -7
- package/docs/M7-AUTONOMOUS-STATE.md +1 -1
- package/docs/M7-TASKSET-DISPATCH.md +1 -1
- package/docs/PROMPT-PACK-LAYERED-RECALL.md +3 -3
- package/docs/PROMPT-SET-STRICT.md +3 -3
- package/docs/RELEASE-GO-NOGO.md +1 -1
- package/docs/STATUS-BOARD.md +1 -1
- package/docs/USER-GUIDE.en.md +21 -21
- package/docs/USER-GUIDE.zh-CN.md +21 -21
- package/docs/WHITEPAPER.md +1 -1
- package/docs/prompts/FIX-AGENT-P12-FULL-REGRESSION.md +1 -1
- package/docs/prompts/LIVE-VERIFY-ZCODE.md +2 -2
- package/docs/prompts/P2-semantic-recall.md +2 -2
- package/docs/prompts/P7-write-fix.md +1 -1
- package/docs/prompts/RELEASE-AGENT.md +4 -4
- package/docs/prompts/ZCODE-DROPIN.md +1 -1
- package/lib/acceptance.js +6 -6
- package/lib/activation-host.js +15 -14
- package/lib/activation-inbox-state.js +4 -4
- package/lib/activation-inbox.js +44 -44
- package/lib/board-mode.js +3 -3
- package/lib/client.js +225 -41
- package/lib/config-io.js +12 -12
- package/lib/context-bridge.js +22 -22
- package/lib/context-host.js +24 -23
- package/lib/datadir.js +95 -0
- package/lib/degrade.js +21 -21
- package/lib/dsh-home.js +8 -8
- package/lib/engine-identity.js +12 -12
- package/lib/engine-switch.js +3 -3
- package/lib/episodic-store.js +13 -13
- package/lib/evidence-agg.js +4 -4
- package/lib/evidence-store.js +12 -12
- package/lib/fact-store.js +47 -47
- package/lib/handoff-anchor.js +4 -4
- package/lib/hub-io.js +356 -3
- package/lib/index-sync.js +5 -5
- package/lib/index.js +426 -242
- package/lib/intent-clean-safe.js +3 -3
- package/lib/intent-clean.js +1 -1
- package/lib/l0-extract.js +15 -15
- package/lib/l0-index-sync.js +8 -8
- package/lib/l0-index.js +11 -11
- package/lib/ledger-criteria.js +8 -8
- package/lib/m4-corpus.js +1 -1
- package/lib/m7-index-sync-host.js +2 -2
- package/lib/m7-wire.js +24 -24
- package/lib/memory-anchor.js +1 -1
- package/lib/memory-envelope.js +15 -15
- package/lib/memory-hub.js +95 -50
- package/lib/memory-importance.js +7 -7
- package/lib/memory-mutation.js +5 -5
- package/lib/note-status-apply.js +2 -2
- package/lib/note-status.js +7 -7
- package/lib/policies/activation_policy_v2.json +2 -2
- package/lib/policies/recall_intent_lr_v1.json +1 -1
- package/lib/procedure-observation.js +2 -2
- package/lib/procedure-store.js +117 -28
- package/lib/python-setup.js +6 -5
- package/lib/python-sidecar-client.js +26 -26
- package/lib/recall-fusion.js +12 -12
- package/lib/recall-stats.js +36 -1
- package/lib/rerank-host.js +11 -11
- package/lib/rules-edit.js +9 -9
- package/lib/rules-layer.js +26 -26
- package/lib/semantic-decide.js +7 -7
- package/lib/semantic-js.js +13 -13
- package/lib/shadow-host.js +7 -6
- package/lib/shadow-retrieval.js +40 -40
- package/lib/skill-export-host.js +7 -7
- package/lib/skill-export.js +6 -6
- package/lib/state-commit.js +10 -10
- package/lib/storage-manage.js +6 -6
- package/lib/tier-layer-inject.js +28 -28
- package/lib/tier0-catalog.js +4 -4
- package/lib/wb-contract.js +45 -45
- package/lib/wb-sidecar.js +11 -11
- package/package.json +3 -4
- package/python/m7_activation_features_v2.py +7 -7
- package/python/m7_embedding_v1.py +12 -12
- package/python/policies/activation_policy_v2.json +2 -2
- package/python/policies/decision-record-activation-v2-delta-exp-override-20260824.json +1 -1
- package/python/policies/decision-record-reasoning-kind-admission-20260826.json +2 -2
- package/python/policies/decision-record-stale-gate-per-candidate-20260825.json +2 -2
- package/python/policies/recall_intent_lr_v1.json +1 -1
- package/python/verify_policy_artifact.py +3 -3
- package/python/worker_semantic_v1.py +18 -18
- package/python/worker_v1.py +18 -18
package/lib/shadow-retrieval.js
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
* 不写 audit、不产生任何模型可见副作用(M4 唯一模型行为=不影响模型行为)。
|
|
5
5
|
*
|
|
6
6
|
* 组成:
|
|
7
|
-
* 1) 策略常量(
|
|
7
|
+
* 1) 策略常量(SHADOW_GATE_POLICY_PRE_V1 / SHADOW_LEXICAL_BUDGET_PRE_V1 / 版本化词典)
|
|
8
8
|
* 2) RetrievalContextSnapshot validator + 快照纯校验
|
|
9
|
-
* 3) memoryIndexVersion(canonical corpus tuples →
|
|
10
|
-
* 4) GateSignals/GateDecision +
|
|
9
|
+
* 3) memoryIndexVersion(canonical corpus tuples → idx_pre_ + sha256)
|
|
10
|
+
* 4) GateSignals/GateDecision + gate_pre_v1(硬抑制顺序 + 权重滞回 + cooldown)
|
|
11
11
|
* 5) 确定性 tokenizer(NFKC/lowercase/词形保留/CJK 2-gram/stop words) + QueryPlan + queryDigest
|
|
12
|
-
* 6)
|
|
12
|
+
* 6) lexical_pre_v1(term/heading coverage、phrase、recency、total、排序/去重/预算/drop)
|
|
13
13
|
* 7) Candidate 身份(retrievalId/candidateId 确定性) + ShadowCandidate 构造
|
|
14
14
|
* 8) replay pure core(canonical 结果排除 recordedAt/latency/runtimeTag)
|
|
15
15
|
* 全部函数对同输入逐字段确定;所有新增文本 UTF-8 无 BOM。
|
|
@@ -21,16 +21,16 @@ const sha256Str = (s) => sha256Hex(Buffer.from(String(s), 'utf8'))
|
|
|
21
21
|
const first32 = (h) => h.slice(0, 32)
|
|
22
22
|
const clamp01 = (x) => Math.max(0, Math.min(1, Number(x) || 0))
|
|
23
23
|
|
|
24
|
-
export const
|
|
25
|
-
export const GATE_POLICY_VERSION = '
|
|
26
|
-
export const LEXICAL_POLICY_VERSION = '
|
|
27
|
-
export const INDEX_PREFIX = '
|
|
28
|
-
export const RETRIEVAL_PREFIX = '
|
|
29
|
-
export const CANDIDATE_PREFIX = '
|
|
30
|
-
export const NAMESPACE = 'dsh-auto-memory'
|
|
24
|
+
export const STOPWORDS_HIT_PRE_V2 = Object.freeze(['一一', 'sub', 'exp', 'sup', 'Lex', '第二', '一番', '一直', '一个', '一些', '许多', '种', '有的是', '也就是说', '啊', '阿', '哎', '哎呀', '哎哟', '唉', '俺', '俺们', '按', '按照', '吧', '吧哒', '把', '罢了', '被', '本', '本着', '比', '比方', '比如', '鄙人', '彼', '彼此', '边', '别', '别的', '别说', '并', '并且', '不比', '不成', '不单', '不但', '不独', '不管', '不光', '不过', '不仅', '不拘', '不论', '不怕', '不然', '不如', '不特', '不惟', '不问', '不只', '朝', '朝着', '趁', '趁着', '乘', '冲', '除', '除此之外', '除非', '除了', '此', '此间', '此外', '从', '从而', '打', '待', '但', '但是', '当', '当着', '到', '得', '的', '的话', '等', '等等', '地', '第', '叮咚', '对', '对于', '多', '多少', '而', '而况', '而且', '而是', '而外', '而言', '而已', '尔后', '反过来', '反过来说', '反之', '非但', '非徒', '否则', '嘎', '嘎登', '该', '赶', '个', '各', '各个', '各位', '各种', '各自', '给', '根据', '跟', '故', '故此', '固然', '关于', '管', '归', '果然', '果真', '过', '哈', '哈哈', '呵', '和', '何', '何处', '何况', '何时', '嘿', '哼', '哼唷', '呼哧', '乎', '哗', '还是', '还有', '换句话说', '换言之', '或', '或是', '或者', '极了', '及', '及其', '及至', '即', '即便', '即或', '即令', '即若', '即使', '几', '几时', '己', '既', '既然', '既是', '继而', '加之', '假如', '假若', '假使', '鉴于', '将', '较', '较之', '叫', '接着', '结果', '借', '紧接着', '进而', '尽', '尽管', '经', '经过', '就', '就是', '就是说', '据', '具体地说', '具体说来', '开始', '开外', '靠', '咳', '可', '可见', '可是', '可以', '况且', '啦', '来', '来着', '离', '例如', '哩', '连', '连同', '两者', '了', '临', '另', '另外', '另一方面', '论', '嘛', '吗', '慢说', '漫说', '冒', '么', '每', '每当', '们', '莫若', '某', '某个', '某些', '拿', '哪', '哪边', '哪儿', '哪个', '哪里', '哪年', '哪怕', '哪天', '哪些', '哪样', '那', '那边', '那儿', '那个', '那会儿', '那里', '那么', '那么些', '那么样', '那时', '那些', '那样', '乃', '乃至', '呢', '能', '你', '你们', '您', '宁', '宁可', '宁肯', '宁愿', '哦', '呕', '啪达', '旁人', '呸', '凭', '凭借', '其', '其次', '其二', '其他', '其它', '其一', '其余', '其中', '起', '起见', '岂但', '恰恰相反', '前后', '前者', '且', '然而', '然后', '然则', '让', '人家', '任', '任何', '任凭', '如', '如此', '如果', '如何', '如其', '如若', '如上所述', '若', '若非', '若是', '啥', '上下', '尚且', '设若', '设使', '甚而', '甚么', '甚至', '省得', '时候', '什么', '什么样', '使得', '是', '是的', '首先', '谁', '谁知', '顺', '顺着', '似的', '虽', '虽然', '虽说', '虽则', '随', '随着', '所', '所以', '他', '他们', '他人', '它', '它们', '她', '她们', '倘', '倘或', '倘然', '倘若', '倘使', '腾', '替', '通过', '同', '同时', '哇', '万一', '往', '望', '为', '为何', '为了', '为什么', '为着', '喂', '嗡嗡', '我', '我们', '呜', '呜呼', '乌乎', '无论', '无宁', '毋宁', '嘻', '吓', '相对而言', '像', '向', '向着', '嘘', '呀', '焉', '沿', '沿着', '要', '要不', '要不然', '要不是', '要么', '要是', '也', '也罢', '也好', '一', '一般', '一旦', '一方面', '一来', '一切', '一样', '一则', '依', '依照', '矣', '以', '以便', '以及', '以免', '以至', '以至于', '以致', '抑或', '因', '因此', '因而', '因为', '哟', '用', '由', '由此可见', '由于', '有', '有的', '有关', '有些', '又', '于', '于是', '于是乎', '与', '与此同时', '与否', '与其', '越是', '云云', '哉', '再说', '再者', '在', '在下', '咱', '咱们', '则', '怎', '怎么', '怎么办', '怎么样', '怎样', '咋', '照', '照着', '者', '这', '这边', '这儿', '这个', '这会儿', '这就是说', '这里', '这么', '这么点儿', '这么些', '这么样', '这时', '这些', '这样', '正如', '吱', '之', '之类', '之所以', '之一', '只是', '只限', '只要', '只有', '至', '至于', '诸位', '着', '着呢', '自', '自从', '自个儿', '自各儿', '自己', '自家', '自身', '综上所述', '总的来看', '总的来说', '总的说来', '总而言之', '总之', '纵', '纵令', '纵然', '纵使', '遵照', '作为', '兮', '呃', '呗', '咚', '咦', '喏', '啐', '喔唷', '嗬', '嗯', '嗳']);
|
|
25
|
+
export const GATE_POLICY_VERSION = 'gate_pre_v1'
|
|
26
|
+
export const LEXICAL_POLICY_VERSION = 'lexical_pre_v2'
|
|
27
|
+
export const INDEX_PREFIX = 'idx_pre_'
|
|
28
|
+
export const RETRIEVAL_PREFIX = 'ret_pre_'
|
|
29
|
+
export const CANDIDATE_PREFIX = 'cand_pre_'
|
|
30
|
+
export const NAMESPACE = 'dsh-auto-memory-pre'
|
|
31
31
|
|
|
32
32
|
/** §9.4 冻结权重(变更必须升级 policyVersion)。 */
|
|
33
|
-
export const
|
|
33
|
+
export const SHADOW_GATE_POLICY_PRE_V1 = Object.freeze({
|
|
34
34
|
schemaVersion: 1,
|
|
35
35
|
policyVersion: GATE_POLICY_VERSION,
|
|
36
36
|
weights: Object.freeze({
|
|
@@ -54,14 +54,14 @@ export const SHADOW_GATE_POLICY_V1 = Object.freeze({
|
|
|
54
54
|
stopWords: Object.freeze(['的', '了', '是', '和', '与', '及', '在', '我', '你', '它', '这', '那', '就', '都', '也', 'the', 'a', 'an', 'and', 'or', 'of', 'to', 'in', 'on', 'for', 'with', 'is', 'are', 'was', 'were', 'be', 'it', 'this', 'that']),
|
|
55
55
|
// §6:inputSource plugin allowlist(v1 为空=不按 inputSource 归类 plugin-generated;仅 sourcePlugin 非空才判定)
|
|
56
56
|
pluginInputAllowlist: Object.freeze([]),
|
|
57
|
-
//
|
|
57
|
+
// lexical_pre_v2:BM25 参数(Lucene 经典默认)+停用词来源
|
|
58
58
|
bm25: Object.freeze({ k1: 1.2, b: 0.75 }),
|
|
59
59
|
stopwordsSource: 'hit_stopwords.txt (github leiyusi123/stopwords), filtered',
|
|
60
60
|
}),
|
|
61
61
|
})
|
|
62
62
|
|
|
63
63
|
/** §12.5 硬预算。 */
|
|
64
|
-
export const
|
|
64
|
+
export const SHADOW_LEXICAL_BUDGET_PRE_V1 = Object.freeze({
|
|
65
65
|
windowSegments: 8,
|
|
66
66
|
windowChars: 4096,
|
|
67
67
|
queryTerms: 32,
|
|
@@ -124,7 +124,7 @@ export function validateSnapshot(snap) {
|
|
|
124
124
|
}
|
|
125
125
|
if (!Array.isArray(snap.window)) problems.push('window')
|
|
126
126
|
else {
|
|
127
|
-
if (snap.window.length >
|
|
127
|
+
if (snap.window.length > SHADOW_LEXICAL_BUDGET_PRE_V1.windowSegments) problems.push('window-exceeds-8')
|
|
128
128
|
let chars = 0
|
|
129
129
|
for (const w of snap.window) {
|
|
130
130
|
if (!w || typeof w !== 'object') { problems.push('window-entry'); continue }
|
|
@@ -135,13 +135,13 @@ export function validateSnapshot(snap) {
|
|
|
135
135
|
if (!Number.isInteger(w.contextVersion)) problems.push('window.contextVersion')
|
|
136
136
|
if (typeof w.ts !== 'number') problems.push('window.ts')
|
|
137
137
|
}
|
|
138
|
-
if (chars >
|
|
138
|
+
if (chars > SHADOW_LEXICAL_BUDGET_PRE_V1.windowChars) problems.push('window-exceeds-4096-chars')
|
|
139
139
|
}
|
|
140
140
|
if (problems.length) return { ok: false, reason: 'invalid:' + problems.join(',') }
|
|
141
141
|
return { ok: true, snapshot: snap }
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
/** §8 memoryIndexVersion:canonical corpus tuples →
|
|
144
|
+
/** §8 memoryIndexVersion:canonical corpus tuples → idx_pre_ + first32hex(sha256)。 */
|
|
145
145
|
export function memoryIndexVersion(sources) {
|
|
146
146
|
// source tuple = [scope, sourceRef, sourceEpoch, sourceVersion, fileDigest]
|
|
147
147
|
const tuples = (Array.isArray(sources) ? sources : []).map((s) => [
|
|
@@ -209,11 +209,11 @@ export function tokenize(text, opts = {}) {
|
|
|
209
209
|
return out
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
-
/** 版本化 stop words 过滤(§10.2#5):
|
|
213
|
-
const STOPWORDS_HIT_SET = new Set(
|
|
212
|
+
/** 版本化 stop words 过滤(§10.2#5):lexical_pre_v2 起用哈工大停用词表(STOPWORDS_HIT_PRE_V2,507 词)+原版本化小表兜底;错误码/文件名/长度>1 的标识符不可移除。 */
|
|
213
|
+
const STOPWORDS_HIT_SET = new Set(STOPWORDS_HIT_PRE_V2)
|
|
214
214
|
export function isStopWord(term) {
|
|
215
215
|
if (STOPWORDS_HIT_SET.has(term)) return true
|
|
216
|
-
const d =
|
|
216
|
+
const d = SHADOW_GATE_POLICY_PRE_V1.dictionaries
|
|
217
217
|
return d.stopWords.includes(term)
|
|
218
218
|
}
|
|
219
219
|
|
|
@@ -235,7 +235,7 @@ export function buildQueryPlan(snapshot, opts = {}) {
|
|
|
235
235
|
if (isStopWord(t)) continue
|
|
236
236
|
const cur = seenTerms.get(t)
|
|
237
237
|
const wgt = Math.max(cur ? cur.weight : 0, weights[origin] || 0)
|
|
238
|
-
if (Buffer.byteLength(t, 'utf8') >
|
|
238
|
+
if (Buffer.byteLength(t, 'utf8') > SHADOW_LEXICAL_BUDGET_PRE_V1.termBytes) {
|
|
239
239
|
oversize.push(t)
|
|
240
240
|
continue
|
|
241
241
|
}
|
|
@@ -245,14 +245,14 @@ export function buildQueryPlan(snapshot, opts = {}) {
|
|
|
245
245
|
// P12 排序:weight 降序优先(截断保高权重词),term 字典序升序作稳定 tiebreak;截断逻辑与 truncated 标记不变
|
|
246
246
|
const terms = [...seenTerms.values()].sort((a, b) => (b.weight - a.weight) || (a.term < b.term ? -1 : a.term > b.term ? 1 : 0))
|
|
247
247
|
let truncated = false
|
|
248
|
-
if (terms.length >
|
|
249
|
-
terms.length =
|
|
248
|
+
if (terms.length > SHADOW_LEXICAL_BUDGET_PRE_V1.queryTerms) {
|
|
249
|
+
terms.length = SHADOW_LEXICAL_BUDGET_PRE_V1.queryTerms
|
|
250
250
|
truncated = true
|
|
251
251
|
}
|
|
252
252
|
for (const t of terms) termBytes += Buffer.byteLength(t.term, 'utf8')
|
|
253
|
-
if (termBytes >
|
|
253
|
+
if (termBytes > SHADOW_LEXICAL_BUDGET_PRE_V1.queryBytes) {
|
|
254
254
|
// 从尾部丢弃直到 ≤2048
|
|
255
|
-
while (termBytes >
|
|
255
|
+
while (termBytes > SHADOW_LEXICAL_BUDGET_PRE_V1.queryBytes && terms.length) {
|
|
256
256
|
termBytes -= Buffer.byteLength(terms.pop().term, 'utf8')
|
|
257
257
|
truncated = true
|
|
258
258
|
}
|
|
@@ -278,7 +278,7 @@ export function buildQueryPlan(snapshot, opts = {}) {
|
|
|
278
278
|
|
|
279
279
|
// ========== §9 Gate ==========
|
|
280
280
|
|
|
281
|
-
const D = () =>
|
|
281
|
+
const D = () => SHADOW_GATE_POLICY_PRE_V1.dictionaries
|
|
282
282
|
|
|
283
283
|
function hasAny(text, list) {
|
|
284
284
|
const norm = normalizeText(text)
|
|
@@ -344,9 +344,9 @@ export function computeSignals(snapshot, queryPlan, recentHits = []) {
|
|
|
344
344
|
return signals
|
|
345
345
|
}
|
|
346
346
|
|
|
347
|
-
/** §9.4
|
|
347
|
+
/** §9.4 gate_pre_v1:硬抑制 → 权重滞回 → 决策。同步纯函数,零 IO。 */
|
|
348
348
|
export function gatePreV1(snapshot, opts = {}) {
|
|
349
|
-
const policy =
|
|
349
|
+
const policy = SHADOW_GATE_POLICY_PRE_V1
|
|
350
350
|
const latchedPrev = opts.previousLatch === true
|
|
351
351
|
const cooldownRemaining = Math.max(0, Number(opts.cooldownRemaining) || 0)
|
|
352
352
|
const hard = opts.hardSuppress || {}
|
|
@@ -396,20 +396,20 @@ export function gatePreV1(snapshot, opts = {}) {
|
|
|
396
396
|
|
|
397
397
|
|
|
398
398
|
|
|
399
|
-
// ========== §11 Candidate 身份 + §12
|
|
399
|
+
// ========== §11 Candidate 身份 + §12 lexical_pre_v1 ==========
|
|
400
400
|
|
|
401
401
|
const MEMORY_ID_STRICT = /^mem_[0-9a-f]{32}$/
|
|
402
402
|
|
|
403
403
|
/** §11 retrievalId(确定性,可重放;非长期内容身份)。 */
|
|
404
404
|
export function buildRetrievalId(sessionId, contextVersion, triggerSegmentId, memoryIndexVersion) {
|
|
405
|
-
const sessionIdHash = first32(sha256Str('retrieval-v1\u0000' + sessionId))
|
|
406
|
-
const parts = ['retrieval-v1', sessionIdHash, contextVersion, triggerSegmentId, memoryIndexVersion, GATE_POLICY_VERSION, LEXICAL_POLICY_VERSION]
|
|
405
|
+
const sessionIdHash = first32(sha256Str('retrieval-pre-v1\u0000' + sessionId))
|
|
406
|
+
const parts = ['retrieval-pre-v1', sessionIdHash, contextVersion, triggerSegmentId, memoryIndexVersion, GATE_POLICY_VERSION, LEXICAL_POLICY_VERSION]
|
|
407
407
|
return RETRIEVAL_PREFIX + first32(sha256Str(JSON.stringify(parts)))
|
|
408
408
|
}
|
|
409
409
|
|
|
410
410
|
/** §11 candidateId:同 memoryId 内容变化后 candidateId 因版本/digest 改变。 */
|
|
411
411
|
export function buildCandidateId(retrievalId, memoryId, sourceEpoch, sourceVersion, recordDigest) {
|
|
412
|
-
const parts = ['candidate-v1', retrievalId, memoryId, sourceEpoch, sourceVersion, recordDigest]
|
|
412
|
+
const parts = ['candidate-pre-v1', retrievalId, memoryId, sourceEpoch, sourceVersion, recordDigest]
|
|
413
413
|
return CANDIDATE_PREFIX + first32(sha256Str(JSON.stringify(parts)))
|
|
414
414
|
}
|
|
415
415
|
|
|
@@ -450,18 +450,18 @@ function phraseHit(normText, normPhrase) {
|
|
|
450
450
|
export function sanitizeExcerpt(text) {
|
|
451
451
|
const cleaned = String(text == null ? '' : text).replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F]/g, '')
|
|
452
452
|
const buf = Buffer.from(cleaned, 'utf8')
|
|
453
|
-
if (buf.length <=
|
|
454
|
-
return buf.subarray(0,
|
|
453
|
+
if (buf.length <= SHADOW_LEXICAL_BUDGET_PRE_V1.excerptBytes) return cleaned
|
|
454
|
+
return buf.subarray(0, SHADOW_LEXICAL_BUDGET_PRE_V1.excerptBytes).toString('utf8').replace(/[\uFFFD]+$/, '')
|
|
455
455
|
}
|
|
456
456
|
|
|
457
457
|
/**
|
|
458
|
-
*
|
|
458
|
+
* lexical_pre_v1 检索核心(§10-§13):纯函数,零 IO。
|
|
459
459
|
* @param {{memoryIndexVersion?:string,sources:Array,records:Array}} corpus 纯内存 fixture
|
|
460
460
|
* @param {object} queryPlan buildQueryPlan 输出
|
|
461
461
|
* @param {{triggerTs?:number,mode?:'retrieve'|'prefetch',dayBoundaryMinutes?:number}} opts
|
|
462
462
|
*/
|
|
463
463
|
export function lexicalSearch(corpus, queryPlan, opts = {}) {
|
|
464
|
-
const B =
|
|
464
|
+
const B = SHADOW_LEXICAL_BUDGET_PRE_V1
|
|
465
465
|
const dropped = []
|
|
466
466
|
const drop = (stage, reason, extra) => dropped.push(Object.assign({ stage, reason }, extra || {}))
|
|
467
467
|
const records = Array.isArray(corpus && corpus.records) ? corpus.records : []
|
|
@@ -478,8 +478,8 @@ export function lexicalSearch(corpus, queryPlan, opts = {}) {
|
|
|
478
478
|
const termTotalWeight = queryPlan.terms.reduce((a, t) => a + t.weight, 0) || 1
|
|
479
479
|
const normPhrases = (queryPlan.phrases || []).map((p) => normalizeText(p))
|
|
480
480
|
|
|
481
|
-
//
|
|
482
|
-
const bm25 =
|
|
481
|
+
// lexical_pre_v2:BM25 语料统计(df/avgdl;Okapi 经典参数 k1=1.2 b=0.75)
|
|
482
|
+
const bm25 = SHADOW_GATE_POLICY_PRE_V1.dictionaries.bm25 || { k1: 1.2, b: 0.75 }
|
|
483
483
|
let totalTokens = 0
|
|
484
484
|
const docTokensList = []
|
|
485
485
|
const DF = new Map() // term → 出现该词的记录数(document frequency)
|
|
@@ -504,7 +504,7 @@ export function lexicalSearch(corpus, queryPlan, opts = {}) {
|
|
|
504
504
|
}
|
|
505
505
|
const normHeading = normalizeText(rec.heading || '')
|
|
506
506
|
const normBody = normalizeText(text)
|
|
507
|
-
//
|
|
507
|
+
// lexical_pre_v2:BM25 覆盖率——idf 加权(稀有词贡献大)+ tf 饱和(k1)+ 文档长度归一(b)
|
|
508
508
|
const docTokens = tokenize(normHeading + ' ' + normBody)
|
|
509
509
|
const tfMap = new Map()
|
|
510
510
|
for (const tk of docTokens) tfMap.set(tk, (tfMap.get(tk) || 0) + 1)
|
|
@@ -667,7 +667,7 @@ export function replay({ contextSnapshots, corpusSnapshot, gatePolicy, lexicalPo
|
|
|
667
667
|
},
|
|
668
668
|
})
|
|
669
669
|
latch = dec.latched
|
|
670
|
-
cooldown = dec.action === 'retrieve' ?
|
|
670
|
+
cooldown = dec.action === 'retrieve' ? SHADOW_GATE_POLICY_PRE_V1.cooldownSegments : Math.max(0, cooldown - 1)
|
|
671
671
|
}
|
|
672
672
|
return results
|
|
673
673
|
}
|
package/lib/skill-export-host.js
CHANGED
|
@@ -20,9 +20,9 @@ import { join } from 'node:path'
|
|
|
20
20
|
import { renderSkillMarkdownPre, validateSkillMarkdownPre } from './skill-export.js'
|
|
21
21
|
|
|
22
22
|
/** 导出目录前缀(用于识别"这是本插件的产物",避免误覆盖用户手写技能)。 */
|
|
23
|
-
export const
|
|
23
|
+
export const SKILL_EXPORT_PREFIX_PRE_V1 = 'mem-skill-'
|
|
24
24
|
/** 自检标记:出现在 SKILL.md 里即证明是本插件导出物。 */
|
|
25
|
-
export const
|
|
25
|
+
export const SKILL_EXPORT_STAMP_PRE_V1 = 'mem-skill-export-pre-v1'
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* 解析技能导出根目录。
|
|
@@ -94,7 +94,7 @@ export function exportSkillForPre(procedure, opts = {}) {
|
|
|
94
94
|
if (!r.ok) return { ok: false, reason: r.reason }
|
|
95
95
|
|
|
96
96
|
// 自带标记 + 完整性自检(判据 = 用户 ⑪-1/⑪-3 的硬要求)
|
|
97
|
-
const content = r.content + '\n<!-- ' +
|
|
97
|
+
const content = r.content + '\n<!-- ' + SKILL_EXPORT_STAMP_PRE_V1 + ' -->\n'
|
|
98
98
|
const v = validateSkillMarkdownPre(content, { projectName, hasPrograms: programs.length > 0 })
|
|
99
99
|
if (!v.ok) return { ok: false, reason: 'notice-incomplete:' + v.problems.join('|') }
|
|
100
100
|
|
|
@@ -103,8 +103,8 @@ export function exportSkillForPre(procedure, opts = {}) {
|
|
|
103
103
|
if (existsSync(dir) && !opts.force) {
|
|
104
104
|
const old = join(dir, 'SKILL.md')
|
|
105
105
|
const prev = readIfExists(old)
|
|
106
|
-
const isOurs = prev != null && prev.includes(
|
|
107
|
-
const isOursByName = r.dirName.startsWith(
|
|
106
|
+
const isOurs = prev != null && prev.includes(SKILL_EXPORT_STAMP_PRE_V1)
|
|
107
|
+
const isOursByName = r.dirName.startsWith(SKILL_EXPORT_PREFIX_PRE_V1)
|
|
108
108
|
if (!isOurs && isOursByName) {
|
|
109
109
|
// 按名字是我们的,但内容不是 ⇒ 用户手写过,别动
|
|
110
110
|
if (prev != null) return { ok: false, reason: 'dir-occupied-by-foreign' }
|
|
@@ -132,11 +132,11 @@ export function listExportedSkillsPre(skillsRoot) {
|
|
|
132
132
|
let entries = []
|
|
133
133
|
try { entries = readdirSync(root) } catch (_) { return [] }
|
|
134
134
|
for (const name of entries) {
|
|
135
|
-
if (!name.startsWith(
|
|
135
|
+
if (!name.startsWith(SKILL_EXPORT_PREFIX_PRE_V1)) continue
|
|
136
136
|
const dir = join(root, name)
|
|
137
137
|
try { if (!statSync(dir).isDirectory()) continue } catch (_) { continue }
|
|
138
138
|
const prev = readIfExists(join(dir, 'SKILL.md'))
|
|
139
|
-
if (prev == null || !prev.includes(
|
|
139
|
+
if (prev == null || !prev.includes(SKILL_EXPORT_STAMP_PRE_V1)) continue
|
|
140
140
|
const nameLine = /^name:\s*(.+)$/m.exec(prev)
|
|
141
141
|
const projLine = /适用项目\*\*:`([^`]+)`/.exec(prev)
|
|
142
142
|
out.push({
|
package/lib/skill-export.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* 本模块是**纯渲染层**(无 IO、无状态),便于直接单测:
|
|
13
13
|
* - `renderSkillMarkdownPre(procedure, opts)` → SKILL.md 文本
|
|
14
14
|
* - `skillDirNamePre(procedure)` → 目录名(稳定、可预测)
|
|
15
|
-
* - `
|
|
15
|
+
* - `SKILL_USAGE_NOTICE_PRE_V1` → 使用约束条款(导出物必须原样包含)
|
|
16
16
|
*
|
|
17
17
|
* 设计纪律(与 procedure 引擎同源):
|
|
18
18
|
* ① **不新增状态源** —— 导出物是 procedure 的**派生物**,不是新的事实来源;
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
import { createHash } from 'node:crypto'
|
|
26
26
|
|
|
27
27
|
/** 导出物的使用约束条款 —— **必须原样出现在每个 SKILL.md 中**(用户 ⑪-1 硬要求)。 */
|
|
28
|
-
export const
|
|
28
|
+
export const SKILL_USAGE_NOTICE_PRE_V1 = [
|
|
29
29
|
'> **⚠️ 使用约束(必读)**',
|
|
30
30
|
'>',
|
|
31
31
|
'> 本技能由 **dsh-auto-memory** 从一次真实工作过程**自动沉淀**而来,**不是通用最佳实践**。',
|
|
@@ -40,7 +40,7 @@ export const SKILL_USAGE_NOTICE_V1 = [
|
|
|
40
40
|
].join('\n')
|
|
41
41
|
|
|
42
42
|
/** SKILL.md 中必须出现的约束锚点(供套件断言,防止被后续改动悄悄删掉)。 */
|
|
43
|
-
export const
|
|
43
|
+
export const SKILL_NOTICE_ANCHORS_PRE_V1 = Object.freeze([
|
|
44
44
|
'使用约束(必读)',
|
|
45
45
|
'附带程序仅供参考',
|
|
46
46
|
'只做迁移,不要直接运行',
|
|
@@ -76,7 +76,7 @@ const MAX_TITLE_LEN = 80
|
|
|
76
76
|
*/
|
|
77
77
|
export function skillDirNamePre(procedure) {
|
|
78
78
|
const id = String((procedure && procedure.procedureId) || '')
|
|
79
|
-
const short = id.replace(/^
|
|
79
|
+
const short = id.replace(/^proc_pre_/, '').slice(0, 12)
|
|
80
80
|
const title = String((procedure && procedure.title) || '')
|
|
81
81
|
const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 40)
|
|
82
82
|
// 纯中文(或纯符号)标题:ASCII slug 为空 ⇒ 用标题哈希代替,绝不留 'untitled'
|
|
@@ -130,7 +130,7 @@ export function renderSkillMarkdownPre(procedure, opts = {}) {
|
|
|
130
130
|
L.push('')
|
|
131
131
|
L.push('# ' + String(p.title).trim())
|
|
132
132
|
L.push('')
|
|
133
|
-
L.push(
|
|
133
|
+
L.push(SKILL_USAGE_NOTICE_PRE_V1)
|
|
134
134
|
L.push('')
|
|
135
135
|
|
|
136
136
|
// ── 来源(⑪-3:必须标注适用于哪个项目)──
|
|
@@ -224,7 +224,7 @@ export function validateSkillMarkdownPre(content, expect = {}) {
|
|
|
224
224
|
const s = String(content == null ? '' : content)
|
|
225
225
|
const problems = []
|
|
226
226
|
if (!s.trim()) problems.push('empty')
|
|
227
|
-
for (const a of
|
|
227
|
+
for (const a of SKILL_NOTICE_ANCHORS_PRE_V1) if (!s.includes(a)) problems.push('missing-notice:' + a)
|
|
228
228
|
if (!/^---\r?\n[\s\S]*?\r?\n---/.test(s)) problems.push('no-frontmatter')
|
|
229
229
|
if (!/^name:\s*\S+/m.test(s)) problems.push('no-name')
|
|
230
230
|
if (!/^description:\s*\S+/m.test(s)) problems.push('no-description')
|
package/lib/state-commit.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 统一状态提交契约(
|
|
2
|
+
* 统一状态提交契约(state_commit_pre_v1)—— P1 主体(2026-09-15)。
|
|
3
3
|
*
|
|
4
4
|
* 依据:`docs/internal/DESIGN-P1-STATE-COMMIT-20260915.md`(已获用户批准)§2.1 / §2.2 / §2.3,
|
|
5
5
|
* `MASTER-PLAN-3.0.md` Phase 1、`TODO-GRAPH.html` 卡 V2-P1。
|
|
@@ -25,10 +25,10 @@
|
|
|
25
25
|
*/
|
|
26
26
|
import { createHash } from 'node:crypto'
|
|
27
27
|
|
|
28
|
-
export const STATE_COMMIT_VERSION_PRE = '
|
|
28
|
+
export const STATE_COMMIT_VERSION_PRE = 'state_commit_pre_v1'
|
|
29
29
|
|
|
30
|
-
/** miv 前缀(与 `shadow-retrieval.js:144` 既有口径一致:`
|
|
31
|
-
export const MIV_PREFIX_PRE = '
|
|
30
|
+
/** miv 前缀(与 `shadow-retrieval.js:144` 既有口径一致:`idx_pre_` + first32hex)。 */
|
|
31
|
+
export const MIV_PREFIX_PRE = 'idx_pre_'
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
34
|
* 三种状态的**命名隔离**(卡内要求"三种状态不能混")。
|
|
@@ -40,10 +40,10 @@ export const TASK_STATE_PRE = Object.freeze(['open', 'done', 'passed'])
|
|
|
40
40
|
export const ARCHIVE_STATE_PRE = Object.freeze(['active', 'archived'])
|
|
41
41
|
|
|
42
42
|
/** 提交单据必填字段(缺任一 ⇒ fail-closed 拒绝,不猜测)。 */
|
|
43
|
-
export const
|
|
43
|
+
export const COMMIT_REQUIRED_PRE_V1 = Object.freeze(['workspaceKey', 'boardId', 'txId', 'actor'])
|
|
44
44
|
|
|
45
45
|
/** 冲突/拒绝原因码 → 可读中文。 */
|
|
46
|
-
export const
|
|
46
|
+
export const COMMIT_REASONS_PRE_V1 = Object.freeze({
|
|
47
47
|
'not-object': '传入的不是对象',
|
|
48
48
|
'missing-field': '提交单据缺必填字段',
|
|
49
49
|
'invalid-writes': 'writes 形状非法(必须是数组)',
|
|
@@ -55,7 +55,7 @@ export const COMMIT_REASONS_V1 = Object.freeze({
|
|
|
55
55
|
/** 原因码 → 可读中文(未知码原样返回)。 */
|
|
56
56
|
export function describeCommitReasonPre(code) {
|
|
57
57
|
const k = String(code == null ? '' : code)
|
|
58
|
-
return
|
|
58
|
+
return COMMIT_REASONS_PRE_V1[k] || k || '未知原因'
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
function asNonEmptyString(v) {
|
|
@@ -86,13 +86,13 @@ function sha256HexPre(text) {
|
|
|
86
86
|
* 它**不替换**契约 §8 的 `shadow-retrieval.js:145 memoryIndexVersion(sources)` ——
|
|
87
87
|
* 那个吃的是 **source tuples**(scope/sourceRef/epoch/version/fileDigest),是**建索引**侧的真源。
|
|
88
88
|
* 两者**输入不同、用途不同**,不可互相替换;本函数是"提交边界侧"的口径,
|
|
89
|
-
* 且**刻意保持与 §8 相同的前缀与长度**(`
|
|
89
|
+
* 且**刻意保持与 §8 相同的前缀与长度**(`idx_pre_` + 32 hex),使二者在外部看来同形。
|
|
90
90
|
*
|
|
91
91
|
* ⚠️ 本函数**不负责**收敛 `index.js:4188 tierCurrentMivPre()`(那处的缓存/指纹语义属 T0-2 已交付内容),
|
|
92
92
|
* P1 不动它 —— 见设计稿 §2.2 边界说明。任何"用本函数替换 tier 自造版"的改动都**不在 P1 范围**。
|
|
93
93
|
*
|
|
94
94
|
* 输入:`{ records: [{id, status, l0?}], boardCards?: [{id, status}], scope? }`
|
|
95
|
-
* 输出:`'
|
|
95
|
+
* 输出:`'idx_pre_' + first32hex(sha256(canonical))`
|
|
96
96
|
*
|
|
97
97
|
* canonical 构成(按 id 升序,换行拼接,无尾随空白):
|
|
98
98
|
* 每条 → `<id>\t<status>\t<contentDigest>`;`contentDigest` = 内容摘要(无内容时取空串)。
|
|
@@ -135,7 +135,7 @@ export function boardIdPre(workspaceKey, scope) {
|
|
|
135
135
|
const ws = asNonEmptyString(workspaceKey)
|
|
136
136
|
if (!ws) return null
|
|
137
137
|
const sc = asNonEmptyString(scope) || 'Workspace'
|
|
138
|
-
return '
|
|
138
|
+
return 'board_pre_' + sha256HexPre(ws + '|' + sc).slice(0, 24)
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
/**
|
package/lib/storage-manage.js
CHANGED
|
@@ -19,11 +19,11 @@
|
|
|
19
19
|
import { buildSourceCatalog, loadCorpusSnapshot } from './m4-corpus.js'
|
|
20
20
|
import { parseAnchors } from './memory-anchor.js'
|
|
21
21
|
|
|
22
|
-
export const
|
|
22
|
+
export const STORAGE_MANAGE_VERSION_PRE_V1 = 'storage_manage_pre_v1'
|
|
23
23
|
/** 审计环形缓冲上限(最小投影,不记正文)。 */
|
|
24
24
|
const AUDIT_MAX = 64
|
|
25
25
|
/** 可通过「重建 sidecar」自愈的失效分类(与 loadCorpusSnapshot 的 dropped.reason 对齐)。 */
|
|
26
|
-
export const
|
|
26
|
+
export const REPAIRABLE_REASONS_PRE_V1 = Object.freeze(['sidecar-missing', 'sidecar-invalid', 'stale-source', 'record-stale'])
|
|
27
27
|
|
|
28
28
|
export function createStorageManagerPre(opts = {}) {
|
|
29
29
|
// #15 追加:docStore 必须活读——apply 同步阶段 memoryAnchorEnabled 尚未生效,常量解构会把
|
|
@@ -73,7 +73,7 @@ export function createStorageManagerPre(opts = {}) {
|
|
|
73
73
|
status: 'ok', reasons: [], repairable: [],
|
|
74
74
|
}))
|
|
75
75
|
return {
|
|
76
|
-
ok: true, indexEnabled: false, scannedAt: now(), version:
|
|
76
|
+
ok: true, indexEnabled: false, scannedAt: now(), version: STORAGE_MANAGE_VERSION_PRE_V1,
|
|
77
77
|
sources, stale: [],
|
|
78
78
|
counts: { total: sources.length, ok: sources.length, stale: 0, unrepairable: 0 },
|
|
79
79
|
}
|
|
@@ -87,7 +87,7 @@ export function createStorageManagerPre(opts = {}) {
|
|
|
87
87
|
}
|
|
88
88
|
const sources = catalog.sources.map((s) => {
|
|
89
89
|
const reasons = [...new Set(byRef.get(s.sourceRef) || [])]
|
|
90
|
-
const repairable = reasons.filter((r) =>
|
|
90
|
+
const repairable = reasons.filter((r) => REPAIRABLE_REASONS_PRE_V1.includes(r))
|
|
91
91
|
return {
|
|
92
92
|
sourceRef: s.sourceRef, kind: s.kind, scope: s.scope, file: s.file,
|
|
93
93
|
status: repairable.length ? 'stale' : (reasons.length ? 'unrepairable' : 'ok'),
|
|
@@ -99,7 +99,7 @@ export function createStorageManagerPre(opts = {}) {
|
|
|
99
99
|
ok: true,
|
|
100
100
|
indexEnabled: true,
|
|
101
101
|
scannedAt: now(),
|
|
102
|
-
version:
|
|
102
|
+
version: STORAGE_MANAGE_VERSION_PRE_V1,
|
|
103
103
|
sources,
|
|
104
104
|
stale,
|
|
105
105
|
counts: {
|
|
@@ -224,7 +224,7 @@ export function createStorageManagerPre(opts = {}) {
|
|
|
224
224
|
repair,
|
|
225
225
|
deleteMemory,
|
|
226
226
|
auditLog: () => audit.slice(),
|
|
227
|
-
version:
|
|
227
|
+
version: STORAGE_MANAGE_VERSION_PRE_V1,
|
|
228
228
|
dispose() { audit.length = 0 },
|
|
229
229
|
}
|
|
230
230
|
}
|