@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.
Files changed (93) hide show
  1. package/docs/CONTINUITY-FLOW.md +6 -6
  2. package/docs/HANDBOOK.md +54 -53
  3. package/docs/M-CM-PLAN.md +1 -1
  4. package/docs/M-CM-STATE.md +1 -1
  5. package/docs/M-CM7-HANDOFF-LAYERED-RETRIEVAL.md +1 -1
  6. package/docs/M3B-CONTRACT.md +7 -7
  7. package/docs/M7-AUTONOMOUS-STATE.md +1 -1
  8. package/docs/M7-TASKSET-DISPATCH.md +1 -1
  9. package/docs/PROMPT-PACK-LAYERED-RECALL.md +3 -3
  10. package/docs/PROMPT-SET-STRICT.md +3 -3
  11. package/docs/RELEASE-GO-NOGO.md +1 -1
  12. package/docs/STATUS-BOARD.md +1 -1
  13. package/docs/USER-GUIDE.en.md +21 -21
  14. package/docs/USER-GUIDE.zh-CN.md +21 -21
  15. package/docs/WHITEPAPER.md +1 -1
  16. package/docs/prompts/FIX-AGENT-P12-FULL-REGRESSION.md +1 -1
  17. package/docs/prompts/LIVE-VERIFY-ZCODE.md +2 -2
  18. package/docs/prompts/P2-semantic-recall.md +2 -2
  19. package/docs/prompts/P7-write-fix.md +1 -1
  20. package/docs/prompts/RELEASE-AGENT.md +4 -4
  21. package/docs/prompts/ZCODE-DROPIN.md +1 -1
  22. package/lib/acceptance.js +6 -6
  23. package/lib/activation-host.js +15 -14
  24. package/lib/activation-inbox-state.js +4 -4
  25. package/lib/activation-inbox.js +44 -44
  26. package/lib/board-mode.js +3 -3
  27. package/lib/client.js +225 -41
  28. package/lib/config-io.js +12 -12
  29. package/lib/context-bridge.js +22 -22
  30. package/lib/context-host.js +24 -23
  31. package/lib/datadir.js +95 -0
  32. package/lib/degrade.js +21 -21
  33. package/lib/dsh-home.js +8 -8
  34. package/lib/engine-identity.js +12 -12
  35. package/lib/engine-switch.js +3 -3
  36. package/lib/episodic-store.js +13 -13
  37. package/lib/evidence-agg.js +4 -4
  38. package/lib/evidence-store.js +12 -12
  39. package/lib/fact-store.js +47 -47
  40. package/lib/handoff-anchor.js +4 -4
  41. package/lib/hub-io.js +356 -3
  42. package/lib/index-sync.js +5 -5
  43. package/lib/index.js +426 -242
  44. package/lib/intent-clean-safe.js +3 -3
  45. package/lib/intent-clean.js +1 -1
  46. package/lib/l0-extract.js +15 -15
  47. package/lib/l0-index-sync.js +8 -8
  48. package/lib/l0-index.js +11 -11
  49. package/lib/ledger-criteria.js +8 -8
  50. package/lib/m4-corpus.js +1 -1
  51. package/lib/m7-index-sync-host.js +2 -2
  52. package/lib/m7-wire.js +24 -24
  53. package/lib/memory-anchor.js +1 -1
  54. package/lib/memory-envelope.js +15 -15
  55. package/lib/memory-hub.js +95 -50
  56. package/lib/memory-importance.js +7 -7
  57. package/lib/memory-mutation.js +5 -5
  58. package/lib/note-status-apply.js +2 -2
  59. package/lib/note-status.js +7 -7
  60. package/lib/policies/activation_policy_v2.json +2 -2
  61. package/lib/policies/recall_intent_lr_v1.json +1 -1
  62. package/lib/procedure-observation.js +2 -2
  63. package/lib/procedure-store.js +117 -28
  64. package/lib/python-setup.js +6 -5
  65. package/lib/python-sidecar-client.js +26 -26
  66. package/lib/recall-fusion.js +12 -12
  67. package/lib/recall-stats.js +36 -1
  68. package/lib/rerank-host.js +11 -11
  69. package/lib/rules-edit.js +9 -9
  70. package/lib/rules-layer.js +26 -26
  71. package/lib/semantic-decide.js +7 -7
  72. package/lib/semantic-js.js +13 -13
  73. package/lib/shadow-host.js +7 -6
  74. package/lib/shadow-retrieval.js +40 -40
  75. package/lib/skill-export-host.js +7 -7
  76. package/lib/skill-export.js +6 -6
  77. package/lib/state-commit.js +10 -10
  78. package/lib/storage-manage.js +6 -6
  79. package/lib/tier-layer-inject.js +28 -28
  80. package/lib/tier0-catalog.js +4 -4
  81. package/lib/wb-contract.js +45 -45
  82. package/lib/wb-sidecar.js +11 -11
  83. package/package.json +3 -4
  84. package/python/m7_activation_features_v2.py +7 -7
  85. package/python/m7_embedding_v1.py +12 -12
  86. package/python/policies/activation_policy_v2.json +2 -2
  87. package/python/policies/decision-record-activation-v2-delta-exp-override-20260824.json +1 -1
  88. package/python/policies/decision-record-reasoning-kind-admission-20260826.json +2 -2
  89. package/python/policies/decision-record-stale-gate-per-candidate-20260825.json +2 -2
  90. package/python/policies/recall_intent_lr_v1.json +1 -1
  91. package/python/verify_policy_artifact.py +3 -3
  92. package/python/worker_semantic_v1.py +18 -18
  93. package/python/worker_v1.py +18 -18
