@a9i5k4/dsh-auto-memory 3.1.4 → 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.
Files changed (95) hide show
  1. package/docs/CONTINUITY-FLOW.md +6 -6
  2. package/docs/CONTRIBUTORS.html +966 -471
  3. package/docs/HANDBOOK.md +54 -53
  4. package/docs/M-CM-PLAN.md +1 -1
  5. package/docs/M-CM-STATE.md +1 -1
  6. package/docs/M-CM7-HANDOFF-LAYERED-RETRIEVAL.md +1 -1
  7. package/docs/M3B-CONTRACT.md +7 -7
  8. package/docs/M6-CONTRACT.md +1 -1
  9. package/docs/M7-AUTONOMOUS-STATE.md +1 -1
  10. package/docs/M7-TASKSET-DISPATCH.md +1 -1
  11. package/docs/PROMPT-PACK-LAYERED-RECALL.md +3 -3
  12. package/docs/PROMPT-SET-STRICT.md +3 -3
  13. package/docs/RELEASE-GO-NOGO.md +1 -1
  14. package/docs/STATUS-BOARD.md +1 -1
  15. package/docs/USER-GUIDE.en.md +21 -21
  16. package/docs/USER-GUIDE.zh-CN.md +21 -21
  17. package/docs/WHITEPAPER.md +1 -1
  18. package/docs/landing/index.html +1178 -1624
  19. package/docs/prompts/FIX-AGENT-P12-FULL-REGRESSION.md +1 -1
  20. package/docs/prompts/LIVE-VERIFY-ZCODE.md +2 -2
  21. package/docs/prompts/P2-semantic-recall.md +2 -2
  22. package/docs/prompts/P7-write-fix.md +1 -1
  23. package/docs/prompts/ZCODE-DROPIN.md +1 -1
  24. package/lib/acceptance.js +6 -6
  25. package/lib/activation-host.js +15 -14
  26. package/lib/activation-inbox-state.js +4 -4
  27. package/lib/activation-inbox.js +77 -47
  28. package/lib/board-mode.js +3 -3
  29. package/lib/client.js +206 -34
  30. package/lib/config-io.js +12 -12
  31. package/lib/context-bridge.js +22 -22
  32. package/lib/context-host.js +24 -23
  33. package/lib/datadir.js +95 -0
  34. package/lib/degrade.js +21 -21
  35. package/lib/dsh-home.js +8 -8
  36. package/lib/engine-identity.js +12 -12
  37. package/lib/engine-switch.js +3 -3
  38. package/lib/episodic-store.js +13 -13
  39. package/lib/evidence-agg.js +4 -4
  40. package/lib/evidence-store.js +12 -12
  41. package/lib/fact-store.js +47 -47
  42. package/lib/handoff-anchor.js +4 -4
  43. package/lib/hub-io.js +356 -3
  44. package/lib/index-sync.js +5 -5
  45. package/lib/index.js +381 -236
  46. package/lib/intent-clean-safe.js +3 -3
  47. package/lib/intent-clean.js +1 -1
  48. package/lib/l0-extract.js +15 -15
  49. package/lib/l0-index-sync.js +8 -8
  50. package/lib/l0-index.js +11 -11
  51. package/lib/ledger-criteria.js +8 -8
  52. package/lib/m4-corpus.js +1 -1
  53. package/lib/m7-index-sync-host.js +2 -2
  54. package/lib/m7-wire.js +24 -24
  55. package/lib/memory-anchor.js +1 -1
  56. package/lib/memory-envelope.js +15 -15
  57. package/lib/memory-hub.js +95 -50
  58. package/lib/memory-importance.js +7 -7
  59. package/lib/memory-mutation.js +5 -5
  60. package/lib/note-status-apply.js +2 -2
  61. package/lib/note-status.js +7 -7
  62. package/lib/policies/activation_policy_v2.json +2 -2
  63. package/lib/policies/recall_intent_lr_v1.json +1 -1
  64. package/lib/procedure-observation.js +2 -2
  65. package/lib/procedure-store.js +117 -28
  66. package/lib/python-setup.js +6 -5
  67. package/lib/python-sidecar-client.js +26 -26
  68. package/lib/recall-fusion.js +12 -12
  69. package/lib/recall-stats.js +36 -1
  70. package/lib/rerank-host.js +11 -11
  71. package/lib/rules-edit.js +9 -9
  72. package/lib/rules-layer.js +26 -26
  73. package/lib/semantic-decide.js +7 -7
  74. package/lib/semantic-js.js +13 -13
  75. package/lib/shadow-host.js +7 -6
  76. package/lib/shadow-retrieval.js +40 -40
  77. package/lib/skill-export-host.js +7 -7
  78. package/lib/skill-export.js +6 -6
  79. package/lib/state-commit.js +10 -10
  80. package/lib/storage-manage.js +6 -6
  81. package/lib/tier-layer-inject.js +28 -28
  82. package/lib/tier0-catalog.js +4 -4
  83. package/lib/wb-contract.js +45 -45
  84. package/lib/wb-sidecar.js +11 -11
  85. package/package.json +3 -4
  86. package/python/m7_activation_features_v2.py +7 -7
  87. package/python/m7_embedding_v1.py +12 -12
  88. package/python/policies/activation_policy_v2.json +2 -2
  89. package/python/policies/decision-record-activation-v2-delta-exp-override-20260824.json +1 -1
  90. package/python/policies/decision-record-reasoning-kind-admission-20260826.json +2 -2
  91. package/python/policies/decision-record-stale-gate-per-candidate-20260825.json +2 -2
  92. package/python/policies/recall_intent_lr_v1.json +1 -1
  93. package/python/verify_policy_artifact.py +3 -3
  94. package/python/worker_semantic_v1.py +18 -18
  95. package/python/worker_v1.py +18 -18
