solopup-memory 0.21.0-beta.2 → 0.22.0-beta.3

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 (99) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/COMPATIBILITY.md +8 -5
  3. package/DETECTION.md +139 -0
  4. package/DREAMING-DESIGN.md +167 -0
  5. package/README.en.md +35 -6
  6. package/README.md +44 -7
  7. package/RELEASE.md +36 -0
  8. package/SECURITY.md +11 -0
  9. package/context-history.mjs +20 -3
  10. package/detect.mjs +441 -0
  11. package/eval/bench-detect.mjs +107 -0
  12. package/eval/semantic/cases.json +387 -0
  13. package/eval/semantic-suite.mjs +156 -0
  14. package/index.mjs +117 -27
  15. package/managed-policy.mjs +3 -1
  16. package/package.json +91 -68
  17. package/privacy.mjs +25 -0
  18. package/scripts/demo-source-recall.mjs +123 -0
  19. package/scripts/detect.mjs +69 -0
  20. package/scripts/dream.mjs +205 -0
  21. package/scripts/probe-history.mjs +92 -0
  22. package/scripts/probe-load.mjs +1 -1
  23. package/scripts/probe-package.mjs +6 -2
  24. package/scripts/security-check.mjs +173 -0
  25. package/scripts/soak.mjs +219 -0
  26. package/scripts/telemetry.mjs +187 -0
  27. package/scripts/verify.mjs +3 -1
  28. package/{test-adversarial.mjs → test/test-adversarial.mjs} +5 -5
  29. package/{test-belief-revision.mjs → test/test-belief-revision.mjs} +7 -7
  30. package/{test-candidates.mjs → test/test-candidates.mjs} +4 -4
  31. package/{test-capture-diagnostics.mjs → test/test-capture-diagnostics.mjs} +6 -6
  32. package/{test-capture-field-lineage.mjs → test/test-capture-field-lineage.mjs} +4 -4
  33. package/{test-capture-inputs.mjs → test/test-capture-inputs.mjs} +8 -8
  34. package/{test-capture-jobs.mjs → test/test-capture-jobs.mjs} +5 -5
  35. package/{test-capture-projections.mjs → test/test-capture-projections.mjs} +12 -12
  36. package/{test-capture-revision.mjs → test/test-capture-revision.mjs} +6 -6
  37. package/{test-capture-submission.mjs → test/test-capture-submission.mjs} +3 -3
  38. package/{test-capture-validation-flow.mjs → test/test-capture-validation-flow.mjs} +5 -5
  39. package/{test-capture-validation-job.mjs → test/test-capture-validation-job.mjs} +5 -5
  40. package/{test-capture-validation-lineage.mjs → test/test-capture-validation-lineage.mjs} +2 -2
  41. package/{test-capture-validation.mjs → test/test-capture-validation.mjs} +2 -2
  42. package/{test-capture.mjs → test/test-capture.mjs} +20 -20
  43. package/{test-compaction-compat.mjs → test/test-compaction-compat.mjs} +5 -5
  44. package/{test-compat.mjs → test/test-compat.mjs} +3 -3
  45. package/{test-concurrency.mjs → test/test-concurrency.mjs} +3 -3
  46. package/{test-conflict.mjs → test/test-conflict.mjs} +2 -2
  47. package/{test-context-history.mjs → test/test-context-history.mjs} +1 -1
  48. package/{test-control-schema.mjs → test/test-control-schema.mjs} +1 -1
  49. package/test/test-detect.mjs +125 -0
  50. package/test/test-dream-plan.mjs +72 -0
  51. package/test/test-dsh-registration.mjs +111 -0
  52. package/{test-episodes.mjs → test/test-episodes.mjs} +5 -5
  53. package/{test-explicit-memory.mjs → test/test-explicit-memory.mjs} +4 -4
  54. package/{test-field-lineage.mjs → test/test-field-lineage.mjs} +4 -4
  55. package/{test-history.mjs → test/test-history.mjs} +4 -4
  56. package/{test-inline-summary.mjs → test/test-inline-summary.mjs} +5 -5
  57. package/{test-legacy-source-identity.mjs → test/test-legacy-source-identity.mjs} +2 -2
  58. package/{test-links.mjs → test/test-links.mjs} +4 -4
  59. package/{test-managed-access.mjs → test/test-managed-access.mjs} +29 -5
  60. package/{test-managed-activation.mjs → test/test-managed-activation.mjs} +5 -5
  61. package/{test-meaning.mjs → test/test-meaning.mjs} +6 -6
  62. package/{test-native-service-lifecycle.mjs → test/test-native-service-lifecycle.mjs} +1 -1
  63. package/{test-native-write-validation.mjs → test/test-native-write-validation.mjs} +10 -4
  64. package/{test-packaging.mjs → test/test-packaging.mjs} +32 -5
  65. package/{test-paired-analysis.mjs → test/test-paired-analysis.mjs} +1 -1
  66. package/{test-patch.mjs → test/test-patch.mjs} +11 -11
  67. package/{test-plugin-contract.mjs → test/test-plugin-contract.mjs} +4 -4
  68. package/test/test-privacy.mjs +65 -0
  69. package/{test-project-stores.mjs → test/test-project-stores.mjs} +5 -5
  70. package/{test-publication.mjs → test/test-publication.mjs} +4 -4
  71. package/{test-restore.mjs → test/test-restore.mjs} +11 -11
  72. package/{test-retrieval.mjs → test/test-retrieval.mjs} +2 -2
  73. package/{test-revision-admission.mjs → test/test-revision-admission.mjs} +3 -3
  74. package/{test-rule-admission.mjs → test/test-rule-admission.mjs} +8 -8
  75. package/{test-runtime-startup.mjs → test/test-runtime-startup.mjs} +2 -2
  76. package/{test-schema.mjs → test/test-schema.mjs} +2 -2
  77. package/{test-security.mjs → test/test-security.mjs} +6 -6
  78. package/test/test-semantic-suite.mjs +20 -0
  79. package/{test-service-readiness.mjs → test/test-service-readiness.mjs} +1 -1
  80. package/{test-smell.mjs → test/test-smell.mjs} +4 -4
  81. package/{test-snapshots.mjs → test/test-snapshots.mjs} +3 -3
  82. package/{test-source-completeness.mjs → test/test-source-completeness.mjs} +6 -6
  83. package/{test-source-key.mjs → test/test-source-key.mjs} +2 -2
  84. package/{test-source-retirement.mjs → test/test-source-retirement.mjs} +4 -4
  85. package/{test-status-both.mjs → test/test-status-both.mjs} +12 -4
  86. package/{test-supersede.mjs → test/test-supersede.mjs} +3 -3
  87. package/test/test-telemetry.mjs +60 -0
  88. package/{test-text-admission.mjs → test/test-text-admission.mjs} +6 -6
  89. package/{test-tool-inputs.mjs → test/test-tool-inputs.mjs} +1 -1
  90. package/{test-tool-observations.mjs → test/test-tool-observations.mjs} +6 -6
  91. package/{test-tool-write-guard.mjs → test/test-tool-write-guard.mjs} +3 -3
  92. package/{test-tool-write-lifecycle.mjs → test/test-tool-write-lifecycle.mjs} +3 -3
  93. package/{test-usability.mjs → test/test-usability.mjs} +7 -7
  94. package/{test-validation-source-refs.mjs → test/test-validation-source-refs.mjs} +2 -2
  95. package/{test-validation-transition-context.mjs → test/test-validation-transition-context.mjs} +1 -1
  96. package/{test-withdrawal-archives.mjs → test/test-withdrawal-archives.mjs} +6 -6
  97. package/{test-withdrawal.mjs → test/test-withdrawal.mjs} +2 -2
  98. package/{test.mjs → test/test.mjs} +4 -4
  99. package/version.mjs +1 -1
