@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.
Files changed (92) 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/ZCODE-DROPIN.md +1 -1
  21. package/lib/acceptance.js +6 -6
  22. package/lib/activation-host.js +15 -14
  23. package/lib/activation-inbox-state.js +4 -4
  24. package/lib/activation-inbox.js +44 -44
  25. package/lib/board-mode.js +3 -3
  26. package/lib/client.js +193 -34
  27. package/lib/config-io.js +12 -12
  28. package/lib/context-bridge.js +22 -22
  29. package/lib/context-host.js +24 -23
  30. package/lib/datadir.js +95 -0
  31. package/lib/degrade.js +21 -21
  32. package/lib/dsh-home.js +8 -8
  33. package/lib/engine-identity.js +12 -12
  34. package/lib/engine-switch.js +3 -3
  35. package/lib/episodic-store.js +13 -13
  36. package/lib/evidence-agg.js +4 -4
  37. package/lib/evidence-store.js +12 -12
  38. package/lib/fact-store.js +47 -47
  39. package/lib/handoff-anchor.js +4 -4
  40. package/lib/hub-io.js +356 -3
  41. package/lib/index-sync.js +5 -5
  42. package/lib/index.js +381 -236
  43. package/lib/intent-clean-safe.js +3 -3
  44. package/lib/intent-clean.js +1 -1
  45. package/lib/l0-extract.js +15 -15
  46. package/lib/l0-index-sync.js +8 -8
  47. package/lib/l0-index.js +11 -11
  48. package/lib/ledger-criteria.js +8 -8
  49. package/lib/m4-corpus.js +1 -1
  50. package/lib/m7-index-sync-host.js +2 -2
  51. package/lib/m7-wire.js +24 -24
  52. package/lib/memory-anchor.js +1 -1
  53. package/lib/memory-envelope.js +15 -15
  54. package/lib/memory-hub.js +95 -50
  55. package/lib/memory-importance.js +7 -7
  56. package/lib/memory-mutation.js +5 -5
  57. package/lib/note-status-apply.js +2 -2
  58. package/lib/note-status.js +7 -7
  59. package/lib/policies/activation_policy_v2.json +2 -2
  60. package/lib/policies/recall_intent_lr_v1.json +1 -1
  61. package/lib/procedure-observation.js +2 -2
  62. package/lib/procedure-store.js +117 -28
  63. package/lib/python-setup.js +6 -5
  64. package/lib/python-sidecar-client.js +26 -26
  65. package/lib/recall-fusion.js +12 -12
  66. package/lib/recall-stats.js +36 -1
  67. package/lib/rerank-host.js +11 -11
  68. package/lib/rules-edit.js +9 -9
  69. package/lib/rules-layer.js +26 -26
  70. package/lib/semantic-decide.js +7 -7
  71. package/lib/semantic-js.js +13 -13
  72. package/lib/shadow-host.js +7 -6
  73. package/lib/shadow-retrieval.js +40 -40
  74. package/lib/skill-export-host.js +7 -7
  75. package/lib/skill-export.js +6 -6
  76. package/lib/state-commit.js +10 -10
  77. package/lib/storage-manage.js +6 -6
  78. package/lib/tier-layer-inject.js +28 -28
  79. package/lib/tier0-catalog.js +4 -4
  80. package/lib/wb-contract.js +45 -45
  81. package/lib/wb-sidecar.js +11 -11
  82. package/package.json +3 -4
  83. package/python/m7_activation_features_v2.py +7 -7
  84. package/python/m7_embedding_v1.py +12 -12
  85. package/python/policies/activation_policy_v2.json +2 -2
  86. package/python/policies/decision-record-activation-v2-delta-exp-override-20260824.json +1 -1
  87. package/python/policies/decision-record-reasoning-kind-admission-20260826.json +2 -2
  88. package/python/policies/decision-record-stale-gate-per-candidate-20260825.json +2 -2
  89. package/python/policies/recall_intent_lr_v1.json +1 -1
  90. package/python/verify_policy_artifact.py +3 -3
  91. package/python/worker_semantic_v1.py +18 -18
  92. package/python/worker_v1.py +18 -18
@@ -3,7 +3,7 @@
3
3
  * 零 IO、零依赖(除 node:crypto);不接 Host、不写文件、不启动 Python、不改 prompt。
4
4
  *
5
5
  * 组成:
6
- * 1) 策略/预算常量(CONTEXT_BRIDGE_BUDGET_V1 / 版本化词典)
6
+ * 1) 策略/预算常量(CONTEXT_BRIDGE_BUDGET_PRE_V1 / 版本化词典)
7
7
  * 2) ContextSegmentPre / AuthorizedMemoryRefPre / EvidenceAggregatePre validator
8
8
  * 3) ContextPushEnvelopePre validator + canonical identity(observationId)
9
9
  * 4) AccessEvidencePre validator + deterministic evidenceId
@@ -24,13 +24,13 @@ const first32 = (h) => h.slice(0, 32)
24
24
  const clamp01 = (x) => Math.max(0, Math.min(1, Number(x) || 0))
25
25
 
26
26
  export { NAMESPACE }
27
- export const CONTEXT_BRIDGE_POLICY_VERSION = 'context_bridge_v1'
28
- export const EVIDENCE_POLICY_VERSION = 'evidence_v1'
29
- export const OBSERVATION_PREFIX = 'obs_'
30
- export const EVIDENCE_PREFIX = 'ev_'
27
+ export const CONTEXT_BRIDGE_POLICY_VERSION = 'context_bridge_pre_v1'
28
+ export const EVIDENCE_POLICY_VERSION = 'evidence_pre_v1'
29
+ export const OBSERVATION_PREFIX = 'obs_pre_'
30
+ export const EVIDENCE_PREFIX = 'ev_pre_'
31
31
 
32
32
  /** §4 budget(冻结;变更必须升级 contextPolicyVersion)。 */