@@ -1,5 +1,5 @@
1
1
  /**
2
- * 记忆注入信封 · 分项账本(memory_envelope_v1)—— 契约 T0-3 / 总纲 §0.5 第 6 条。
2
+ * 记忆注入信封 · 分项账本(memory_envelope_pre_v1)—— 契约 T0-3 / 总纲 §0.5 第 6 条。
3
3
  *
4
4
  * 2026-09-14 建立(P0)。**为什么需要这个模块**:
5
5
  *
@@ -15,7 +15,7 @@
15
15
  * **分项账本**:`chars: { rules, memoryReferences, otherDynamic, total }` +
16
16
  * `limits: { memoryReferences, otherDynamic }`。
17
17
  *
18
- * **本模块的三条不变式**(每条都有能红的断言,见 `tests/smoke/smoke-test-t0-3-budget-ledger-pre.mjs`):
18
+ * **本模块的三条不变式**(每条都有能红的断言,见 `tests/smoke/smoke-test-t0-3-budget-ledger.mjs`):
19
19
  * E1 **总计 = 最终序列化长度**:`chars.total === text.length`(逐字节相等,不是估算)。
20
20
  * E2 **不允许未计费尾巴**:每个字符都归属某个分项;`rules + memoryReferences + otherDynamic === total`。
21
21
  * 标题、分隔符、降级说明**都必须作为某一段的文本被计入**(本模块不给任何"隐含前缀"留位置)。
@@ -34,10 +34,10 @@
34
34
  * UTF-8 无 BOM。
35
35
  */
36
36
 
37
- export const MEMORY_ENVELOPE_VERSION = 'memory_envelope_v1'
37
+ export const MEMORY_ENVELOPE_VERSION = 'memory_envelope_pre_v1'
38
38
 
39
39
  /** 分项取值域(固定顺序仅用于**账本展示**;序列化顺序仍由调用方决定)。 */
40
- export const ENVELOPE_BUCKETS_V1 = Object.freeze(['rules', 'memoryReferences', 'otherDynamic'])
40
+ export const ENVELOPE_BUCKETS_PRE_V1 = Object.freeze(['rules', 'memoryReferences', 'otherDynamic'])
41
41
 
42
42
  /**
43
43
  * 段落优先级(★2026-09-15 新增)—— 决定"超额时先丢谁"。
@@ -50,19 +50,19 @@ export const ENVELOPE_BUCKETS_V1 = Object.freeze(['rules', 'memoryReferences', '
50
50
  * - `normal`:默认档。
51
51
  * - `low`:先丢(如最近日志——量最大、时效性最强、可从文件重读)。
52
52
  */
53
- export const ENVELOPE_PRIORITIES_V1 = Object.freeze(['low', 'normal', 'must'])
53
+ export const ENVELOPE_PRIORITIES_PRE_V1 = Object.freeze(['low', 'normal', 'must'])
54
54
  const PRIORITY_RANK = Object.freeze({ low: 0, normal: 1, must: 2 })
55
55
  const normalizePriority = (v) => (Object.prototype.hasOwnProperty.call(PRIORITY_RANK, String(v)) ? String(v) : 'normal')
56
56
 
57
57
  /** 分项中文名(面板/日志可读;I7 精神:机器枚举之外要有人话)。 */