@@ -4,10 +4,10 @@ import {mkdtempSync,realpathSync,mkdirSync,writeFileSync,readFileSync,readdirSyn
4
4
  import {join} from 'node:path'
5
5
  import {tmpdir} from 'node:os'
6
6
  import {createHash} from 'node:crypto'
7
- import {apply,storeDirFor,managedMemoryStatus,withdrawManagedSource} from './index.mjs'
8
- import {writeFixtureMemory} from './fixture-host.mjs'
9
- import {candidateSnapshot} from './candidates.mjs'
10
- import {restorationJournalPath} from './restoration-lock.mjs'
7
+ import {apply,storeDirFor,managedMemoryStatus,withdrawManagedSource} from '../index.mjs'
8
+ import {writeFixtureMemory} from '../fixture-host.mjs'
9
+ import {candidateSnapshot} from '../candidates.mjs'
10
+ import {restorationJournalPath} from '../restoration-lock.mjs'
11
11
  const hash=x=>createHash('sha256').update(x).digest('hex')
12
12
  async function box(run){const root=realpathSync(mkdtempSync(join(tmpdir(),'status-both-'))),home=join(root,'home'),cwd=join(root,'work'),previous=process.env.DSH_HOME;mkdirSync(home);mkdirSync(cwd);writeFileSync(join(cwd,'.dsh-root'),'');process.env.DSH_HOME=home;const f={root,home,cwd,data:join(home,'memory'),tools:[],effects:[],ctx:{}};f.ctx={systemPrompt:{context(){}},workingDirectory:{get:()=>f.cwd},tools:{register:t=>f.tools.push(t)},on(){},effect(fn){f.effects.push(fn())}};apply(f.ctx,{autoCapture:{enabled:false}});f.status=f.tools.find(t=>t.name==='memory_status');f.exec={agent:{session:{id:'status-owner'}}};try{return await run(f)}finally{for(const fn of f.effects.reverse())fn?.();if(previous===undefined)delete process.env.DSH_HOME;else process.env.DSH_HOME=previous;rmSync(root,{recursive:true,force:true})}}
13
13
  function seed(dir,summary,scope='global'){return writeFixtureMemory({dir,summary,type:'user',scope,detail:'Synthetic detail '+summary})}
@@ -40,3 +40,11 @@ test('restoration guard produces only unavailable source control for either stat
40
40
  test('both metadata never turns observation discovery into cross-session access',()=>box(async f=>{
41
41
  const before=fingerprint(f.data);await assert.rejects(f.status.execute({scope:'both',includeObservations:'yes'},f.exec),/invalid-list-option/);assert.deepEqual(fingerprint(f.data),before);await assert.rejects(f.status.execute({scope:'both',includeObservations:true},f.exec),/source-unavailable/)
42
42
  }))
43
+ test('status exposes a bounded L0 lint summary',()=>box(async f=>{
44
+ writeFileSync(join(f.data,'MEMORY.md'),'# 索引\n\n## user\n\n- [nope] 坏类型 — user.md\n')
45
+ const r=await f.status.execute({scope:'global'},f.exec)
46
+ assert.ok(r.lint.errors>=1)
47
+ assert.ok(r.lint.checks.some((check)=>check.id==='I1-invalid-index-line'))
48
+ const text=f.status.output.render({scope:'global'},r).map((item)=>item.text).join('\n')
49
+ assert.match(text,/检测(L0)/)
50
+ }))
@@ -1,7 +1,7 @@
1
1
  import {realpathSync} from 'node:fs'
2
- import {applyWithOwnerFixture as apply} from './fixture-host.mjs'
2
+ import {applyWithOwnerFixture as apply} from '../fixture-host.mjs'
3
3
  /**
4
- * supersede(版本替代)与来源标记。跑法:node --test ~/.dsh/plugins/memory/test-supersede.mjs
4
+ * supersede(版本替代)与来源标记。跑法:node --test ~/.dsh/plugins/memory/test/test-supersede.mjs
5
5
  *
6
6
  * 取代是「修正过期事实」的唯一正确做法:再叠一条会让新旧两条同时被注入,
7
7
  * 模型看到矛盾信息。cc-memory 把这叫 anti-patch,MemOS 把它建模成 MemCube 的版本关系。
@@ -13,7 +13,7 @@ import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, rmSync, existsSync
13
13
  import { join } from 'node:path'
14
14
  import { tmpdir } from 'node:os'
15
15
 
16
- import { storeDirFor, writeMemory } from './index.mjs'
16
+ import { storeDirFor, writeMemory } from '../index.mjs'
17
17
 
18
18
  function sandbox() {
19
19
  const root = realpathSync(mkdtempSync(join(tmpdir(), 'dsh-mem-sup-')))
@@ -0,0 +1,60 @@
1
+ import { test } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'
4
+ import { join } from 'node:path'
5
+ import { tmpdir } from 'node:os'
6
+
7
+ const { collect } = await import('../scripts/telemetry.mjs')
8
+
9
+ const DAY = 86_400_000
10
+
11
+ function audit(now) {
12
+ const at = (daysAgo) => new Date(now - daysAgo * DAY).toISOString()
13
+ return [
14
+ { op: 'capture', outcome: 'ok', inserted: 1, at: at(10) },
15
+ { op: 'capture', outcome: 'ok', inserted: 0, at: at(9) },
16
+ { op: 'capture', outcome: 'error', at: at(8) },
17
+ { op: 'capture', outcome: 'error', at: at(7) },
18
+ { op: 'capture', outcome: 'rejected', at: at(6) },
19
+ { op: 'capture', outcome: 'skipped', reason: 'short-session-below-threshold', at: at(5) },
20
+ { op: 'capture', outcome: 'skipped', reason: 'short-session-below-threshold', at: at(4) },
21
+ { op: 'capture', outcome: 'skipped', reason: 'short-session-below-threshold', at: at(3) },
22
+ ]
23
+ }
24
+
25
+ test('遥测:捕获成功率、零产出天数与短会话跳过都能算出来', () => {
26
+ const root = mkdtempSync(join(tmpdir(), 'dsh-telemetry-'))
27
+ const now = Date.parse('2026-11-08T10:00:00.000Z')
28
+ try {
29
+ writeFileSync(join(root, 'MEMORY.md'), '# 索引\n\n## project\n\n- [project] 一条 — project.md\n')
30
+ mkdirSync(join(root, 'projects', 'v2-x'), { recursive: true })
31
+ writeFileSync(join(root, 'projects', 'v2-x', 'MEMORY.md'), '# 索引\n\n## project\n\n- [project] 两条 — project.md\n')
32
+ writeFileSync(join(root, '.audit.jsonl'), audit(now).map((event) => JSON.stringify(event)).join('\n') + '\n')
33
+ const report = collect(root, now)
34
+ assert.equal(report.capture.total, 8)
35
+ assert.equal(report.capture.skipped, 3)
36
+ assert.equal(report.capture.successRate, 0.25)
37
+ assert.equal(report.output.zeroOutputDays, 10)
38
+ const ids = report.warnings.map((warning) => warning.id)
39
+ assert.ok(ids.includes('T1-low-capture-success'))
40
+ assert.ok(ids.includes('T2-zero-output'))
41
+ assert.ok(ids.includes('T3-short-sessions'))
42
+ assert.equal(report.stores.length, 2)
43
+ } finally {
44
+ rmSync(root, { recursive: true, force: true })
45
+ }
46
+ })
47
+
48
+ test('遥测:没有账本时不编造成功率', () => {
49
+ const root = mkdtempSync(join(tmpdir(), 'dsh-telemetry-empty-'))
50
+ try {
51
+ writeFileSync(join(root, 'MEMORY.md'), '# 索引\n')
52
+ const report = collect(root, Date.parse('2026-11-08T10:00:00.000Z'))
53
+ assert.equal(report.capture.total, 0)
54
+ assert.equal(report.capture.successRate, null)
55
+ assert.equal(report.output.zeroOutputDays, null)
56
+ assert.ok(report.warnings.some((warning) => warning.id === 'T5-no-memory'))
57
+ } finally {
58
+ rmSync(root, { recursive: true, force: true })
59
+ }
60
+ })
@@ -1,14 +1,14 @@
1
- import {applyWithValidationFixture as apply} from './fixture-host.mjs'
1
+ import {applyWithValidationFixture as apply} from '../fixture-host.mjs'
2
2
  import {test} from 'node:test'
3
3
  import assert from 'node:assert/strict'
4
4
  import {mkdtempSync,realpathSync,mkdirSync,readFileSync,existsSync,rmSync} from 'node:fs'
5
5
  import {join} from 'node:path'
6
6
  import {tmpdir} from 'node:os'
7
- import {indexLine, searchStore} from './index.mjs'
8
- import {prepareMountedFixture,writeFixtureMemory} from './fixture-host.mjs'
9
- import {candidateSnapshot,listCandidates} from './candidates.mjs'
10
- import {assessCandidate} from './capture-evidence.mjs'
11
- import {normalizeCaptureDiagnostic} from './capture-diagnostics.mjs'
7
+ import {indexLine, searchStore} from '../index.mjs'
8
+ import {prepareMountedFixture,writeFixtureMemory} from '../fixture-host.mjs'
9
+ import {candidateSnapshot,listCandidates} from '../candidates.mjs'
10
+ import {assessCandidate} from '../capture-evidence.mjs'
11
+ import {normalizeCaptureDiagnostic} from '../capture-diagnostics.mjs'
12
12
 
13
13
  async function box(run){
14
14
  const dir=realpathSync(mkdtempSync(join(tmpdir(),'dsh-text-admission-'))),home=join(dir,'home'),cwd=join(dir,'project'),previous=process.env.DSH_HOME
@@ -1,6 +1,6 @@
1
1
  import test from 'node:test'
2
2
  import assert from 'node:assert/strict'
3
- import {createToolInputTracker} from './tool-inputs.mjs'
3
+ import {createToolInputTracker} from '../tool-inputs.mjs'
4
4
  const message=(id,kind='user')=>({id,role:'user',source:{kind},content:[{type:'text',text:'Synthetic owner statement '+id}]})
5
5
  function fixture(){const tracker=createToolInputTracker(),session={id:'synthetic-session'},agent={session};return{tracker,session,agent,pre:(turn,step,messages)=>tracker.preStep({agent,turn,step,messages}),admit:(m,seq)=>tracker.event(session,{type:'user/message',seq,data:m}),call:(callId,seq,turn=1,step=1)=>tracker.event(session,{type:'tool/call',seq,data:{callId,turn,step}}),exec:callId=>tracker.forExecution({agent,callId})}}
6
6
  test('native call binds to the admitted owner event sequence and declares context-only basis',()=>{const f=fixture(),m=message('a');f.pre(1,1,[m]);f.admit(m,10);f.call('call',11);const proof=f.exec('call');assert.equal(proof.basis,'tool-context');assert.equal(proof.evidence[0].messageId,'synthetic-session:10');assert.match(proof.evidence[0].hash,/^[a-f0-9]{64}$/);assert.match(proof.evidence[0].quote,/owner statement/);assert.equal(JSON.stringify(proof).includes('Synthetic owner statement a'),true)})
@@ -4,12 +4,12 @@ import {mkdtempSync,realpathSync,mkdirSync,readFileSync,writeFileSync,rmSync} fr
4
4
  import {join} from 'node:path'
5
5
  import {tmpdir} from 'node:os'
6
6
  import {createHash} from 'node:crypto'
7
- import {createToolInputTracker} from './tool-inputs.mjs'
8
- import {createObservationTracker,normalizeObservationCertificate,observationRoots,argumentFingerprint} from './tool-observations.mjs'
9
- import {apply,searchStore,withdrawManagedSource,indexLine} from './index.mjs'
10
- import {candidateSnapshot} from './candidates.mjs'
11
- import {prepareSourceRetirement,validateRetiredSource} from './source-retirement.mjs'
12
- import {writeFixtureMemory,scriptedNativeValidationLlm} from './fixture-host.mjs'
7
+ import {createToolInputTracker} from '../tool-inputs.mjs'
8
+ import {createObservationTracker,normalizeObservationCertificate,observationRoots,argumentFingerprint} from '../tool-observations.mjs'
9
+ import {apply,searchStore,withdrawManagedSource,indexLine} from '../index.mjs'
10
+ import {candidateSnapshot} from '../candidates.mjs'
11
+ import {prepareSourceRetirement,validateRetiredSource} from '../source-retirement.mjs'
12
+ import {writeFixtureMemory,scriptedNativeValidationLlm} from '../fixture-host.mjs'
13
13
  const hash=value=>createHash('sha256').update(value).digest('hex')
14
14
  function fixture(){
15
15
  const inputs=createToolInputTracker(),session={id:'native-observation-session',deriveMessages:()=>messages},agent={session},obs=createObservationTracker((session,callId)=>inputs.forExecution({agent:{session},callId})),messages=[]
@@ -5,9 +5,9 @@ import assert from 'node:assert/strict'
5
5
  import {mkdtempSync,mkdirSync,rmSync,existsSync,readFileSync} from 'node:fs'
6
6
  import {join} from 'node:path'
7
7
  import {tmpdir} from 'node:os'
8
- import {apply,storeDirFor} from './index.mjs'
9
- import {candidateSnapshot} from './candidates.mjs'
10
- import {scriptedNativeValidationLlm} from './fixture-host.mjs'
8
+ import {apply,storeDirFor} from '../index.mjs'
9
+ import {candidateSnapshot} from '../candidates.mjs'
10
+ import {scriptedNativeValidationLlm} from '../fixture-host.mjs'
11
11
  async function box(run){const root=realpathSync(mkdtempSync(join(tmpdir(),'tool-write-guard-'))),home=join(root,'home'),previous=process.env.DSH_HOME;mkdirSync(home);process.env.DSH_HOME=home;const handlers=[],tools=[],effects=[],ctx={llm:scriptedNativeValidationLlm(),systemPrompt:{context(){}},workingDirectory:{get:()=>root},tools:{register:tool=>tools.push(tool)},on:(name,handler)=>handlers.push([name,handler]),effect:fn=>{const dispose=fn();if(typeof dispose==='function')effects.push(dispose)}};apply(ctx,{autoCapture:{enabled:false}});const session={id:'synthetic-owner-session'},agent={session,options:{provider:'fixture',model:'synthetic'}},tool=tools.find(tool=>tool.name==='memory_write'),emit=(name,...args)=>{for(const [event,handler] of handlers)if(event===name)handler(...args)},input={id:'owner-input',source:{kind:'user'},role:'user',content:[{type:'text',text:'Synthetic owner statement jade9876'}]},exec={agent,callId:'native-call',rootCallId:'native-call'};const admit=()=>{emit('agent/pre-step',{agent,turn:1,step:1,messages:[input]},()=>({kind:'enter'}));emit('session/event',session,{type:'user/message',seq:10,data:input});emit('session/event',session,{type:'tool/call',seq:11,data:{turn:1,step:1,callId:'native-call'}})};try{return await run({root,dir:join(home,'memory'),tool,exec,emit,input,admit,effects,session})}finally{for(const dispose of effects)dispose();if(previous===undefined)delete process.env.DSH_HOME;else process.env.DSH_HOME=previous;rmSync(root,{recursive:true,force:true})}}
12
12
  const args={meaning:{kind:'claim'},type:'user',scope:'global',summary:'Synthetic selected jade9876',detail:'Synthetic detail jade9876'}
13
13
  test('SDK tool rejects missing owner history, actor and forged model-supplied evidence',()=>box(async f=>{for(const exec of [f.exec,{}, {agent:{session:{id:'synthetic-other'}}}])await assert.rejects(()=>f.tool.execute({...args,evidence:[{messageId:'forged'}]},exec),/source-unavailable/);assert.equal(existsSync(join(f.dir,'MEMORY.md')),false)}))
@@ -3,9 +3,9 @@ import assert from 'node:assert/strict'
3
3
  import {mkdtempSync,rmSync,readFileSync,writeFileSync,existsSync,cpSync,mkdirSync} from 'node:fs'
4
4
  import {join} from 'node:path'
5
5
  import {tmpdir} from 'node:os'
6
- import {writeMemory,indexLine,searchStore,withdrawManagedSource,withdrawManagedSourceKey,forgetMemory,adoptCandidate,recoverMemoryPublication,storeRevision,lookupLinks} from './index.mjs'
7
- import {candidateSnapshot,stageCandidate,withdrawSource,withdrawalPolicy,pruneCandidates,rejectCandidate} from './candidates.mjs'
8
- import {filterWithdrawn} from './withdrawal.mjs'
6
+ import {writeMemory,indexLine,searchStore,withdrawManagedSource,withdrawManagedSourceKey,forgetMemory,adoptCandidate,recoverMemoryPublication,storeRevision,lookupLinks} from '../index.mjs'
7
+ import {candidateSnapshot,stageCandidate,withdrawSource,withdrawalPolicy,pruneCandidates,rejectCandidate} from '../candidates.mjs'
8
+ import {filterWithdrawn} from '../withdrawal.mjs'
9
9
  const proof=(n=1)=>({sessionId:'synthetic-owner',basis:'tool-context',via:'memory_write',callHash:String(n).padStart(64,'a'),evidence:[{messageId:'owner-message-'+n,hash:'b'.repeat(64),quote:'Synthetic owner statement for this turn '+n}]})
10
10
  const box=run=>{const root=mkdtempSync(join(tmpdir(),'tool-write-life-')),dir=join(root,'memory');mkdirSync(dir);try{return run(dir,root)}finally{rmSync(root,{recursive:true,force:true})}}
11
11
  function write(dir,summary='Synthetic selected palette jade9876',options={}){const detail=options.detail??'Synthetic private detail jade9876';return writeMemory({dir,type:'user',scope:'global',summary,detail,line:indexLine('user',summary,options.expires,detail.trim().length>0),provenance:proof(options.n??1),...options})}
@@ -1,8 +1,8 @@
1
- import {writeFixtureMemory} from './fixture-host.mjs'
1
+ import {writeFixtureMemory} from '../fixture-host.mjs'
2
2
  import {realpathSync} from 'node:fs'
3
- import {applyWithOwnerFixture as apply} from './fixture-host.mjs'
3
+ import {applyWithOwnerFixture as apply} from '../fixture-host.mjs'
4
4
  /**
5
- * 可用性测试。跑法:node --test ~/.dsh/plugins/memory/test-usability.mjs
5
+ * 可用性测试。跑法:node --test ~/.dsh/plugins/memory/test/test-usability.mjs
6
6
  *
7
7
  * 这份文件验收五条指标(对应计划里的 U1–U5):
8
8
  * U1 无死路:写入失败必须给出已存在的下一步;满索引一次 compact 恢复可写
@@ -22,7 +22,7 @@ import { join, dirname } from 'node:path'
22
22
  import { tmpdir } from 'node:os'
23
23
  import { fileURLToPath } from 'node:url'
24
24
 
25
- import { countFrameTokens } from './security.mjs'
25
+ import { countFrameTokens } from '../security.mjs'
26
26
  import {
27
27
 
28
28
  pruneStore,
@@ -32,9 +32,9 @@ import {
32
32
  statusOf,
33
33
  storeDirFor,
34
34
  writeMemory,
35
- } from './index.mjs'
35
+ } from '../index.mjs'
36
36
 
37
- const HERE = dirname(fileURLToPath(import.meta.url))
37
+ const HERE = dirname(dirname(fileURLToPath(import.meta.url)))
38
38
 
39
39
  function sandbox() {
40
40
  const root = realpathSync(mkdtempSync(join(tmpdir(), 'dsh-mem-usability-')))
@@ -405,7 +405,7 @@ test('compact:超大主题文件轮转,内容不丢,之后能继续追加'
405
405
  assert.equal(existsSync(join(dir, 'user.md')), false)
406
406
  assert.equal(statSync(join(dir, report.archived[0].to)).size, Buffer.byteLength(big), '内容必须逐字保留')
407
407
 
408
- const { appendTopic } = await import('./index.mjs')
408
+ const { appendTopic } = await import('../index.mjs')
409
409
  appendTopic(dir, 'user', '轮转后仍可追加')
410
410
  assert.match(readFileSync(join(dir, 'user.md'), 'utf8'), /轮转后仍可追加/)
411
411
  } finally {
@@ -1,7 +1,7 @@
1
1
  import test from 'node:test'
2
2
  import assert from 'node:assert/strict'
3
- import {prepareValidationPrompt,ValidationSubmission,validateCandidatesWithLlm} from './capture-validation.mjs'
4
- import {VALIDATION_FIELDS} from './capture-validation-contract.mjs'
3
+ import {prepareValidationPrompt,ValidationSubmission,validateCandidatesWithLlm} from '../capture-validation.mjs'
4
+ import {VALIDATION_FIELDS} from '../capture-validation-contract.mjs'
5
5
  const messages=['跨所有项目的初稿反馈先列两处问题再给办法。','这一长期约定在正式稿上不适用。','请保留这项约定,以供之后的项目使用。'].map((text,i)=>({id:'synthetic-'+i,role:'user',text,hash:String(i).repeat(64)}))
6
6
  const candidate={type:'user',scope:'global',basis:'explicit',summary:'跨项目的初稿反馈先列两处问题再给办法',meaning:{kind:'preference',conditions:[{dimension:'task',value:'review'},{dimension:'document_kind',value:'draft'}]},evidence:messages.slice(0,2).map(({id,text})=>({messageId:id,quote:text}))}
7
7
  function setup(){const prepared=prepareValidationPrompt(messages,[candidate]),r=prepared.records[0],check={candidateId:r.candidateId,fields:Object.fromEntries(VALIDATION_FIELDS.map(k=>[k,'supported'])),taskRestriction:'none',sourceRefs:Object.keys(r.requiredSourceRefs),evidence:[]};return{prepared,check}}
@@ -1,6 +1,6 @@
1
1
  import test from 'node:test'
2
2
  import assert from 'node:assert/strict'
3
- import {validationTransitionContext as build} from './validation-transition-context.mjs'
3
+ import {validationTransitionContext as build} from '../validation-transition-context.mjs'
4
4
  const record=(n,patch={})=>({id:String(n).padStart(64,'a'),status:'recorded',scope:'global',type:'user',summary:'Synthetic default palette '+n,indexLine:'- [user] Synthetic default palette '+n+' — 索引摘要',meaning:{kind:'preference',conditions:[],except:[]},...patch});
5
5
  const target={summary:'Synthetic default palette new',scope:'global'};
6
6
  test('current same-scope interpretations are data and cannot expose old quotes, bodies or other projects',()=>{
@@ -4,11 +4,11 @@ import { mkdtempSync, readFileSync, writeFileSync, readdirSync, rmSync, symlinkS
4
4
  import { spawnSync } from 'node:child_process'
5
5
  import { join } from 'node:path'
6
6
  import { tmpdir } from 'node:os'
7
- import { stageCandidate, listCandidates } from './candidates.mjs'
8
- import { adoptCandidate, withdrawManagedSource, recoverMemoryPublication, storeRevision, searchStore, writeMemory, indexLine } from './index.mjs'
9
- import { commitPublication } from './publication.mjs'
10
- import { withLock } from './security.mjs'
11
- import { MANAGED_LOG_BYTES, MANAGED_LOG_TOTAL_BYTES, isManagedLogName } from './managed-logs.mjs'
7
+ import { stageCandidate, listCandidates } from '../candidates.mjs'
8
+ import { adoptCandidate, withdrawManagedSource, recoverMemoryPublication, storeRevision, searchStore, writeMemory, indexLine } from '../index.mjs'
9
+ import { commitPublication } from '../publication.mjs'
10
+ import { withLock } from '../security.mjs'
11
+ import { MANAGED_LOG_BYTES, MANAGED_LOG_TOTAL_BYTES, isManagedLogName } from '../managed-logs.mjs'
12
12
 
13
13
  function box(run) {
14
14
  const dir = mkdtempSync(join(tmpdir(), 'dsh-archive-withdrawal-'))
@@ -140,7 +140,7 @@ test('forty-one-file withdrawal survives actual SIGKILL and finishes history/arc
140
140
  for (const base of ['.audit.jsonl', '.history.jsonl']) for (let suffix = 1; suffix <= 16; suffix++) writeFileSync(join(dir, `${base}.${suffix}`), readFileSync(join(dir, base)))
141
141
  const preview = withdrawManagedSource(dir, 'archive-source', 'message-1')
142
142
  const options = { apply: true, reviewedIndex: preview.reviewedIndex, candidateRevision: preview.candidateRevision }
143
- const worker = `import {withdrawManagedSource} from ${JSON.stringify(new URL('./index.mjs', import.meta.url).href)};withdrawManagedSource(process.argv[1],'archive-source','message-1',{...JSON.parse(process.argv[2]),publicationFault:event=>{if(event.phase==='file'&&event.index===34)process.kill(process.pid,'SIGKILL')}})`
143
+ const worker = `import {withdrawManagedSource} from ${JSON.stringify(new URL('../index.mjs', import.meta.url).href)};withdrawManagedSource(process.argv[1],'archive-source','message-1',{...JSON.parse(process.argv[2]),publicationFault:event=>{if(event.phase==='file'&&event.index===34)process.kill(process.pid,'SIGKILL')}})`
144
144
  const stopped = spawnSync(process.execPath, ['--input-type=module', '-e', worker, dir, JSON.stringify(options)], { encoding: 'utf8', timeout: 15000 })
145
145
  assert.equal(stopped.signal, 'SIGKILL', stopped.stderr)
146
146
  const journal=JSON.parse(readFileSync(join(dir, '.publication.json')))
@@ -4,8 +4,8 @@ import assert from 'node:assert/strict'
4
4
  import { mkdtempSync, readFileSync, writeFileSync, rmSync, readdirSync, cpSync } from 'node:fs'
5
5
  import { join } from 'node:path'
6
6
  import { tmpdir } from 'node:os'
7
- import { stageCandidate, listCandidates, pruneCandidates, withdrawSource } from './candidates.mjs'
8
- import { adoptCandidate, withdrawManagedSource, recoverMemoryPublication, writeMemory, storeRevision, indexLine, searchStore, lookupLinks, statusOf, forgetMemory, apply } from './index.mjs'
7
+ import { stageCandidate, listCandidates, pruneCandidates, withdrawSource } from '../candidates.mjs'
8
+ import { adoptCandidate, withdrawManagedSource, recoverMemoryPublication, writeMemory, storeRevision, indexLine, searchStore, lookupLinks, statusOf, forgetMemory, apply } from '../index.mjs'
9
9
 
10
10
  function box(run) { const dir = realpathSync(mkdtempSync(join(tmpdir(), 'dsh-withdrawal-'))); try { return run(dir) } finally { rmSync(dir, { recursive: true, force: true }) } }
11
11
  function adopted(dir, detail = '唯一细节:只用于室内对白镜头。', source = 'source-1') {
@@ -1,8 +1,8 @@
1
- import {prepareMountedFixture} from './fixture-host.mjs'
1
+ import {prepareMountedFixture} from '../fixture-host.mjs'
2
2
  import {realpathSync} from 'node:fs'
3
- import {applyWithOwnerFixture as apply} from './fixture-host.mjs'
3
+ import {applyWithOwnerFixture as apply} from '../fixture-host.mjs'
4
4
  /**
5
- * dsh-memory 的单元测试。跑法:node --test ~/.dsh/plugins/memory/test.mjs
5
+ * dsh-memory 的单元测试。跑法:node --test ~/.dsh/plugins/memory/test/test.mjs
6
6
  *
7
7
  * 不依赖 DSH 宿主:用假的 ctx 捕获注册结果,再直接调 provider 的 text() 和工具 execute。
8
8
  * 两条必须守住的约定:
@@ -30,7 +30,7 @@ import {
30
30
  indexLine,
31
31
  storeDirFor,
32
32
  writeMemory,
33
- } from './index.mjs'
33
+ } from '../index.mjs'
34
34
 
35
35
  const CONTEXT_NAME = 'memory:index'
36
36
 
package/version.mjs CHANGED
@@ -2,4 +2,4 @@
2
2
  * 版本单点。package.json 里的 version 必须与它一致,由 test-packaging.mjs 断言。
3
3
  * 打包工具不能读 ESM 常量时会回退读 package.json,所以两处都要在。
4
4
  */
5
- export const VERSION = '0.21.0-beta.2'
5
+ export const VERSION = '0.22.0-beta.3'