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.
- package/CHANGELOG.md +37 -0
- package/COMPATIBILITY.md +8 -5
- package/DETECTION.md +139 -0
- package/DREAMING-DESIGN.md +167 -0
- package/README.en.md +35 -6
- package/README.md +44 -7
- package/RELEASE.md +36 -0
- package/SECURITY.md +11 -0
- package/context-history.mjs +20 -3
- package/detect.mjs +441 -0
- package/eval/bench-detect.mjs +107 -0
- package/eval/semantic/cases.json +387 -0
- package/eval/semantic-suite.mjs +156 -0
- package/index.mjs +117 -27
- package/managed-policy.mjs +3 -1
- package/package.json +91 -68
- package/privacy.mjs +25 -0
- package/scripts/demo-source-recall.mjs +123 -0
- package/scripts/detect.mjs +69 -0
- package/scripts/dream.mjs +205 -0
- package/scripts/probe-history.mjs +92 -0
- package/scripts/probe-load.mjs +1 -1
- package/scripts/probe-package.mjs +6 -2
- package/scripts/security-check.mjs +173 -0
- package/scripts/soak.mjs +219 -0
- package/scripts/telemetry.mjs +187 -0
- package/scripts/verify.mjs +3 -1
- package/{test-adversarial.mjs → test/test-adversarial.mjs} +5 -5
- package/{test-belief-revision.mjs → test/test-belief-revision.mjs} +7 -7
- package/{test-candidates.mjs → test/test-candidates.mjs} +4 -4
- package/{test-capture-diagnostics.mjs → test/test-capture-diagnostics.mjs} +6 -6
- package/{test-capture-field-lineage.mjs → test/test-capture-field-lineage.mjs} +4 -4
- package/{test-capture-inputs.mjs → test/test-capture-inputs.mjs} +8 -8
- package/{test-capture-jobs.mjs → test/test-capture-jobs.mjs} +5 -5
- package/{test-capture-projections.mjs → test/test-capture-projections.mjs} +12 -12
- package/{test-capture-revision.mjs → test/test-capture-revision.mjs} +6 -6
- package/{test-capture-submission.mjs → test/test-capture-submission.mjs} +3 -3
- package/{test-capture-validation-flow.mjs → test/test-capture-validation-flow.mjs} +5 -5
- package/{test-capture-validation-job.mjs → test/test-capture-validation-job.mjs} +5 -5
- package/{test-capture-validation-lineage.mjs → test/test-capture-validation-lineage.mjs} +2 -2
- package/{test-capture-validation.mjs → test/test-capture-validation.mjs} +2 -2
- package/{test-capture.mjs → test/test-capture.mjs} +20 -20
- package/{test-compaction-compat.mjs → test/test-compaction-compat.mjs} +5 -5
- package/{test-compat.mjs → test/test-compat.mjs} +3 -3
- package/{test-concurrency.mjs → test/test-concurrency.mjs} +3 -3
- package/{test-conflict.mjs → test/test-conflict.mjs} +2 -2
- package/{test-context-history.mjs → test/test-context-history.mjs} +1 -1
- package/{test-control-schema.mjs → test/test-control-schema.mjs} +1 -1
- package/test/test-detect.mjs +125 -0
- package/test/test-dream-plan.mjs +72 -0
- package/test/test-dsh-registration.mjs +111 -0
- package/{test-episodes.mjs → test/test-episodes.mjs} +5 -5
- package/{test-explicit-memory.mjs → test/test-explicit-memory.mjs} +4 -4
- package/{test-field-lineage.mjs → test/test-field-lineage.mjs} +4 -4
- package/{test-history.mjs → test/test-history.mjs} +4 -4
- package/{test-inline-summary.mjs → test/test-inline-summary.mjs} +5 -5
- package/{test-legacy-source-identity.mjs → test/test-legacy-source-identity.mjs} +2 -2
- package/{test-links.mjs → test/test-links.mjs} +4 -4
- package/{test-managed-access.mjs → test/test-managed-access.mjs} +29 -5
- package/{test-managed-activation.mjs → test/test-managed-activation.mjs} +5 -5
- package/{test-meaning.mjs → test/test-meaning.mjs} +6 -6
- package/{test-native-service-lifecycle.mjs → test/test-native-service-lifecycle.mjs} +1 -1
- package/{test-native-write-validation.mjs → test/test-native-write-validation.mjs} +10 -4
- package/{test-packaging.mjs → test/test-packaging.mjs} +32 -5
- package/{test-paired-analysis.mjs → test/test-paired-analysis.mjs} +1 -1
- package/{test-patch.mjs → test/test-patch.mjs} +11 -11
- package/{test-plugin-contract.mjs → test/test-plugin-contract.mjs} +4 -4
- package/test/test-privacy.mjs +65 -0
- package/{test-project-stores.mjs → test/test-project-stores.mjs} +5 -5
- package/{test-publication.mjs → test/test-publication.mjs} +4 -4
- package/{test-restore.mjs → test/test-restore.mjs} +11 -11
- package/{test-retrieval.mjs → test/test-retrieval.mjs} +2 -2
- package/{test-revision-admission.mjs → test/test-revision-admission.mjs} +3 -3
- package/{test-rule-admission.mjs → test/test-rule-admission.mjs} +8 -8
- package/{test-runtime-startup.mjs → test/test-runtime-startup.mjs} +2 -2
- package/{test-schema.mjs → test/test-schema.mjs} +2 -2
- package/{test-security.mjs → test/test-security.mjs} +6 -6
- package/test/test-semantic-suite.mjs +20 -0
- package/{test-service-readiness.mjs → test/test-service-readiness.mjs} +1 -1
- package/{test-smell.mjs → test/test-smell.mjs} +4 -4
- package/{test-snapshots.mjs → test/test-snapshots.mjs} +3 -3
- package/{test-source-completeness.mjs → test/test-source-completeness.mjs} +6 -6
- package/{test-source-key.mjs → test/test-source-key.mjs} +2 -2
- package/{test-source-retirement.mjs → test/test-source-retirement.mjs} +4 -4
- package/{test-status-both.mjs → test/test-status-both.mjs} +12 -4
- package/{test-supersede.mjs → test/test-supersede.mjs} +3 -3
- package/test/test-telemetry.mjs +60 -0
- package/{test-text-admission.mjs → test/test-text-admission.mjs} +6 -6
- package/{test-tool-inputs.mjs → test/test-tool-inputs.mjs} +1 -1
- package/{test-tool-observations.mjs → test/test-tool-observations.mjs} +6 -6
- package/{test-tool-write-guard.mjs → test/test-tool-write-guard.mjs} +3 -3
- package/{test-tool-write-lifecycle.mjs → test/test-tool-write-lifecycle.mjs} +3 -3
- package/{test-usability.mjs → test/test-usability.mjs} +7 -7
- package/{test-validation-source-refs.mjs → test/test-validation-source-refs.mjs} +2 -2
- package/{test-validation-transition-context.mjs → test/test-validation-transition-context.mjs} +1 -1
- package/{test-withdrawal-archives.mjs → test/test-withdrawal-archives.mjs} +6 -6
- package/{test-withdrawal.mjs → test/test-withdrawal.mjs} +2 -2
- package/{test.mjs → test/test.mjs} +4 -4
- package/version.mjs +1 -1
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {applyWithValidationFixture as apply} from '
|
|
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,writeFileSync,existsSync,rmSync} from 'node:fs'
|
|
5
5
|
import {join} from 'node:path'
|
|
6
6
|
import {tmpdir} from 'node:os'
|
|
7
|
-
import {parseCaptureBatch,buildCaptureDiagnostic,normalizeCaptureDiagnostic,captureDiagnosticOutcome,CAPTURE_RESPONSE_BYTES} from '
|
|
8
|
-
import {extractWithLlm} from '
|
|
9
|
-
import {searchStore, indexLine} from '
|
|
10
|
-
import {listCaptureJobs,captureJobStatus,claimCaptureJob,finishCaptureJob} from '
|
|
11
|
-
import {writeFixtureMemory} from '
|
|
7
|
+
import {parseCaptureBatch,buildCaptureDiagnostic,normalizeCaptureDiagnostic,captureDiagnosticOutcome,CAPTURE_RESPONSE_BYTES} from '../capture-diagnostics.mjs'
|
|
8
|
+
import {extractWithLlm} from '../capture-llm.mjs'
|
|
9
|
+
import {searchStore, indexLine} from '../index.mjs'
|
|
10
|
+
import {listCaptureJobs,captureJobStatus,claimCaptureJob,finishCaptureJob} from '../capture-jobs.mjs'
|
|
11
|
+
import {writeFixtureMemory} from '../fixture-host.mjs'
|
|
12
12
|
const item=(n=1)=>({type:'user',summary:'持久有效的测试条目'+n})
|
|
13
13
|
test('an explicit empty array is a model-reported no-op, never proof that no durable information exists',()=>{
|
|
14
14
|
const batch=parseCaptureBatch('[]',3),d=buildCaptureDiagnostic(batch)
|
|
@@ -5,10 +5,10 @@ import assert from 'node:assert/strict'
|
|
|
5
5
|
import {mkdtempSync,realpathSync,mkdirSync,readFileSync,rmSync} from 'node:fs'
|
|
6
6
|
import {join} from 'node:path'
|
|
7
7
|
import {tmpdir} from 'node:os'
|
|
8
|
-
import {apply,searchStore,withdrawManagedSource} from '
|
|
9
|
-
import {candidateSnapshot} from '
|
|
10
|
-
import {VALIDATION_FIELDS} from '
|
|
11
|
-
import {writeFixtureMemory} from '
|
|
8
|
+
import {apply,searchStore,withdrawManagedSource} from '../index.mjs'
|
|
9
|
+
import {candidateSnapshot} from '../candidates.mjs'
|
|
10
|
+
import {VALIDATION_FIELDS} from '../capture-validation-contract.mjs'
|
|
11
|
+
import {writeFixtureMemory} from '../fixture-host.mjs'
|
|
12
12
|
const modes=['value','expiring-value','complete','changed-condition','inferred','wrong-target','unsupported','uncertain','withdraw-during','root-overflow','self-excluding'];
|
|
13
13
|
for(const mode of modes)test('automatic code-owned field lineage: '+mode,async()=>{
|
|
14
14
|
const base=realpathSync(mkdtempSync(join(tmpdir(),'auto-field-lineage-'))),home=join(base,'home'),cwd=join(base,'project'),root=join(home,'memory'),previous=process.env.DSH_HOME;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {applyWithValidationFixture as apply} from '
|
|
2
|
-
import {prepareMountedFixture} from '
|
|
1
|
+
import {applyWithValidationFixture as apply} from '../fixture-host.mjs'
|
|
2
|
+
import {prepareMountedFixture} from '../fixture-host.mjs'
|
|
3
3
|
import {realpathSync} from 'node:fs'
|
|
4
4
|
import {test} from 'node:test'
|
|
5
5
|
import assert from 'node:assert/strict'
|
|
@@ -8,10 +8,10 @@ import {join} from 'node:path'
|
|
|
8
8
|
import {tmpdir} from 'node:os'
|
|
9
9
|
import {fileURLToPath} from 'node:url'
|
|
10
10
|
import {spawnSync} from 'node:child_process'
|
|
11
|
-
import {captureMessage} from '
|
|
12
|
-
import {captureSessionEligible,captureInputDescriptor,enqueueCaptureInput,listCaptureInputs,pendingCaptureInputs,bindCaptureInput,blockCaptureInput,reconstructCaptureInput,pruneCaptureInputs,observeCaptureSource} from '
|
|
13
|
-
import {claimCaptureJob,listCaptureJobs,finishCaptureJob} from '
|
|
14
|
-
import {storeDirFor, withdrawManagedSource} from '
|
|
11
|
+
import {captureMessage} from '../capture-evidence.mjs'
|
|
12
|
+
import {captureSessionEligible,captureInputDescriptor,enqueueCaptureInput,listCaptureInputs,pendingCaptureInputs,bindCaptureInput,blockCaptureInput,reconstructCaptureInput,pruneCaptureInputs,observeCaptureSource} from '../capture-inputs.mjs'
|
|
13
|
+
import {claimCaptureJob,listCaptureJobs,finishCaptureJob} from '../capture-jobs.mjs'
|
|
14
|
+
import {storeDirFor, withdrawManagedSource} from '../index.mjs'
|
|
15
15
|
|
|
16
16
|
const options={everyTurns:1,maxEntries:3,maxTokens:800,maxInputBytes:24*1024,timeoutMs:25000}
|
|
17
17
|
const route={provider:'fixture',model:'fixture-model'}
|
|
@@ -83,7 +83,7 @@ test('损坏、多余正文字段、链接及无效引用拒绝并保留现场',
|
|
|
83
83
|
}))
|
|
84
84
|
|
|
85
85
|
test('来源入队后真实进程SIGKILL,重开保留尚未发出的输入',()=>box(dir=>{
|
|
86
|
-
const modulePath=fileURLToPath(new URL('
|
|
86
|
+
const modulePath=fileURLToPath(new URL('../capture-inputs.mjs',import.meta.url))
|
|
87
87
|
const code=`import{enqueueCaptureInput}from${JSON.stringify(modulePath)};enqueueCaptureInput(process.env.INPUT_DIR,${JSON.stringify(descriptor())});process.kill(process.pid,'SIGKILL')`
|
|
88
88
|
const child=spawnSync(process.execPath,['--input-type=module','-e',code],{env:{...process.env,INPUT_DIR:dir},timeout:5000})
|
|
89
89
|
assert.equal(child.signal,'SIGKILL');assert.equal(pendingCaptureInputs(dir,session()).length,1)
|
|
@@ -105,7 +105,7 @@ test('清理只选已知终态,双版本过期拒绝;待处理与未决保
|
|
|
105
105
|
}))
|
|
106
106
|
|
|
107
107
|
test('输入CLI默认只读,明确清理须同时带两份版本',()=>box(dir=>{
|
|
108
|
-
const script=fileURLToPath(new URL('
|
|
108
|
+
const script=fileURLToPath(new URL('../scripts/capture-jobs.mjs',import.meta.url)),env={...process.env,DSH_HOME:dir}
|
|
109
109
|
const run=(...args)=>spawnSync(process.execPath,[script,...args],{env,encoding:'utf8',timeout:5000})
|
|
110
110
|
assert.equal(run('inputs').status,0);assert.equal(existsSync(join(dir,'memory')),false)
|
|
111
111
|
const store=join(dir,'memory'),row=enqueueCaptureInput(store,descriptor());blockCaptureInput(store,row.id,'source-changed')
|
|
@@ -6,10 +6,10 @@ import { tmpdir } from 'node:os'
|
|
|
6
6
|
import { spawn, spawnSync } from 'node:child_process'
|
|
7
7
|
import { createHash } from 'node:crypto'
|
|
8
8
|
import { fileURLToPath } from 'node:url'
|
|
9
|
-
import { claimCaptureJob, assertCaptureClaim, finishCaptureJob, listCaptureJobs, recoverCaptureJobs, abandonCaptureJob, retryBusyWrite } from '
|
|
10
|
-
import { withLock } from '
|
|
9
|
+
import { claimCaptureJob, assertCaptureClaim, finishCaptureJob, listCaptureJobs, recoverCaptureJobs, abandonCaptureJob, retryBusyWrite } from '../capture-jobs.mjs'
|
|
10
|
+
import { withLock } from '../security.mjs'
|
|
11
11
|
|
|
12
|
-
const modulePath = fileURLToPath(new URL('
|
|
12
|
+
const modulePath = fileURLToPath(new URL('../capture-jobs.mjs', import.meta.url))
|
|
13
13
|
const hash = (v) => createHash('sha256').update(String(v)).digest('hex')
|
|
14
14
|
const options = { now: 1000, leaseMs: 1000 }
|
|
15
15
|
const file = (dir) => join(dir, '.capture-jobs.json')
|
|
@@ -121,7 +121,7 @@ test('两个真实进程并发领取同一快照,只有一个领取成功', as
|
|
|
121
121
|
})
|
|
122
122
|
|
|
123
123
|
test('诊断 CLI 默认只读;recover --apply 只标未决,不执行模型请求', () => box((dir) => {
|
|
124
|
-
const script = fileURLToPath(new URL('
|
|
124
|
+
const script = fileURLToPath(new URL('../scripts/capture-jobs.mjs', import.meta.url))
|
|
125
125
|
const env = { ...process.env, DSH_HOME: dir }
|
|
126
126
|
const run = (...args) => spawnSync(process.execPath, [script, ...args], { env, encoding: 'utf8', timeout: 5000 })
|
|
127
127
|
assert.equal(run().status, 0); assert.equal(existsSync(join(dir, 'memory')), false)
|
|
@@ -286,7 +286,7 @@ test('两个真实进程对同一预览并发处置,仅一次生效,另一
|
|
|
286
286
|
})
|
|
287
287
|
|
|
288
288
|
test('处置 CLI 默认预览,缺版本/未知参数/过期版本不修改记录,明确应用后仍为未知结果', () => box((dir) => {
|
|
289
|
-
const script = fileURLToPath(new URL('
|
|
289
|
+
const script = fileURLToPath(new URL('../scripts/capture-jobs.mjs', import.meta.url))
|
|
290
290
|
const run = (...args) => spawnSync(process.execPath, [script, ...args], { env: { ...process.env, DSH_HOME: dir }, encoding: 'utf8', timeout: 5000 })
|
|
291
291
|
const job = claimCaptureJob(join(dir, 'memory'), hash('cli-close'), options)
|
|
292
292
|
const before = readFileSync(file(join(dir,'memory')),'utf8')
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import {applyWithValidationFixture as apply} from '
|
|
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{readFileSync,mkdtempSync,realpathSync,mkdirSync,rmSync,existsSync}from'node:fs'
|
|
5
5
|
import{join}from'node:path'
|
|
6
6
|
import{tmpdir}from'node:os'
|
|
7
|
-
import{captureProjection}from'
|
|
8
|
-
import{candidateDetail}from'
|
|
9
|
-
import{buildUserPrompt,extractWithLlm}from'
|
|
10
|
-
import{captureInputDescriptor,reconstructCaptureInput,enqueueCaptureInput,bindCaptureInput,listCaptureInputs,blockCaptureInput}from'
|
|
11
|
-
import{claimCaptureJob,recoverCaptureJobs,listCaptureJobs}from'
|
|
12
|
-
import {indexLine, searchStore, storeRevision, withdrawManagedSource} from '
|
|
13
|
-
import{writeFixtureMemory,prepareMountedFixture}from'
|
|
14
|
-
import{candidateSnapshot}from'
|
|
7
|
+
import{captureProjection}from'../capture-projections.mjs'
|
|
8
|
+
import{candidateDetail}from'../withdrawal.mjs'
|
|
9
|
+
import{buildUserPrompt,extractWithLlm}from'../capture-llm.mjs'
|
|
10
|
+
import{captureInputDescriptor,reconstructCaptureInput,enqueueCaptureInput,bindCaptureInput,listCaptureInputs,blockCaptureInput}from'../capture-inputs.mjs'
|
|
11
|
+
import{claimCaptureJob,recoverCaptureJobs,listCaptureJobs}from'../capture-jobs.mjs'
|
|
12
|
+
import {indexLine, searchStore, storeRevision, withdrawManagedSource} from '../index.mjs'
|
|
13
|
+
import{writeFixtureMemory,prepareMountedFixture}from'../fixture-host.mjs'
|
|
14
|
+
import{candidateSnapshot}from'../candidates.mjs'
|
|
15
15
|
const record={id:'a'.repeat(64),type:'user',scope:'global',summary:'只对初稿用标记 jade26',meaning:{kind:'preference',conditions:[]},detail:'Synthetic detail restricted to drafts.',origin:'tool-context',sessionId:'synthetic',evidence:[{messageId:'synthetic:1',quote:'Synthetic draft-only source'}]}
|
|
16
16
|
const line='[global] - [user] 只对初稿用标记 jade26 — user.md'
|
|
17
17
|
test('projection exposes complete existing fields and source references as data, not a new message',()=>{
|
|
@@ -28,7 +28,7 @@ test('structured projections fit the input byte budget atomically and report omi
|
|
|
28
28
|
for(const limit of [1024,2048,24576,65536]){const prompt=buildUserPrompt([{id:'source',role:'user',text:'原话只对初稿适用'}],[line],3,limit,[],projected);assert.ok(Buffer.byteLength(prompt)<=limit,`budget ${limit}`);assert.match(prompt,/省略/);for(const row of prompt.split('\n').filter(x=>x.startsWith('{')))assert.doesNotThrow(()=>JSON.parse(row))}
|
|
29
29
|
})
|
|
30
30
|
test('the actual v25 descriptor cannot resume under a changed extraction contract',()=>{
|
|
31
|
-
const f=JSON.parse(readFileSync(new URL('
|
|
31
|
+
const f=JSON.parse(readFileSync(new URL('../corpus/capture-contract-v25.json',import.meta.url))),session={...f.session,surface:{nodes:[1]},isOwnSeq:n=>n===1},events=[];events[1]=f.event
|
|
32
32
|
const next=captureInputDescriptor(session,[f.message],f.cwd,f.options,f.route)
|
|
33
33
|
assert.notEqual(next.configurationHash,f.descriptor.configurationHash);assert.notEqual(next.id,f.descriptor.id)
|
|
34
34
|
assert.throws(()=>reconstructCaptureInput(f.descriptor,session,events,f.cwd,f.options,f.route),/configuration-changed/)
|
|
@@ -62,7 +62,7 @@ for(const mode of ['repair','stale','withdrawn'])test('live capture projection a
|
|
|
62
62
|
})
|
|
63
63
|
|
|
64
64
|
for(const status of ['pending','bound','blocked'])test('changed contract cannot create another durable invocation for the same owner sources: '+status,()=>{
|
|
65
|
-
const f=JSON.parse(readFileSync(new URL('
|
|
65
|
+
const f=JSON.parse(readFileSync(new URL('../corpus/capture-contract-v25.json',import.meta.url))),root=mkdtempSync(join(tmpdir(),'dsh-contract-alias-'))
|
|
66
66
|
try{
|
|
67
67
|
enqueueCaptureInput(root,f.descriptor,{now:100})
|
|
68
68
|
if(status==='bound'){const job=claimCaptureJob(root,f.descriptor.id,{now:100,leaseMs:1000});bindCaptureInput(root,f.descriptor.id,job.id,{now:101});recoverCaptureJobs(root,{now:2000})}
|
|
@@ -73,6 +73,6 @@ for(const status of ['pending','bound','blocked'])test('changed contract cannot
|
|
|
73
73
|
}finally{rmSync(root,{recursive:true,force:true})}
|
|
74
74
|
})
|
|
75
75
|
test('genuine new owner source can create a new durable input under the new contract',()=>{
|
|
76
|
-
const f=JSON.parse(readFileSync(new URL('
|
|
76
|
+
const f=JSON.parse(readFileSync(new URL('../corpus/capture-contract-v25.json',import.meta.url))),root=mkdtempSync(join(tmpdir(),'dsh-contract-new-'))
|
|
77
77
|
try{enqueueCaptureInput(root,f.descriptor,{now:100});const next=captureInputDescriptor(f.session,[{...f.message,seq:2,id:f.session.id+':2'}],f.cwd,f.options,f.route);assert.equal(enqueueCaptureInput(root,next,{now:101}).id,next.id);assert.equal(listCaptureInputs(root).inputs.length,2)}finally{rmSync(root,{recursive:true,force:true})}
|
|
78
78
|
})
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {applyWithValidationFixture as apply} from '
|
|
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 {captureRevision} from '
|
|
8
|
-
import {parseCandidates} from '
|
|
9
|
-
import {indexLine, lookupLinks, storeDirFor} from '
|
|
10
|
-
import {prepareMountedFixture,writeFixtureMemory} from '
|
|
11
|
-
import {listCandidates} from '
|
|
7
|
+
import {captureRevision} from '../capture-revision.mjs'
|
|
8
|
+
import {parseCandidates} from '../capture-llm.mjs'
|
|
9
|
+
import {indexLine, lookupLinks, storeDirFor} from '../index.mjs'
|
|
10
|
+
import {prepareMountedFixture,writeFixtureMemory} from '../fixture-host.mjs'
|
|
11
|
+
import {listCandidates} from '../candidates.mjs'
|
|
12
12
|
|
|
13
13
|
const oldSummary='个人长期默认配色使用 violet-4826',newSummary='个人长期默认配色使用 silver-5937',otherSummary='日常答复保持简洁且先给结论'
|
|
14
14
|
const oldLine=indexLine('user',oldSummary),target='[global] '+oldLine
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {test} from 'node:test'
|
|
2
2
|
import assert from 'node:assert/strict'
|
|
3
|
-
import {CaptureSubmission,CAPTURE_SUBMISSION_NAME,CAPTURE_SUBMISSION_TOOL} from '
|
|
4
|
-
import {extractWithLlm,CAPTURE_REQUEST_CONTRACT} from '
|
|
5
|
-
import {TEXT_MEMORY_POLICY} from '
|
|
3
|
+
import {CaptureSubmission,CAPTURE_SUBMISSION_NAME,CAPTURE_SUBMISSION_TOOL} from '../capture-submission.mjs'
|
|
4
|
+
import {extractWithLlm,CAPTURE_REQUEST_CONTRACT} from '../capture-llm.mjs'
|
|
5
|
+
import {TEXT_MEMORY_POLICY} from '../memory-policy.mjs'
|
|
6
6
|
const candidate={type:'user',summary:'长期固定搭档负责声音,本人负责剪辑',scope:'global',basis:'explicit',meaning:{kind:'claim'},evidence:[{messageId:'synthetic-user',quote:'我的固定搭档负责声音,我本人负责剪辑'}]}
|
|
7
7
|
function chunks(entries=[candidate],options={}){
|
|
8
8
|
const raw=options.raw??JSON.stringify({entries}),name=options.name??CAPTURE_SUBMISSION_NAME,id='synthetic-submit'
|
|
@@ -3,11 +3,11 @@ import assert from 'node:assert/strict'
|
|
|
3
3
|
import {mkdtempSync,realpathSync,mkdirSync,readFileSync,rmSync,existsSync} from 'node:fs'
|
|
4
4
|
import {tmpdir} from 'node:os'
|
|
5
5
|
import {join} from 'node:path'
|
|
6
|
-
import {apply,indexLine} from '
|
|
7
|
-
import {writeFixtureMemory,prepareMountedFixture} from '
|
|
8
|
-
import {candidateSnapshot,listCandidates} from '
|
|
9
|
-
import {listCaptureJobs} from '
|
|
10
|
-
import {VALIDATION_FIELDS} from '
|
|
6
|
+
import {apply,indexLine} from '../index.mjs'
|
|
7
|
+
import {writeFixtureMemory,prepareMountedFixture} from '../fixture-host.mjs'
|
|
8
|
+
import {candidateSnapshot,listCandidates} from '../candidates.mjs'
|
|
9
|
+
import {listCaptureJobs} from '../capture-jobs.mjs'
|
|
10
|
+
import {VALIDATION_FIELDS} from '../capture-validation-contract.mjs'
|
|
11
11
|
for(const mode of ['approved','rejected','uncertain','validation-error','correction-rejected','transition-rejected','stale'])test('actual automatic acquisition → durable validation → source-bound admission: '+mode,async()=>{
|
|
12
12
|
const base=realpathSync(mkdtempSync(join(tmpdir(),'dsh-validated-flow-'))),home=join(base,'home'),cwd=join(base,'project'),root=join(home,'memory'),previous=process.env.DSH_HOME
|
|
13
13
|
mkdirSync(home);mkdirSync(cwd);process.env.DSH_HOME=home
|
|
@@ -3,10 +3,10 @@ import assert from 'node:assert/strict'
|
|
|
3
3
|
import {mkdtempSync,readFileSync,rmSync} from 'node:fs'
|
|
4
4
|
import {tmpdir} from 'node:os'
|
|
5
5
|
import {join} from 'node:path'
|
|
6
|
-
import {runCandidateValidation} from '
|
|
7
|
-
import {VALIDATION_FIELDS} from '
|
|
8
|
-
import {listCaptureJobs,claimCaptureJob,finishCaptureJob,captureJobStatus} from '
|
|
9
|
-
import {buildCaptureDiagnostic} from '
|
|
6
|
+
import {runCandidateValidation} from '../capture-validation-job.mjs'
|
|
7
|
+
import {VALIDATION_FIELDS} from '../capture-validation-contract.mjs'
|
|
8
|
+
import {listCaptureJobs,claimCaptureJob,finishCaptureJob,captureJobStatus} from '../capture-jobs.mjs'
|
|
9
|
+
import {buildCaptureDiagnostic} from '../capture-diagnostics.mjs'
|
|
10
10
|
import {writeFileSync} from 'node:fs'
|
|
11
11
|
import {spawn,spawnSync} from 'node:child_process'
|
|
12
12
|
import {existsSync} from 'node:fs'
|
|
@@ -41,7 +41,7 @@ test('a source/revision change during validation propagates and prevents a publi
|
|
|
41
41
|
assert.equal(calls,1);assert.equal(listCaptureJobs(dir).jobs[0].status,'failed')
|
|
42
42
|
}))
|
|
43
43
|
test('SIGKILL after a validation invocation is claimed cannot resend that child from a new process',()=>box(async dir=>{
|
|
44
|
-
const marker=join(dir,'invocation-started'),moduleUrl=new URL('
|
|
44
|
+
const marker=join(dir,'invocation-started'),moduleUrl=new URL('../capture-validation-job.mjs',import.meta.url).href
|
|
45
45
|
const opts={jobDir:dir,parentId:'e'.repeat(64),messages:[message],candidates:[candidate],sessionId:'synthetic-crash',timeoutMs:5000}
|
|
46
46
|
const script=`import{runCandidateValidation}from${JSON.stringify(moduleUrl)};import{writeFileSync}from'node:fs';let calls=0;let ticker;const opts=${JSON.stringify(opts)};opts.llm={async prepareCall(config){return{config,async*stream(){calls++;writeFileSync(${JSON.stringify(marker)},'started');if(process.argv[1]==='issue'){ticker=setInterval(()=>{},1000);await new Promise(()=>{})}yield{type:'text-delta',text:'{"checks":[]}'};yield{type:'finish',reason:{kind:'stop'}}}}}};const r=await runCandidateValidation(opts);clearInterval(ticker);console.log(JSON.stringify({calls,reason:r.reason}));`
|
|
47
47
|
const child=spawn(process.execPath,['--input-type=module','-e',script,'issue'],{stdio:['ignore','pipe','pipe']})
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {VALIDATION_FIELDS} from '
|
|
1
|
+
import {VALIDATION_FIELDS} from '../capture-validation-contract.mjs'
|
|
2
2
|
/** Scripted pipeline regression; optional source override reproduces the frozen dev28 bug. */
|
|
3
3
|
import {test} from 'node:test'
|
|
4
4
|
import assert from 'node:assert/strict'
|
|
@@ -6,7 +6,7 @@ import {mkdtempSync,realpathSync,mkdirSync,readFileSync,rmSync} from 'node:fs'
|
|
|
6
6
|
import {join,resolve} from 'node:path'
|
|
7
7
|
import {tmpdir} from 'node:os'
|
|
8
8
|
import {pathToFileURL} from 'node:url'
|
|
9
|
-
const baseUrl=process.env.DSH_VALIDATION_LINEAGE_SOURCE?pathToFileURL(resolve(process.env.DSH_VALIDATION_LINEAGE_SOURCE)+'/'):new URL('
|
|
9
|
+
const baseUrl=process.env.DSH_VALIDATION_LINEAGE_SOURCE?pathToFileURL(resolve(process.env.DSH_VALIDATION_LINEAGE_SOURCE)+'/'):new URL('../',import.meta.url)
|
|
10
10
|
const {apply,withdrawManagedSource,searchStore}=await import(new URL('index.mjs',baseUrl))
|
|
11
11
|
const {candidateSnapshot}=await import(new URL('candidates.mjs',baseUrl))
|
|
12
12
|
const {writeFixtureMemory}=await import(new URL('fixture-host.mjs',baseUrl))
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {test} from 'node:test'
|
|
2
2
|
import assert from 'node:assert/strict'
|
|
3
|
-
import {prepareValidationPrompt,ValidationSubmission,validateCandidatesWithLlm,validationCandidateId} from '
|
|
4
|
-
import {VALIDATION_FIELDS,VALIDATION_NAME} from '
|
|
3
|
+
import {prepareValidationPrompt,ValidationSubmission,validateCandidatesWithLlm,validationCandidateId} from '../capture-validation.mjs'
|
|
4
|
+
import {VALIDATION_FIELDS,VALIDATION_NAME} from '../capture-validation-contract.mjs'
|
|
5
5
|
const message={id:'owner-1',role:'user',text:'我在所有项目处理反馈时都先看最重要的两处问题,然后再看怎么改。'}
|
|
6
6
|
const candidate={type:'user',scope:'global',basis:'explicit',summary:'处理反馈先看两处问题再给修改办法,跨项目通用',meaning:{kind:'preference',conditions:[{dimension:'task',value:'review'}]},evidence:[{messageId:message.id,quote:message.text}]}
|
|
7
7
|
const prepared=()=>prepareValidationPrompt([message],[candidate])
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {applyWithValidationFixture as apply} from '
|
|
2
|
-
import {prepareMountedFixture,writeFixtureMemory} from '
|
|
1
|
+
import {applyWithValidationFixture as apply} from '../fixture-host.mjs'
|
|
2
|
+
import {prepareMountedFixture,writeFixtureMemory} from '../fixture-host.mjs'
|
|
3
3
|
import {realpathSync} from 'node:fs'
|
|
4
4
|
/**
|
|
5
|
-
* 自动捕获的测试。跑法:node --test ~/.dsh/plugins/memory/test-capture.mjs
|
|
5
|
+
* 自动捕获的测试。跑法:node --test ~/.dsh/plugins/memory/test/test-capture.mjs
|
|
6
6
|
*
|
|
7
7
|
* 这里用假 LLM 验证「提示词 → 解析 → 过闸 → 落库 → 审计」整条链路,
|
|
8
8
|
* 不产生真实模型调用。
|
|
@@ -15,13 +15,13 @@ import { join, dirname } from 'node:path'
|
|
|
15
15
|
import { tmpdir } from 'node:os'
|
|
16
16
|
import { fileURLToPath } from 'node:url'
|
|
17
17
|
|
|
18
|
-
import { chunkText, parseCandidates, buildUserPrompt, extractWithLlm, selectCaptureControls } from '
|
|
19
|
-
import { captureMessage, assessCandidate } from '
|
|
20
|
-
import { listCaptureJobs, recoverCaptureJobs, abandonCaptureJob } from '
|
|
21
|
-
import { listCandidates, stageCandidate, candidateSnapshot } from '
|
|
22
|
-
import {writeMemory, indexLine, storeDirFor, storeRevision, forgetMemory, adoptCandidate, withdrawManagedSource, searchStore} from '
|
|
18
|
+
import { chunkText, parseCandidates, buildUserPrompt, extractWithLlm, selectCaptureControls } from '../capture-llm.mjs'
|
|
19
|
+
import { captureMessage, assessCandidate } from '../capture-evidence.mjs'
|
|
20
|
+
import { listCaptureJobs, recoverCaptureJobs, abandonCaptureJob } from '../capture-jobs.mjs'
|
|
21
|
+
import { listCandidates, stageCandidate, candidateSnapshot } from '../candidates.mjs'
|
|
22
|
+
import {writeMemory, indexLine, storeDirFor, storeRevision, forgetMemory, adoptCandidate, withdrawManagedSource, searchStore} from '../index.mjs'
|
|
23
23
|
|
|
24
|
-
const HERE = dirname(fileURLToPath(import.meta.url))
|
|
24
|
+
const HERE = dirname(dirname(fileURLToPath(import.meta.url)))
|
|
25
25
|
|
|
26
26
|
function sandbox() {
|
|
27
27
|
const root = realpathSync(mkdtempSync(join(tmpdir(), 'dsh-mem-capture-')))
|
|
@@ -261,7 +261,7 @@ test('每 N 轮触发一次:候选经写入闸落库并留下审计', async ()
|
|
|
261
261
|
{ type: 'feedback', summary: '改稿必须附「改了什么、为什么」', detail: '来自用户纠正', scope: 'global', meaning:{kind:'claim'},basis: 'explicit', evidence: [{ messageId: 's1:event-2', quote: '改稿必须附「改了什么、为什么」' }] },
|
|
262
262
|
{ type: 'user', summary: '验收要量化到字数与标点', scope: 'global', meaning:{kind:'claim'},basis: 'explicit', evidence: [{ messageId: 's1:event-2', quote: '验收要量化到字数与标点' }] },
|
|
263
263
|
])
|
|
264
|
-
const { applyWithValidationFixture: apply } = await import('
|
|
264
|
+
const { applyWithValidationFixture: apply } = await import('../fixture-host.mjs')
|
|
265
265
|
const { ctx, handlers } = mount(box, { llm })
|
|
266
266
|
prepareMountedFixture()
|
|
267
267
|
apply(ctx, { autoCapture: { everyTurns: 3 } })
|
|
@@ -308,7 +308,7 @@ test('每 N 轮触发一次:候选经写入闸落库并留下审计', async ()
|
|
|
308
308
|
test('没有 llm 时不触发自动捕获,仍保留显式工具来源监听', async () => {
|
|
309
309
|
const box = sandbox()
|
|
310
310
|
try {
|
|
311
|
-
const { applyWithValidationFixture: apply } = await import('
|
|
311
|
+
const { applyWithValidationFixture: apply } = await import('../fixture-host.mjs')
|
|
312
312
|
const { ctx, handlers } = mount(box, {})
|
|
313
313
|
prepareMountedFixture()
|
|
314
314
|
apply(ctx, {})
|
|
@@ -321,7 +321,7 @@ test('没有 llm 时不触发自动捕获,仍保留显式工具来源监听',
|
|
|
321
321
|
test('配置 enabled:false 时关闭自动捕获', async () => {
|
|
322
322
|
const box = sandbox()
|
|
323
323
|
try {
|
|
324
|
-
const { applyWithValidationFixture: apply } = await import('
|
|
324
|
+
const { applyWithValidationFixture: apply } = await import('../fixture-host.mjs')
|
|
325
325
|
const { ctx, handlers } = mount(box, { llm: fakeLlm([]) })
|
|
326
326
|
prepareMountedFixture()
|
|
327
327
|
apply(ctx, { autoCapture: { enabled: false } })
|
|
@@ -334,7 +334,7 @@ test('配置 enabled:false 时关闭自动捕获', async () => {
|
|
|
334
334
|
test('模型输出坏 JSON 时只记审计,不写入、不抛错', async () => {
|
|
335
335
|
const box = sandbox()
|
|
336
336
|
try {
|
|
337
|
-
const { applyWithValidationFixture: apply } = await import('
|
|
337
|
+
const { applyWithValidationFixture: apply } = await import('../fixture-host.mjs')
|
|
338
338
|
const { ctx, handlers } = mount(box, { llm: fakeLlm('这不是 JSON') })
|
|
339
339
|
prepareMountedFixture()
|
|
340
340
|
apply(ctx, { autoCapture: { everyTurns: 1 } })
|
|
@@ -358,7 +358,7 @@ test('重复候选不会写第二次(走既有幂等闸)', async () => {
|
|
|
358
358
|
const message = promptMessages(request).find((m) => m.role === 'user')
|
|
359
359
|
return [{ type: 'user', summary: '回答要结论先行', scope: 'global', meaning:{kind:'claim'},basis: 'explicit', evidence: [{ messageId: message.id, quote: '回答一直都要结论先行' }] }]
|
|
360
360
|
})
|
|
361
|
-
const { applyWithValidationFixture: apply } = await import('
|
|
361
|
+
const { applyWithValidationFixture: apply } = await import('../fixture-host.mjs')
|
|
362
362
|
const { ctx, handlers } = mount(box, { llm })
|
|
363
363
|
prepareMountedFixture()
|
|
364
364
|
apply(ctx, { autoCapture: { everyTurns: 1 } })
|
|
@@ -440,7 +440,7 @@ test('信念:推断只成为候选,同一消息重复引用不增加独立
|
|
|
440
440
|
})
|
|
441
441
|
|
|
442
442
|
async function runCapture(box, payload, cwd = box.cwd, sessionId = 'evidence-session') {
|
|
443
|
-
const { applyWithValidationFixture: apply } = await import('
|
|
443
|
+
const { applyWithValidationFixture: apply } = await import('../fixture-host.mjs')
|
|
444
444
|
const { ctx, handlers } = mount(box, { llm: fakeLlm(payload) })
|
|
445
445
|
ctx.workingDirectory.get = () => cwd
|
|
446
446
|
prepareMountedFixture()
|
|
@@ -551,7 +551,7 @@ test('接线:模型推断持久化但不进入长期索引或注入上下文',
|
|
|
551
551
|
assert.doesNotMatch(readFileSync(join(project, '.audit.jsonl'), 'utf8'), /暖色/)
|
|
552
552
|
writeFixtureMemory({ type: 'user', dir: box.store, line: indexLine('user', '已确认的有效偏好使用中文回答'), scope: 'global' })
|
|
553
553
|
const { ctx, captured } = mount(box)
|
|
554
|
-
const { applyWithValidationFixture: apply } = await import('
|
|
554
|
+
const { applyWithValidationFixture: apply } = await import('../fixture-host.mjs')
|
|
555
555
|
prepareMountedFixture()
|
|
556
556
|
apply(ctx, { autoCapture: { enabled: false } })
|
|
557
557
|
const injected = captured.context.text({ agent: { session: {} } })
|
|
@@ -660,7 +660,7 @@ test('能力失败仅保存固定错误码,同一快照重开不把失败当
|
|
|
660
660
|
let prepares = 0
|
|
661
661
|
const llm = fakeLlm([])
|
|
662
662
|
llm.prepareCall = async () => { prepares++; throw Object.assign(new Error('synthetic private capability diagnostic'), { code: 'INVALID_MODEL_REASONING' }) }
|
|
663
|
-
const { applyWithValidationFixture: apply } = await import('
|
|
663
|
+
const { applyWithValidationFixture: apply } = await import('../fixture-host.mjs')
|
|
664
664
|
const run = async () => {
|
|
665
665
|
const { ctx, handlers } = mount(box, { llm })
|
|
666
666
|
prepareMountedFixture()
|
|
@@ -691,7 +691,7 @@ test('显式捕获路由优先于主会话模型,预算不被主请求头覆
|
|
|
691
691
|
return prepare(config, ...rest)
|
|
692
692
|
}
|
|
693
693
|
const { ctx, handlers } = mount(box, { llm })
|
|
694
|
-
const { applyWithValidationFixture: apply } = await import('
|
|
694
|
+
const { applyWithValidationFixture: apply } = await import('../fixture-host.mjs')
|
|
695
695
|
prepareMountedFixture()
|
|
696
696
|
apply(ctx, { autoCapture: { everyTurns: 1, provider: 'configured-capture', model: 'configured-model', maxTokens: 777 } })
|
|
697
697
|
const emit = ((session,event)=>{for(const [name,handler] of handlers)if(name==='session/event')handler(session,event)}), session = { id: 'explicit-route-session' }
|
|
@@ -706,7 +706,7 @@ test('显式捕获路由优先于主会话模型,预算不被主请求头覆
|
|
|
706
706
|
test('不完整或空白显式路由在注册能力前明确拒绝配置', async () => {
|
|
707
707
|
const box = sandbox()
|
|
708
708
|
try {
|
|
709
|
-
const { applyWithValidationFixture: apply } = await import('
|
|
709
|
+
const { applyWithValidationFixture: apply } = await import('../fixture-host.mjs')
|
|
710
710
|
for (const route of [{ provider: 'one' }, { model: 'one' }, { provider: ' ', model: 'one' }, { provider: 'one', model: 3 }]) {
|
|
711
711
|
const llm = fakeLlm([]), { ctx, captured, handlers } = mount(box, { llm })
|
|
712
712
|
assert.throws(() => apply(ctx, { autoCapture: { ...route } }), /memory-invalid-config/)
|
|
@@ -754,7 +754,7 @@ test('作业接线:损坏账本暂停自动捕获,六工具与主注入保
|
|
|
754
754
|
mkdirSync(box.store, { recursive: true }); writeFileSync(join(box.store, '.capture-jobs.json'), '{bad')
|
|
755
755
|
const llm = fakeLlm([])
|
|
756
756
|
const { ctx, handlers, captured } = mount(box, { llm })
|
|
757
|
-
const { applyWithValidationFixture: apply } = await import('
|
|
757
|
+
const { applyWithValidationFixture: apply } = await import('../fixture-host.mjs'); prepareMountedFixture(); apply(ctx)
|
|
758
758
|
assert.equal(handlers.filter(([name]) => name === 'session/event').length, 1); assert.equal(llm.calls, 0)
|
|
759
759
|
assert.equal(captured.tools.length, 6); assert.equal(typeof captured.context.text, 'function')
|
|
760
760
|
const marker = JSON.parse(readFileSync(join(box.store, '.loaded.json')))
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {writeFixtureMemory} from '
|
|
1
|
+
import {writeFixtureMemory} from '../fixture-host.mjs'
|
|
2
2
|
import {realpathSync} from 'node:fs'
|
|
3
|
-
import {applyWithOwnerFixture as apply} from '
|
|
3
|
+
import {applyWithOwnerFixture as apply} from '../fixture-host.mjs'
|
|
4
4
|
/**
|
|
5
5
|
* 记忆系统 × 宿主对话压缩(compaction)的兼容性测试。
|
|
6
|
-
* 跑法:node --test ~/.dsh/plugins/memory/test-compaction-compat.mjs
|
|
6
|
+
* 跑法:node --test ~/.dsh/plugins/memory/test/test-compaction-compat.mjs
|
|
7
7
|
*
|
|
8
8
|
* 背景:DSH 自带对话压缩(dsh-compaction-basic + /compact + 工具输出修剪),
|
|
9
9
|
* 会在 token 压力上升时把较早历史换成摘要。记忆系统必须和它互不干扰。
|
|
@@ -30,8 +30,8 @@ import {
|
|
|
30
30
|
statusOf,
|
|
31
31
|
storeDirFor,
|
|
32
32
|
writeMemory,
|
|
33
|
-
} from '
|
|
34
|
-
import { entryId } from '
|
|
33
|
+
} from '../index.mjs'
|
|
34
|
+
import { entryId } from '../security.mjs'
|
|
35
35
|
|
|
36
36
|
/** 宿主 tool-result-pruner 的默认阈值,见 dsh-base/cordis.patch.yml。 */
|
|
37
37
|
const HOST_TOOL_RESULT_LIMIT = 8192
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {realpathSync} from 'node:fs'
|
|
2
|
-
import {applyWithOwnerFixture as apply} from '
|
|
2
|
+
import {applyWithOwnerFixture as apply} from '../fixture-host.mjs'
|
|
3
3
|
/**
|
|
4
|
-
* 宿主 API 兼容与降级。跑法:node --test ~/.dsh/plugins/memory/test-compat.mjs
|
|
4
|
+
* 宿主 API 兼容与降级。跑法:node --test ~/.dsh/plugins/memory/test/test-compat.mjs
|
|
5
5
|
*
|
|
6
6
|
* 目的:宿主升级后能区分「插件坏了」和「宿主变了」。
|
|
7
7
|
* 没有 systemPrompt.context 就没法注入 → 立刻抛错(响);
|
|
@@ -14,7 +14,7 @@ import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs'
|
|
|
14
14
|
import { join } from 'node:path'
|
|
15
15
|
import { tmpdir } from 'node:os'
|
|
16
16
|
|
|
17
|
-
import { selfCheck, storeDirFor } from '
|
|
17
|
+
import { selfCheck, storeDirFor } from '../index.mjs'
|
|
18
18
|
|
|
19
19
|
function sandbox() {
|
|
20
20
|
const root = realpathSync(mkdtempSync(join(tmpdir(), 'dsh-mem-compat-')))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {realpathSync} from 'node:fs'
|
|
2
2
|
/**
|
|
3
|
-
* 并发与故障注入。跑法:node --test ~/.dsh/plugins/memory/test-concurrency.mjs
|
|
3
|
+
* 并发与故障注入。跑法:node --test ~/.dsh/plugins/memory/test/test-concurrency.mjs
|
|
4
4
|
*
|
|
5
5
|
* 这两类最容易被本地测试放过:
|
|
6
6
|
* · 进程内锁测过了,跨进程仍可能丢更新
|
|
@@ -24,9 +24,9 @@ import { join, dirname } from 'node:path'
|
|
|
24
24
|
import { fileURLToPath } from 'node:url'
|
|
25
25
|
import { tmpdir } from 'node:os'
|
|
26
26
|
|
|
27
|
-
import { apply, storeDirFor, writeMemory } from '
|
|
27
|
+
import { apply, storeDirFor, writeMemory } from '../index.mjs'
|
|
28
28
|
|
|
29
|
-
const HERE = dirname(fileURLToPath(import.meta.url))
|
|
29
|
+
const HERE = dirname(dirname(fileURLToPath(import.meta.url)))
|
|
30
30
|
const WORKER = join(HERE, 'worker-write.mjs')
|
|
31
31
|
|
|
32
32
|
function sandbox() {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 冲突检测(同一事实、不同取值)。跑法:node --test ~/.dsh/plugins/memory/test-conflict.mjs
|
|
2
|
+
* 冲突检测(同一事实、不同取值)。跑法:node --test ~/.dsh/plugins/memory/test/test-conflict.mjs
|
|
3
3
|
*
|
|
4
4
|
* 规则只有一条:**非取值部分完全相同,且恰好一个取值不同** → 疑似冲突。
|
|
5
5
|
* 它产生「警告」而不是「拒绝」,因为文本结构无法区分「改口」和「枚举」:
|
|
@@ -14,7 +14,7 @@ import { mkdtempSync, mkdirSync, readFileSync, rmSync } from 'node:fs'
|
|
|
14
14
|
import { join } from 'node:path'
|
|
15
15
|
import { tmpdir } from 'node:os'
|
|
16
16
|
|
|
17
|
-
import { conflictSignature, findConflict, storeDirFor, writeMemory } from '
|
|
17
|
+
import { conflictSignature, findConflict, storeDirFor, writeMemory } from '../index.mjs'
|
|
18
18
|
|
|
19
19
|
function sandbox() {
|
|
20
20
|
const root = mkdtempSync(join(tmpdir(), 'dsh-mem-conflict-'))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {test} from 'node:test'
|
|
2
2
|
import assert from 'node:assert/strict'
|
|
3
|
-
import {memoryContextProjection,reconcileMemoryContext,assertCurrentMemoryRequest,setupMemoryHistory,HISTORY_PROJECTION_KEY} from '
|
|
3
|
+
import {memoryContextProjection,reconcileMemoryContext,assertCurrentMemoryRequest,setupMemoryHistory,HISTORY_PROJECTION_KEY} from '../context-history.mjs'
|
|
4
4
|
const header='Current runtime context. This snapshot supersedes earlier runtime-context snapshots.'
|
|
5
5
|
const context=(id,text,extra=[])=>{const sections=[{name:'memory:index',text},...extra];return{id,role:'user',source:{kind:'runtime-context',form:'snapshot',sections},content:[{type:'text',text:header+'\n\n'+sections.map(s=>s.text).join('\n\n')}]}}
|
|
6
6
|
const freeze=value=>{if(value&&typeof value==='object'){Object.values(value).forEach(freeze);Object.freeze(value)}return value}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import test from 'node:test'
|
|
2
2
|
import assert from 'node:assert/strict'
|
|
3
3
|
import {DatabaseSync} from 'node:sqlite'
|
|
4
|
-
import {CONTROL_APPLICATION_ID,CONTROL_SCHEMA_SQL,assertControlSchema,readControlStateRows} from '
|
|
4
|
+
import {CONTROL_APPLICATION_ID,CONTROL_SCHEMA_SQL,assertControlSchema,readControlStateRows} from '../control-schema.mjs'
|
|
5
5
|
const setup=db=>db.exec(CONTROL_SCHEMA_SQL+'; PRAGMA application_id='+CONTROL_APPLICATION_ID+'; PRAGMA user_version=1;')
|
|
6
6
|
const box=run=>{const db=new DatabaseSync(':memory:');try{setup(db);run(db)}finally{db.close()}}
|
|
7
7
|
test('one live connection reads newly added and removed schema rows rather than a cached approval',()=>box(db=>{
|