58
- export const ENVELOPE_BUCKET_TEXT_V1 = Object.freeze({
58
+ export const ENVELOPE_BUCKET_TEXT_PRE_V1 = Object.freeze({
59
59
  rules: '规则类',
60
60
  memoryReferences: '记忆参考类',
61
61
  otherDynamic: '其他动态内容',
62
62
  })
63
63
 
64
64
  /** 账本相关的原因码 → 可读中文。 */
65
- export const ENVELOPE_REASONS_V1 = Object.freeze({
65
+ export const ENVELOPE_REASONS_PRE_V1 = Object.freeze({
66
66
  'over-limit-dropped': '整段丢弃(超出分项上限)',
67
67
  'over-limit-truncated': '截断(单段本身即超上限)',
68
68
  'over-budget-total': '合计超出注入预算(分项账本如实记录,不掩盖)',
@@ -100,7 +100,7 @@ export function composeMemoryEnvelopePre(input = {}) {
100
100
  const segs = rawSegs.map((s, i) => {
101
101
  const text = clean(s && s.text)
102
102
  let bucket = clean(s && s.bucket)
103
- if (!ENVELOPE_BUCKETS_V1.includes(bucket)) {
103
+ if (!ENVELOPE_BUCKETS_PRE_V1.includes(bucket)) {
104
104
  degradations.push({
105
105
  code: 'unknown-bucket', bucket: 'otherDynamic',
106
106
  text: '[降级] 第 ' + (i + 1) + ' 段分项名非法(' + (bucket || '(空)') + ')→ 已计入其他动态内容,未丢弃',
@@ -159,7 +159,7 @@ export function composeMemoryEnvelopePre(input = {}) {
159
159
  // 全是 must ⇒ 不丢,如实报告超额(I7:可见,但不牺牲最高价值内容)
160
160
  degradations.push({
161
161
  code: 'over-limit-must', bucket, inject: true,
162
- text: '[降级] ' + ENVELOPE_BUCKET_TEXT_V1[bucket] + '超出分项上限 ' + lim + ' 字符,但剩余 '
162
+ text: '[降级] ' + ENVELOPE_BUCKET_TEXT_PRE_V1[bucket] + '超出分项上限 ' + lim + ' 字符,但剩余 '
163
163
  + mine.length + ' 段均为 must 级(不可丢)⇒ 本轮**保留全部**并如实报告超额 '
164
164
  + (bucketTotal(bucket) - lim) + ' 字符(不静默丢弃硬性内容)',
165
165
  })
@@ -173,7 +173,7 @@ export function composeMemoryEnvelopePre(input = {}) {
173
173
  dropped.push({ bucket, kind: victim.kind, chars: victim.text.length, priority: victim.priority, reason: 'over-limit-dropped' })
174
174
  degradations.push({
175
175
  code: 'over-limit-dropped', bucket, inject: true,
176
- text: '[降级] ' + ENVELOPE_BUCKET_TEXT_V1[bucket] + '超出分项上限 ' + lim + ' 字符 → 丢弃优先级最低的一段'
176
+ text: '[降级] ' + ENVELOPE_BUCKET_TEXT_PRE_V1[bucket] + '超出分项上限 ' + lim + ' 字符 → 丢弃优先级最低的一段'
177
177
  + (victim.kind ? '(' + victim.kind + ',priority=' + normalizePriority(victim.priority) + ')' : '')
178
178
  + ',未静默',
179
179
  })
@@ -187,7 +187,7 @@ export function composeMemoryEnvelopePre(input = {}) {
187
187
  dropped.push({ bucket, kind: only.kind, chars: only.text.length, priority: only.priority, reason: 'over-limit-truncated' })
188
188
  degradations.push({
189
189
  code: 'over-limit-truncated', bucket, inject: true,
190
- text: '[降级] ' + ENVELOPE_BUCKET_TEXT_V1[bucket] + '上限 ' + lim + ' 字符过小(放不下截断标记)→ 本段本轮为空',
190
+ text: '[降级] ' + ENVELOPE_BUCKET_TEXT_PRE_V1[bucket] + '上限 ' + lim + ' 字符过小(放不下截断标记)→ 本段本轮为空',
191
191
  })
192
192
  } else {
193
193
  const before = only.text.length
@@ -201,14 +201,14 @@ export function composeMemoryEnvelopePre(input = {}) {
201
201
  truncated.push({ bucket, kind: only.kind, charsBefore: before, charsAfter: only.text.length, reason: 'over-limit-truncated' })
202
202
  degradations.push({
203
203
  code: 'over-limit-truncated', bucket, inject: true,
204
- text: '[降级] ' + ENVELOPE_BUCKET_TEXT_V1[bucket] + '只剩一段仍超上限 ' + lim + ' 字符 → 已截断(' + before + ' → ' + only.text.length + ')',
204
+ text: '[降级] ' + ENVELOPE_BUCKET_TEXT_PRE_V1[bucket] + '只剩一段仍超上限 ' + lim + ' 字符 → 已截断(' + before + ' → ' + only.text.length + ')',
205
205
  })
206
206
  }
207
207
  // 截断后复核:must 段自身超限时如实报告,不假装达标
208
208
  if (bucketTotal(bucket) > lim) {
209
209
  degradations.push({
210
210
  code: 'over-limit-must', bucket, inject: true,
211
- text: '[降级] ' + ENVELOPE_BUCKET_TEXT_V1[bucket] + '截断后仍超上限 ' + lim + ' 字符(剩余为 must 级不可丢)⇒ 如实报告超额 '
211
+ text: '[降级] ' + ENVELOPE_BUCKET_TEXT_PRE_V1[bucket] + '截断后仍超上限 ' + lim + ' 字符(剩余为 must 级不可丢)⇒ 如实报告超额 '
212
212
  + (bucketTotal(bucket) - lim) + ' 字符',
213
213
  })
214
214
  }
@@ -227,7 +227,7 @@ export function composeMemoryEnvelopePre(input = {}) {
227
227
  // ── ④ 超额可见性:分项账本如实记录,**不用任何写法掩盖**(v2 明确要求) ──
228
228
  const budgetChars = Number.isFinite(Number(o.budgetChars)) && Number(o.budgetChars) > 0 ? Math.floor(Number(o.budgetChars)) : null
229
229
  if (budgetChars !== null && chars.total > budgetChars) {
230
- const parts = ENVELOPE_BUCKETS_V1.map((b) => ENVELOPE_BUCKET_TEXT_V1[b] + ' ' + chars[b]).join(' · ')
230
+ const parts = ENVELOPE_BUCKETS_PRE_V1.map((b) => ENVELOPE_BUCKET_TEXT_PRE_V1[b] + ' ' + chars[b]).join(' · ')
231
231
  degradations.push({
232
232
  code: 'over-budget-total', bucket: null, inject: false,
233
233
  text: '[降级] 合计 ' + chars.total + ' 字符超出注入预算 ' + budgetChars + '(分项账本如实记录: ' + parts
@@ -252,6 +252,6 @@ export function composeMemoryEnvelopePre(input = {}) {
252
252
  /** 分项账本 → 单行可读文本(进降级行 / 面板 / 排障都用它,口径唯一)。 */
253
253
  export function describeEnvelopeCharsPre(envelope) {
254
254
  const c = (envelope && envelope.chars) || { rules: 0, memoryReferences: 0, otherDynamic: 0, total: 0 }
255
- return ENVELOPE_BUCKETS_V1.map((b) => ENVELOPE_BUCKET_TEXT_V1[b] + ' ' + (c[b] || 0)).join(' · ')
255
+ return ENVELOPE_BUCKETS_PRE_V1.map((b) => ENVELOPE_BUCKET_TEXT_PRE_V1[b] + ' ' + (c[b] || 0)).join(' · ')
256
256
  + ' · 合计 ' + (c.total || 0)
257
257
  }
package/lib/memory-hub.js CHANGED
@@ -26,10 +26,52 @@ import { isObservationOnlyPre } from './procedure-observation.js'
26
26
  */
27
27
  import { createHash } from 'node:crypto'
28
28
 
29
- export const MEMORY_HUB_POLICY_VERSION = 'memory_hub_v1'
29
+ export const MEMORY_HUB_POLICY_VERSION = 'memory_hub_pre_v1'
30
+
31
+ /**
32
+ * ★A-10b(2026-09-23 用户拍板「按推荐方案全修」):条目「观察数」= evidence 各计数之和。
33
+ *
34
+ * 用途:待审批列表的**主排序键**(用户指定口径:观察数降序 → 同数按时间新→旧)。
35
+ * 只读;缺字段按 0 —— 旧快照或异常来源可能缺键(同名规范化在 procedure-store 的
36
+ * `normalizeEvidencePre` 里,本模块不重复实现,只做无副作用的读数兜底)。
37
+ *
38
+ * @param {object} p 条目
39
+ * @returns {number}
40
+ */
41
+ function evidenceTotalPre(p) {
42
+ const ev = (p && p.evidence) || {}
43
+ const n = (v) => Number(v) || 0
44
+ return n(ev.seen) + n(ev.read) + n(ev.cite) + n(ev.reuse) + n(ev.success) + n(ev.correction)
45
+ }
46
+
47
+ /** 比较器:观察数降序 → 时间新→旧(时间取自 updatedAt,缺则 createdAt,再缺按 0)。 */
48
+ function pipelineCmpPre(a, b) {
49
+ const d = evidenceTotalPre(b) - evidenceTotalPre(a)
50
+ if (d) return d
51
+ const ts = (p) => Number((p && (p.updatedAt || p.createdAt)) || 0)
52
+ return ts(b) - ts(a)
53
+ }
54
+
55
+ /**
56
+ * ★A-10b(2026-09-23):待审批列表排序 —— **只在投影层排,不动 store.query() 本体**。
57
+ *
58
+ * 为什么不在 `procedure-store.js` 的 `query()` 里加 `.sort()`:`query()` 有多个消费方
59
+ * (hub 投影 / 测试 / 其它只读面),改它等于改所有消费方的顺序契约;而排序是**这一张列表的展示口径**。
60
+ *
61
+ * 用户指定口径(原话「观察数降序 → 同数按时间新→旧」):见 `pipelineCmpPre`。
62
+ * `Array.prototype.sort` 在本机 V8 上是稳定排序 ⇒ 同键保持 store 内原有相对顺序。
63
+ *
64
+ * ★调用点写法定死:必须**链在 `.filter(...).map(...)` 的尾巴上**(即 `pipeline:` 那个投影表达式的最后一段),
65
+ * **不得**把比较器包在 `stores.procedures.query()` 调用外面。原因:`smoke-test-r2-promotion.mjs`
66
+ * 的 R2a-2 用**字面量锚点**(`pipeline:` 紧跟 `stores.procedures.query()`)作为源码切片起点来锁「只读路径」,
67
+ * 包在外面会让切片起点落在别处、断言取不到 evaluatePromotion ⇒ 打红那条守卫(**实测踩过**:
68
+ * 连本注释里逐字写出该锚点都会把 `indexOf` 引到注释上,故本注释刻意拆开写)。
69
+ * R2a-2 守的**语义**是「pipeline 必须从只读 query() 出发、且不得调 promote()」,
70
+ * 链尾加排序不改变该语义 ⇒ 按守卫守的语义调整写法,不改守卫。
71
+ */
30
72
 
31
73
  /** judgement-shadow 8 类候选 → 记忆层映射(不识别的不消费)。 */
32
- export const KIND_TO_LAYER_V1 = Object.freeze({
74
+ export const KIND_TO_LAYER_PRE_V1 = Object.freeze({
33
75
  semantic_candidate: 'semantic',
34
76
  profile_candidate: 'semantic',
35
77
  procedure_candidate: 'procedure',
@@ -53,7 +95,11 @@ export function createMemoryHubPre(opts = {}) {
53
95
  const { createProcedureStorePre } = opts._stores || {}
54
96
  // ★T10:机械 procedure 切片开关(默认关闭;缺省值在 index.js 的 DEFAULT_CONFIG)。
55
97
  // 这里只读、不判定语义 —— 具体用途见 crossFeed() 的 procedure 分支。
56
- const mechanicalProcedureFeedEnabled = opts.mechanicalProcedureFeedEnabled === true
98
+ // ★A-1(2026-09-23):机械 procedure 切片开关**已退役**(用户裁定「机械通路已被我弃用」)。
99
+ // 原 `mechanicalProcedureFeedEnabled` 只包住 crossFeed 里那条 `intent.slice(0,40)` 分支;
100
+ // 该分支产出的观察行既非真技能、又要用户在审批列表里手动清理 ⇒ 整段删除。
101
+ // ⚠️ 只删该分支:fact 分支、episode 巩固、judgement 消费一律不受影响(用户硬规矩
102
+ // 「单一开关不得顺带改变其他功能行为」)。index.js 对应 getter 与设置项同轮一并移除。
57
103
  const nowFn = typeof opts.now === 'function' ? opts.now : () => Date.now()
58
104
  const log = typeof opts.log === 'function' ? opts.log : () => {}
59
105
 
@@ -76,7 +122,7 @@ export function createMemoryHubPre(opts = {}) {
76
122
  function ingestJudgement(row) {
77
123
  if (!row || typeof row !== 'object') { stats.skipped++; return { skipped: true, reason: 'not-object' } }
78
124
  const kind = row.kindCandidate
79
- const layer = KIND_TO_LAYER_V1[kind]
125
+ const layer = KIND_TO_LAYER_PRE_V1[kind]
80
126
  if (!layer) { stats.skipped++; return { skipped: true, reason: 'unknown-kind:' + kind } }
81
127
  stats.judgedRows++
82
128
  try {
@@ -195,47 +241,27 @@ export function createMemoryHubPre(opts = {}) {
195
241
  // 把 actions 直接当成 steps 会让观察行看起来像真流程,且与后续同名富候选撞车后被合并
196
242
  // 而丢失富候选的 successCriteria(晋升永久卡死)。现在显式标 observationOnly=true,
197
243
  // 并在 title 前过滤运行时信封(避免注入文本变成技能标题)。
198
- const procedureIntent = stripRuntimeIntentPre(ep.intent).trim()
199
- // ★T10(2026-09-20 用户报「技能名/内容看不懂」):**门控机械切片**。
200
- // 关闭时整段跳过 ⇒ 不再产出 `intent.slice(0,40)` 这种机械观察行。
201
- // ⚠️ 只包住 procedure 分支:下方 fact 分支与循环外的逻辑一律不受影响。
202
- if (mechanicalProcedureFeedEnabled && ep.success && stores.procedures && procedureIntent && procedureIntent !== '(未提取)' && ep.actions && ep.actions.length) {
203
- const cand = {
204
- title: procedureIntent.slice(0, 40),
205
- riskLevel: 'low',
206
- steps: ['观察任务:' + procedureIntent.slice(0, 80)],
207
- observationOnly: true,
208
- sourceEpisodes: [ep.episodeId],
209
- sourceMemoryIds: [],
210
- }
211
- const r = stores.procedures.observe(cand)
212
- out.push({ from: 'episode', to: 'procedure', outcome: r.ok ? 'observed' : r.reason })
213
- }
214
- // 有未决事项的 episode → 事实候选(不直接固化,留给 judgement)
215
- if (ep.unresolved && ep.unresolved.length && stores.facts) {
216
- // ★ T1-1(2026-09-19 真机追加):**fact 分支必须与 procedure 分支同样过清洗器**。
217
- // 根因:上面 procedure 分支早已调 `stripRuntimeIntentPre`(:152),但本分支直接用
218
- // **未清洗**的 `ep.intent` ⇒ 运行时信封/U+FFFD 原样进 facts.json ⇒ 前端面板乱码(⑩-a),
219
- // 且经 `hubFlushTick` 写回 `MEMORY.md` 污染注入面与语义语料(⑩-b)。实测证据:
220
- // `fact_ac4920327df6601f25200d66e52df71f` 的 subject 含 22 个 U+FFFD;
221
- // 另两条的 object 内嵌 `Current DSH file policy: …` / `Approval prompts are disabled …`。
222
- // 清洗后再截断,且**空值回退**到 'episode'(保持原 `|| 'episode'` 语义不变)。
223
- // ★ F5(行内残留):清洗器是**按行**判断的,真人与信封挤在同一行时整行必须保留
224
- // (删了会丢人话)⇒ 此时「清洗后是否变化」检测不到脏。故再补一道 `looksRuntimeResiduePre`:
225
- // 该字段**含任何运行时痕迹即整体判脏并丢弃**(置空 ⇒ 走下面的空值回退),
226
- // 而不是把半截信封写进 facts.json。
227
- const rawIntent = String(ep.intent == null ? '' : ep.intent)
228
- const rawObject = String(ep.unresolved[0] == null ? '' : ep.unresolved[0])
229
- const factIntent = looksRuntimeResiduePre(rawIntent) ? '' : stripRuntimeIntentPre(rawIntent).trim()
230
- const factObject = looksRuntimeResiduePre(rawObject) ? '' : stripRuntimeIntentPre(rawObject).trim()
231
- const cand = {
232
- scope: 'Workspace', subject: factIntent.slice(0, 30) || 'episode', predicate: '有未决事项',
233
- object: factObject.slice(0, 60), sourceKind: 'inference',
234
- sourceClass: 'semantic-candidate', provenance: [ep.episodeId],
235
- }
236
- const r = stores.facts.upsert(cand)
237
- out.push({ from: 'episode', to: 'fact', outcome: r.outcome })
238
- }
244
+ // ★A-1(2026-09-23,用户裁定「机械通路已被我弃用」):**机械 procedure 切片整段删除**。
245
+ // 这里原本是 `intent.slice(0,40)` 造观察行 —— 无模型介入、产出的既不是技能名也不是
246
+ // 步骤,只会堆在审批列表里等用户手动清理。开关 `hubMechanicalProcedureFeedEnabled`
247
+ // 及其设置项、getter 同轮一并移除(该开关只服务这一条分支)。
248
+ // ⚠️ 只删这一段:下方 fact 分支、episode 巩固、judgement 消费一律不受影响
249
+ // (用户硬规矩「单一开关不得顺带改变其他功能的行为」)。
250
+ // ⇒ procedural 线的写入自此只剩两条正经通路:模型直写 memory_procedure、用户手动。
251
+ // ★M8-R2(2026-09-23,用户拍板):**删除「情节 → 事实」这条设计外的边**。
252
+ // 背景:设计图的三个 store 输入是分开画的 —— 事实吃判定影子的语义/画像候选,
253
+ // 情节只喂技能。本处曾多出一条图上没有的边,判据仅要求待决数组非空,
254
+ // 而待决抽取是「凡含问号即命中」的纯正则 ⇒ **用户随口一问即被当成事实来源**。
255
+ //
256
+ // 实测后果(报告 docs/internal/M8-PATHWAY-REVIEW-20260923.md §3):
257
+ // 该边累计产出 11 条事实,**11/11 全部是用户口语残句**(谓词统一为「有未决事项」),
258
+ // 零条可用知识;其中一条经回写通路进入项目长期记忆并被每轮无条件注入,
259
+ // 导致下一轮把一段过期的用户原话当成实时指令执行。
260
+ //
261
+ // ⇒ 判据:**情节记录的是「发生过什么」,不是「世界是什么样」**。
262
+ // 会话状态(未决事项)属过程数据,不得升格为长期记忆。
263
+ // 本边删除后,事实层的输入只剩设计规定的两条(判定影子 + 解析富化)。
264
+ // ⚠️ 只删这一条边:下方 episode 巩固、judgement 消费、技能观察一律不受影响。
239
265
  }
240
266
  return { ok: true, fed: out }
241
267
  }
@@ -269,7 +295,12 @@ export function createMemoryHubPre(opts = {}) {
269
295
  } : null,
270
296
  procedures: stores.procedures ? {
271
297
  size: stores.procedures.size,
272
- active: stores.procedures.activeProcedures().map((p) => ({ procedureId: p.procedureId, title: p.title, stage: p.stage, riskLevel: p.riskLevel, evidence: p.evidence })),
298
+ // ★M8-B 第6步(2026-09-23):投影**必须带上归属库**。前端双库视图要回答
299
+ // 「这条技能属于哪个库」,而缺省语义是 `global`(字段缺失=通用库,见
300
+ // procedure-store.js 的 PROCEDURE_DEFAULT_SCOPE_PRE_V1)⇒ 这里显式补齐缺省,
301
+ // 让前端拿到的是**已解释过**的值,而不是「undefined 该当通用库还是未知」。
302
+ // 纯增字段:既有消费方按名取用,不受影响(同 A-10b 补 updatedAt/createdAt 的手法)。
303
+ active: stores.procedures.activeProcedures().map((p) => ({ procedureId: p.procedureId, title: p.title, stage: p.stage, riskLevel: p.riskLevel, evidence: p.evidence, scope: p.scope === 'workspace' ? 'workspace' : 'global', workspaceRef: p.workspaceRef || '', addedBy: p.addedBy || '' })),
273
304
  candidates: stores.procedures.query({ stage: 'candidate' }).map((p) => ({ procedureId: p.procedureId, title: p.title, stage: p.stage })),
274
305
  // 2026-08-30 审批面:全部未 active/未 deprecated 技能(observed/candidate/validated),
275
306
  // 供 hubTab 审批按钮(晋升/激活/弃用)操作
@@ -302,12 +333,20 @@ export function createMemoryHubPre(opts = {}) {
302
333
  return {
303
334
  procedureId: p.procedureId, title: p.title, stage: p.stage, riskLevel: p.riskLevel,
304
335
  evidence: p.evidence, pinned: !!p.pinned, observationOnly: isObservationOnlyPre(p),
336
+ // ★M8-B 第6步(2026-09-23):归属库 + 添加者,供前端「这条属于哪个库」与
337
+ // 转移操作(提升为全局 / 收纳到工作区)。缺省显式补齐为 global(同 active 投影)。
338
+ scope: p.scope === 'workspace' ? 'workspace' : 'global',
339
+ workspaceRef: p.workspaceRef || '', addedBy: p.addedBy || '',
340
+ // ★A-10b(2026-09-23):排序的次键需要时间戳 —— 投影行**必须带上**,
341
+ // 否则 sortPipelinePre 的 `updatedAt || createdAt` 恒为 0,同观察数时退化成磁盘序。
342
+ // 纯增字段(既有消费方按名取用,不受影响)。
343
+ updatedAt: p.updatedAt, createdAt: p.createdAt,
305
344
  // R4 预览用:晋升后会注入的真实 checklist 文本
306
345
  steps: Array.isArray(p.steps) ? p.steps.slice(0, 12) : [],
307
346
  successCriteria: Array.isArray(p.successCriteria) ? p.successCriteria.slice(0, 6) : [],
308
347
  promotion,
309
348
  }
310
- }),
349
+ }).sort(pipelineCmpPre),
311
350
  stats: stores.procedures.getStats ? stores.procedures.getStats() : null,
312
351
  } : null,
313
352
  }
@@ -315,7 +354,7 @@ export function createMemoryHubPre(opts = {}) {
315
354
 
316
355
  function snapshot() {
317
356
  return {
318
- schemaVersion: 1, namespace: 'dsh-auto-memory', policyVersion: MEMORY_HUB_POLICY_VERSION,
357
+ schemaVersion: 1, namespace: 'dsh-auto-memory-pre', policyVersion: MEMORY_HUB_POLICY_VERSION,
319
358
  savedAt: nowFn(),
320
359
  episodic: stores.episodic ? stores.episodic.snapshot() : null,
321
360
  facts: stores.facts ? stores.facts.snapshot({ includeRevoked: true }) : null,
@@ -377,8 +416,14 @@ export function procedureCandidateFromRow(row) {
377
416
  if (kind !== 'procedure_candidate') return null
378
417
  const sourceIds = Array.isArray(row.sourceIds) ? row.sourceIds : []
379
418
  if (!sourceIds.length) return null
380
- // 行内通常只有 memoryId 引用 + 摘要; 步骤用可用的文本特征
381
- const title = String(row.title || ('流程 ' + sourceIds[0].slice(-8)))
419
+ // ★A-2(2026-09-23,用户报「技能库里全是『流程 d3c24f99』这种一句话」):
420
+ // **没有真标题就不生成候选** —— 旧实现用 `'流程 ' + sourceIds[0].slice(-8)` 兜底,
421
+ // 于是一条只有 memoryId 的行会被造成「流程 xxxxxxxx」的技能,库里 9 条此类占位。
422
+ // 兜底是"为了不丢行",但对技能库而言**造一条垃圾比丢一条行更贵**:它会出现在
423
+ // 审批列表、可能被注入、还要用户手动清理。⇒ 改为拒收(返回 null,上层按 not-procedure 跳过)。
424
+ // 判据与 procedureCandidateFromRow 的其余校验同源:**宁缺毋滥**。
425
+ const title = String(row.title || '').trim()
426
+ if (!title) return null
382
427
  const excerpt = String(row.excerpt || row.predicate || '').slice(0, 200)
383
428
  return {
384
429
  title: title.slice(0, 60),
@@ -1,5 +1,5 @@
1
1
  /**
2
- * memory-importance-pre —— evidence → importance 纯核心(M8-2, 2026-09-09; memory_importance_v1)。
2
+ * memory-importance-pre —— evidence → importance 纯核心(M8-2, 2026-09-09; memory_importance_pre_v1)。
3
3
  *
4
4
  * 背景(M8-2 任务书):六类证据 seen/read/cite/reuse/success/correction 已由 M5 写入
5
5
  * (lib/context-bridge.js:45 枚举),消费侧聚合为 fact-store.js:341 evidenceFor(memoryId)
@@ -23,13 +23,13 @@
23
23
  * 数据不可达;待 P3 RRF 实际接线时作为加权因子之一落点(importance 禁止作为唯一排序依据)。
24
24
  */
25
25
 
26
- export const MEMORY_IMPORTANCE_VERSION = 'memory_importance_v1'
26
+ export const MEMORY_IMPORTANCE_VERSION = 'memory_importance_pre_v1'
27
27
 
28
28
  /** 中性值:无 evidence 记录时返回,保证不因此置顶或垫底。 */
29
- export const IMPORTANCE_NEUTRAL_V1 = 0.5
29
+ export const IMPORTANCE_NEUTRAL_PRE_V1 = 0.5
30
30
 
31
31
  /** 权重常数(冻结;调整即新版本)。 */
32
- export const IMPORTANCE_WEIGHTS_V1 = Object.freeze({
32
+ export const IMPORTANCE_WEIGHTS_PRE_V1 = Object.freeze({
33
33
  diversityDivisor: 3, // distinctSessions 饱和点:≥3 个不同会话记满
34
34
  successReuseDivisor: 4, // success+reuse 饱和点:合计 ≥4 次记满
35
35
  posGain: 0.3, // 正向增益上限(0.5 + 0.3 = 0.8)
@@ -57,14 +57,14 @@ export function computeImportancePre(agg) {
57
57
  const total = seen + read + cite + reuse + success + correction
58
58
  // 无任何证据 → 中性(0.5):既不置顶也不垫底(验收 3)
59
59
  if (total === 0 && distinctSessions === 0) {
60
- return { importance: IMPORTANCE_NEUTRAL_V1, correctionRate: 0, neutral: true, total: 0 }
60
+ return { importance: IMPORTANCE_NEUTRAL_PRE_V1, correctionRate: 0, neutral: true, total: 0 }
61
61
  }
62
62
  // correctionRate 口径与 procedure-store.js:268-269 逐字一致(禁止另立)
63
63
  const correctionRate = total > 0 ? correction / total : 0
64
- const w = IMPORTANCE_WEIGHTS_V1
64
+ const w = IMPORTANCE_WEIGHTS_PRE_V1
65
65
  const diversity = Math.min(1, distinctSessions / w.diversityDivisor)
66
66
  const successReuse = Math.min(1, (success + reuse) / w.successReuseDivisor)
67
67
  const pos = 0.5 * diversity + 0.5 * successReuse
68
- const importance = clamp01(IMPORTANCE_NEUTRAL_V1 + w.posGain * pos - w.negGain * correctionRate)
68
+ const importance = clamp01(IMPORTANCE_NEUTRAL_PRE_V1 + w.posGain * pos - w.negGain * correctionRate)
69
69
  return { importance, correctionRate, neutral: false, total }
70
70
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * 记忆写入保护门(memory_mutation_v1)—— 3.0 主体拥有「共同提交与保护入口」。
2
+ * 记忆写入保护门(memory_mutation_pre_v1)—— 3.0 主体拥有「共同提交与保护入口」。
3
3
  *
4
4
  * 2026-09-14 建立(P0)。**边界(总纲 §0.5 / ROUND3 §3.1 定案,必须遵守)**:
5
5
  * - **本模块**只接收**规范化投影**:`{beforeIds, afterIds, protectedRegions, changes}`。
@@ -13,7 +13,7 @@
13
13
  * 实际事故是「白板被整篇覆盖成骨架」(规范已批准、代码从未实现)。
14
14
  * 2026-09-14 实测缺口:`PLAN.md` 一个锚点、一个分区标记都没有。
15
15
  *
16
- * **三条保护**(每条都有能红断言,见 `tests/smoke/smoke-test-t0-8-mutation-gate-pre.mjs`):
16
+ * **三条保护**(每条都有能红断言,见 `tests/smoke/smoke-test-t0-8-mutation-gate.mjs`):
17
17
  * M1 **丢卡保护**:`beforeIds` 里有、`afterIds` 里没有的 id,必须出现在 `archived` 里(显式归档 + 留痕);
18
18
  * 否则**拒绝写入**并报出差异清单(不是静默接受)。
19
19
  * M2 **用户区保护**(B4 预授权默认值:每卡分「模型维护区 / 用户备注区」):
@@ -31,10 +31,10 @@
31
31
  */
32
32
  import { createHash } from 'node:crypto'
33
33
 
34
- export const MEMORY_MUTATION_VERSION = 'memory_mutation_v1'
34
+ export const MEMORY_MUTATION_VERSION = 'memory_mutation_pre_v1'
35
35
 
36
36
  /** 拒绝/提示的原因码 → 可读中文。 */
37
- export const MUTATION_REASONS_V1 = Object.freeze({
37
+ export const MUTATION_REASONS_PRE_V1 = Object.freeze({
38
38
  'card-disappeared': '卡片消失且无归档记录(契约 §4:不允许凭空消失)',
39
39
  'protected-region-lost': '受保护区域(用户备注区)未被原样带回',
40
40
  'protected-region-modified': '受保护区域被改动(必须逐字节保留)',
@@ -46,7 +46,7 @@ export const MUTATION_REASONS_V1 = Object.freeze({
46
46
  /** 原因码 → 可读中文(未知码原样返回)。 */
47
47
  export function describeMutationReasonPre(code) {
48
48
  const k = String(code == null ? '' : code)
49
- return MUTATION_REASONS_V1[k] || k || '未知原因'
49
+ return MUTATION_REASONS_PRE_V1[k] || k || '未知原因'
50
50
  }
51
51
 
52
52
  const asStringArray = (v) => (Array.isArray(v) ? v.map((x) => String(x == null ? '' : x)).filter(Boolean) : null)
@@ -18,7 +18,7 @@
18
18
  *
19
19
  * 纪律:纯函数、零 IO、fail-soft(不改动即返回 null,绝不返回半成品文本)。
20
20
  */
21
- import { NOTE_STATUS_OPEN_V1, renderStatusLinePre, stripStatusLinePre, statusOfBodyPre } from './note-status.js'
21
+ import { NOTE_STATUS_OPEN_PRE_V1, renderStatusLinePre, stripStatusLinePre, statusOfBodyPre } from './note-status.js'
22
22
 
23
23
  /** 锚点:与 `l0-extract.js` / `memory-anchor.js` 同形态(此处独立声明,避免耦合)。 */
24
24
  const ANCHOR_RE = /<!--\s*memory:(mem_[0-9a-f]{32})\s*-->/g
@@ -115,4 +115,4 @@ export function readRecordStatusPre(text, memoryId) {
115
115
  }
116
116
 
117
117
  /** 供反向锁使用:确认本模块**不碰锚点语法**。 */
118
- export const NOTE_STATUS_MARKER_V1 = NOTE_STATUS_OPEN_V1
118
+ export const NOTE_STATUS_MARKER_PRE_V1 = NOTE_STATUS_OPEN_PRE_V1
@@ -29,7 +29,7 @@
29
29
  */
30
30
 
31
31
  /** 状态行开标记。**刻意不复用** `MARKER_OPEN`(`<!-- memory:`)—— 复用会撞锚点契约。 */
32
- export const NOTE_STATUS_OPEN_V1 = '<!-- dsh-status:'
32
+ export const NOTE_STATUS_OPEN_PRE_V1 = '<!-- dsh-status:'
33
33
 
34
34
  /**
35
35
  * 状态行语法(F1 候选 (a):HTML 注释形态)。
@@ -49,10 +49,10 @@ const NOTE_STATUS_ATTR_RE = /(\w+)=(?:"([^"]*)"|([^\s"]+))/g
49
49
  const MEMORY_ID_RE = /^mem_[0-9a-f]{32}$/
50
50
 
51
51
  /** 三态取值域(与 `l0-extract.js` 的 `L0_STATUSES` 同源;此处独立声明以免循环依赖)。 */
52
- export const NOTE_STATUSES_V1 = Object.freeze(['current', 'superseded', 'retracted'])
52
+ export const NOTE_STATUSES_PRE_V1 = Object.freeze(['current', 'superseded', 'retracted'])
53
53
 
54
54
  /** reason 上限:状态行是**元数据**不是正文,超长即视为脏数据。 */
55
- export const NOTE_STATUS_REASON_MAX_V1 = 120
55
+ export const NOTE_STATUS_REASON_MAX_PRE_V1 = 120
56
56
 
57
57
  /**
58
58
  * 渲染状态行(**纯函数**)。
@@ -67,11 +67,11 @@ export const NOTE_STATUS_REASON_MAX_V1 = 120
67
67
  export function renderStatusLinePre(status, opts = {}) {
68
68
  try {
69
69
  if (status !== 'superseded' && status !== 'retracted') return ''
70
- let line = NOTE_STATUS_OPEN_V1 + ' ' + status
70
+ let line = NOTE_STATUS_OPEN_PRE_V1 + ' ' + status
71
71
  const by = opts && opts.supersededBy ? String(opts.supersededBy).trim() : ''
72
72
  if (MEMORY_ID_RE.test(by)) line += ' by=' + by
73
73
  const reason = opts && opts.reason
74
- ? String(opts.reason).replace(/[\r\n]+/g, ' ').replace(/--+>/g, '').trim().slice(0, NOTE_STATUS_REASON_MAX_V1)
74
+ ? String(opts.reason).replace(/[\r\n]+/g, ' ').replace(/--+>/g, '').trim().slice(0, NOTE_STATUS_REASON_MAX_PRE_V1)
75
75
  : ''
76
76
  if (reason) line += ' reason="' + reason.replace(/"/g, "'") + '"'
77
77
  return line + ' -->'
@@ -101,7 +101,7 @@ export function parseStatusLinePre(line) {
101
101
  if (key === 'by') {
102
102
  if (MEMORY_ID_RE.test(String(val || ''))) out.supersededBy = String(val)
103
103
  } else if (key === 'reason') {
104
- const r = String(val || '').replace(/[\r\n]+/g, ' ').trim().slice(0, NOTE_STATUS_REASON_MAX_V1)
104
+ const r = String(val || '').replace(/[\r\n]+/g, ' ').trim().slice(0, NOTE_STATUS_REASON_MAX_PRE_V1)
105
105
  if (r) out.reason = r
106
106
  }
107
107
  }
@@ -140,7 +140,7 @@ export function statusOfBodyPre(body) {
140
140
  */
141
141
  export function stripStatusLinePre(body) {
142
142
  const s = String(body == null ? '' : body)
143
- if (!s.includes(NOTE_STATUS_OPEN_V1)) return s
143
+ if (!s.includes(NOTE_STATUS_OPEN_PRE_V1)) return s
144
144
  // ★ 2026-09-21 bugfix(A-4):原实现 `split(/\r?\n/).join('\n')` 会把**所有** CRLF 改写成裸 LF,
145
145
  // 而本函数的调用方 `applyStatusToRecordPre` 作用于**目标条目正文**并回写整篇文档 ⇒
146
146
  // 每次 supersedes/retract/restore 都破坏该条目的行尾,与「除状态行外逐字节保持原样」的
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "policyVersion": "activation_policy_v2",
4
- "parentPolicyVersion": "m7_semantic_threshold_v1",
3
+ "policyVersion": "activation_policy_pre_v2",
4
+ "parentPolicyVersion": "m7_semantic_threshold_pre_v1",
5
5
  "createdAt": "2026-08-25",
6
6
  "runId": "label-review-cal20260824-1954",
7
7
  "goldDigest": "e74ccdcda998ae51596adf8119c818ac84fc52a4fa657fd687daeda7d14a072d",