@a9i5k4/dsh-auto-memory 2.5.3 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/README.md +171 -1
  2. package/README.zh-CN.md +171 -1
  3. package/docs/CONTRIBUTORS.html +471 -0
  4. package/docs/HANDOFF-CRITERIA.md +92 -0
  5. package/docs/INTEGRATION-ANALYSIS.md +350 -348
  6. package/docs/USER-GUIDE.en.md +56 -1
  7. package/docs/USER-GUIDE.zh-CN.md +57 -2
  8. package/docs/internal/ACCEPT-35-LIVE.md +143 -0
  9. package/docs/internal/ACCEPTANCE-20260914.md +90 -0
  10. package/docs/internal/ARCH-REVIEW-BRIEF.md +411 -0
  11. package/docs/internal/ARCH-REVIEW-REQUEST.md +201 -0
  12. package/docs/internal/ARCH-REVIEW-ROUND2.md +169 -0
  13. package/docs/internal/ARCH-REVIEW-ROUND3.md +206 -0
  14. package/docs/internal/AUDIT-WB-GRAPH-FULL-20260916.md +314 -0
  15. package/docs/internal/CONCURRENCY-INVESTIGATION-20260917.md +192 -0
  16. package/docs/internal/CROSS-SESSION-SEARCH-PATH-DECISION.md +72 -0
  17. package/docs/internal/CROSS-SESSION-SEARCH-RESEARCH.md +131 -0
  18. package/docs/internal/DECISIONS-20260914-SESSION.md +269 -0
  19. package/docs/internal/DESIGN-P1-STATE-COMMIT-20260915.md +219 -0
  20. package/docs/internal/DIRECTION-CHECK-WB-GRAPH-20260916.md +132 -0
  21. package/docs/internal/FEEDBACK-TO-DSHAPI-RELAY.md +13 -0
  22. package/docs/internal/GH-DISCUSSION-5732-COMMENT.md +74 -0
  23. package/docs/internal/GPT-ACCEPTANCE-PROMPT-20260916.md +352 -0
  24. package/docs/internal/GPT-REVIEW-PROMPT.md +216 -0
  25. package/docs/internal/GROUP-WEBHOOK-SETUP.md +33 -0
  26. package/docs/internal/KICKOFF-P0.md +254 -0
  27. package/docs/internal/MASTER-PLAN-3.0.md +411 -0
  28. package/docs/internal/MEMORY-MUTATION-AND-INDEX-DESIGN.md +85 -0
  29. package/docs/internal/MERGE-CONFLICT-SCAN-20260914.md +222 -0
  30. package/docs/internal/PENDING-FIXES-20260916.md +289 -0
  31. package/docs/internal/RAG-KARPATHY-PROGRAM.md +229 -0
  32. package/docs/internal/REPORT-P0-NIGHTLY.md +212 -0
  33. package/docs/internal/REPORT-P5-ACCEPTANCE.md +31 -0
  34. package/docs/internal/REPORT-WB-GRAPH-NIGHTLY.md +153 -0
  35. package/docs/internal/REVIEW-WB-GRAPH-SELF.md +81 -0
  36. package/docs/internal/ROADMAP-20260917-WEEK.md +305 -0
  37. package/docs/internal/ROADMAP.md +106 -0
  38. package/docs/internal/RUN-P0-NIGHTLY.md +227 -0
  39. package/docs/internal/S10-CONSTRUCTION-HANDOFF-20260917.md +175 -0
  40. package/docs/internal/S10-GAPS-PLAIN-20260917.md +125 -0
  41. package/docs/internal/SEMANTIC-ARCHITECTURE-SPEC.md +360 -0
  42. package/docs/internal/SESSION-FILE-REPAIR-PROTOCOL.md +90 -0
  43. package/docs/internal/THREE-LAYER-CONTRACT.md +210 -0
  44. package/docs/internal/TODO-BACKLOG.md +263 -142
  45. package/docs/internal/TODO-GRAPH.html +715 -0
  46. package/docs/internal/TODO-GRAPH.html.bak-20260914-v2 +493 -0
  47. package/docs/internal/TODO-GRAPH.html.bak-20260915-alsfix +710 -0
  48. package/docs/internal/TODO-GRAPH.html.bak-20260915-p1 +710 -0
  49. package/docs/internal/TODO-GRAPH.html.bak-20260915-p6a-rev +703 -0
  50. package/docs/internal/TODO-GRAPH.html.bak-20260915-wshint +710 -0
  51. package/docs/internal/TODO-GRAPH.html.bak-20260916-batch +715 -0
  52. package/docs/internal/WB-FORMAT-CONVENTION.md +112 -0
  53. package/docs/internal/WB-GRAPH-DECISIONS-20260914.md +71 -0
  54. package/docs/internal/reviews/CLAIM-VERIFICATION-20260914.md +56 -0
  55. package/docs/internal/reviews/PLAN-gpt6astra-round2-20260914.md +787 -0
  56. package/docs/internal/reviews/REVIEW-gpt6astra-20260914.md +112 -0
  57. package/docs/internal/reviews/ROUND3-REVIEW-INTEGRATION-20260914.md +230 -0
  58. package/docs/prompts/M8-3-enable-verify.md +49 -49
  59. package/lib/acceptance.js +71 -0
  60. package/lib/activation-host.js +90 -9
  61. package/lib/activation-inbox.js +25 -7
  62. package/lib/board-mode.js +30 -0
  63. package/lib/client.js +878 -75
  64. package/lib/context-bridge.js +2 -2
  65. package/lib/context-host.js +70 -6
  66. package/lib/engine-identity.js +149 -0
  67. package/lib/engine-switch.js +247 -0
  68. package/lib/episodic-store.js +11 -10
  69. package/lib/evidence-store.js +2 -2
  70. package/lib/fact-store.js +1 -1
  71. package/lib/fs-retry.js +46 -0
  72. package/lib/index.js +1987 -153
  73. package/lib/intent-clean-safe.js +40 -0
  74. package/lib/intent-clean.js +12 -16
  75. package/lib/l0-extract.js +263 -149
  76. package/lib/l0-index-sync.js +195 -0
  77. package/lib/l0-index.js +349 -239
  78. package/lib/ledger-criteria.js +142 -0
  79. package/lib/m7-index-sync-host.js +65 -4
  80. package/lib/m7-wire.js +3 -3
  81. package/lib/memory-anchor.js +56 -1
  82. package/lib/memory-envelope.js +252 -0
  83. package/lib/memory-hub.js +14 -4
  84. package/lib/memory-mutation.js +246 -0
  85. package/lib/memory-writer.js +204 -24
  86. package/lib/procedure-observation.js +48 -0
  87. package/lib/procedure-store.js +34 -17
  88. package/lib/python-setup.js +1 -1
  89. package/lib/rerank-host.js +160 -0
  90. package/lib/rules-layer.js +261 -0
  91. package/lib/semantic-js.js +15 -0
  92. package/lib/shadow-retrieval.js +3 -3
  93. package/lib/state-commit.js +245 -0
  94. package/lib/subagent-gc.js +4 -8
  95. package/lib/tier-layer-inject.js +650 -0
  96. package/lib/tier0-catalog.js +693 -0
  97. package/lib/water-window.js +263 -186
  98. package/lib/wb-contract.js +495 -0
  99. package/lib/wb-sidecar.js +839 -0
  100. package/lib/ws-overview-rank.js +2 -2
  101. package/package.json +1 -1
  102. package/python/m7_embedding_v1.py +5 -5
  103. package/python/worker_semantic_v1.py +17 -6
  104. package/python/worker_v1.py +38 -4