@@ -1,5 +1,5 @@
1
1
  /**
2
- * rerank-host-pre —— P4 精排多级档位 + 有界异步窗口(2026-09-16, rerank_host_v1)。
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 = 'rerank_host_v1'
25
+ export const RERANK_HOST_VERSION = 'rerank_host_pre_v1'
26
26
 
27
27
  /** 有界异步窗口: 从入队起算(毫秒), 含排队与计算; 到期不续命。 */
28
- export const RERANK_WINDOW_MS_V1 = 60000
28
+ export const RERANK_WINDOW_MS_PRE_V1 = 60000
29
29
  /** 登记的清理宽限: 超窗后仍允许 killFn 在此宽限内终止进程。 */
30
- export const RERANK_KILL_GRACE_MS_V1 = 5000
30
+ export const RERANK_KILL_GRACE_MS_PRE_V1 = 5000
31
31
  /** 缓存上限(条): 有界结果缓存, LRU 逐出。 */
32
- export const RERANK_CACHE_MAX_V1 = 16
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 > RERANK_WINDOW_MS_V1) { cache.delete(k); stats.expiredDrops++ }
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 <= RERANK_WINDOW_MS_V1) {
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 > RERANK_WINDOW_MS_V1) { stats.expiredDrops++; return }
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 > RERANK_CACHE_MAX_V1) {
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 > RERANK_WINDOW_MS_V1 + RERANK_KILL_GRACE_MS_V1)
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: RERANK_WINDOW_MS_V1, killGraceMs: RERANK_KILL_GRACE_MS_V1,
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 RULE_DATE_SECTION_RE_V1 = /^##\s*\d{4}-\d{2}-\d{2}\s*$/
37
+ export const RULE_DATE_SECTION_RE_PRE_V1 = /^##\s*\d{4}-\d{2}-\d{2}\s*$/
38
38
 
39
39
  /** 条目行:允许前导缩进 + `- `(渲染器同样只看 `- ` 行)。 */
40
- export const RULE_ITEM_RE_V1 = /^(\s*)-\s+(.*)$/
40
+ export const RULE_ITEM_RE_PRE_V1 = /^(\s*)-\s+(.*)$/
41
41
 
42
42
  /** 单条规则长度上限(与写闸门同量级,防止一次塞进整篇文档)。 */
