dsh-context-compression-improved 0.4.0 → 0.5.1

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 (36) hide show
  1. package/CHANGELOG.ja.md +34 -0
  2. package/CHANGELOG.ko.md +34 -0
  3. package/CHANGELOG.md +38 -0
  4. package/CHANGELOG.zh.md +30 -0
  5. package/docs/installation.md +25 -1
  6. package/docs/installation.zh.md +24 -1
  7. package/package.json +1 -1
  8. package/packages/selector/lib/{review-registry.js → advisor-state.js} +105 -4
  9. package/packages/selector/lib/client.d.ts +7 -0
  10. package/packages/selector/lib/client.js +32 -2
  11. package/packages/selector/lib/index.d.ts +7 -0
  12. package/packages/selector/lib/index.js +150 -3
  13. package/packages/selector/lib/pruner.d.ts +82 -0
  14. package/packages/selector/lib/pruner.js +545 -11
  15. package/packages/selector/src/client/preset-options.ts +2 -0
  16. package/packages/selector/src/index.ts +108 -0
  17. package/packages/selector/src/preset-overlay.ts +60 -1
  18. package/packages/selector/src/profiles.ts +48 -0
  19. package/packages/selector/src/pruner/state.ts +3 -0
  20. package/packages/selector/src/pruner.ts +113 -0
  21. package/packages/selector/src/runtime/audit.ts +22 -0
  22. package/packages/selector/src/runtime/config.ts +58 -0
  23. package/packages/selector/src/runtime/tokenpilot/advisor-prompt.ts +188 -0
  24. package/packages/selector/src/runtime/tokenpilot/advisor-state.ts +133 -0
  25. package/packages/selector/src/runtime/tokenpilot/advisor.ts +419 -0
  26. package/packages/selector/src/runtime/tokenpilot/sidechannel.ts +24 -9
  27. package/packages/selector/src/runtime/types.ts +21 -0
  28. package/packages/selector/tests/advisor-report.host.spec.ts +223 -0
  29. package/packages/selector/tests/built/client-artifact.spec.ts +9 -5
  30. package/packages/selector/tests/runtime/advisor-invariant.spec.ts +272 -0
  31. package/packages/selector/tests/runtime/advisor.spec.ts +226 -0
  32. package/packages/selector/tests/runtime/audit.spec.ts +44 -0
  33. package/packages/selector/tests/runtime/tokenpilot/profile-baseline.spec.ts +12 -0
  34. package/packages/selector/tests/standing-generation.host.spec.ts +54 -5
  35. package/scripts/packed-components-smoke.mjs +30 -8
  36. package/scripts/packed-install-e2e.mjs +69 -15
