@a9i5k4/dsh-auto-memory 3.1.5 → 3.1.6
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/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 +193 -34
- 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 +381 -236
- 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/rerank-host.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* rerank-host-pre —— P4 精排多级档位 + 有界异步窗口(2026-09-16,
|
|
2
|
+
* rerank-host-pre —— P4 精排多级档位 + 有界异步窗口(2026-09-16, rerank_host_pre_v1)。
|
|
3
3
|
*
|
|
4
4
|
* 权威依据: MASTER-PLAN-3.0.md §Phase 4 (H2 按实测重写) + TODO-GRAPH V2-P4 卡。
|
|
5
5
|
* 实测推翻方案假设(留痕): bge-reranker-v2-m3 P95 37.4 秒(50 对/题, RSS 3.84GB);
|
|
@@ -22,14 +22,14 @@
|
|
|
22
22
|
* 纯主机侧状态机, 模型调用经 opts.rerankFn 注入(测试可控), 零 fs/零网络。
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
-
export const RERANK_HOST_VERSION = '
|
|
25
|
+
export const RERANK_HOST_VERSION = 'rerank_host_pre_v1'
|
|
26
26
|
|
|
27
27
|
/** 有界异步窗口: 从入队起算(毫秒), 含排队与计算; 到期不续命。 */
|
|
28
|
-
export const
|
|
28
|
+
export const RERANK_WINDOW_MS_PRE_V1 = 60000
|
|
29
29
|
/** 登记的清理宽限: 超窗后仍允许 killFn 在此宽限内终止进程。 */
|
|
30
|
-
export const
|
|
30
|
+
export const RERANK_KILL_GRACE_MS_PRE_V1 = 5000
|
|
31
31
|
/** 缓存上限(条): 有界结果缓存, LRU 逐出。 */
|
|
32
|
-
export const
|
|
32
|
+
export const RERANK_CACHE_MAX_PRE_V1 = 16
|
|
33
33
|
|
|
34
34
|
/** 档位解析: 'off'/'fast'/'enthusiast' 之外一律按 off(fail closed)。 */
|
|
35
35
|
export function resolveRerankTierPre(raw) {
|
|
@@ -79,7 +79,7 @@ export function createRerankHostPre(opts = {}) {
|
|
|
79
79
|
function pruneExpired() {
|
|
80
80
|
const t = now()
|
|
81
81
|
for (const [k, v] of cache) {
|
|
82
|
-
if (t - v.at >
|
|
82
|
+
if (t - v.at > RERANK_WINDOW_MS_PRE_V1) { cache.delete(k); stats.expiredDrops++ }
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
|
|
@@ -100,7 +100,7 @@ export function createRerankHostPre(opts = {}) {
|
|
|
100
100
|
if (typeof opts.rerankFn !== 'function') { stats.tierRejects++; return { mode: 'skip', reason: 'no-worker' } }
|
|
101
101
|
const inputKey = computeRerankInputKeyPre({ query, orderedIds, memoryIndexVersion })
|
|
102
102
|
const hit = cache.get(inputKey)
|
|
103
|
-
if (hit && now() - hit.at <=
|
|
103
|
+
if (hit && now() - hit.at <= RERANK_WINDOW_MS_PRE_V1) {
|
|
104
104
|
// T5-4R: 复用前调用方必须重跑当轮状态/fv2/冷却/预算检查 — 本模块只担保 inputKey 完全匹配。
|
|
105
105
|
stats.reused++
|
|
106
106
|
return { mode: 'reuse', scores: hit.scores, inputKey }
|
|
@@ -115,10 +115,10 @@ export function createRerankHostPre(opts = {}) {
|
|
|
115
115
|
.then(() => opts.rerankFn({ query, ordered: Array.isArray(orderedIds) ? orderedIds.map(String) : [] }))
|
|
116
116
|
.then((scores) => {
|
|
117
117
|
// 到期不续命: 完成时已超窗 ⇒ 丢弃(没有下一轮就过期, 不制造请求)。
|
|
118
|
-
if (now() - startedAt >
|
|
118
|
+
if (now() - startedAt > RERANK_WINDOW_MS_PRE_V1) { stats.expiredDrops++; return }
|
|
119
119
|
// 缓存条目记入队时刻(卡内口径: 窗口从入队起算, 不是完成时刻 — 否则慢计算等于续命)。
|
|
120
120
|
cache.set(inputKey, { at: startedAt, scores: Array.isArray(scores) ? scores : [] })
|
|
121
|
-
if (cache.size >
|
|
121
|
+
if (cache.size > RERANK_CACHE_MAX_PRE_V1) {
|
|
122
122
|
const oldest = [...cache.entries()].sort((a, b) => a[1].at - b[1].at)[0]
|
|
123
123
|
if (oldest) cache.delete(oldest[0])
|
|
124
124
|
}
|
|
@@ -143,7 +143,7 @@ export function createRerankHostPre(opts = {}) {
|
|
|
143
143
|
|
|
144
144
|
/** 是否存在超窗未完成的运行任务(调用方据此在宽限内 kill)。 */
|
|
145
145
|
function isRunningBeyondWindowPre() {
|
|
146
|
-
return !!(running && now() - running.startedAt >
|
|
146
|
+
return !!(running && now() - running.startedAt > RERANK_WINDOW_MS_PRE_V1 + RERANK_KILL_GRACE_MS_PRE_V1)
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
function debugView() {
|
|
@@ -152,7 +152,7 @@ export function createRerankHostPre(opts = {}) {
|
|
|
152
152
|
version: RERANK_HOST_VERSION, tier: resolved.tier, active: resolved.active,
|
|
153
153
|
running: running ? { inputKey: running.inputKey, elapsedMs: now() - running.startedAt, beyondWindow: isRunningBeyondWindowPre() } : null,
|
|
154
154
|
cacheSize: cache.size, stats,
|
|
155
|
-
windowMs:
|
|
155
|
+
windowMs: RERANK_WINDOW_MS_PRE_V1, killGraceMs: RERANK_KILL_GRACE_MS_PRE_V1,
|
|
156
156
|
}
|
|
157
157
|
}
|
|
158
158
|
|
package/lib/rules-edit.js
CHANGED
|
@@ -34,13 +34,13 @@
|
|
|
34
34
|
*/
|
|
35
35
|
|
|
36
36
|
/** 日期段标题(与 rules-layer.js 的 DATE_SECTION_RE 同口径)。 */
|
|
37
|
-
export const
|
|
37
|
+
export const RULE_DATE_SECTION_RE_PRE_V1 = /^##\s*\d{4}-\d{2}-\d{2}\s*$/
|
|
38
38
|
|
|
39
39
|
/** 条目行:允许前导缩进 + `- `(渲染器同样只看 `- ` 行)。 */
|
|
40
|
-
export const
|
|
40
|
+
export const RULE_ITEM_RE_PRE_V1 = /^(\s*)-\s+(.*)$/
|
|
41
41
|
|
|
42
42
|
/** 单条规则长度上限(与写闸门同量级,防止一次塞进整篇文档)。 */
|
|
43
|
-
export const
|
|
43
|
+
export const RULE_ITEM_MAX_CHARS_PRE_V1 = 2000
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
46
|
* 列出所有规则条目(**行号从 0 开始**,供后续增删改定位)。
|
|
@@ -54,8 +54,8 @@ export function listRuleItemsPre(text) {
|
|
|
54
54
|
let curDate = null
|
|
55
55
|
for (let i = 0; i < lines.length; i++) {
|
|
56
56
|
const l = lines[i]
|
|
57
|
-
if (
|
|
58
|
-
const m =
|
|
57
|
+
if (RULE_DATE_SECTION_RE_PRE_V1.test(l)) { curDate = l.replace(/^##\s*/, '').trim(); continue }
|
|
58
|
+
const m = RULE_ITEM_RE_PRE_V1.exec(l)
|
|
59
59
|
if (!m) continue
|
|
60
60
|
const body = m[2].trim()
|
|
61
61
|
if (!body) continue
|
|
@@ -86,7 +86,7 @@ export function updateRuleItemPre(text, idx, nextBody) {
|
|
|
86
86
|
const body = String(nextBody == null ? '' : nextBody).trim()
|
|
87
87
|
if (!Number.isInteger(idx) || idx < 0 || idx >= items.length) return { ok: false, error: 'index-out-of-range' }
|
|
88
88
|
if (!body) return { ok: false, error: 'empty-body' }
|
|
89
|
-
if (body.length >
|
|
89
|
+
if (body.length > RULE_ITEM_MAX_CHARS_PRE_V1) return { ok: false, error: 'too-long' }
|
|
90
90
|
if (/\r|\n/.test(body)) return { ok: false, error: 'multiline-not-allowed' }
|
|
91
91
|
const lines = splitLinesPre(text)
|
|
92
92
|
const it = items[idx]
|
|
@@ -118,7 +118,7 @@ export function removeRuleItemPre(text, idx) {
|
|
|
118
118
|
export function appendRuleItemPre(text, body, opts = {}) {
|
|
119
119
|
const b = String(body == null ? '' : body).trim()
|
|
120
120
|
if (!b) return { ok: false, error: 'empty-body' }
|
|
121
|
-
if (b.length >
|
|
121
|
+
if (b.length > RULE_ITEM_MAX_CHARS_PRE_V1) return { ok: false, error: 'too-long' }
|
|
122
122
|
if (/\r|\n/.test(b)) return { ok: false, error: 'multiline-not-allowed' }
|
|
123
123
|
const lines = splitLinesPre(text)
|
|
124
124
|
const want = opts && opts.dateSection ? String(opts.dateSection).trim() : ''
|
|
@@ -133,11 +133,11 @@ export function appendRuleItemPre(text, body, opts = {}) {
|
|
|
133
133
|
// 找目标日期段,插到该段内最后一个非空行之后
|
|
134
134
|
let secAt = -1
|
|
135
135
|
for (let i = 0; i < lines.length; i++) {
|
|
136
|
-
if (
|
|
136
|
+
if (RULE_DATE_SECTION_RE_PRE_V1.test(lines[i]) && lines[i].replace(/^##\s*/, '').trim() === want) { secAt = i; break }
|
|
137
137
|
}
|
|
138
138
|
if (secAt < 0) return { ok: false, error: 'date-section-not-found' }
|
|
139
139
|
let end = secAt + 1
|
|
140
|
-
while (end < lines.length && !
|
|
140
|
+
while (end < lines.length && !RULE_DATE_SECTION_RE_PRE_V1.test(lines[end])) end++
|
|
141
141
|
let ins = end
|
|
142
142
|
while (ins > secAt + 1 && lines[ins - 1].trim() === '') ins--
|
|
143
143
|
lines.splice(ins, 0, newLine)
|
package/lib/rules-layer.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 规则层抽取(
|
|
2
|
+
* 规则层抽取(rules_layer_pre_v1)—— P6A 的"分类"部分(Phase 6 拆分中的 6A)。
|
|
3
3
|
*
|
|
4
4
|
* 2026-09-14 建立(P0 第五步)。**它解决的是用户最痛的病之一**:
|
|
5
5
|
* 注入开场白原来把所有记忆统一降格为「只是背景事实与规则参考」(`lib/index.js` 的
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
* 等价性由守卫 tests/smoke/smoke-test-p9-rules-lifecycle-pre.mjs 逐例对照断言。
|
|
39
39
|
*/
|
|
40
40
|
|
|
41
|
-
export const RULES_LAYER_VERSION = '
|
|
41
|
+
export const RULES_LAYER_VERSION = 'rules_layer_pre_v1'
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
44
|
* ★P6B(2026-09-15 · 用户裁定开工):日志行内 kind 标记 —— 规则分类的**持久化**落点。
|
|
@@ -50,18 +50,18 @@ export const RULES_LAYER_VERSION = 'rules_layer_v1'
|
|
|
50
50
|
* 可被本模块的条目切分生态直接消费,且重启/迁移不丢(不依赖任何 sidecar)。
|
|
51
51
|
* kind 作**可选参数**(缺省 fact):不传时写入行与旧版逐字节一致(新旧并存 + 开关回退纪律)。
|
|
52
52
|
*/
|
|
53
|
-
export const
|
|
53
|
+
export const LOG_KINDS_PRE_V1 = Object.freeze(['rule', 'preference', 'fact', 'todo'])
|
|
54
54
|
|
|
55
55
|
/** 行内标记的正则形态(渲染/解析两侧共用,不各写一套)。 */
|
|
56
|
-
export const
|
|
56
|
+
export const LOG_KIND_TAG_RE_PRE_V1 = /\s*\[kind:(rule|preference|fact|todo)\]\s*/
|
|
57
57
|
|
|
58
58
|
/** 结构化前缀(高置信):既有用户级记忆的真实形态。 */
|
|
59
|
-
export const
|
|
59
|
+
export const RULE_MARKERS_PRE_V1 = Object.freeze([
|
|
60
60
|
'【用户硬性规则', '【硬性规则', '【规则】', '【必须遵守', '【禁止', '【用户规则',
|
|
61
61
|
])
|
|
62
62
|
|
|
63
63
|
/** 约束语汇(中置信)—— 单靠它**不足以**判定为规则(见文件头 T7-4 说明)。 */
|
|
64
|
-
export const
|
|
64
|
+
export const RULE_CONSTRAINT_WORDS_PRE_V1 = Object.freeze([
|
|
65
65
|
'严禁', '绝不', '必须', '不得', '禁止', '务必', '一律', '永远不', '不要再', '不准',
|
|
66
66
|
])
|
|
67
67
|
|
|
@@ -70,7 +70,7 @@ export const RULE_CONSTRAINT_WORDS_V1 = Object.freeze([
|
|
|
70
70
|
* **不是**当前有效的规则要求 ⇒ 降级为 `candidate`(待确认候选),不得自动取得不可裁地位。
|
|
71
71
|
* 这是 v2 §3.3(Q7 第 2 条)给的反例在代码里的落点。
|
|
72
72
|
*/
|
|
73
|
-
export const
|
|
73
|
+
export const RULE_DESCRIPTION_MARKERS_PRE_V1 = Object.freeze([
|
|
74
74
|
'据文档', '文档写着', '文档说', '曾经', '已取消', '已废弃', '不再要求', '原要求', '旧规则', '据说',
|
|
75
75
|
])
|
|
76
76
|
|
|
@@ -100,10 +100,10 @@ const clean = (s) => String(s == null ? '' : s)
|
|
|
100
100
|
* 条目状态行语法(与 note-status.js **完全一致**;刻意内联见文件头 P9 说明)。
|
|
101
101
|
* 只认整行、只认三态;非法 memoryId / 未知属性一律丢弃(fail-soft)。
|
|
102
102
|
*/
|
|
103
|
-
const
|
|
104
|
-
const
|
|
105
|
-
const
|
|
106
|
-
const
|
|
103
|
+
const STATUS_LINE_RE_PRE_V1 = /^<!--\s*dsh-status:\s*(current|superseded|retracted)\s*((?:\w+=(?:"[^"]*"|[^\s"]+)\s*)*)-->$/
|
|
104
|
+
const STATUS_ATTR_RE_PRE_V1 = /(\w+)=(?:"([^"]*)"|([^\s"]+))/g
|
|
105
|
+
const MEM_ID_RE_PRE_V1 = /^mem_[0-9a-f]{32}$/
|
|
106
|
+
const STATUS_REASON_MAX_PRE_V1 = 120
|
|
107
107
|
|
|
108
108
|
/**
|
|
109
109
|
* 从条目正文解析状态:**最后一个**状态行胜出(口径同 note-status.js 的 statusOfBodyPre)。
|
|
@@ -114,17 +114,17 @@ const STATUS_REASON_MAX_V1 = 120
|
|
|
114
114
|
export function statusOfEntryPre(body) {
|
|
115
115
|
const lines = clean(body).split(/\r?\n/)
|
|
116
116
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
117
|
-
const m =
|
|
117
|
+
const m = STATUS_LINE_RE_PRE_V1.exec(lines[i].trim())
|
|
118
118
|
if (!m) continue
|
|
119
119
|
const out = { status: m[1] }
|
|
120
|
-
|
|
120
|
+
STATUS_ATTR_RE_PRE_V1.lastIndex = 0
|
|
121
121
|
let a
|
|
122
|
-
while ((a =
|
|
122
|
+
while ((a = STATUS_ATTR_RE_PRE_V1.exec(m[2] || '')) !== null) {
|
|
123
123
|
const k = a[1]
|
|
124
124
|
const v = a[2] !== undefined ? a[2] : a[3]
|
|
125
|
-
if (k === 'by') { if (
|
|
125
|
+
if (k === 'by') { if (MEM_ID_RE_PRE_V1.test(String(v || ''))) out.supersededBy = String(v) }
|
|
126
126
|
else if (k === 'reason') {
|
|
127
|
-
const r = String(v || '').replace(/[\r\n]+/g, ' ').trim().slice(0,
|
|
127
|
+
const r = String(v || '').replace(/[\r\n]+/g, ' ').trim().slice(0, STATUS_REASON_MAX_PRE_V1)
|
|
128
128
|
if (r) out.reason = r
|
|
129
129
|
}
|
|
130
130
|
}
|
|
@@ -141,7 +141,7 @@ export function statusOfEntryPre(body) {
|
|
|
141
141
|
export function stripStatusLinesPre(body) {
|
|
142
142
|
const s = clean(body)
|
|
143
143
|
if (!s.includes('<!-- dsh-status:')) return s
|
|
144
|
-
return s.split(/\r?\n/).filter((ln) =>
|
|
144
|
+
return s.split(/\r?\n/).filter((ln) => STATUS_LINE_RE_PRE_V1.exec(ln.trim()) === null).join('\n')
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
/**
|
|
@@ -177,13 +177,13 @@ export function splitMemoryEntriesPre(text) {
|
|
|
177
177
|
function classifyText(rawText) {
|
|
178
178
|
const text = clean(rawText)
|
|
179
179
|
if (!text.trim()) return { kind: 'reference', confidence: 'none', reasons: ['空内容'] }
|
|
180
|
-
const marker =
|
|
180
|
+
const marker = RULE_MARKERS_PRE_V1.find((k) => text.includes(k))
|
|
181
181
|
if (marker) {
|
|
182
182
|
return { kind: 'rule', confidence: 'high', reasons: ['结构化前缀 ' + marker] }
|
|
183
183
|
}
|
|
184
|
-
const words =
|
|
184
|
+
const words = RULE_CONSTRAINT_WORDS_PRE_V1.filter((w) => text.includes(w))
|
|
185
185
|
if (!words.length) return { kind: 'reference', confidence: 'none', reasons: ['无规则标记与约束语汇'] }
|
|
186
|
-
const desc =
|
|
186
|
+
const desc = RULE_DESCRIPTION_MARKERS_PRE_V1.filter((w) => text.includes(w))
|
|
187
187
|
if (desc.length) {
|
|
188
188
|
// ★ v2 修正的落点:含"必须"但是**描述/引文**,不得自动升级为规则
|
|
189
189
|
return {
|
|
@@ -303,7 +303,7 @@ export function ruleSummaryPre(text, max = 200) {
|
|
|
303
303
|
// 去掉列表前缀再返回,保持一条一行
|
|
304
304
|
let body = l.replace(/^[-*+]\s+/, '')
|
|
305
305
|
// ★P6B:剥行内 kind 标记与前置时间戳(只影响渲染,不回写盘上原文)
|
|
306
|
-
body = body.replace(
|
|
306
|
+
body = body.replace(LOG_KIND_TAG_RE_PRE_V1, ' ').trim()
|
|
307
307
|
body = body.replace(/^\d{1,2}:\d{2}\s+/, '').trim()
|
|
308
308
|
if (!body) continue
|
|
309
309
|
return body.length > max ? body.slice(0, Math.max(1, max - 1)) + '…' : body
|
|
@@ -327,8 +327,8 @@ export function renderRulesSectionPre(rules, opts = {}) {
|
|
|
327
327
|
const r = rules && typeof rules === 'object' ? rules : {}
|
|
328
328
|
const list = Array.isArray(r.rules) ? r.rules : []
|
|
329
329
|
if (!list.length) return { text: '', chars: 0, guide: '' }
|
|
330
|
-
const title = clean((opts && opts.title) ||
|
|
331
|
-
const guide = clean((opts && opts.guide) ||
|
|
330
|
+
const title = clean((opts && opts.title) || RULES_SECTION_TITLE_PRE_V1)
|
|
331
|
+
const guide = clean((opts && opts.guide) || RULES_SECTION_GUIDE_PRE_V1)
|
|
332
332
|
// ★P9:空摘要**不渲染**(旧写法会产出裸 "- " 行;放宽小节标记后更易出现)
|
|
333
333
|
const rows = list.map((x) => '- ' + ruleSummaryPre(x.text)).filter((r) => r !== '- ')
|
|
334
334
|
if (!rows.length) return { text: '', chars: 0, guide: '' }
|
|
@@ -340,13 +340,13 @@ export function renderRulesSectionPre(rules, opts = {}) {
|
|
|
340
340
|
* 规则段标题(T7-3:与参考段的措辞**必须不同**;断言直接比对两者)。
|
|
341
341
|
* 用户可通过 `promptLayerOverrides.snapshotRulesTitle` 覆盖。
|
|
342
342
|
*/
|
|
343
|
-
export const
|
|
343
|
+
export const RULES_SECTION_TITLE_PRE_V1 = '[规则 — 用户级硬性约束 · 必须遵守]'
|
|
344
344
|
|
|
345
345
|
/**
|
|
346
346
|
* 规则段引导语:**约束语**("以下为必须遵守的约束"),不是"只是参考"。
|
|
347
347
|
* 这句话就是 P6A「措辞」要修的病:旧开场白把规矩与资料统一降格为「只是背景事实与规则参考」。
|
|
348
348
|
*/
|
|
349
|
-
export const
|
|
349
|
+
export const RULES_SECTION_GUIDE_PRE_V1 = '以下条目是用户明确要求长期遵守的约束,不是可选背景。凡与其它内容冲突,以本节为准;无法满足时必须显式说明。'
|
|
350
350
|
|
|
351
351
|
/** 参考段引导语(保留"参考"语义,与规则段措辞不同)。 */
|
|
352
|
-
export const
|
|
352
|
+
export const REFERENCE_SECTION_GUIDE_PRE_V1 = '以下为背景资料与历史记录,供参考与检索定位;与上面的规则冲突时不适用。'
|
package/lib/semantic-decide.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* JS 端激活判定核(
|
|
2
|
+
* JS 端激活判定核(js_activation_decide_pre_v1) —— 2026-08-27 补全 JS 默认闭环。
|
|
3
3
|
*
|
|
4
|
-
* 与 Python
|
|
5
|
-
* - 意图头:char_wb 2-4gram + sublinear TF-IDF + L2 归一 + LR + Platt(读
|
|
4
|
+
* 与 Python m7_activation_features_pre_v2.decide_activation_v2 逐字段对齐:
|
|
5
|
+
* - 意图头:char_wb 2-4gram + sublinear TF-IDF + L2 归一 + LR + Platt(读 recall_intent_lr_pre_v1.json)
|
|
6
6
|
* - 两车道:explicit(interrogative/recall-ctx) / proactive
|
|
7
7
|
* - echo veto / completeness / margin / delta 门 → decision(lane/reasonCodes 与 Python 一致)
|
|
8
8
|
*
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import { readFileSync } from 'node:fs'
|
|
17
17
|
|
|
18
|
-
export const JS_ACTIVATION_DECIDE_VERSION = '
|
|
18
|
+
export const JS_ACTIVATION_DECIDE_VERSION = 'js_activation_decide_pre_v1'
|
|
19
19
|
|
|
20
20
|
// ---- token 常量(与 Python 逐字一致) ----
|
|
21
21
|
const INTERROG = ['什么', '如何', '怎么', '哪些', '哪个', '为什么', '多少', '吗', '呢', '是不是', '对不对', '有没有', '怎么用', '怎么回事', '是什么', 'how', 'what', 'why', 'which', 'where', 'when']
|
|
@@ -108,7 +108,7 @@ function lexicalContainment(queryText, candidateText) {
|
|
|
108
108
|
return hit / q.size
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
/** 意图头(读
|
|
111
|
+
/** 意图头(读 recall_intent_lr_pre_v1.json,与 Python RecallIntentHead 对齐)。 */
|
|
112
112
|
function createRecallIntentHead(artifact) {
|
|
113
113
|
const fs = artifact.featureSchema
|
|
114
114
|
const vf = fs && fs.vectorizer
|
|
@@ -249,8 +249,8 @@ export function decideActivationV2(features, head, policy) {
|
|
|
249
249
|
|
|
250
250
|
function pack(features, policy, snapshot, decision, reasonCodes, lane) {
|
|
251
251
|
return {
|
|
252
|
-
featurePolicyVersion: (policy && policy.policyVersion) || '
|
|
253
|
-
activationPolicyVersion: (policy && policy.policyVersion) || '
|
|
252
|
+
featurePolicyVersion: (policy && policy.policyVersion) || 'activation_policy_pre_v2',
|
|
253
|
+
activationPolicyVersion: (policy && policy.policyVersion) || 'activation_policy_pre_v2',
|
|
254
254
|
decision,
|
|
255
255
|
reasonCodes,
|
|
256
256
|
advisoryOnly: null,
|
package/lib/semantic-js.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* C2 内置语义引擎宿主(
|
|
2
|
+
* C2 内置语义引擎宿主(js_semantic_engine_pre_v1)+ 资产下载器(js_semantic_dl_pre_v1)。
|
|
3
3
|
* 2026-08-26 用户裁定:C2 是默认主路径,本模块把 js-semantic-trial 试验模块接入生产。
|
|
4
4
|
*
|
|
5
5
|
* 组成:
|
|
@@ -20,10 +20,10 @@ import path from 'node:path'
|
|
|
20
20
|
import { resolveDshHomePre } from './dsh-home.js'
|
|
21
21
|
import { homedir } from 'node:os'
|
|
22
22
|
|
|
23
|
-
export const JS_SEMANTIC_ENGINE_VERSION = '
|
|
24
|
-
export const JS_SEMANTIC_DL_VERSION = '
|
|
23
|
+
export const JS_SEMANTIC_ENGINE_VERSION = 'js_semantic_engine_pre_v1'
|
|
24
|
+
export const JS_SEMANTIC_DL_VERSION = 'js_semantic_dl_pre_v1'
|
|
25
25
|
/** D6 冻结融合权重(与 worker DEFAULT_SEARCH_POLICY 一致)。 */
|
|
26
|
-
export const
|
|
26
|
+
export const D6_FUSION_WEIGHTS_PRE_V1 = Object.freeze({ dense: 0.7, lexical: 0.3 })
|
|
27
27
|
|
|
28
28
|
// ========== 1) D6 minmax 融合(纯函数) ==========
|
|
29
29
|
|
|
@@ -58,7 +58,7 @@ export function fuseD6Pre(pairs) {
|
|
|
58
58
|
memoryId: p.memoryId,
|
|
59
59
|
denseN,
|
|
60
60
|
lexN,
|
|
61
|
-
fused:
|
|
61
|
+
fused: D6_FUSION_WEIGHTS_PRE_V1.dense * denseN + D6_FUSION_WEIGHTS_PRE_V1.lexical * lexN,
|
|
62
62
|
}
|
|
63
63
|
})
|
|
64
64
|
.sort((x, y) => (y.fused !== x.fused ? y.fused - x.fused : (x.memoryId < y.memoryId ? -1 : 1)))
|
|
@@ -381,7 +381,7 @@ export function createJsSemanticEnginePre(opts = {}) {
|
|
|
381
381
|
async rank(corpusSnap, queryText) {
|
|
382
382
|
try {
|
|
383
383
|
const miv = String((corpusSnap && corpusSnap.memoryIndexVersion) || '')
|
|
384
|
-
if (!miv || !/^
|
|
384
|
+
if (!miv || !/^idx_pre_[0-9a-f]{32}$/.test(miv)) return null
|
|
385
385
|
const t = await ensureTier()
|
|
386
386
|
const built = await buildIndexIfStale(miv, corpusSnap && corpusSnap.records)
|
|
387
387
|
if (!built.entries.length) return { miv, scores: new Map() }
|
|
@@ -425,22 +425,22 @@ function clean(t) {
|
|
|
425
425
|
// ========== 3) 资产下载器 ==========
|
|
426
426
|
|
|
427
427
|
/** 资产清单:SHA256 于 2026-08-26 从已验证开发树副本冻结(Xenova/multilingual-e5-small q8)。 */
|
|
428
|
-
const
|
|
428
|
+
const E5_SMALL_Q8_FILES_PRE_V1 = Object.freeze([
|
|
429
429
|
Object.freeze({ rel: 'multilingual-e5-small/onnx/model_quantized.onnx', bytes: 118308185, sha256: 'f80102d3f2a1229f387d3c81909990d8945513e347b0eab049f7de3c6f98c193' }),
|
|
430
430
|
Object.freeze({ rel: 'multilingual-e5-small/tokenizer.json', bytes: 17082730, sha256: '0b44a9d7b51c3c62626640cda0e2c2f70fdacdc25bbbd68038369d14ebdf4c39' }),
|
|
431
431
|
Object.freeze({ rel: 'multilingual-e5-small/config.json', bytes: 658, sha256: 'cb99455288675345e1a4f411438d5d0adbba5fbd3a67ea4fb03c015433b996c1' }),
|
|
432
432
|
Object.freeze({ rel: 'multilingual-e5-small/special_tokens_map.json', bytes: 167, sha256: 'd05497f1da52c5e09554c0cd874037a083e1dc1b9cfd48034d1c717f1afc07a7' }),
|
|
433
433
|
Object.freeze({ rel: 'multilingual-e5-small/tokenizer_config.json', bytes: 443, sha256: 'a1d6bc8734a6f635dc158508bef000f8e2e5a759c7d92f984b2c86e5ff53425b' }),
|
|
434
434
|
])
|
|
435
|
-
export const
|
|
435
|
+
export const E5_SMALL_Q8_MANIFEST_PRE_V1 = Object.freeze({
|
|
436
436
|
model: E5_MODELS_SUBDIR,
|
|
437
437
|
dtype: 'q8',
|
|
438
|
-
files:
|
|
439
|
-
totalBytes:
|
|
438
|
+
files: E5_SMALL_Q8_FILES_PRE_V1,
|
|
439
|
+
totalBytes: E5_SMALL_Q8_FILES_PRE_V1.reduce((s, f) => s + f.bytes, 0),
|
|
440
440
|
})
|
|
441
441
|
|
|
442
442
|
/** 双通道:cn=hf-mirror(国内可达)/ intl=huggingface 官方。auto=国内优先(用户群主体)。 */
|
|
443
|
-
export const
|
|
443
|
+
export const SEMANTIC_DL_MIRRORS_PRE_V1 = Object.freeze({
|
|
444
444
|
cn: 'https://hf-mirror.com/Xenova/multilingual-e5-small/resolve/main/',
|
|
445
445
|
intl: 'https://huggingface.co/Xenova/multilingual-e5-small/resolve/main/',
|
|
446
446
|
})
|
|
@@ -451,7 +451,7 @@ export const SEMANTIC_DL_MIRRORS_V1 = Object.freeze({
|
|
|
451
451
|
*/
|
|
452
452
|
export function createSemanticDownloaderPre(opts = {}) {
|
|
453
453
|
const modelsRoot = String(opts.modelsRoot || '')
|
|
454
|
-
const manifest = opts.manifest ||
|
|
454
|
+
const manifest = opts.manifest || E5_SMALL_Q8_MANIFEST_PRE_V1
|
|
455
455
|
const doFetch = opts.fetchImpl || ((u, o) => fetch(u, o))
|
|
456
456
|
let st = { phase: 'idle', file: '', bytesDone: 0, bytesTotal: manifest.totalBytes, mirrorUsed: '', error: '', startedAt: 0 }
|
|
457
457
|
let cancelFlag = false
|
|
@@ -525,7 +525,7 @@ export function createSemanticDownloaderPre(opts = {}) {
|
|
|
525
525
|
if (cancelFlag) break
|
|
526
526
|
try {
|
|
527
527
|
setState({ mirrorUsed: m })
|
|
528
|
-
const r = await fetchToFile(f,
|
|
528
|
+
const r = await fetchToFile(f, SEMANTIC_DL_MIRRORS_PRE_V1[m])
|
|
529
529
|
const final = path.join(modelsRoot, f.rel)
|
|
530
530
|
mkdirSync(path.dirname(final), { recursive: true })
|
|
531
531
|
try { unlinkSync(final) } catch (_) {}
|
package/lib/shadow-host.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { memoryDir } from './datadir.js'
|
|
1
2
|
/**
|
|
2
3
|
* M4-3 Host Shadow Wiring(docs/M4-CONTRACT.md §5/§14/§15/§17)。
|
|
3
4
|
* 桥接 lib/index.js(M2 ContextObserver/M3 sidecar)与 M4-1 纯核心:
|
|
@@ -16,7 +17,7 @@ import { createHash } from 'node:crypto'
|
|
|
16
17
|
import {
|
|
17
18
|
validateSnapshot, buildQueryPlan, computeSignals, gatePreV1,
|
|
18
19
|
lexicalSearch, buildCandidates, buildRetrievalId, replay as replayCore,
|
|
19
|
-
|
|
20
|
+
SHADOW_GATE_POLICY_PRE_V1, LEXICAL_POLICY_VERSION, GATE_POLICY_VERSION, NAMESPACE,
|
|
20
21
|
} from './shadow-retrieval.js'
|
|
21
22
|
import { buildSourceCatalog, loadCorpusSnapshot, CorpusRegistry, canonicalize } from './m4-corpus.js'
|
|
22
23
|
|
|
@@ -46,12 +47,12 @@ export function truncateAuditEvent(ev) {
|
|
|
46
47
|
* 表放**模块级**而非闭包级:同进程多实例共享 ⇒ 不会各自持一份而双写。
|
|
47
48
|
* 语义同 index.js 的 diagThrottled:首条立即透出,窗口内重复压掉。
|
|
48
49
|
*/
|
|
49
|
-
const
|
|
50
|
+
const DIAG_THROTTLE_MS_PRE_V1 = 300000
|
|
50
51
|
const _diagAt = new Map()
|
|
51
52
|
function shouldReportDiag(key) {
|
|
52
53
|
try {
|
|
53
54
|
const now = Date.now()
|
|
54
|
-
if (now - (Number(_diagAt.get(key)) || 0) <
|
|
55
|
+
if (now - (Number(_diagAt.get(key)) || 0) < DIAG_THROTTLE_MS_PRE_V1) return false
|
|
55
56
|
_diagAt.set(key, now)
|
|
56
57
|
} catch (_) { /* 计时失败不阻断上报 */ }
|
|
57
58
|
return true
|
|
@@ -67,7 +68,7 @@ export function createShadowHost({ engine, onDiag = null, recallStats = null })
|
|
|
67
68
|
const states = new WeakMap() // runtime → shadow state(lazy)
|
|
68
69
|
const volatileRing = [] // ≤64 events(最小投影)
|
|
69
70
|
const stats = { evaluated: 0, retrieved: 0, prefetched: 0, suppressed: 0, stale: 0, errors: 0 }
|
|
70
|
-
const registry = new CorpusRegistry({ sidecarDir: path.join(
|
|
71
|
+
const registry = new CorpusRegistry({ sidecarDir: path.join(memoryDir('index', dshHome), 'files') })
|
|
71
72
|
let lastMemoryIndexVersion = null
|
|
72
73
|
let lastCombo = null
|
|
73
74
|
let enableEpoch = 0
|
|
@@ -155,7 +156,7 @@ export function createShadowHost({ engine, onDiag = null, recallStats = null })
|
|
|
155
156
|
}
|
|
156
157
|
|
|
157
158
|
/** durable audit 目录与日期分片。 */
|
|
158
|
-
function auditDir() { return path.join(
|
|
159
|
+
function auditDir() { return path.join(memoryDir('retrieval', dshHome), 'audit') }
|
|
159
160
|
function dshHome() {
|
|
160
161
|
// ★#86-3:统一口径(M4-4 的「必须拼 .dsh」约定已由 resolveDshHomePre 保证)。
|
|
161
162
|
return resolveDshHomeForEnginePre(engine)
|
|
@@ -291,7 +292,7 @@ export function createShadowHost({ engine, onDiag = null, recallStats = null })
|
|
|
291
292
|
const dec = gatePreV1(snapshot, { previousLatch: st.latched, cooldownRemaining, signals: computeSignals(snapshot, qp, st.recentHits), queryPlan: qp })
|
|
292
293
|
st.lastGate = { action: dec.action, hesitation: dec.hesitation, rawScore: dec.rawScore, latched: dec.latched }
|
|
293
294
|
st.latched = dec.latched
|
|
294
|
-
st.cooldownUntilSegment = dec.action === 'retrieve' ? (st.processedSegmentCount +
|
|
295
|
+
st.cooldownUntilSegment = dec.action === 'retrieve' ? (st.processedSegmentCount + SHADOW_GATE_POLICY_PRE_V1.cooldownSegments) : st.cooldownUntilSegment
|
|
295
296
|
stats.evaluated++
|
|
296
297
|
if (dec.action === 'suppress') { stats.suppressed++; pushSuppressed(dec, snapshot); return }
|
|
297
298
|
// latest-wins abort
|