43
- export const RULE_ITEM_MAX_CHARS_V1 = 2000
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 (RULE_DATE_SECTION_RE_V1.test(l)) { curDate = l.replace(/^##\s*/, '').trim(); continue }
58
- const m = RULE_ITEM_RE_V1.exec(l)
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 > RULE_ITEM_MAX_CHARS_V1) return { ok: false, error: 'too-long' }
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 > RULE_ITEM_MAX_CHARS_V1) return { ok: false, error: 'too-long' }
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 (RULE_DATE_SECTION_RE_V1.test(lines[i]) && lines[i].replace(/^##\s*/, '').trim() === want) { secAt = i; break }
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 && !RULE_DATE_SECTION_RE_V1.test(lines[end])) end++
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)
@@ -1,5 +1,5 @@
1
1
  /**
2
- * 规则层抽取(rules_layer_v1)—— P6A 的"分类"部分(Phase 6 拆分中的 6A)。
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 = 'rules_layer_v1'
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 LOG_KINDS_V1 = Object.freeze(['rule', 'preference', 'fact', 'todo'])
53
+ export const LOG_KINDS_PRE_V1 = Object.freeze(['rule', 'preference', 'fact', 'todo'])
54
54
 
55
55
  /** 行内标记的正则形态(渲染/解析两侧共用,不各写一套)。 */
56
- export const LOG_KIND_TAG_RE_V1 = /\s*\[kind:(rule|preference|fact|todo)\]\s*/
56
+ export const LOG_KIND_TAG_RE_PRE_V1 = /\s*\[kind:(rule|preference|fact|todo)\]\s*/
57
57
 
58
58
  /** 结构化前缀(高置信):既有用户级记忆的真实形态。 */
59
- export const RULE_MARKERS_V1 = Object.freeze([
59
+ export const RULE_MARKERS_PRE_V1 = Object.freeze([
60
60
  '【用户硬性规则', '【硬性规则', '【规则】', '【必须遵守', '【禁止', '【用户规则',
61
61
  ])
62
62
 
63
63
  /** 约束语汇(中置信)—— 单靠它**不足以**判定为规则(见文件头 T7-4 说明)。 */
64
- export const RULE_CONSTRAINT_WORDS_V1 = Object.freeze([
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 RULE_DESCRIPTION_MARKERS_V1 = Object.freeze([
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 STATUS_LINE_RE_V1 = /^<!--\s*dsh-status:\s*(current|superseded|retracted)\s*((?:\w+=(?:"[^"]*"|[^\s"]+)\s*)*)-->$/
104
- const STATUS_ATTR_RE_V1 = /(\w+)=(?:"([^"]*)"|([^\s"]+))/g
105
- const MEM_ID_RE_V1 = /^mem_[0-9a-f]{32}$/
106
- const STATUS_REASON_MAX_V1 = 120
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 = STATUS_LINE_RE_V1.exec(lines[i].trim())
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
- STATUS_ATTR_RE_V1.lastIndex = 0
120
+ STATUS_ATTR_RE_PRE_V1.lastIndex = 0
121
121
  let a
122
- while ((a = STATUS_ATTR_RE_V1.exec(m[2] || '')) !== null) {
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 (MEM_ID_RE_V1.test(String(v || ''))) out.supersededBy = String(v) }
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, STATUS_REASON_MAX_V1)
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) => STATUS_LINE_RE_V1.exec(ln.trim()) === null).join('\n')
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 = RULE_MARKERS_V1.find((k) => text.includes(k))
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 = RULE_CONSTRAINT_WORDS_V1.filter((w) => text.includes(w))
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 = RULE_DESCRIPTION_MARKERS_V1.filter((w) => text.includes(w))
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(LOG_KIND_TAG_RE_V1, ' ').trim()
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) || RULES_SECTION_TITLE_V1)
331
- const guide = clean((opts && opts.guide) || RULES_SECTION_GUIDE_V1)
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 RULES_SECTION_TITLE_V1 = '[规则 — 用户级硬性约束 · 必须遵守]'
343
+ export const RULES_SECTION_TITLE_PRE_V1 = '[规则 — 用户级硬性约束 · 必须遵守]'
344
344
 