@@ -0,0 +1,226 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import type { SessionEvent } from '@deepseek-ai/dsh-session'
3
+ import {
4
+ collectTaskSemantics,
5
+ prefixDecay,
6
+ selectScoringCandidates,
7
+ type AdvisorCandidate,
8
+ } from '../../src/runtime/tokenpilot/advisor.ts'
9
+ import {
10
+ ADVISOR_RECERTIFIED_LIMIT,
11
+ ADVISOR_SCORES_LIMIT,
12
+ getAdvisorState,
13
+ invalidateOnTaskChange,
14
+ recordRecertified,
15
+ recordScore,
16
+ } from '../../src/runtime/tokenpilot/advisor-state.ts'
17
+ import {
18
+ buildAdvisorScoringUserPrompt,
19
+ parseAdvisorScores,
20
+ parseAdvisorSummary,
21
+ } from '../../src/runtime/tokenpilot/advisor-prompt.ts'
22
+
23
+ function todoWriteEvent(data: unknown): SessionEvent {
24
+ return { type: 'todo/write', seq: 1, time: 0, data } as unknown as SessionEvent
25
+ }
26
+
27
+ function userMessageEvent(text: string): SessionEvent {
28
+ return {
29
+ type: 'user/message',
30
+ seq: 2,
31
+ time: 0,
32
+ data: { content: [{ type: 'text', text }] },
33
+ } as unknown as SessionEvent
34
+ }
35
+
36
+ describe('collectTaskSemantics (K5)', () => {
37
+ it('parses a structured todo/write payload', () => {
38
+ const semantics = collectTaskSemantics([
39
+ todoWriteEvent({ todos: [{ content: 'migrate gates', status: 'in_progress' }, 'write tests'] }),
40
+ ])
41
+ expect(semantics).toBeDefined()
42
+ expect(semantics?.source).toBe('todos')
43
+ expect(semantics?.taskText).toContain('migrate gates')
44
+ expect(semantics?.taskText).toContain('write tests')
45
+ // Same content ⇒ same version token (deterministic).
46
+ const again = collectTaskSemantics([todoWriteEvent({ todos: [{ content: 'migrate gates', status: 'in_progress' }, 'write tests'] })])
47
+ expect(again?.todoVersion).toBe(semantics?.todoVersion)
48
+ })
49
+
50
+ it('degrades a malformed todo payload to its raw JSON string', () => {
51
+ const semantics = collectTaskSemantics([todoWriteEvent({ todos: 'not-an-array' })])
52
+ expect(semantics).toBeDefined()
53
+ expect(semantics?.source).toBe('raw-todo')
54
+ expect(semantics?.taskText).toContain('not-an-array')
55
+ })
56
+
57
+ it('falls back to recent user/message text when no todo/write exists', () => {
58
+ const semantics = collectTaskSemantics([
59
+ userMessageEvent('earlier request'),
60
+ userMessageEvent('please refactor the pruner gates'),
61
+ ])
62
+ expect(semantics).toBeDefined()
63
+ expect(semantics?.source).toBe('messages')
64
+ expect(semantics?.taskText).toContain('refactor the pruner gates')
65
+ })
66
+
67
+ it('picks the most recent todo/write event', () => {
68
+ const semantics = collectTaskSemantics([
69
+ todoWriteEvent({ todos: ['old task'] }),
70
+ userMessageEvent('something else'),
71
+ todoWriteEvent({ todos: ['new task'] }),
72
+ ])
73
+ expect(semantics?.taskText).toBe('new task')
74
+ })
75
+
76
+ it('returns undefined for an empty log', () => {
77
+ expect(collectTaskSemantics([])).toBeUndefined()
78
+ })
79
+ })
80
+
81
+ describe('prefixDecay (K6)', () => {
82
+ const candidates: AdvisorCandidate[] = [
83
+ { seq: 1, characterPressure: 3_000, preview: 'a' },
84
+ { seq: 2, characterPressure: 1_000, preview: 'b' },
85
+ ]
86
+ // prefixDecay takes the weight face only; previews are scoring-prompt inputs.
87
+ const weights = candidates.map(({ seq, characterPressure }) => ({ seq, characterPressure }))
88
+
89
+ it('is deterministic and weights by character pressure', () => {
90
+ const scores = new Map([[1, { score: 0 }], [2, { score: 1 }]])
91
+ const first = prefixDecay(weights, scores)
92
+ const second = prefixDecay(weights, scores)
93
+ expect(first).toEqual(second)
94
+ // weight 3000×0 + 1000×1 over 4000 ⇒ relevance 0.25 ⇒ decay 0.75.
95
+ expect(first.decay).toBeCloseTo(0.75, 12)
96
+ expect(first.weightedChars).toBe(4_000)
97
+ })
98
+
99
+ it('counts unscored candidates as neutral 0.5', () => {
100
+ const decay = prefixDecay(weights, new Map())
101
+ expect(decay.decay).toBeCloseTo(0.5, 12)
102
+ })
103
+
104
+ it('is 0 with no candidates and ignores zero-pressure candidates', () => {
105
+ expect(prefixDecay([], new Map()).decay).toBe(0)
106
+ expect(prefixDecay([{ seq: 9, characterPressure: 0 }], new Map()).decay).toBe(0)
107
+ })
108
+ })
109
+
110
+ describe('selectScoringCandidates', () => {
111
+ const candidates: AdvisorCandidate[] = [
112
+ { seq: 1, characterPressure: 8_000, preview: 'auth module login handling' },
113
+ { seq: 2, characterPressure: 2_000, preview: 'tiny fragment' },
114
+ { seq: 3, characterPressure: 9_000, preview: 'login auth token refresh' },
115
+ { seq: 4, characterPressure: 12_000, preview: 'unrelated weather report' },
116
+ ]
117
+
118
+ it('applies the watermark, the character floor, and the sample limit', () => {
119
+ const state = { watermarkSeq: 1 }
120
+ const picked = selectScoringCandidates(candidates, state, {
121
+ taskKeywords: new Set(['login', 'auth', 'token']),
122
+ minChars: 4_000,
123
+ sampleLimit: 1,
124
+ taskChanged: false,
125
+ })
126
+ // seq 2 below floor, seq 1 at/below watermark; overlap ranks seq 3 first.
127
+ expect(picked.map(item => item.seq)).toEqual([3])
128
+ })
129
+
130
+ it('ignores the watermark when the task semantics changed', () => {
131
+ const picked = selectScoringCandidates(candidates, { watermarkSeq: 4 }, {
132
+ taskKeywords: new Set(['login']),
133
+ minChars: 4_000,
134
+ sampleLimit: 16,
135
+ taskChanged: true,
136
+ })
137
+ // Overlap ties (seq 1 and 3 both match "login") break by character pressure.
138
+ expect(picked.map(item => item.seq)).toEqual([3, 1, 4])
139
+ })
140
+ })
141
+
142
+ describe('advisor-state bounds (K8)', () => {
143
+ it('evicts the oldest score beyond the LRU limit', () => {
144
+ const session = {} as Parameters<typeof getAdvisorState>[0]
145
+ const state = getAdvisorState(session)
146
+ for (let seq = 0; seq < ADVISOR_SCORES_LIMIT + 10; seq += 1) {
147
+ recordScore(state, seq, { score: 0.5, turn: seq })
148
+ }
149
+ expect(state.scores.size).toBe(ADVISOR_SCORES_LIMIT)
150
+ expect(state.scores.has(0)).toBe(false)
151
+ expect(state.scores.has(ADVISOR_SCORES_LIMIT + 9)).toBe(true)
152
+ // Re-touching a seq moves it to the newest position.
153
+ recordScore(state, 10, { score: 0.9, turn: 999 })
154
+ recordScore(state, ADVISOR_SCORES_LIMIT + 10, { score: 0.1, turn: 1_000 })
155
+ expect(state.scores.has(10)).toBe(true)
156
+ expect(state.scores.size).toBe(ADVISOR_SCORES_LIMIT)
157
+ })
158
+
159
+ it('bounds recertified marks the same way', () => {
160
+ const session = {} as Parameters<typeof getAdvisorState>[0]
161
+ const state = getAdvisorState(session)
162
+ for (let seq = 0; seq < ADVISOR_RECERTIFIED_LIMIT + 5; seq += 1) {
163
+ recordRecertified(state, seq, seq)
164
+ }
165
+ expect(state.recertified.size).toBe(ADVISOR_RECERTIFIED_LIMIT)
166
+ expect(state.recertified.has(0)).toBe(false)
167
+ })
168
+
169
+ it('invalidates the summary when the task version changes', () => {
170
+ const session = {} as Parameters<typeof getAdvisorState>[0]
171
+ const state = getAdvisorState(session)
172
+ state.summary = { overallTask: 'x', activeSubtasks: [], keywords: [], todoVersion: 'aaa', turn: 1 }
173
+ state.lastSummaryTurn = 1
174
+ expect(invalidateOnTaskChange(state, 'bbb')).toBe(true)
175
+ expect(state.summary).toBeUndefined()
176
+ expect(state.lastSummaryTurn).toBe(-1)
177
+ expect(invalidateOnTaskChange(state, 'bbb')).toBe(false)
178
+ })
179
+ })
180
+
181
+ describe('advisor prompts (K7)', () => {
182
+ it('parses a well-formed summary answer', () => {
183
+ const parsed = parseAdvisorSummary(
184
+ 'Sure! {"overallTask":"migrate gates","activeSubtasks":["port fresh gate"],"keywords":["gates","pruner"]}',
185
+ )
186
+ expect(parsed?.overallTask).toBe('migrate gates')
187
+ expect(parsed?.keywords).toEqual(['gates', 'pruner'])
188
+ })
189
+
190
+ it('fails open on a malformed summary answer', () => {
191
+ expect(parseAdvisorSummary(undefined)).toBeUndefined()
192
+ expect(parseAdvisorSummary('')).toBeUndefined()
193
+ expect(parseAdvisorSummary('not json at all')).toBeUndefined()
194
+ expect(parseAdvisorSummary('{"overallTask":""}')).toBeUndefined()
195
+ expect(parseAdvisorSummary('{"overallTask":"x"}')).toBeUndefined()
196
+ })
197
+
198
+ it('parses JSON-lines scoring answers and drops invalid rows', () => {
199
+ const parsed = parseAdvisorScores(
200
+ '{"seq":1,"score":0.9,"reason":"current task"}\n'
201
+ + 'noise line {"seq":2,"score":1.5}\n'
202
+ + '{"seq":99,"score":0.5}\n'
203
+ + '{"seq":3,"score":0.1}\n',
204
+ new Set([1, 2, 3]),
205
+ )
206
+ expect(parsed?.size).toBe(2)
207
+ expect(parsed?.get(1)?.score).toBe(0.9)
208
+ expect(parsed?.get(3)?.score).toBe(0.1)
209
+ expect(parsed?.has(2)).toBe(false)
210
+ expect(parsed?.has(99)).toBe(false)
211
+ })
212
+
213
+ it('fails open on empty or all-garbage scoring answers', () => {
214
+ expect(parseAdvisorScores(undefined, new Set([1]))).toBeUndefined()
215
+ expect(parseAdvisorScores('', new Set([1]))).toBeUndefined()
216
+ expect(parseAdvisorScores('garbage only', new Set([1]))).toBeUndefined()
217
+ })
218
+
219
+ it('builds a scoring prompt that keeps previews one-per-line', () => {
220
+ const prompt = buildAdvisorScoringUserPrompt('task text', ['sub'], [
221
+ { seq: 7, preview: 'some\npreview' },
222
+ ])
223
+ expect(prompt).toContain('task: task text')
224
+ expect(prompt).toContain('seq=7 | some preview')
225
+ })
226
+ })
@@ -213,4 +213,48 @@ describe('context-compression audit records', () => {
213
213
  ) as CompressionAuditRecord
214
214
  expect(parsed).toEqual(record)
215
215
  })