@@ -12,8 +12,8 @@
12
12
  */
13
13
  import path from 'node:path'
14
14
 
15
- /** 日志文件名日期识别(与 index.js readWorkspaceMemory 的 DATE_RE 同源)。 */
16
- const DATE_RE = /\d{4}-\d{2}-\d{2}/
15
+ /** 日期词干须整串匹配,与 index.js 的 DATE_RE 格式对齐,避免临时/备份文件参与排序(issue #51)。 */
16
+ const DATE_RE = /^(\d{4})-(\d{2})-(\d{2})$/
17
17
 
18
18
  /**
19
19
  * 按记忆活跃度(最新日志 mtime)降序排列工作区。
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@a9i5k4/dsh-auto-memory",
3
3
  "description": "Proactive associative memory for DSH: zero-prompt recall injected before the model speaks, three-layer auto-consolidation, skill crystallization, and Astra-style context management - handoff ledgers, PLAN whiteboard, water-level sensing. Local-first, model-agnostic, zero deps. 主动联想记忆+Astra 式上下文管理:自动唤回/自动沉淀/技能固化/交接账本与白板跨窗口续命/水位感知。",
4
- "version": "2.5.3",
4
+ "version": "3.0.0",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "exports": {
@@ -13,10 +13,10 @@ Pure re-implementation of the frozen policy for the production sidecar path
13
13
  -> configHash; mismatch = stale = full rebuild
14
14
 
15
15
  Providers:
16
- hash-pre-v1 deterministic stdlib-only embedding (sha256-seeded bag of
16
+ hash-v1 deterministic stdlib-only embedding (sha256-seeded bag of
17
17
  token-trigram dims). Zero dependencies, zero network. Used
18
18
  by CI and offline protocol tests. NOT a quality provider.
19
- bge-m3-pre-v1 real model via transformers (lazy import; requires the
19
+ bge-m3-v1 real model via transformers (lazy import; requires the
20
20
  pinned local snapshot dir passed in the embedding config).
21
21
 
22
22
  No DSH file reads; no writes except what the worker explicitly passes in.
@@ -25,9 +25,9 @@ import hashlib
25
25
  import json
26
26
  import re
27
27
 
28
- PROVIDER_REAL = 'bge-m3-pre-v1'
28
+ PROVIDER_REAL = 'bge-m3-v1'
29
29
  PROVIDER_REAL_INT8 = 'bge-m3-onnx-int8-v1'
30
- PROVIDER_HASH = 'hash-pre-v1'
30
+ PROVIDER_HASH = 'hash-v1'
31
31
  CHUNK_POLICY_VERSION = 'm7_chunk_v1'
32
32
  CHUNK_MAX_TOKENS = 512
33
33
  QUERY_MAX_TOKENS = 256
@@ -74,7 +74,7 @@ def config_hash(provider, model_revision, dimension):
74
74
 
75
75
  def chunk_id_for(memory_id, record_digest, ordinal):
76
76
  return 'chk_' + sha_hex(
77
- ('m7-chunk-pre-v1\u0000' + memory_id + '\u0000' + record_digest +
77
+ ('m7-chunk-v1\u0000' + memory_id + '\u0000' + record_digest +
78
78
  '\u0000' + str(ordinal)).encode('utf-8'))[:32]
79
79
 
80
80
 
@@ -19,7 +19,7 @@ index_sync rejection matrix, same atomic derived-corpus persistence. Adds:
19
19
 
20
20
  Embedding backend is selected by an optional JSON config file passed via
21
21
  the DSH_M7_EMBEDDING_CONFIG environment variable (no CLI change, no JS
22
- change): {"provider":"bge-m3-pre-v1"|"hash-pre-v1", "modelDir":...,
22
+ change): {"provider":"bge-m3-v1"|"hash-v1", "modelDir":...,
23
23
  "modelRevision":..., "dimension":1024, "torchThreads":16}.
24
24
  Without the env var the worker degrades to fake-worker behavior (protocol
25
25
  alive, embedding not ready) - never crashes, never changes ack semantics.
@@ -64,7 +64,7 @@ def wsref_of(workspace_key):
64
64
  artifact of differing miv values)."""
65
65
  canon = canonical_workspace_key(workspace_key)
66
66
  return 'wsr_' + hashlib.sha256(
67
- ('evidence-wsref-pre-v1\u0000' + canon).encode('utf-8')).hexdigest()[:32]
67
+ ('evidence-wsref-v1\u0000' + canon).encode('utf-8')).hexdigest()[:32]
68
68
 
69
69
 
70
70
  def _tokenize(text, stopwords=frozenset()):
@@ -640,7 +640,7 @@ class SemanticWorker(base.Worker):
640
640
  if not cands:
641
641
  return None
642
642
  activation_id = 'act_' + base.first32(
643
- base.sha_str('m7-semantic-activation-pre-v1\u0000' + obs))
643
+ base.sha_str('m7-semantic-activation-v1\u0000' + obs))
644
644
  created = req.get('sentAt', 0)
645
645
  ttl = int(pol['ttlSteps'])
646
646
  return {
@@ -690,12 +690,23 @@ class SemanticWorker(base.Worker):
690
690
  if act is None:
691
691
  return None
692
692
  reasons = ','.join(str(x) for x in (out.get('reasonCodes') or []))
693
+
694
+ def _num(x):
695
+ try:
696
+ return float(x or 0.0)
697
+ except (TypeError, ValueError):
698
+ return 0.0
699
+
700
+ # 2026-09-14 P1-⑮:reason 串此前只有 lane/decision/reasonCodes,注入侧看不到
701
+ # 0-1 相似度(intent 概率 / 稠密分 / 融合 margin)。数值段放在 reasonCodes **之前**,
702
+ # 这样 160 字符截断只会砍掉代码列表,不会砍掉分值。
693
703
  act['threshold'] = {
694
704
  'policyVersion': featv2.ACTIVATION_POLICY_VERSION,
695
705
  'score': round(score, 6),
696
706
  'threshold': float(th.get('tauHi', 0.45)),
697
- 'reason': ('fv2 lane=%s %s %s' % (
698
- feats.get('lane'), out.get('decision'), reasons))[:160],
707
+ 'reason': ('fv2 lane=%s %s intent=%.2f dense=%.2f margin=%.2f %s' % (
708
+ feats.get('lane'), out.get('decision'), score,
709
+ _num(feats.get('denseTop')), _num(feats.get('margin')), reasons))[:160],
699
710
  }
700
711
  act['level'] = 'excerpt'
701
712
  return act
@@ -1356,7 +1367,7 @@ def main():
1356
1367
  args, _unknown = ap.parse_known_args()
1357
1368
  if args.selftest:
1358
1369
  base.run_selftest()
1359
- w = SemanticWorker('ep', '', {'provider': 'hash-pre-v1',
1370
+ w = SemanticWorker('ep', '', {'provider': 'hash-v1',
1360
1371
  'dimension': 64})
1361
1372
  view = w.embedding_view()
1362
1373
  assert view['enabled'] is True and view['ready'] is False
@@ -22,12 +22,23 @@ import os
22
22
  import re
23
23
  import sys
24
24
  import tempfile
25
+ import time
25
26
 
26
27
  PROTOCOL = 'm7_wire_v1'
27
28
  NAMESPACE = 'dsh-auto-memory'
28
29
  INDEX_POLICY = 'index_sync_v1'
29
30
  MAX_LINE_BYTES = 256 * 1024
30
31
  MAX_RECORDS_PER_PAGE = 64
32
+ # ★2026-09-17 修「索引永久不就绪 → 语义唤回全程降级」:JS 侧契约是「新 miv latest-wins;
33
+ # 旧 in-flight sync abort/cancel」,但 abort 只作用于 JS 的 fetch,Python 从不收到取消帧;
34
+ # 而 active_sync 原先只在 reject_sync / commit 两处清零 ⇒ 一次「begin 之后没收 page/commit」
35
+ # 的同步会把槽永久占住,此后每次 index_begin 都被回 sync-in-progress。
36
+ # 实测取证:engine-switch-state.json = failed:2527 / error:"sync-in-progress" / startedAt:0;
37
+ # 诊断日志 index-not-ready 累计 22945 行(sync-in-progress 占 664),自 2026-08-26 起持续;
38
+ # worker 于 00:49:50 重启后仍有 1593 行 ⇒ 重启只是暂时缓解,状态会重新形成。
39
+ # 本常量 = 「距最后一次活动多久仍无进展即视为持有者已死」的阈值;取 150s > JS 的 syncTimeoutMs
40
+ # (120s),保证 JS 先放弃、worker 后回收。
41
+ SYNC_TAKEOVER_IDLE_SECONDS = 150
31
42
  STDERR_BUDGET = 64
32
43
 
33
44
  JS_TYPES = frozenset(['health', 'context_push', 'index_sync_begin', 'index_sync_page',
@@ -136,7 +147,7 @@ class Worker:
136
147
  self.active_sync = None # at most one in-flight index sync (JS serializes anyway)
137
148
  self.derived = {} # (workspaceRef, scope) -> current committed entry ONLY (old versions discarded)
138
149
  self.counts = {'frames': 0, 'errors': 0, 'acks': 0, 'activations': 0, 'commits': 0,
139
- 'cancels': 0, 'close_sessions': 0}
150
+ 'cancels': 0, 'close_sessions': 0, 'sync_takeovers': 0}
140
151
 
141
152
  # ---------- outbound ----------
142
153
 
@@ -246,7 +257,7 @@ class Worker:
246
257
  continue
247
258
  if not RE_HEX64.match(fdig) or not RE_HEX64.match(rdig):
248
259
  continue
249
- activation_id = 'act_' + first32(sha_str('m7-fake-activation-pre-v1\u0000' + obs))
260
+ activation_id = 'act_' + first32(sha_str('m7-fake-activation-v1\u0000' + obs))
250
261
  cand = {
251
262
  'candidateId': 'cand_' + first32(sha_str(activation_id + '\u0000' + mid + '\u0000' + str(i))),
252
263
  'memoryId': mid,
@@ -262,7 +273,7 @@ class Worker:
262
273
  candidates.append(cand)
263
274
  if not candidates:
264
275
  return None
265
- activation_id = 'act_' + first32(sha_str('m7-fake-activation-pre-v1\u0000' + obs))
276
+ activation_id = 'act_' + first32(sha_str('m7-fake-activation-v1\u0000' + obs))
266
277
  created = req.get('sentAt', 0)
267
278
  ttl_steps = 2
268
279
  activation = {
@@ -340,11 +351,33 @@ class Worker:
340
351
  require((record_count == 0) == (page_count == 0), 'invalid-payload', 'count-consistency')
341
352
  require(p.get('indexPolicyVersion') == INDEX_POLICY, 'invalid-payload', 'indexPolicyVersion')
342
353
  if self.active_sync is not None:
343
- return [self.index_ack(req, p, 'begin', False, reason='sync-in-progress')]
354
+ prev = self.active_sync
355
+ prev_id = str(prev.get('syncId', ''))
356
+ try:
357
+ last_seen = float(prev.get('lastActivityAt') or prev.get('startedAt') or 0)
358
+ except Exception:
359
+ last_seen = 0.0
360
+ idle = (time.time() - last_seen) if last_seen else 0.0
361
+ same_key = (str(prev.get('workspaceRef', '')) == ws_ref
362
+ and prev.get('scope') == scope)
363
+ superseded = same_key and prev_id != sync_id
364
+ idle_dead = idle > SYNC_TAKEOVER_IDLE_SECONDS
365
+ if superseded or idle_dead:
366
+ # 只在「同 key 被新 syncId 取代」或「超过阈值无任何活动」时接管;
367
+ # 跨工作区并发、以及正在推进的长同步,仍按原样回 sync-in-progress。
368
+ self.counts['sync_takeovers'] = self.counts.get('sync_takeovers', 0) + 1
369
+ diag('index_sync takeover: %s -> %s (%s)' % (
370
+ prev_id or '(none)', sync_id,
371
+ 'superseded' if superseded else 'idle:%.0fs' % idle))
372
+ self.active_sync = None
373
+ else:
374
+ return [self.index_ack(req, p, 'begin', False, reason='sync-in-progress')]
344
375
  self.active_sync = {
345
376
  'syncId': sync_id, 'workspaceRef': ws_ref, 'scope': scope, 'memoryIndexVersion': miv,
346
377
  'recordCount': record_count, 'pageCount': page_count, 'tuples': tuples,
347
378
  'pages': {}, 'next_page': 0, 'received': 0, 'page_digests': [],
379
+ 'startedAt': time.time(),
380
+ 'lastActivityAt': time.time(),
348
381
  }
349
382
  return [self.index_ack(req, p, 'begin', True)]
350
383
 
@@ -412,6 +445,7 @@ class Worker:
412
445
  st['next_page'] = page_no + 1
413
446
  st['received'] += len(records)
414
447
  st['page_digests'].append(recomputed)
448
+ st['lastActivityAt'] = time.time()
415
449
  return [self.index_ack(req, base, 'page', True, extra={
416
450
  'pageNo': page_no, 'receivedPages': len(st['pages']), 'receivedRecords': st['received']})]
417
451