345
345
  /**
346
346
  * 规则段引导语:**约束语**("以下为必须遵守的约束"),不是"只是参考"。
347
347
  * 这句话就是 P6A「措辞」要修的病:旧开场白把规矩与资料统一降格为「只是背景事实与规则参考」。
348
348
  */
349
- export const RULES_SECTION_GUIDE_V1 = '以下条目是用户明确要求长期遵守的约束,不是可选背景。凡与其它内容冲突,以本节为准;无法满足时必须显式说明。'
349
+ export const RULES_SECTION_GUIDE_PRE_V1 = '以下条目是用户明确要求长期遵守的约束,不是可选背景。凡与其它内容冲突,以本节为准;无法满足时必须显式说明。'
350
350
 
351
351
  /** 参考段引导语(保留"参考"语义,与规则段措辞不同)。 */
352
- export const REFERENCE_SECTION_GUIDE_V1 = '以下为背景资料与历史记录,供参考与检索定位;与上面的规则冲突时不适用。'
352
+ export const REFERENCE_SECTION_GUIDE_PRE_V1 = '以下为背景资料与历史记录,供参考与检索定位;与上面的规则冲突时不适用。'
@@ -1,8 +1,8 @@
1
1
  /**
2
- * JS 端激活判定核(js_activation_decide_v1) —— 2026-08-27 补全 JS 默认闭环。
2
+ * JS 端激活判定核(js_activation_decide_pre_v1) —— 2026-08-27 补全 JS 默认闭环。
3
3
  *
4
- * 与 Python m7_activation_features_v2.decide_activation_v2 逐字段对齐:
5
- * - 意图头:char_wb 2-4gram + sublinear TF-IDF + L2 归一 + LR + Platt(读 recall_intent_lr_v1.json)
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 = 'js_activation_decide_v1'
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
- /** 意图头(读 recall_intent_lr_v1.json,与 Python RecallIntentHead 对齐)。 */
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) || 'activation_policy_v2',
253
- activationPolicyVersion: (policy && policy.policyVersion) || 'activation_policy_v2',
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,
@@ -1,5 +1,5 @@
1
1
  /**
2
- * C2 内置语义引擎宿主(js_semantic_engine_v1)+ 资产下载器(js_semantic_dl_v1)。
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 = 'js_semantic_engine_v1'
24
- export const JS_SEMANTIC_DL_VERSION = 'js_semantic_dl_v1'
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 D6_FUSION_WEIGHTS_V1 = Object.freeze({ dense: 0.7, lexical: 0.3 })
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: D6_FUSION_WEIGHTS_V1.dense * denseN + D6_FUSION_WEIGHTS_V1.lexical * lexN,
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 || !/^idx_[0-9a-f]{32}$/.test(miv)) return null
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 E5_SMALL_Q8_FILES_V1 = Object.freeze([
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 E5_SMALL_Q8_MANIFEST_V1 = Object.freeze({
435
+ export const E5_SMALL_Q8_MANIFEST_PRE_V1 = Object.freeze({
436
436
  model: E5_MODELS_SUBDIR,
437
437
  dtype: 'q8',
438
- files: E5_SMALL_Q8_FILES_V1,
439
- totalBytes: E5_SMALL_Q8_FILES_V1.reduce((s, f) => s + f.bytes, 0),
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 SEMANTIC_DL_MIRRORS_V1 = Object.freeze({
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 || E5_SMALL_Q8_MANIFEST_V1
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, SEMANTIC_DL_MIRRORS_V1[m])
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 (_) {}
@@ -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
- SHADOW_GATE_POLICY_V1, LEXICAL_POLICY_VERSION, GATE_POLICY_VERSION, NAMESPACE,
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 DIAG_THROTTLE_MS_V1 = 300000
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) < DIAG_THROTTLE_MS_V1) return false
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(dshHome(), 'memory', 'index', 'files') })
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(dshHome(), 'memory', 'retrieval-pre', 'audit') }
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 + SHADOW_GATE_POLICY_V1.cooldownSegments) : st.cooldownUntilSegment
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