216
+
217
+ it('keeps advisor-outcome records free of prompts, keys, and content', () => {
218
+ const record: CompressionAuditRecord = {
219
+ schemaVersion: 1,
220
+ kind: 'advisor-outcome',
221
+ sessionId: 'advisor-session',
222
+ phase: 'decay',
223
+ ok: true,
224
+ sampledCount: 16,
225
+ decay: 0.42,
226
+ weightedChars: 57_688,
227
+ turnIndex: 12,
228
+ latencyMs: 0,
229
+ }
230
+
231
+ const line = formatCompressionAudit(record)
232
+ const parsed = JSON.parse(line.slice(COMPRESSION_AUDIT_PREFIX.length)) as CompressionAuditRecord
233
+ expect(parsed).toEqual(record)
234
+ // Only enums and numbers: never the todo snapshot, assistant text, prompts, or keys.
235
+ expect(line).not.toContain('prompt')
236
+ expect(line).not.toContain('"content"')
237
+ expect(line).not.toContain('"text"')
238
+ expect(line).not.toContain('apiKey')
239
+ expect(line).not.toContain('todo')
240
+ })
241
+
242
+ it('carries failure reason codes and the LLM channel on advisor-outcome records', () => {
243
+ const record: CompressionAuditRecord = {
244
+ schemaVersion: 1,
245
+ kind: 'advisor-outcome',
246
+ sessionId: 'advisor-session',
247
+ phase: 'scoring',
248
+ channel: 'direct',
249
+ ok: false,
250
+ sampledCount: 0,
251
+ turnIndex: 3,
252
+ reason: 'no-direct-endpoint',
253
+ latencyMs: 4,
254
+ }
255
+ const parsed = JSON.parse(
256
+ formatCompressionAudit(record).slice(COMPRESSION_AUDIT_PREFIX.length),
257
+ ) as CompressionAuditRecord
258
+ expect(parsed).toEqual(record)
259
+ })
216
260
  })