33
- export const CONTEXT_BRIDGE_BUDGET_V1 = Object.freeze({
33
+ export const CONTEXT_BRIDGE_BUDGET_PRE_V1 = Object.freeze({
34
34
  maxSegments: 8,
35
35
  maxInputBytes: 4096,
36
36
  maxMemoryRefs: 8,
@@ -42,16 +42,16 @@ export const CONTEXT_BRIDGE_BUDGET_V1 = Object.freeze({
42
42
  })
43
43
 
44
44
  /** §6 AccessKindPre 枚举。 */
45
- export const ACCESS_KINDS_V1 = Object.freeze(['seen', 'read', 'cite', 'reuse', 'success', 'correction'])
45
+ export const ACCESS_KINDS_PRE_V1 = Object.freeze(['seen', 'read', 'cite', 'reuse', 'success', 'correction'])
46
46
 
47
47
  /** §5 ContextAckPre reason 枚举(版本化)。 */
48
- export const ACK_REASONS_V1 = Object.freeze(['ok', 'disabled', 'busy', 'unsupported', 'oversize', 'stale'])
48
+ export const ACK_REASONS_PRE_V1 = Object.freeze(['ok', 'disabled', 'busy', 'unsupported', 'oversize', 'stale'])
49
49
 
50
50
  /** §7 cite 引用分类器 v1:只认完整 memoryId token(保守;变更必须升级版本)。 */
51
- export const CITATION_MEMORY_ID_PATTERN_V1 = /mem_[0-9a-f]{32}/g
51
+ export const CITATION_MEMORY_ID_PATTERN_PRE_V1 = /mem_[0-9a-f]{32}/g
52
52
 
53
53
  /** §7 correction 分类器 v1 冻结词典(用户纠正/拒绝/反例;precision-first)。 */
54
- export const CORRECTION_LEXICON_V1 = Object.freeze([
54
+ export const CORRECTION_LEXICON_PRE_V1 = Object.freeze([
55
55
  '不对', '错了', '不是这样', '反了', '纠正', '过时', '失效', '别再用', '作废',
56
56
  'wrong', 'incorrect', 'not right', 'actually no', 'outdated', 'deprecated', 'obsolete', 'disagree',
57
57
  ])
@@ -96,7 +96,7 @@ export function validateAuthorizedMemoryRefPre(ref) {
96
96
  if (typeof ref.recordDigest !== 'string' || !HEX64_RE.test(ref.recordDigest)) p.push('recordDigest')
97
97
  if (ref.excerpt !== undefined) {
98
98
  if (typeof ref.excerpt !== 'string') p.push('excerpt')
99
- else if (Buffer.byteLength(ref.excerpt, 'utf8') > CONTEXT_BRIDGE_BUDGET_V1.excerptBytes) p.push('excerpt-budget')
99
+ else if (Buffer.byteLength(ref.excerpt, 'utf8') > CONTEXT_BRIDGE_BUDGET_PRE_V1.excerptBytes) p.push('excerpt-budget')
100
100
  }
101
101
  if (p.length) return { ok: false, reason: 'invalid:' + p.join(',') }
102
102
  return { ok: true, ref }
@@ -124,7 +124,7 @@ export function validateAccessEvidencePre(ev) {
124
124
  if (ev.schemaVersion !== 1) p.push('schemaVersion')
125
125
  if (ev.namespace !== NAMESPACE) p.push('namespace')
126
126
  if (typeof ev.evidenceId !== 'string' || !ev.evidenceId.startsWith(EVIDENCE_PREFIX)) p.push('evidenceId')
127
- if (!ACCESS_KINDS_V1.includes(ev.kind)) p.push('kind')
127
+ if (!ACCESS_KINDS_PRE_V1.includes(ev.kind)) p.push('kind')
128
128
  if (typeof ev.memoryId !== 'string' || !MEMORY_ID_RE.test(ev.memoryId)) p.push('memoryId')
129
129
  if (typeof ev.anchorId !== 'string' || !ev.anchorId) p.push('anchorId')
130
130
  if (!['Session', 'Workspace', 'User'].includes(ev.scope)) p.push('scope')
@@ -157,13 +157,13 @@ export function validateAccessEvidencePre(ev) {
157
157
 
158
158
  /** §4 canonical identity:observationId 由 sessionId+contextVersion+trigger digest+policy 决定。 */
159
159
  export function buildObservationId(sessionId, contextVersion, triggerDigest, policyVersion = CONTEXT_BRIDGE_POLICY_VERSION) {
160
- const parts = ['context-push-v1', String(sessionId || ''), Number(contextVersion) || 0, String(triggerDigest || ''), policyVersion]
160
+ const parts = ['context-push-pre-v1', String(sessionId || ''), Number(contextVersion) || 0, String(triggerDigest || ''), policyVersion]
161
161
  return OBSERVATION_PREFIX + first32(sha256Str(JSON.stringify(parts)))
162
162
  }
163
163
 
164
164
  /** §9 幂等 identity:evidenceId 由 kind/memory/session 坐标/policy 决定(同事件重放同 id)。 */
165
165
  export function buildEvidenceId(input) {
166
- const parts = ['access-evidence-v1', input.kind, input.memoryId, String(input.sessionId || ''), input.eventSeq | 0,
166
+ const parts = ['access-evidence-pre-v1', input.kind, input.memoryId, String(input.sessionId || ''), input.eventSeq | 0,
167
167
  input.nativeSeq === undefined ? null : input.nativeSeq, input.callId || null, input.contextVersion | 0,
168
168
  String(input.workspaceKey || ''), input.policyVersion || EVIDENCE_POLICY_VERSION]
169
169
  return EVIDENCE_PREFIX + first32(sha256Str(JSON.stringify(parts)))
@@ -283,7 +283,7 @@ export function createAccessEvidencePre(input) {
283
283
  * 完整 provenance(无可靠 owner 不建 evidence,§3)→ 每个一条 cite evidence。precision-first。
284
284
  */
285
285
  export function createCiteEvidencesFromText(input) {
286
- const ids = [...new Set(String(input.text == null ? '' : input.text).match(CITATION_MEMORY_ID_PATTERN_V1) || [])].sort()
286
+ const ids = [...new Set(String(input.text == null ? '' : input.text).match(CITATION_MEMORY_ID_PATTERN_PRE_V1) || [])].sort()
287
287
  const out = []
288
288
  for (const mid of ids) {
289
289
  const rec = (input.knownRecords || []).find((r) => r.memoryId === mid)
@@ -301,7 +301,7 @@ export function createCiteEvidencesFromText(input) {
301
301
  /** correction 扫描:同一文本同时出现完整 memoryId 且命中纠正词典 → 每个一条 correction。 */
302
302
  export function createCorrectionEvidencesFromText(input) {
303
303
  const norm = String(input.text == null ? '' : input.text).normalize('NFKC').replace(/[A-Z]/g, (c) => c.toLowerCase())
304
- if (!CORRECTION_LEXICON_V1.some((w) => norm.includes(w))) return []
304
+ if (!CORRECTION_LEXICON_PRE_V1.some((w) => norm.includes(w))) return []
305
305
  return createCiteEvidencesFromText(input).map((ev) => ({
306
306
  ...ev,
307
307
  kind: 'correction',
@@ -388,7 +388,7 @@ export function createFakeContextSinkPre(opts = {}) {
388
388
  frames.push(JSON.parse(JSON.stringify(frame)))
389
389
  if (frames.length > capacity) frames.shift()
390
390
  stats.accepted++
391
- return { observationId: id, accepted: true, workerEpoch: 'fake-epoch-v1', reason: 'ok' }
391
+ return { observationId: id, accepted: true, workerEpoch: 'fake-epoch-pre-v1', reason: 'ok' }
392
392
  },
393
393
  async closeSession() { stats.closedSessions++ },
394
394
  async dispose() { frames.length = 0 },
@@ -401,7 +401,7 @@ export function validateContextAckPre(ack) {
401
401
  if (typeof ack.observationId !== 'string' || !ack.observationId) return { ok: false, reason: 'observationId' }
402
402
  if (typeof ack.accepted !== 'boolean') return { ok: false, reason: 'accepted' }
403
403
  if (ack.workerEpoch !== undefined && typeof ack.workerEpoch !== 'string') return { ok: false, reason: 'workerEpoch' }
404
- if (ack.reason !== undefined && !ACK_REASONS_V1.includes(ack.reason)) return { ok: false, reason: 'reason' }
404
+ if (ack.reason !== undefined && !ACK_REASONS_PRE_V1.includes(ack.reason)) return { ok: false, reason: 'reason' }
405
405
  return { ok: true, ack }
406
406
  }
407
407
 
@@ -424,7 +424,7 @@ export function buildAuthorizedMemoryRefFromRecord(rec, excerptText) {
424
424
  * input: {session, cursor, index, trigger, window, memoryRefs, evidence, now, policyVersionGate?, policyVersionLexical?}
425
425
  */
426
426
  export function buildContextPushEnvelopePre(input) {
427
- const B = CONTEXT_BRIDGE_BUDGET_V1
427
+ const B = CONTEXT_BRIDGE_BUDGET_PRE_V1
428
428
  const drop = []
429
429
  const session = input && input.session ? input.session : {}
430
430
  const cursor = input && input.cursor ? input.cursor : {}
@@ -483,8 +483,8 @@ export function buildContextPushEnvelopePre(input) {
483
483
  evidence: aggs.map((a) => a.aggregate),
484
484
  policy: {
485
485
  contextPolicyVersion: CONTEXT_BRIDGE_POLICY_VERSION,
486
- gatePolicyVersion: String(input.policyVersionGate || 'gate_v1'),
487
- lexicalPolicyVersion: String(input.policyVersionLexical || 'lexical_v2'),
486
+ gatePolicyVersion: String(input.policyVersionGate || 'gate_pre_v1'),
487
+ lexicalPolicyVersion: String(input.policyVersionLexical || 'lexical_pre_v2'),
488
488
  evidencePolicyVersion: EVIDENCE_POLICY_VERSION,
489
489
  },
490
490
  budget: { maxSegments: B.maxSegments, maxInputBytes: B.maxInputBytes, maxMemoryRefs: B.maxMemoryRefs, maxEvidenceItems: B.maxEvidenceItems },
@@ -534,7 +534,7 @@ export function createContextPushBridge(opts = {}) {
534
534
  const sink = opts.sink
535
535
  if (!sink || typeof sink.push !== 'function') throw new Error('context-bridge: sink required')
536
536
  const signal = opts.signal
537
- const sent = new BoundedIdSet(CONTEXT_BRIDGE_BUDGET_V1.sentObservationIds)
537
+ const sent = new BoundedIdSet(CONTEXT_BRIDGE_BUDGET_PRE_V1.sentObservationIds)
538
538
  const inflight = new Map()
539
539
  const stats = { sent: 0, accepted: 0, duplicates: 0, superseded: 0, aborted: 0, errors: 0 }
540
540
  async function push(frame) {
@@ -1,3 +1,4 @@
1
+ import { memoryDir } from './datadir.js'
1
2
  /**
2
3
  * M5-3 Context Bridge Host Wiring(docs/M5-CONTRACT.md §12 M5-3)。
3
4
  * 桥接 lib/index.js(M2 SessionRuntime/事件流)与 M5-1 纯核心+M5-2 Store:
@@ -5,7 +6,7 @@
5
6
  * - accepted Segment → 组装 ContextPushEnvelopePre(trigger/window/memoryRefs/aggregates)
6
7
  * → Null/Fake sink(按 config.contextSinkMode 切换)→ push bridge(幂等/latest-wins/abort)
7
8
  * - read coverage/cite/correction → AccessEvidencePre → EvidenceEventStore(隐私投影落盘)
8
- * - memoryRefs 选择复用 M4 lexical_v2 可解释基线与 m4-corpus 授权校验链;
9
+ * - memoryRefs 选择复用 M4 lexical_pre_v2 可解释基线与 m4-corpus 授权校验链;
9
10
  * M4 Shadow audit 候选绝不追认为 seen/read(本模块不读 shadow audit,结构隔离)。
10
11
  * 默认关闭(associativeMemoryEnabled && contextBridgeEnabled 双门):零构造、零 IO、零留存。
11
12
  * 本模块自身无 spawn/HTTP;contextSinkMode='python' 仅在 assoc∧bridge∧pythonBackend 三重门下
@@ -20,8 +21,8 @@ import {
20
21
  buildContextPushEnvelopePre, buildAuthorizedMemoryRefFromRecord, createAccessEvidencePre,
21
22
  createCiteEvidencesFromText, createCorrectionEvidencesFromText, computeReadCoverage,
22
23
  createContextPushBridge, createNullContextSinkPre, createFakeContextSinkPre,
23
- CORRECTION_LEXICON_V1,
24
- CONTEXT_BRIDGE_BUDGET_V1, CONTEXT_BRIDGE_POLICY_VERSION, EVIDENCE_POLICY_VERSION,
24
+ CORRECTION_LEXICON_PRE_V1,
25
+ CONTEXT_BRIDGE_BUDGET_PRE_V1, CONTEXT_BRIDGE_POLICY_VERSION, EVIDENCE_POLICY_VERSION,
25
26
  } from './context-bridge.js'
26
27
  import { EvidenceEventStore, rebuildAggregates, workspaceRefOf } from './evidence-store.js'
27
28
  import { createPythonContextSinkPre } from './context-sink-python.js'
@@ -42,11 +43,11 @@ function diagCtx(msg) {
42
43
  // ★#86-3:统一口径。原实现把 `USERPROFILE || HOME` **直接**当 DSH_HOME(不拼 .dsh)
43
44
  // ⇒ 在 Windows 上得到 `C:\Users\X` 而非 `C:\Users\X\.dsh`,与其余 6 处**指向不同目录**。
44
45
  // ★同时修掉一个**潜伏 bug**:原代码在 `DSH_HOME` 已设置时仍 `path.join(base, '.dsh', ...)`,
45
- // 即落成 `$DSH_HOME/.dsh/dsh-auto-memory-diagnose.log`(**多拼一层 .dsh**);
46
+ // 即落成 `$DSH_HOME/.dsh/dsh-auto-memory-pre-diagnose.log`(**多拼一层 .dsh**);
46
47
  // 未设置时才恰好正确。resolveDshHomePre() 返回的**已经是 dsh home 本身**,故不再拼 .dsh。
47
48
  const base = resolveDshHomePre()
48
49
  if (!base) return
49
- appendFileSync(path.join(base, 'dsh-auto-memory-diagnose.log'),
50
+ appendFileSync(path.join(base, 'dsh-auto-memory-pre-diagnose.log'),
50
51
  new Date().toISOString() + ' [ctx-host] ' + String(msg).slice(0, 300) + '\n', 'utf8')
51
52
  } catch (e) {}
52
53
  }
@@ -119,7 +120,7 @@ export function createContextHost(opts = {}) {
119
120
  let bridge = null
120
121
  let store = null
121
122
  let aggCache = null // {wsRef, miv, list}
122
- const registry = new CorpusRegistry({ sidecarDir: path.join(dshHome(), 'memory', 'index', 'files') })
123
+ const registry = new CorpusRegistry({ sidecarDir: path.join(memoryDir('index', dshHome), 'files') })
123
124
 
124
125
  function dshHome() {
125
126
  // ★#86-3:统一口径(含 __homedirFn 注入点保留)
@@ -165,14 +166,14 @@ export function createContextHost(opts = {}) {
165
166
  }
166
167
  function storeFor() {
167
168
  if (!store) store = new EvidenceEventStore({
168
- root: path.join(dshHome(), 'memory', 'evidence'),
169
+ root: memoryDir('evidence', dshHome),
169
170
  // ★P3-9(组2,2026-09-22):注入统一节流诊断通道;不注入时 store 内回落 console.error。
170
171
  onDiag: (key, msg) => { try { diagCtx(msg) } catch (_) {} },
171
172
  })
172
173
  return store
173
174
  }
174
175
  function evidenceRootExists() {
175
- const p = path.join(dshHome(), 'memory', 'evidence')
176
+ const p = memoryDir('evidence', dshHome)
176
177
  try { return existsSync(p) } catch (_) { return false }
177
178
  }
178
179
  function stateFor(runtime) {
@@ -338,7 +339,7 @@ export function createContextHost(opts = {}) {
338
339
  const readyPromise = needIndexReady ? ensureIndexReadyFor(paths, corpusSnap) : Promise.resolve({ ready: true })
339
340
  // ---- envelope 组装与推送 ----
340
341
  const ringItems = runtime.segments ? runtime.segments.snapshot() : []
341
- const winRaw = ringItems.slice(-CONTEXT_BRIDGE_BUDGET_V1.maxSegments).map((w) => ({
342
+ const winRaw = ringItems.slice(-CONTEXT_BRIDGE_BUDGET_PRE_V1.maxSegments).map((w) => ({
342
343
  segmentId: w.id, digest: w.digest, kind: w.kind, eventSeq: w.eventSeq,
343
344
  contextVersion: w.contextVersion, ts: w.ts, text: w.text,
344
345
  toolName: w.toolName != null ? w.toolName : null,
@@ -390,7 +391,7 @@ export function createContextHost(opts = {}) {
390
391
  // D6 融合候选池=词法 kept ∪ 稠密 top-K(语料中找回记录对象;语义强命中但词法零分者由此入选)
391
392
  const poolMap = new Map()
392
393
  for (const k of ls.kept) if (k && k.memoryId) poolMap.set(k.memoryId, k)
393
- const denseTop = [...rank.scores.entries()].sort((a, b) => b[1] - a[1]).slice(0, CONTEXT_BRIDGE_BUDGET_V1.maxMemoryRefs)
394
+ const denseTop = [...rank.scores.entries()].sort((a, b) => b[1] - a[1]).slice(0, CONTEXT_BRIDGE_BUDGET_PRE_V1.maxMemoryRefs)
394
395
  for (const [mid] of denseTop) {
395
396
  if (!poolMap.has(mid)) {
396
397
  const rec = (corpusSnap.records || []).find((r) => r && r.memoryId === mid)
@@ -404,20 +405,20 @@ export function createContextHost(opts = {}) {
404
405
  }))).map((f) => poolMap.get(f.memoryId)).filter(Boolean)
405
406
  }
406
407
  const refs = []
407
- for (const k of keptList.slice(0, CONTEXT_BRIDGE_BUDGET_V1.maxMemoryRefs)) {
408
+ for (const k of keptList.slice(0, CONTEXT_BRIDGE_BUDGET_PRE_V1.maxMemoryRefs)) {
408
409
  const r = buildAuthorizedMemoryRefFromRecord(k, k.text)
409
410
  if (r.ok) refs.push(r.ref)
410
411
  }
411
- const aggs = aggregatesFor(wsRef, corpusSnap).slice(0, CONTEXT_BRIDGE_BUDGET_V1.maxEvidenceItems)
412
+ const aggs = aggregatesFor(wsRef, corpusSnap).slice(0, CONTEXT_BRIDGE_BUDGET_PRE_V1.maxEvidenceItems)
412
413
  const built = buildContextPushEnvelopePre({
413
414
  session: { sessionId: runtime.sessionId || '', agentId: runtime.agentId || '', workspaceKey: paths.workspaceKey, scope: 'Workspace' },
414
415
  cursor: { eventSeq: seg.eventSeq, nativeSeq: seg.nativeSeq, contextVersion: runtime.contextVersion },
415
- index: { memoryIndexVersion: corpusSnap ? corpusSnap.memoryIndexVersion : ('idx_' + '0'.repeat(32)), sourceEpochs: corpusSnap ? corpusSnap.sources.map((s) => s.sourceEpoch) : [] },
416
+ index: { memoryIndexVersion: corpusSnap ? corpusSnap.memoryIndexVersion : ('idx_pre_' + '0'.repeat(32)), sourceEpochs: corpusSnap ? corpusSnap.sources.map((s) => s.sourceEpoch) : [] },
416
417
  trigger: {
417
418
  segmentId: seg.id, digest: seg.digest, kind: seg.kind, eventSeq: seg.eventSeq,
418
419
  contextVersion: seg.contextVersion, ts: seg.ts, text: seg.text,
419
420
  },
420
- window: winRaw.slice(0, CONTEXT_BRIDGE_BUDGET_V1.maxSegments),
421
+ window: winRaw.slice(0, CONTEXT_BRIDGE_BUDGET_PRE_V1.maxSegments),
421
422
  memoryRefs: refs,
422
423
  evidence: aggs,
423
424
  now: Date.now(),
@@ -506,13 +507,13 @@ export function createContextHost(opts = {}) {
506
507
  ' dense=' + num2(dec._denseTop) + ' margin=' + num2(dec._margin) +
507
508
  ' ' + (dec.reasonCodes || []).join(',')).slice(0, 160)
508
509
  const act = {
509
- schemaVersion: 1, namespace: 'dsh-auto-memory', kind: 'activation_request',
510
- activationId: 'act_' + (obs.startsWith('obs_') ? obs.slice(8, 40) : 'jsdecide'),
511
- observationId: obs, workerEpoch: 'js-decide-v1',
510
+ schemaVersion: 1, namespace: 'dsh-auto-memory-pre', kind: 'activation_request',
511
+ activationId: 'act_pre_' + (obs.startsWith('obs_pre_') ? obs.slice(8, 40) : 'jsdecide'),
512
+ observationId: obs, workerEpoch: 'js-decide-pre-v1',
512
513
  sessionId: session.sessionId || '', agentId: session.agentId || '',
513
514
  workspaceKey: session.workspaceKey || '', scope: session.scope || 'Workspace',
514
515
  contextVersion: cursor.contextVersion || 0, memoryIndexVersion: built.frame.index ? built.frame.index.memoryIndexVersion : '',
515
- threshold: { policyVersion: dec.features ? 'activation_policy_v2' : 'activation_policy_v2', score: (dec.features && dec.features.intentProb) || 0, threshold: 0.45, reason: jsReason },
516
+ threshold: { policyVersion: dec.features ? 'activation_policy_pre_v2' : 'activation_policy_pre_v2', score: (dec.features && dec.features.intentProb) || 0, threshold: 0.45, reason: jsReason },
516
517
  level: 'excerpt',
517
518
  candidates: (function () {
518
519
  // 2026-08-27 优化③:候选方案档位(balanced=3×40 / dense=6×20 / custom=自定义)
@@ -523,7 +524,7 @@ export function createContextHost(opts = {}) {
523
524
  else { n = 3; ex = 40 } // balanced 默认
524
525
  return refs.slice(0, n).map(function (r, i) {
525
526
  return {
526
- candidateId: 'cand_' + String(obs || 'js').slice(-32) + i,
527
+ candidateId: 'cand_pre_' + String(obs || 'js').slice(-32) + i,
527
528
  memoryId: r.memoryId, anchorId: r.anchorId || '', scope: r.scope || 'Workspace',
528
529
  sourceRef: r.sourceRef || '', sourceEpoch: r.sourceEpoch || '', sourceVersion: r.sourceVersion || 1,
529
530
  fileDigest: r.fileDigest || '', recordDigest: r.recordDigest || '',
@@ -551,7 +552,7 @@ export function createContextHost(opts = {}) {
551
552
  if (actives.length && typeof engine._jsSemanticRank === 'function') {
552
553
  try {
553
554
  const recs = actives.map((p) => ({ memoryId: String(p.procedureId), text: String(p.title || '') + ' ' + (Array.isArray(p.steps) ? p.steps.join(' ') : String(p.steps || '')) }))
554
- const miv = 'idx_' + createHash('sha256').update(recs.map((r) => r.memoryId).join(',')).digest('hex').slice(0, 32)
555
+ const miv = 'idx_pre_' + createHash('sha256').update(recs.map((r) => r.memoryId).join(',')).digest('hex').slice(0, 32)
555
556
  const rank = await engine._jsSemanticRank({ memoryIndexVersion: miv, records: recs }, jsDecideQueryText)
556
557
  if (rank && rank.scores && rank.scores.size) {
557
558
  const [topId, topScore] = [...rank.scores.entries()].sort((a, b) => b[1] - a[1])[0]
@@ -647,7 +648,7 @@ export function createContextHost(opts = {}) {
647
648
  let attributedPrefix = 'none'
648
649
  try {
649
650
  const norm = String(seg.text == null ? '' : seg.text).normalize('NFKC').replace(/[A-Z]/g, (c) => c.toLowerCase())
650
- lexHits = CORRECTION_LEXICON_V1.filter((w) => norm.includes(w)).length
651
+ lexHits = CORRECTION_LEXICON_PRE_V1.filter((w) => norm.includes(w)).length
651
652
  if (lexHits > 0) {
652
653
  const st = storeFor()
653
654
  const recent = selectCorrectionAttributionPre({ events: st.loadEvents().events, now: Date.now() })
@@ -768,7 +769,7 @@ export function createContextHost(opts = {}) {
768
769
  const JSDECIDE_SHADOW_MAX_LINES = 256
769
770
  function jsDecideShadowLog(entry) {
770
771
  try {
771
- const dir = path.join(dshHome(), 'memory', 'semantic')
772
+ const dir = memoryDir('semantic', dshHome)
772
773
  const f = path.join(dir, 'js-decide-shadow.jsonl')
773
774
  let line = JSON.stringify(entry)
774
775
  if (!line) return
@@ -798,7 +799,7 @@ export function createContextHost(opts = {}) {
798
799
  capturedPathKeys: [...pathsByKey.keys()].slice(0, 8),
799
800
  lastSegmentRuntimeKey: lastSegmentRuntimeKey,
800
801
  lastSegmentSessionRef: lastSegmentSessionRef,
801
- evidenceDirPath: path.join(dshHome(), 'memory', 'evidence'),
802
+ evidenceDirPath: memoryDir('evidence', dshHome),
802
803
  durableEventsOnDisk: evidenceRootExists() ? storeFor().loadEvents().events.length : 0,
803
804
  stats: { ...stats, bridge: { ...b.stats } },
804
805
  storeStats: store ? { ...store.stats } : null,
package/lib/datadir.js ADDED
@@ -0,0 +1,95 @@
1
+ /**
2
+ * 记忆数据目录的唯一收口(去 pre,2026-09-23)。
3
+ *
4
+ * 历史:目录名原为 `<name>-pre`。去 pre 后统一裸名。
5
+ *
6
+ * 迁移策略:**不 move,只增量补齐**。三条实测依据:
7
+ * ① 改名前活宿主仍在往旧目录写(hub-pre/facts.json 改名后仍有写入)⇒ move 会分脑丢数据;
8
+ * ② **裸名目录可能早已存在**(实测 `memory/hub/` 创建于 09-01、内含 3 个 0 条的 JSON 空壳),
9
+ * 所以「新目录不存在才复制」这个判据会**整条跳过**,导致 56 条 procedures(108 KB)
10
+ * 与 9.3 MB facts 在重启后全部不可见 —— 属数据丢失级缺陷;
11
+ * ③ 因而判据必须是**逐文件语义判据**,而不是「目录是否存在」。
12
+ *
13
+ * 逐文件规则(fail toward duplication, never toward loss):
14
+ * · 目标缺失 → 复制
15
+ * · 目标在、源更「有内容」 → 复制(仅当目标被判定为**空壳**:能解析为 JSON 且集合为空)
16
+ * · 其余 → 跳过(绝不覆盖已有真实数据)
17
+ *
18
+ * 三条纪律:
19
+ * ① 惰性:调用时机与原先的 path.join 完全一致(环境变量已就位,不会误写真实 home)
20
+ * ② 幂等:补齐过的文件不再复制;重复调用零副作用
21
+ * ③ fail-soft:任何异常都不抛,返回原定路径
22
+ */
23
+ import fs from 'node:fs'
24
+ import path from 'node:path'
25
+ import { resolveDshHomePre } from './dsh-home.js'
26
+
27
+ /** 已处理过的 (base,name) —— 键必须含 home,否则引擎作用域(各自 dshHome)会互相串味。 */
28
+ const migrated = new Set()
29
+
30
+ /** 把 JSON 文件判成「空壳」:能解析,且其中的集合型字段全是空数组/空对象。 */
31
+ function isEmptyShell(file) {
32
+ try {
33
+ const j = JSON.parse(fs.readFileSync(file, 'utf8'))
34
+ if (!j || typeof j !== 'object') return false
35
+ const colls = Object.values(j).filter((v) => Array.isArray(v) || (v && typeof v === 'object'))
36
+ if (colls.length === 0) return false // 没有任何集合字段 ⇒ 不是可判定的 store,保守视为有内容
37
+ return colls.every((v) => (Array.isArray(v) ? v.length === 0 : Object.keys(v).length === 0))
38
+ } catch (_) {
39
+ return false // 解析失败 ⇒ 保守视为有内容,不覆盖
40
+ }
41
+ }
42
+
43
+ /** 逐文件增量补齐:只写「目标缺失」或「目标是空壳」的文件。 */
44
+ function mergeInto(srcDir, dstDir) {
45
+ let copied = 0
46
+ const walk = (rel) => {
47
+ const sAbs = path.join(srcDir, rel)
48
+ for (const e of fs.readdirSync(sAbs, { withFileTypes: true })) {
49
+ const childRel = rel ? path.join(rel, e.name) : e.name
50
+ if (e.isDirectory()) { walk(childRel); continue }
51
+ const s = path.join(srcDir, childRel)
52
+ const d = path.join(dstDir, childRel)
53
+ const dExists = fs.existsSync(d)
54
+ if (dExists && !(s.endsWith('.json') && isEmptyShell(d) && !isEmptyShell(s))) continue
55
+ fs.mkdirSync(path.dirname(d), { recursive: true })
56
+ fs.copyFileSync(s, d)
57
+ copied++
58
+ }
59
+ }
60
+ walk('')
61
+ return copied
62
+ }
63
+
64
+ export function memoryDir(name, homeFn) {
65
+ // homeFn 由调用方注入:引擎作用域模块必须传各自的 dshHome(口径与原代码逐字一致)。
66
+ const home = typeof homeFn === 'function' ? homeFn() : resolveDshHomePre()
67
+ const base = path.join(home, 'memory')
68
+ const nw = path.join(base, name)
69
+ const key = base + '\u0000' + name
70
+ if (migrated.has(key)) return nw
71
+ migrated.add(key)
72
+ try {
73
+ const od = path.join(base, name + '-pre')
74
+ if (fs.existsSync(od)) {
75
+ fs.mkdirSync(nw, { recursive: true })
76
+ const n = mergeInto(od, nw)
77
+ if (n > 0) {
78
+ fs.appendFileSync(
79
+ path.join(base, 'datadir-migration.log'),
80
+ new Date().toISOString() + ' merged ' + n + ' file(s) ' + name + '-pre -> ' + name + '\n'
81
+ )
82
+ }
83
+ }
84
+ return nw
85
+ } catch (e) {
86
+ // fail-soft:补齐失败时**回退读旧目录**(读得到历史数据优先于名字干净)。
87
+ try {
88
+ const od = path.join(base, name + '-pre')
89
+ if (fs.existsSync(od)) return od
90
+ } catch (_) { /* ignore */ }
91
+ return nw
92
+ }
93
+ }
94
+
95
+ export default { memoryDir }
package/lib/degrade.js CHANGED
@@ -30,13 +30,13 @@
30
30
  */
31
31
 
32
32
  /** 身份常量(枚举类常量须配断言兜底 —— 本仓纪律)。 */
33
- export const DEGRADE_SCHEMA_V1 = 'degrade_v1'
33
+ export const DEGRADE_SCHEMA_PRE_V1 = 'degrade_pre_v1'
34
34
 
35
35
  /** 环形缓冲上限:防内存无界增长。 */
36
- export const DEGRADE_CAP_V1 = 200
36
+ export const DEGRADE_CAP_PRE_V1 = 200
37
37
 
38
38
  /** 单条 reason 截断长度:与既有 diag 同口径,且防长文本撑爆状态文件。 */
39
- export const DEGRADE_REASON_MAX_V1 = 200
39
+ export const DEGRADE_REASON_MAX_PRE_V1 = 200
40
40
 
41
41
  /**
42
42
  * 臂状态枚举(fail-closed 校验)。
@@ -46,10 +46,10 @@ export const DEGRADE_REASON_MAX_V1 = 200
46
46
  * - `disabled` 被配置关闭
47
47
  * - `unknown` 无法判定(**不得**当作正常,面板应显式呈现)
48
48
  */
49
- export const ARM_STATES_V1 = Object.freeze(['active', 'no-input', 'degraded', 'disabled', 'unknown'])
49
+ export const ARM_STATES_PRE_V1 = Object.freeze(['active', 'no-input', 'degraded', 'disabled', 'unknown'])
50
50
 
51
51
  /** 已知降级 kind(仅作文档/断言用,**不限制**调用方传入新 kind)。 */
52
- export const DEGRADE_KINDS_V1 = Object.freeze([
52
+ export const DEGRADE_KINDS_PRE_V1 = Object.freeze([
53
53
  'semantic-arm', // 语义臂择优失败 → 回退词法
54
54
  'evidence-arm', // evidence 聚合失败 → importance 中性
55
55
  'l0-sync', // L0 索引同步失败 → 索引陈旧
@@ -63,7 +63,7 @@ export const DEGRADE_KINDS_V1 = Object.freeze([
63
63
  * @param {Function} [opts.now] 取时函数(注入便于测试确定性)
64
64
  */
65
65
  export function createDegradeSinkPre(opts = {}) {
66
- const cap = Number.isFinite(opts.cap) && opts.cap > 0 ? Math.floor(opts.cap) : DEGRADE_CAP_V1
66
+ const cap = Number.isFinite(opts.cap) && opts.cap > 0 ? Math.floor(opts.cap) : DEGRADE_CAP_PRE_V1
67
67
  const now = typeof opts.now === 'function' ? opts.now : () => Date.now()
68
68
 
69
69
  /** @type {Map<string, number>} kind → 累计次数(不受环形淘汰影响) */
@@ -84,7 +84,7 @@ export function createDegradeSinkPre(opts = {}) {
84
84
  if (recent.length >= cap) { recent.shift(); evicted += 1 }
85
85
  recent.push({
86
86
  kind: k,
87
- reason: String(reason == null ? '' : reason).slice(0, DEGRADE_REASON_MAX_V1),
87
+ reason: String(reason == null ? '' : reason).slice(0, DEGRADE_REASON_MAX_PRE_V1),
88
88
  at: now(),
89
89
  })
90
90
  } catch (_) { /* 元规则:留痕失败不得影响主流程 */ }
@@ -95,7 +95,7 @@ export function createDegradeSinkPre(opts = {}) {
95
95
  let out
96
96
  try {
97
97
  out = {
98
- schemaVersion: DEGRADE_SCHEMA_V1,
98
+ schemaVersion: DEGRADE_SCHEMA_PRE_V1,
99
99
  updatedAt: new Date(now()).toISOString(),
100
100
  counts: Object.fromEntries(counts),
101
101
  recent: recent.map((r) => ({ ...r, at: new Date(r.at).toISOString() })),
@@ -104,7 +104,7 @@ export function createDegradeSinkPre(opts = {}) {
104
104
  }
105
105
  } catch (_) {
106
106
  // 快照失败也要给出**结构性合法**的最小对象,不能让读取方拿到 undefined
107
- out = { schemaVersion: DEGRADE_SCHEMA_V1, updatedAt: null, counts: {}, recent: [], evicted, cap }
107
+ out = { schemaVersion: DEGRADE_SCHEMA_PRE_V1, updatedAt: null, counts: {}, recent: [], evicted, cap }
108
108
  }
109
109
  return out
110
110
  }
@@ -126,14 +126,14 @@ export function createDegradeSinkPre(opts = {}) {
126
126
  * 这不是"降级记录",而是**状态陈述** —— 目的是让「某条臂没在工作」可见,
127
127
  * 而不必靠"每次 recall 都报错"来推断。例:`evidence: 'no-input'` 一眼可见。
128
128
  *
129
- * @param {Record<string, string>} states 臂名 → 状态(须属 ARM_STATES_V1;非法值归一为 'unknown')
129
+ * @param {Record<string, string>} states 臂名 → 状态(须属 ARM_STATES_PRE_V1;非法值归一为 'unknown')
130
130
  */
131
131
  export function deriveArmsHealthPre(states) {
132
132
  const out = {}
133
133
  try {
134
134
  for (const [arm, st] of Object.entries(states || {})) {
135
135
  const s = String(st == null ? '' : st)
136
- out[String(arm)] = ARM_STATES_V1.includes(s) ? s : 'unknown'
136
+ out[String(arm)] = ARM_STATES_PRE_V1.includes(s) ? s : 'unknown'
137
137
  }
138
138
  } catch (_) { /* fail-soft */ }
139
139
  return out
@@ -192,10 +192,10 @@ export function persistDegradeLedgerPre(p) {
192
192
  // 故两者**并列而不混用**:各自的 counts/recent 互不干扰。
193
193
 
194
194
  /** 配额探针的身份常量(枚举类常量须配断言兜底 —— 本仓纪律)。 */
195
- export const QUOTA_PROBE_SCHEMA_V1 = 'quota_probe_v1'
195
+ export const QUOTA_PROBE_SCHEMA_PRE_V1 = 'quota_probe_pre_v1'
196
196
 
197
197
  /** 采样环上限:与降级台账同口径(有界,防内存无界增长)。 */
198
- export const QUOTA_PROBE_CAP_V1 = 200
198
+ export const QUOTA_PROBE_CAP_PRE_V1 = 200
199
199
 
200
200
  /**
201
201
  * 配额判定结论枚举(fail-closed 校验)。
@@ -204,10 +204,10 @@ export const QUOTA_PROBE_CAP_V1 = 200
204
204
  * - `balanced` 既有丢弃但未持续、占用也合理
205
205
  * - `insufficient-data` **样本不足,不猜**(这是默认值 —— 宁可说不知道)
206
206
  */
207
- export const QUOTA_VERDICTS_V1 = Object.freeze(['under-quota', 'over-quota', 'balanced', 'insufficient-data'])
207
+ export const QUOTA_VERDICTS_PRE_V1 = Object.freeze(['under-quota', 'over-quota', 'balanced', 'insufficient-data'])
208
208
 
209
209
  /** 判定阈值(集中声明,便于断言锁定与后续按观测调参)。 */
210
- export const QUOTA_THRESHOLDS_V1 = Object.freeze({
210
+ export const QUOTA_THRESHOLDS_PRE_V1 = Object.freeze({
211
211
  /** 至少这么多轮采样才敢下结论(少于它一律 insufficient-data)。 */
212
212
  minSamples: 8,
213
213
  /** 某层"出现丢弃"的轮数占比 ≥ 此值 ⇒ under-quota。 */
@@ -227,7 +227,7 @@ export const QUOTA_THRESHOLDS_V1 = Object.freeze({
227
227
  * @param {Function} [opts.now] 取时函数(注入便于测试确定性)
228
228
  */
229
229
  export function createQuotaProbePre(opts = {}) {
230
- const cap = Number.isFinite(opts.cap) && opts.cap > 0 ? Math.floor(opts.cap) : QUOTA_PROBE_CAP_V1
230
+ const cap = Number.isFinite(opts.cap) && opts.cap > 0 ? Math.floor(opts.cap) : QUOTA_PROBE_CAP_PRE_V1
231
231
  const now = typeof opts.now === 'function' ? opts.now : () => Date.now()
232
232
  /** @type {Array<object>} 最近采样(有界) */
233
233
  let samples = []
@@ -269,7 +269,7 @@ export function createQuotaProbePre(opts = {}) {
269
269
  let out
270
270
  try {
271
271
  out = {
272
- schemaVersion: QUOTA_PROBE_SCHEMA_V1,
272
+ schemaVersion: QUOTA_PROBE_SCHEMA_PRE_V1,
273
273
  updatedAt: new Date(now()).toISOString(),
274
274
  samples: samples.map((s) => ({ ...s, at: new Date(s.at).toISOString(), perLayer: { ...s.perLayer } })),
275
275
  evicted,
@@ -277,7 +277,7 @@ export function createQuotaProbePre(opts = {}) {
277
277
  }
278
278
  } catch (_) {
279
279
  // 快照失败也要给出**结构性合法**的最小对象,不能让读取方拿到 undefined
280
- out = { schemaVersion: QUOTA_PROBE_SCHEMA_V1, updatedAt: null, samples: [], evicted, cap }
280
+ out = { schemaVersion: QUOTA_PROBE_SCHEMA_PRE_V1, updatedAt: null, samples: [], evicted, cap }
281
281
  }
282
282
  return out
283
283
  }
@@ -300,14 +300,14 @@ export function createQuotaProbePre(opts = {}) {
300
300
  * 且把"凭什么这么判"的原始数据(dropRate/perLayer/usage)一并返回,供人复核。
301
301
  *
302
302
  * @param {object} snap `createQuotaProbePre().snapshot()` 的产物
303
- * @param {object} [opts] 覆盖阈值(默认取 QUOTA_THRESHOLDS_V1)
303
+ * @param {object} [opts] 覆盖阈值(默认取 QUOTA_THRESHOLDS_PRE_V1)
304
304
  * @returns {{version:string, verdict:string, samples:number, dropRate:number,
305
305
  * usage:number, perLayer:object, reasons:string[]}}
306
306
  */
307
307
  export function deriveQuotaVerdictPre(snap, opts = {}) {
308
- const th = { ...QUOTA_THRESHOLDS_V1, ...(opts || {}) }
308
+ const th = { ...QUOTA_THRESHOLDS_PRE_V1, ...(opts || {}) }
309
309
  const base = {
310
- version: 'quota_verdict_v1',
310
+ version: 'quota_verdict_pre_v1',
311
311
  verdict: 'insufficient-data',
312
312
  samples: 0,
313
313
  dropRate: 0,
package/lib/dsh-home.js CHANGED
@@ -49,13 +49,13 @@ import os from 'node:os'
49
49
  import path from 'node:path'
50
50
 
51
51
  /** 环境变量名(集中一处,便于将来改名)。 */
52
- export const DSH_HOME_ENV_V1 = 'DSH_HOME'
52
+ export const DSH_HOME_ENV_PRE_V1 = 'DSH_HOME'
53
53
 
54
54
  /** 默认子目录名。 */
55
- export const DSH_HOME_DIRNAME_V1 = '.dsh'
55
+ export const DSH_HOME_DIRNAME_PRE_V1 = '.dsh'
56
56
 
57
57
  /** 全失败时的最后兜底(相对路径,保证返回非空)。 */
58
- export const DSH_HOME_FALLBACK_V1 = '.dsh'
58
+ export const DSH_HOME_FALLBACK_PRE_V1 = '.dsh'
59
59
 
60
60
  /**
61
61
  * 取 home 基准目录(用于拼 `.dsh`)。**永不抛**。
@@ -90,18 +90,18 @@ export function resolveDshHomePre(override) {
90
90
  } catch (_) {}
91
91
  // ② 环境变量
92
92
  try {
93
- const env = process.env[DSH_HOME_ENV_V1]
93
+ const env = process.env[DSH_HOME_ENV_PRE_V1]
94
94
  if (env && String(env).trim()) return String(env).trim()
95
95
  } catch (_) {}
96
96
  // ③ / ④ 基准目录 + .dsh
97
97
  const base = homeBasePre()
98
98
  if (base) {
99
99
  try {
100
- return path.join(base, DSH_HOME_DIRNAME_V1)
100
+ return path.join(base, DSH_HOME_DIRNAME_PRE_V1)
101
101
  } catch (_) {}
102
102
  }
103
103
  // ⑤ 最后兜底
104
- return DSH_HOME_FALLBACK_V1
104
+ return DSH_HOME_FALLBACK_PRE_V1
105
105
  }
106
106
 
107
107
  /**
@@ -127,14 +127,14 @@ export function resolveDshHomeForEnginePre(engine) {
127
127
  } catch (_) {}
128
128
  // ② 环境变量(与原实现同优先级)
129
129
  try {
130
- const env = process.env[DSH_HOME_ENV_V1]
130
+ const env = process.env[DSH_HOME_ENV_PRE_V1]
131
131
  if (env && String(env).trim()) return String(env).trim()
132
132
  } catch (_) {}
133
133
  // ③ 既有注入点 __homedirFn:返回的是「home 基准目录」,仍需拼 .dsh
134
134
  try {
135
135
  if (typeof e.__homedirFn === 'function') {
136
136
  const base = e.__homedirFn()
137
- if (base && String(base).trim()) return path.join(String(base).trim(), DSH_HOME_DIRNAME_V1)
137
+ if (base && String(base).trim()) return path.join(String(base).trim(), DSH_HOME_DIRNAME_PRE_V1)
138
138
  }
139
139
  } catch (_) {}
140
140
  // ④ 默认链(homedir → USERPROFILE/HOME → '.dsh')