@@ -66,6 +66,18 @@ describe('tokenpilot-inspired preset', () => {
66
66
  reviewTimeoutTurns: 6,
67
67
  cacheHitDiscountAlpha: 0.1,
68
68
  reviewHighImpactTokens: 4000,
69
+ // Advisory advisor ships off with its documented defaults. The
70
+ // resolved matrix intentionally gains this group so it follows the
71
+ // estimator's resolved-consumption pattern; see
72
+ // .agents/plans/ctx-relevance-advisor/spec.md decision record.
73
+ advisor: {
74
+ mode: '',
75
+ timeoutMs: 8000,
76
+ refreshTurns: 8,
77
+ scoreThreshold: 0.35,
78
+ sampleLimit: 16,
79
+ minTokens: 250,
80
+ },
69
81
  })
70
82
  // Other profiles never carry the capability matrix.
71
83
  expect(resolvePolicy(config, 'balanced').presetOptions).toBeUndefined()
@@ -101,6 +101,37 @@ async function overlayFiles(sourceMarker: string): Promise<{ path: string, rende
101
101
  return files
102
102
  }
103
103
 
104
+ /**
105
+ * The mtime this platform actually persists for one given stamp.
106
+ *
107
+ * `node:fs` `utimes` writes the seconds component through a 32-bit field on
108
+ * Windows: on node v22 win32, `utimes(file, 4677832452 s)` reads back as
109
+ * `382865156 s` — exactly `seconds >>> 0` — while sub-second precision
110
+ * survives. The standing stamp deliberately parks identities around year 2162
111
+ * (`STANDING_MTIME_EPOCH_SECONDS + 0xffffffff`, src/preset-overlay.ts), so its
112
+ * seconds component sits past that 2^32-second horizon (max year 2106) and the
113
+ * persisted literal differs on that platform. Deterministically so: the wrap is
114
+ * a pure function of the identity stamp, which is why identity → mtime stays
115
+ * stable and the collision escalation still absorbs any merge.
116
+ *
117
+ * Instead of hardcoding the platform or weakening the assertion, calibrate:
118
+ * ask the filesystem what it stores for exactly this stamp, then require the
119
+ * overlay to persist that same value. On a platform with a full-width seconds
120
+ * field this collapses to the stamp itself, so the assertion stays strict.
121
+ */
122
+ async function platformPersistedMs(stampMs: number): Promise<number> {
123
+ const directory = await mkdtemp(join(tmpdir(), 'dsh-selector-mtime-probe-'))
124
+ const probe = join(directory, 'probe')
125
+ try {
126
+ await writeFile(probe, 'probe')
127
+ const stamp = new Date(stampMs)
128
+ await utimes(probe, stamp, stamp)
129
+ return (await stat(probe)).mtimeMs
130
+ } finally {
131
+ await rm(directory, { recursive: true, force: true })
132
+ }
133
+ }
134
+
104
135
  let sourceRoot: string | undefined
105
136
 
106
137
  /** Rewrite the source preset to an equal-length marker module. */
@@ -329,8 +360,17 @@ describe('real AgentPresets standing generations with the overlay threshold', ()
329
360
  expect(firstStat.size).toBe(secondStat.size)
330
361
  expect(firstStat.mtimeMs % 1000).toBe(0)
331
362
  expect(secondStat.mtimeMs % 1000).toBe(0)
332
- expect(firstStat.mtimeMs).toBe(Math.floor(standingStampMsAtWindow(collision.firstIdentity, 0) / 1000) * 1000)
333
- expect(secondStat.mtimeMs).toBe(Math.floor(standingStampMsAtWindow(collision.secondIdentity, 1) / 1000) * 1000)
363
+ // The coarse metadata surface floors both stamps to a whole second, and the
364
+ // platform may additionally wrap the seconds field (platformPersistedMs),
365
+ // so require exactly what the filesystem stores for those identity stamps.
366
+ const firstStamp = await platformPersistedMs(
367
+ Math.floor(standingStampMsAtWindow(collision.firstIdentity, 0) / 1000) * 1000,
368
+ )
369
+ const secondStamp = await platformPersistedMs(
370
+ Math.floor(standingStampMsAtWindow(collision.secondIdentity, 1) / 1000) * 1000,
371
+ )
372
+ expect(Math.floor(firstStat.mtimeMs / 1000)).toBe(Math.floor(firstStamp / 1000))
373
+ expect(Math.floor(secondStat.mtimeMs / 1000)).toBe(Math.floor(secondStamp / 1000))
334
374
 
335
375
  await installation.dispose()
336
376
  })
@@ -382,11 +422,19 @@ describe('real AgentPresets standing generations with the overlay threshold', ()
382
422
  expect(files.length).toBeGreaterThanOrEqual(3)
383
423
  const buckets = await Promise.all(files.map(async file => Math.floor((await stat(file.path)).mtimeMs / 1000)))
384
424
  expect(new Set(buckets).size).toBe(buckets.length)
385
- // No staging leftovers survive a publish.
425
+ // No staging leftovers survive a publish. A spec file running in parallel
426
+ // disposes its own store while this scan runs, so a vanished directory means
427
+ // "no leftovers to find" rather than a failure (mirrors overlayFiles).
386
428
  const storeDirs = await Promise.all(
387
429
  (await readdir(tmpdir(), { withFileTypes: true }))
388
430
  .filter(entry => entry.isDirectory() && entry.name.startsWith('dsh-context-compression-presets-'))
389
- .map(async entry => readdir(join(tmpdir(), entry.name))),
431
+ .map(async entry => {
432
+ try {
433
+ return await readdir(join(tmpdir(), entry.name))
434
+ } catch {
435
+ return []
436
+ }
437
+ }),
390
438
  )
391
439
  expect(storeDirs.flat().some(name => name.endsWith('.tmp'))).toBe(false)
392
440
 
@@ -451,8 +499,9 @@ describe('real AgentPresets standing generations with the overlay threshold', ()
451
499
  ? /standard-([0-9a-f]+)\.agent\.cordis\.yml$/u.exec(files[0]!.path)?.[1]
452
500
  : undefined
453
501
  expect(identity).toBeDefined()
502
+ const persistedStamp = await platformPersistedMs(standingStampMs(identity as string))
454
503
  expect(Math.floor(details.mtimeMs / 1000))
455
- .toBe(Math.floor(standingStampMs(identity as string) / 1000))
504
+ .toBe(Math.floor(persistedStamp / 1000))
456
505
  const observedSubSecond = details.mtimeMs % 1000
457
506
  const expectedSubSecond = standingStampMs(identity as string) % 1000
458
507
  expect(observedSubSecond === 0 || Math.abs(observedSubSecond - expectedSubSecond) < 1).toBe(true)
@@ -561,13 +561,25 @@ try {
561
561
  && JSON.stringify(originalImage).includes('packed-image-800x600')
562
562
  && JSON.stringify(originalImage).includes('"type":"image"'),
563
563
  'packed vision image-bearing result is no longer intact on the surface')
564
+ // The character basis retired the `exact-tokenizer-unavailable` skip: a
565
+ // History planning skip can no longer refuse for a missing exact count (see
566
+ // the HistoryPlanOutcome note in src/pruner/types.ts). A vision session
567
+ // without the exact tokenizer is therefore judged on characters and its
568
+ // skip carries a real reason (observed: fresh/aggregate `at-or-below-
569
+ // trigger`, history `below-profile-trigger`) — assert that observable
570
+ // contract instead of the removed literal.
564
571
  for (const component of ['fresh', 'history']) {
565
- assert(visionAudit.some(record => record.kind === 'component-evaluation'
566
- && record.sessionId === String(visionImage.id)
567
- && record.component === component
568
- && record.status === 'skipped'
569
- && record.reason === 'exact-tokenizer-unavailable'),
570
- `packed vision image session lacks the ${component} exact-tokenizer-unavailable audit`)
572
+ const record = visionAudit.find(entry => entry.kind === 'component-evaluation'
573
+ && entry.sessionId === String(visionImage.id)
574
+ && entry.component === component)
575
+ assert(record !== undefined,
576
+ `packed vision image session lacks the ${component} component-evaluation audit`)
577
+ assert(record.status === 'skipped',
578
+ `packed vision ${component} status is ${String(record.status)}, expected skipped`)
579
+ assert(record.measurementKind === 'characters',
580
+ `packed vision ${component} was decided on ${String(record.measurementKind)}, expected characters`)
581
+ assert(typeof record.reason === 'string' && record.reason.length > 0,
582
+ `packed vision ${component} skip carries no diagnostic reason`)
571
583
  }
572
584
 
573
585
  console.info(`PACKED_VISION_E2E ${JSON.stringify({
@@ -651,9 +663,19 @@ try {
651
663
  && firstIndex('history') < firstIndex('tail-trim'),
652
664
  'installed component rewrite order is wrong')
653
665
  const pipelineRewrites = rewrites.filter(record => record.sessionId === String(session.id))
666
+ // The character basis decides on Unicode code points, so a committed rewrite
667
+ // may report an unchanged token figure while still reclaiming characters
668
+ // (observed once here: history `historical-tool-result-aging` landed at
669
+ // 129 -> 129 with tokensRemoved 0). The rewrite audit carries no character
670
+ // field yet, so assert what the basis can actually guarantee: integer
671
+ // accounting, a consistent removal figure, and never an increase. A strict
672
+ // token decrease would re-encode the retired exact-token authority — see the
673
+ // measurementBasis note in src/runtime/audit.ts.
654
674
  assert(pipelineRewrites.every(record => Number.isSafeInteger(record.tokensBefore)
655
- && Number.isSafeInteger(record.tokensAfter) && record.tokensBefore > record.tokensAfter),
656
- 'installed rewrite lacks exact decreasing token evidence')
675
+ && Number.isSafeInteger(record.tokensAfter)
676
+ && record.tokensBefore >= record.tokensAfter
677
+ && record.tokensRemoved === record.tokensBefore - record.tokensAfter),
678
+ 'installed rewrite token accounting is inconsistent or increasing')
657
679
  assert(audit.some(record => record.kind === 'native-auto-compact'
658
680
  && record.sessionId === String(session.id)),
659
681
  'installed Runtime did not audit official Native summary')
@@ -2,7 +2,7 @@ import { createHash } from 'node:crypto'
2
2
  import { spawn } from 'node:child_process'
3
3
  import { createReadStream } from 'node:fs'
4
4
  import { copyFile, mkdir, mkdtemp, readFile, readdir, realpath, rm, stat, writeFile } from 'node:fs/promises'
5
- import { readFileSync } from 'node:fs'
5
+ import { readFileSync, readdirSync } from 'node:fs'
6
6
  import { createServer } from 'node:http'
7
7
  import { createRequire } from 'node:module'
8
8
  import { tmpdir } from 'node:os'
@@ -27,8 +27,17 @@ const officialHostPackages = Object.entries(rootManifest.devDependencies)
27
27
  .filter(([name]) => name.startsWith('@deepseek-ai/'))
28
28
  .map(([name, version]) => `${name}@${version}`)
29
29
 
30
+ // Only the package-manager shims need a shell on Windows; every real executable
31
+ // must be spawned directly, because cmd.exe re-parses arguments: `^` is its
32
+ // escape character, so `HEAD^{tree}` reached git as `HEAD{tree}`, and a
33
+ // multi-line `node -e` settings script was cut at its first newline into
34
+ // `[eval]:1 const` / `SyntaxError: Unexpected end of input`. Both killed the
35
+ // official-clone leg for reasons unrelated to what it asserts.
36
+ const SHELL_COMMANDS = new Set(['npm', 'npm.cmd', 'pnpm', 'pnpm.cmd'])
37
+ const usesShell = (command) => process.platform === 'win32' && SHELL_COMMANDS.has(command)
38
+
30
39
  const run = (command, args, options = {}) => new Promise((resolve, reject) => {
31
- const child = spawn(command, args, { stdio: 'inherit', shell: process.platform === 'win32', ...options })
40
+ const child = spawn(command, args, { stdio: 'inherit', shell: usesShell(command), ...options })
32
41
  child.once('error', reject)
33
42
  child.once('exit', (code, signal) => {
34
43
  if (code === 0) resolve()
@@ -37,7 +46,7 @@ const run = (command, args, options = {}) => new Promise((resolve, reject) => {
37
46
  })
38
47
 
39
48
  const capture = (command, args, options = {}) => new Promise((resolve, reject) => {
40
- const child = spawn(command, args, { shell: process.platform === 'win32', ...options, stdio: ['ignore', 'pipe', 'pipe'] })
49
+ const child = spawn(command, args, { shell: usesShell(command), ...options, stdio: ['ignore', 'pipe', 'pipe'] })
41
50
  let stdout = ''
42
51
  let stderr = ''
43
52
  child.stdout.setEncoding('utf8')
@@ -56,7 +65,7 @@ const capture = (command, args, options = {}) => new Promise((resolve, reject) =
56
65
  })
57
66
 
58
67
  const captureOutcome = (command, args, options = {}) => new Promise((resolve, reject) => {
59
- const child = spawn(command, args, { shell: process.platform === 'win32', ...options, stdio: ['ignore', 'pipe', 'pipe'] })
68
+ const child = spawn(command, args, { shell: usesShell(command), ...options, stdio: ['ignore', 'pipe', 'pipe'] })
60
69
  let stdout = ''
61
70
  let stderr = ''
62
71
  child.stdout.setEncoding('utf8')
@@ -306,7 +315,23 @@ async function runOfficialCloneCliSmoke(referenceRoot, registry, upgradeFrom, ca
306
315
  const dshHome = join(temporaryRoot, 'dsh-home')
307
316
  let added = false
308
317
  const environment = { ...process.env, DSH_HOME: dshHome }
309
- const dsh = (...args) => run('pnpm', ['dsh', ...args], { cwd: worktree, env: environment })
318
+ let lastLifecycle = { command: '(none)', code: 0, stdout: '', stderr: '' }
319
+ const dsh = async (...args) => {
320
+ const outcome = await captureOutcome('pnpm', ['dsh', ...args], { cwd: worktree, env: environment })
321
+ lastLifecycle = {
322
+ command: `pnpm dsh ${args.join(' ')}`,
323
+ code: outcome.code ?? -1,
324
+ stdout: outcome.stdout,
325
+ stderr: outcome.stderr,
326
+ }
327
+ if (outcome.code !== 0) {
328
+ throw new Error([
329
+ `${lastLifecycle.command} exited ${String(lastLifecycle.code)}`,
330
+ outcome.stdout.trim(),
331
+ outcome.stderr.trim(),
332
+ ].filter(Boolean).join('\n'))
333
+ }
334
+ }
310
335
  const dumpConfig = () => capture('pnpm', ['dsh', '--profile', 'web', '--dump-config'], {
311
336
  cwd: worktree,
312
337
  env: environment,
@@ -316,7 +341,36 @@ async function runOfficialCloneCliSmoke(referenceRoot, registry, upgradeFrom, ca
316
341
  const profilePackages = () => {
317
342
  const profileRoot = join(dshHome, 'profiles/web')
318
343
  const profileRequire = createRequire(join(profileRoot, 'package.json'))
319
- const selectorPath = profileRequire.resolve('dsh-context-compression-improved/package.json')
344
+ let selectorPath
345
+ try {
346
+ selectorPath = profileRequire.resolve('dsh-context-compression-improved/package.json')
347
+ } catch (error) {
348
+ // A silent no-op lifecycle command is the difference between "the package
349
+ // manager refused" and "the CLI exited 0 without installing anything";
350
+ // carry that evidence instead of a bare MODULE_NOT_FOUND.
351
+ const declared = (() => {
352
+ try {
353
+ return JSON.stringify(JSON.parse(readFileSync(join(profileRoot, 'package.json'), 'utf8')).dependencies ?? {})
354
+ } catch {
355
+ return '(profile package.json unreadable)'
356
+ }
357
+ })()
358
+ const installed = (() => {
359
+ try {
360
+ return JSON.stringify(readdirSync(join(profileRoot, 'node_modules')).slice(0, 40))
361
+ } catch {
362
+ return '(profiles/web/node_modules unreadable)'
363
+ }
364
+ })()
365
+ throw new Error([
366
+ `official profile ${profileRoot} does not resolve dsh-context-compression-improved`,
367
+ `last lifecycle: ${lastLifecycle.command} (exit ${String(lastLifecycle.code)})`,
368
+ `lifecycle stdout: ${lastLifecycle.stdout.trim()}`,
369
+ `lifecycle stderr: ${lastLifecycle.stderr.trim()}`,
370
+ `profile dependencies: ${declared}`,
371
+ `profiles/web/node_modules: ${installed}`,
372
+ ].join('\n'), { cause: error })
373
+ }
320
374
  return {
321
375
  profileRoot,
322
376
  selectorPath,
@@ -623,8 +677,8 @@ async function runOfficialCloneCliSmoke(referenceRoot, registry, upgradeFrom, ca
623
677
  // $DSH_HOME/profiles/node_modules — the shared-module fallback the
624
678
  // plugin's harness peers resolve through in every later raw-node proof.
625
679
  const addedDump = await dumpConfig()
626
- assert(addedDump.includes('context-compression-selector-bundle'),
627
- 'official post-add dump lacks the selector Bundle layer')
680
+ assert(addedDump.includes('context-compression-improved-bundle'),
681
+ 'official post-add dump lacks the context-compression Bundle layer')
628
682
 
629
683
  // Real profile start on the previous release, BEFORE seeding: the
630
684
  // previous-release schema predates the autoCompact section, so this probe
@@ -646,11 +700,11 @@ async function runOfficialCloneCliSmoke(referenceRoot, registry, upgradeFrom, ca
646
700
  const loadProof = await provePluginLoads()
647
701
  const postUpBoot = await runProfileBootProbe('post-up', true)
648
702
  const upDump = await dumpConfig()
649
- for (const marker of ['context-compression-selector-bundle', 'presetOverlay: true']) {
703
+ for (const marker of ['context-compression-improved-bundle', 'presetOverlay: true']) {
650
704
  assert(upDump.includes(marker), `official post-up dump lacks ${marker}`)
651
705
  }
652
- assert(upDump.match(/context-compression-selector-bundle/gu)?.length === 1,
653
- 'official post-up dump contains more than one selector Bundle layer')
706
+ assert(upDump.match(/context-compression-improved-bundle/gu)?.length === 1,
707
+ 'official post-up dump contains more than one context-compression Bundle layer')
654
708
 
655
709
  // pnpm's peers check is informational: plugin peers resolve through the
656
710
  // healed profiles/node_modules fallback, which pnpm cannot see by design.
@@ -661,14 +715,14 @@ async function runOfficialCloneCliSmoke(referenceRoot, registry, upgradeFrom, ca
661
715
 
662
716
  await dsh('plugin', '--profile', 'web', 'remove', 'dsh-context-compression-improved')
663
717
  const removedDump = await dumpConfig()
664
- assert(!removedDump.includes('context-compression-selector-bundle'),
665
- 'official CLI remove left the selector Bundle layer active')
718
+ assert(!removedDump.includes('context-compression-improved-bundle'),
719
+ 'official CLI remove left the context-compression Bundle layer active')
666
720
 
667
721
  await dsh('plugin', '--profile', 'web', 'add',
668
722
  'dsh-context-compression-improved@latest', '--registry', registry)
669
723
  const secondDump = await dumpConfig()
670
- assert(secondDump.includes('context-compression-selector-bundle'),
671
- 'official CLI reinstall did not restore the selector Bundle layer')
724
+ assert(secondDump.includes('context-compression-improved-bundle'),
725
+ 'official CLI reinstall did not restore the context-compression Bundle layer')
672
726
 
673
727
  return {
674
728
  tag: before.tag,