@x1a0f3n9/dsh-session-snapshot 0.1.5-rc.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.
@@ -0,0 +1,104 @@
1
+ /** Immutable Session-generation filenames used by recorded-session fixtures. */
2
+ /** One canonical recorded-session fixture filename. */
3
+ export interface SessionFixtureFile {
4
+ /** Parent is `0`; positive values are child/ordinal slots. */
5
+ readonly index: number;
6
+ /** Physical Session format generation; zero is encoded by omission. */
7
+ readonly version: number;
8
+ /** Canonical filename. */
9
+ readonly name: string;
10
+ }
11
+ /** One canonical persistence filename in a Session's own storage directory. */
12
+ export interface PersistedSessionFile {
13
+ /** Physical Session format generation; zero is encoded by omission. */
14
+ readonly version: number;
15
+ /** Physical compression selected by the backend. */
16
+ readonly compression: 'raw' | 'zstd';
17
+ /** Canonical basename. */
18
+ readonly name: string;
19
+ }
20
+ /**
21
+ * Return the canonical fixture filename for one parent/ordinal and generation.
22
+ *
23
+ * @param index - Parent `0` or a positive child/ordinal slot.
24
+ * @param version - Physical format generation; `0` is omitted.
25
+ * @returns The lowercase canonical JSONL filename.
26
+ */
27
+ export declare function sessionFixtureName(index: number, version: number): string;
28
+ /**
29
+ * Name the native-writer oracle for a retained historical replay role.
30
+ * This expected output never participates in replay generation selection.
31
+ * @param index - Parent `0` or a positive child/ordinal slot.
32
+ * @returns The expected-output JSONL basename.
33
+ */
34
+ export declare function writerSnapshotName(index: number): string;
35
+ /**
36
+ * Parse one canonical recorded-session fixture filename.
37
+ *
38
+ * @param name - Basename from a scenario directory.
39
+ * @returns Parsed role and generation, or `undefined` for an unrelated file.
40
+ */
41
+ export declare function parseSessionFixtureName(name: string): SessionFixtureFile | undefined;
42
+ /**
43
+ * Select the highest generation for every parent/ordinal fixture role.
44
+ * Older generations remain in the directory but do not count as extra Sessions.
45
+ *
46
+ * @param names - File basenames in one scenario directory.
47
+ * @returns Parent first, followed by contiguous child/ordinal roles.
48
+ */
49
+ export declare function sessionFixtureFiles(names: readonly string[]): SessionFixtureFile[];
50
+ /**
51
+ * Validate and order a scenario directory's selected Session fixture filenames.
52
+ *
53
+ * @param names - File basenames in one scenario directory.
54
+ * @returns Highest-generation parent and child/ordinal filenames.
55
+ */
56
+ export declare function sessionFixtureNames(names: readonly string[]): string[];
57
+ /**
58
+ * Read the declared physical generation from one Session JSONL header.
59
+ *
60
+ * @param content - Complete UTF-8 JSONL content.
61
+ * @param label - Diagnostic filename or path.
62
+ * @returns The declared non-negative format generation.
63
+ */
64
+ export declare function sessionHeaderVersion(content: string, label: string): number;
65
+ /**
66
+ * Require one fixture's canonical filename generation to equal its header.
67
+ *
68
+ * @param name - Canonical fixture basename.
69
+ * @param content - Complete UTF-8 JSONL content.
70
+ * @returns The validated format generation.
71
+ */
72
+ export declare function assertSessionFixtureVersion(name: string, content: string): number;
73
+ /**
74
+ * Return the canonical persistence basename for one generation and compression.
75
+ *
76
+ * @param version - Physical format generation; `0` is omitted.
77
+ * @param compression - Backend compression mode.
78
+ * @returns The canonical persistence basename.
79
+ */
80
+ export declare function persistedSessionFilename(version: number, compression?: 'raw' | 'zstd'): string;
81
+ /**
82
+ * Parse a canonical persistence basename from a Session's own directory.
83
+ *
84
+ * @param name - Candidate basename.
85
+ * @returns Its generation and compression, or `undefined` for noise and noncanonical names.
86
+ */
87
+ export declare function parsePersistedSessionFilename(name: string): PersistedSessionFile | undefined;
88
+ /**
89
+ * Select one highest-generation persistence path per physical Session directory.
90
+ *
91
+ * @param paths - Relative or absolute paths beneath a sessions root.
92
+ * @param compression - Compression selected by the snapshot composition.
93
+ * @returns Stable path order with older generations and filesystem noise omitted.
94
+ */
95
+ export declare function latestPersistedSessionPaths(paths: readonly string[], compression?: 'raw' | 'zstd'): string[];
96
+ /**
97
+ * Require one persistence basename's generation to equal its Session header.
98
+ *
99
+ * @param name - Canonical persistence basename.
100
+ * @param content - Complete uncompressed UTF-8 JSONL content.
101
+ * @returns The validated generation.
102
+ */
103
+ export declare function assertPersistedSessionVersion(name: string, content: string): number;
104
+ //# sourceMappingURL=session-files.d.ts.map
@@ -0,0 +1,430 @@
1
+ /**
2
+ * Keyless-by-default ACP snapshot suite factory. Each scenario drives the real
3
+ * subprocess and compares normalized stdout; comparable session fixtures are
4
+ * both replay input and expected output. Record mode refreshes reproducible
5
+ * model scenarios from the live API, while refresh mode replays committed
6
+ * scripts and rewrites derived artifacts without a key.
7
+ * Replay scenarios run concurrently because each subprocess owns unique temp
8
+ * cwd and persistence roots and reads only committed fixtures. Record and
9
+ * refresh stay serial while writing.
10
+ *
11
+ * Exactly one scenario per header-composition class pins the tokenized header
12
+ * sequence and the tokenized `system/message` sequence. Its prompt and
13
+ * tool-schema sequences live in independent sidecars, each of which may be
14
+ * shared with another class pin when the bytes are identical. Every live
15
+ * header and system prompt is checked against the composed pin, so
16
+ * session-dependent composition must declare a separate class instead of
17
+ * escaping coverage.
18
+ * @module @x1a0f3n9/dsh-session-snapshot/suite
19
+ */
20
+ import { type AgentUnderTest, type HarvestedLog } from './harness.ts';
21
+ import { type SnapshotSessionFormatManifest } from './manifest.ts';
22
+ import { type CwdPathMode, type NormalizeContext } from './normalize.ts';
23
+ /** A snapshot scenario and how its fixtures are produced. */
24
+ export interface Scenario {
25
+ name: string;
26
+ /** Deployment environment for this scenario's subprocess. */
27
+ env?: NodeJS.ProcessEnv;
28
+ /** Whether the scenario drives at least one model turn (so a JSONL expected output applies). */
29
+ hasModelTurn: boolean;
30
+ /**
31
+ * Whether the run persists a comparable session log to diff against the
32
+ * selected parent Session fixture. Defaults to {@link hasModelTurn} (a model turn
33
+ * always produces a log worth comparing). Set it independently for a scenario
34
+ * that produces a non-trivial durable log without calling the model.
35
+ */
36
+ comparesLog?: boolean;
37
+ /**
38
+ * Whether `test:snapshot:record` regenerates this scenario's current-version
39
+ * Session fixtures from the LIVE API. `recorded` scenarios are model-driven and reproducible;
40
+ * `authored` scenarios (fixtures hand-written or hand-harvested — e.g. a
41
+ * provider error or a cancel the live API can't be coaxed into
42
+ * deterministically, a deterministic hook scenario, or a scripted repetition
43
+ * a live model won't reproduce) are NEVER re-recorded.
44
+ */
45
+ recorded: boolean;
46
+ /** Historical generation retained as a read-only migration fixture. */
47
+ sessionFormat?: SnapshotSessionFormatManifest;
48
+ /**
49
+ * Whether replay is driven by a hand-written `replay.override.json` sidecar
50
+ * (a `ReplayOverrideDoc` that replaces or patches the script derived from
51
+ * selected parent Session fixture) — the throw/hang cases chunks cannot express. The fixture
52
+ * guard requires the sidecar exactly when this is set: the harness forwards
53
+ * the file purely on existence, so an unregistered stray sidecar would
54
+ * silently alter the derived script. The guard fails loud on either
55
+ * mismatch. Defaults to false (replay derives from the fixture's
56
+ * `assistant/chunk` events).
57
+ */
58
+ overridden?: boolean;
59
+ /**
60
+ * Whether this scenario is its header class's sole tokenized request-header
61
+ * pin. Prompt and tool-schema sidecars are selected independently, while
62
+ * every classmate is checked for equality with the reconstructed header.
63
+ */
64
+ pinsHeader?: boolean;
65
+ /**
66
+ * Header-pinning scenario whose `system-prompt.expected.md` this pin reuses.
67
+ * Defaults to this scenario. The source must own its prompt sidecar and
68
+ * declare the same {@link expectedPromptChanges}; only valid on a pin.
69
+ */
70
+ systemPromptSource?: string;
71
+ /**
72
+ * Header-pinning scenario whose `tool-schemas.expected.json` this pin reuses.
73
+ * Defaults to this scenario. The source must own its schema sidecar and
74
+ * declare the same {@link expectedHeaderChanges}; only valid on a pin.
75
+ */
76
+ toolSchemasSource?: string;
77
+ /**
78
+ * Child fixture indices whose own schema sequence is pinned separately,
79
+ * where `1` names `session.1.jsonl` and
80
+ * `tool-schemas.1.expected.json`. The class pin still owns every other
81
+ * request-header field.
82
+ */
83
+ pinsChildToolSchemas?: readonly number[];
84
+ /**
85
+ * Child fixture indices whose own system prompt is pinned separately, where
86
+ * `1` names `session.1.jsonl` and `system-prompt.1.expected.md`. A child
87
+ * scope that installs its own prompt section (the continuable `report`
88
+ * guidance) composes a prompt the class pin cannot describe.
89
+ */
90
+ pinsChildSystemPrompts?: readonly number[];
91
+ /**
92
+ * How many changed `request/header` snapshots this PINNING scenario's primary
93
+ * fixture legitimately carries (default 0). Each change owns one more schema
94
+ * set in the structured JSON pin; any other count fails. Only valid on the pin.
95
+ */
96
+ expectedHeaderChanges?: number;
97
+ /**
98
+ * How many later `system/message` events — replacements of surface node 0
99
+ * or in-history appends — this PINNING scenario's primary fixture
100
+ * legitimately carries after the initial prompt (default 0). Each change's
101
+ * full prompt text is kept in the readable Markdown pin; any other count
102
+ * fails. Only valid on the pin.
103
+ */
104
+ expectedPromptChanges?: number;
105
+ /**
106
+ * Which header-composition class this scenario belongs to. Scenarios that
107
+ * boot the same config compose the same header; each class has exactly one
108
+ * {@link pinsHeader} scenario, and the uniformity guard compares every
109
+ * other member against ITS class's pin. Defaults to `'default'`; a
110
+ * scenario booting an alternate config ({@link configPath}) whose tool
111
+ * list or prompt sections differ by construction carries its own class.
112
+ */
113
+ headerClass?: string;
114
+ /**
115
+ * Alternate live profile patch (absolute) this scenario boots instead of
116
+ * {@link AgentUnderTest.configPath}. Its basename must still end in
117
+ * `cordis.yml` so the launcher finds the replay sibling. A scenario whose
118
+ * patch changes the composed header also needs its own
119
+ * {@link headerClass}.
120
+ */
121
+ configPath?: string;
122
+ /**
123
+ * Parent directory for the generated session cwd. Defaults to the platform
124
+ * temp directory; set this when temp is itself part of the behavior under
125
+ * test and the scenario needs an independent project location.
126
+ */
127
+ workspaceParent?: string;
128
+ /**
129
+ * Optional final workspace preparation after the committed fixture is
130
+ * copied. Reserve this for paths that Git cannot represent portably; normal
131
+ * scenario files belong under the scenario's `workspace/` directory.
132
+ */
133
+ prepareWorkspace?: (cwd: string) => void | Promise<void>;
134
+ /**
135
+ * Whether Windows additionally compares stdout with native separators against
136
+ * `stdout.expected.windows.jsonl`. The shared canonical stdout expected output is still
137
+ * compared on every platform, and the fixture guard requires this sidecar
138
+ * exactly when the option is set.
139
+ */
140
+ pinsNativeWindowsStdout?: boolean;
141
+ /**
142
+ * Whether the scenario requires a non-Windows host, such as for POSIX process
143
+ * semantics or generated paths Windows cannot represent. The scenario's run
144
+ * test is skipped on Windows; its fixtures stay guarded on every platform.
145
+ */
146
+ posixOnly?: boolean;
147
+ /**
148
+ * Whether the scenario boots a composition that needs a usable `pwsh`
149
+ * (the pwsh-tool-turn scenario). The run test is skipped when the suite's
150
+ * {@link SnapshotSuiteOptions.hasPwsh} probe is false; fixtures stay guarded
151
+ * on every platform.
152
+ */
153
+ pwshOnly?: boolean;
154
+ }
155
+ /**
156
+ * Whether a scenario's run test is skipped for this mode and host: record mode
157
+ * skips authored (non-`recorded`) scenarios and explicit historical Session
158
+ * generations, {@link Scenario.posixOnly} scenarios skip on Windows, and
159
+ * {@link Scenario.pwshOnly} scenarios skip when the caller's `hasPwsh` probe
160
+ * is false.
161
+ *
162
+ * @param scenario The scenario whose run test is being registered.
163
+ * @param recording Whether the suite runs in record mode.
164
+ * @param platform The running Node platform, injectable for unit coverage.
165
+ * @param hasPwsh The caller's pwsh-availability probe; `pwshOnly` scenarios
166
+ * skip unless it is true.
167
+ * @returns True when the scenario's run test must not execute.
168
+ */
169
+ export declare function scenarioSkipped(scenario: Scenario, recording: boolean, platform?: NodeJS.Platform, hasPwsh?: boolean): boolean;
170
+ /** One stdout expected output selected for a platform run. */
171
+ interface StdoutExpectedVariant {
172
+ file: string;
173
+ cwdPathMode: CwdPathMode;
174
+ }
175
+ /**
176
+ * Select the shared stdout expected output plus any platform-native assertion declared by a scenario.
177
+ *
178
+ * @param scenario The scenario whose stdout contract is being selected.
179
+ * @param platform The running Node platform, injectable for unit coverage.
180
+ * @returns The ordered expected-output variants: shared canonical first, then optional Windows native.
181
+ */
182
+ export declare function stdoutExpectedVariants(scenario: Scenario, platform?: NodeJS.Platform): StdoutExpectedVariant[];
183
+ /** One suite's inputs: the agent to boot, where its fixtures live, and its scenario table. */
184
+ export interface SnapshotSuiteOptions {
185
+ /** The agent composition every scenario boots. */
186
+ agent: AgentUnderTest;
187
+ /** Absolute path of the suite's `snapshots/` directory (one subdir per scenario). */
188
+ snapshotsDir: string;
189
+ /** The scenario table; exactly one entry per header class must set `pinsHeader`. */
190
+ scenarios: Scenario[];
191
+ /**
192
+ * `replay` (keyless, the default tier), `record` (live API; re-records the
193
+ * `recorded` scenarios' fixtures and refreshes the Vitest expected outputs under
194
+ * `--update`), or `refresh` (keyless replay that rewrites stdout expected outputs and
195
+ * comparable session fixtures from the replay run). The caller derives this
196
+ * from `$DSH_SNAPSHOT` — env reading stays outside this library.
197
+ */
198
+ mode: 'replay' | 'record' | 'refresh';
199
+ /**
200
+ * Whether a real `pwsh` executable is available on this host (the probe the
201
+ * caller owns; `pwshOnly` scenarios skip when this is not true).
202
+ */
203
+ hasPwsh?: boolean;
204
+ }
205
+ /** One scenario's generated claim on a shared snapshot file. */
206
+ export interface SharedSnapshotClaim {
207
+ /** Scenario that first generated the snapshot in this suite run. */
208
+ scenario: string;
209
+ /** Complete generated file content. */
210
+ content: string;
211
+ }
212
+ /** One committed snapshot file and its complete content. */
213
+ export interface NamedSnapshotContent {
214
+ /** Diagnostic path of the committed file. */
215
+ path: string;
216
+ /** Complete committed file content. */
217
+ content: string;
218
+ }
219
+ /**
220
+ * Record one scenario's generated content for a shared snapshot source.
221
+ * A later claimant must generate identical bytes; otherwise record/refresh
222
+ * would make the final file depend on scenario order.
223
+ *
224
+ * @param claims Claims already made in this suite run, keyed by source path.
225
+ * @param source The shared snapshot path being claimed.
226
+ * @param scenario The scenario generating the content.
227
+ * @param content The complete content the scenario generated.
228
+ * @returns Nothing.
229
+ */
230
+ export declare function claimSharedSnapshot(claims: Map<string, SharedSnapshotClaim>, source: string, scenario: string, content: string): void;
231
+ /**
232
+ * Reject byte-identical committed snapshots stored under different paths.
233
+ *
234
+ * @param kind Human-readable snapshot kind for the diagnostic.
235
+ * @param snapshots The committed files to compare.
236
+ * @returns Nothing.
237
+ */
238
+ export declare function assertUniqueSnapshotContents(kind: string, snapshots: readonly NamedSnapshotContent[]): void;
239
+ /**
240
+ * Derive normalization values from a fixture's own session header. Recorded ids and cwd differ
241
+ * from the live replay run; the non-empty sentinel for missing cwd avoids accidental empty-
242
+ * string replacement.
243
+ *
244
+ * @param fixture The selected committed parent Session fixture content.
245
+ * @returns The fixture's own volatile values, ready for {@link normalizeSessionLog}.
246
+ */
247
+ export declare function fixtureContext(fixture: string): NormalizeContext;
248
+ /**
249
+ * The `data.header` payload of every `request/header` event in a session
250
+ * JSONL, in log order, with the log's volatile values scrubbed first
251
+ * ({@link normalizeSessionLog}) so headers harvested from different runs —
252
+ * each embedding its own generated cwd — compare on equal footing.
253
+ *
254
+ * @param rawLog The session `.jsonl` content to extract headers from.
255
+ * @param ctx The volatile values of the run that produced it.
256
+ * @returns The normalized `data.header` payloads, in log order.
257
+ */
258
+ export declare function normalizedHeaders(rawLog: string, ctx: NormalizeContext): unknown[];
259
+ /**
260
+ * The normalized prompt text of every `system/message` event in a session
261
+ * JSONL, in log order: the first is the initial system prompt (surface node 0)
262
+ * and each later one replaced it or, on an in-history route, appended the
263
+ * changed prompt after the cached history. An empty `content` yields `''`; a
264
+ * `system/message` without a text block is omitted.
265
+ *
266
+ * @param rawLog The session `.jsonl` content to inspect.
267
+ * @param ctx The volatile values of the run that produced it.
268
+ * @returns The normalized system prompts, in log order.
269
+ */
270
+ export declare function normalizedSystemPrompts(rawLog: string, ctx: NormalizeContext): string[];
271
+ /**
272
+ * Whether every model request in a session JSONL is preceded by its system
273
+ * prompt: the log has no `request/header`, or a `system/message` event comes
274
+ * before its first `request/header`.
275
+ *
276
+ * @param rawLog The session `.jsonl` content to inspect.
277
+ * @returns True when the first `request/header` (if any) follows a `system/message`.
278
+ */
279
+ export declare function systemPromptPrecedesRequests(rawLog: string): boolean;
280
+ /**
281
+ * The normalized tool-schema arrays carried by request headers in a session
282
+ * JSONL, in log order. Headers without an array-valued tools field are omitted
283
+ * so callers can assert one schema set per header explicitly.
284
+ *
285
+ * @param rawLog The session `.jsonl` content to inspect.
286
+ * @param ctx The volatile values of the run that produced it.
287
+ * @returns The normalized initial tool-schema arrays, in header order.
288
+ */
289
+ export declare function normalizedToolSchemas(rawLog: string, ctx: NormalizeContext): unknown[][];
290
+ /** The structured contents of a tool-schema sidecar. */
291
+ export interface ToolSchemasSnapshot {
292
+ /** The complete tool schemas from the pinned request header. */
293
+ initial: unknown[];
294
+ /** Complete tool schemas from subsequent changed-header snapshots. */
295
+ changes: unknown[][];
296
+ }
297
+ /**
298
+ * Render the full tool-schema sequence as canonical, readable JSON.
299
+ *
300
+ * @param initial The pinned request header's complete tool schemas.
301
+ * @param changes Complete tool schemas from later changed headers.
302
+ * @returns A pretty-printed JSON snapshot ending in one newline.
303
+ */
304
+ export declare function formatToolSchemasSnapshot(initial: readonly unknown[], changes?: readonly unknown[][]): string;
305
+ /**
306
+ * Parse and validate the stable top-level fields of a tool-schema sidecar.
307
+ *
308
+ * @param snapshot The JSON sidecar text.
309
+ * @returns Its initial and changed-header schema sets.
310
+ */
311
+ export declare function parseToolSchemasSnapshot(snapshot: string): ToolSchemasSnapshot;
312
+ /**
313
+ * Restore one sidecar schema set into a tokenized pinned header.
314
+ *
315
+ * @param header The parsed request header carrying `tools: "{{tools}}"`.
316
+ * @param schemas The complete schemas for this full header snapshot.
317
+ * @returns A copy of the header with its complete schemas restored.
318
+ */
319
+ export declare function restorePinnedToolSchemas(header: unknown, schemas: readonly unknown[]): unknown;
320
+ /**
321
+ * Render a normalized prompt as a repository-friendly Markdown snapshot.
322
+ * Prompt text is unchanged except that a missing terminal newline is added so
323
+ * the committed file follows the repository newline contract.
324
+ *
325
+ * @param prompt The normalized initial system prompt (surface node 0).
326
+ * @param changes Full normalized prompts from later `system/message` events, replacements or in-history appends.
327
+ * @returns Markdown snapshot text ending in a newline.
328
+ */
329
+ export declare function formatSystemPromptSnapshot(prompt: string, changes?: readonly string[]): string;
330
+ /**
331
+ * Split a prompt sidecar into its initial prompt and each later change, the
332
+ * inverse of {@link formatSystemPromptSnapshot}.
333
+ *
334
+ * @param snapshot The Markdown sidecar text.
335
+ * @returns The initial prompt snapshot plus one entry per later `system/message`.
336
+ */
337
+ export declare function parseSystemPromptSnapshot(snapshot: string): {
338
+ initial: string;
339
+ changes: string[];
340
+ };
341
+ /**
342
+ * Reject a child prompt sidecar that cannot own distinct, canonical prompt text.
343
+ * @param sidecar - committed child prompt snapshot.
344
+ * @param classPin - initial prompt snapshot owned by the scenario's header class.
345
+ * @param label - repository-relative fixture label for diagnostics.
346
+ */
347
+ export declare function assertChildSystemPromptSnapshot(sidecar: string, classPin: string, label: string): void;
348
+ /**
349
+ * Count changed `request/header` snapshots in a session JSONL.
350
+ *
351
+ * @param rawLog The session `.jsonl` content.
352
+ * @returns How many headers carry reason `change`.
353
+ */
354
+ export declare function headerChangeCount(rawLog: string): number;
355
+ /** A literal replacement from a fresh replay-run volatile to its existing fixture value. */
356
+ export interface FixtureReplacement {
357
+ /** The fresh replay run's volatile value. */
358
+ from: string;
359
+ /** The existing fixture value retained during write-back. */
360
+ to: string;
361
+ }
362
+ /**
363
+ * Carry committed UUIDs into unchanged, unambiguous messages in fresh session fixtures.
364
+ *
365
+ * @param logs Fresh fixture-ready session JSONL contents for one scenario.
366
+ * @param fixtures Existing fixture contents in matching order; missing fixtures may be empty strings.
367
+ * @returns The fresh contents with only reusable message UUIDs replaced.
368
+ */
369
+ export declare function stabilizeFixtureMessageIds(logs: readonly string[], fixtures: readonly string[]): string[];
370
+ /**
371
+ * Find tool calls whose structured result reports `UNKNOWN_TOOL`.
372
+ *
373
+ * Snapshot refresh must not turn a missing registration into accepted behavior;
374
+ * intentional unknown-tool behavior belongs in a focused unit or e2e test.
375
+ *
376
+ * @param rawLog The session JSONL to inspect.
377
+ * @returns The failing call ids in log order, using a diagnostic placeholder when absent.
378
+ */
379
+ export declare function unknownToolCallIds(rawLog: string): string[];
380
+ /**
381
+ * Build refresh write-back replacements for per-log session ids, cwd values,
382
+ * and spill paths. Durable message ids have a later structural owner.
383
+ *
384
+ * @param logs The freshly harvested logs, in fixture order.
385
+ * @param fixtures The existing fixture contents, in matching order.
386
+ * @returns Literal replacements from fresh values to the fixture's existing values.
387
+ */
388
+ export declare function refreshFixtureReplacements(logs: HarvestedLog[], fixtures: string[]): FixtureReplacement[];
389
+ /**
390
+ * Rewrite a fresh replay-produced log so repeated refreshes do not churn
391
+ * volatile fixture fields. Meaningful event payloads come from `fresh`; the
392
+ * existing fixture lends normalized-equivalent values, including non-message ids, paths,
393
+ * creation/event times, spill locators, and hook durations, only when the
394
+ * complete record layout aligns and volatile strings form a consistent
395
+ * bijection. Complete durable-message ids are excluded because the later
396
+ * fixture-ready structural pass owns them. Ambiguous layouts or mappings
397
+ * keep fresh strings. Packed timing gaps expand from zero when a projected
398
+ * fixture omits `time0`, so packing does not shift later records;
399
+ * fresh semantic values and fragment arrays remain authoritative.
400
+ *
401
+ * @param fresh The newly harvested session JSONL.
402
+ * @param existing The committed fixture JSONL being refreshed.
403
+ * @param replacements Cross-log literal replacements from {@link refreshFixtureReplacements}.
404
+ * @param freshContext The harvested run's ids, cwd, and every cwd alias.
405
+ * @returns The stabilized JSONL content to write back.
406
+ */
407
+ export declare function stabilizeRefreshLog(fresh: string, existing: string, replacements: FixtureReplacement[], freshContext: NormalizeContext): string;
408
+ /**
409
+ * Check every selected role for tool/path defects and current generations for canonical prompt/identity storage.
410
+ * Historical roles retain their released bytes, including roles retired by the current writer.
411
+ * @param dir Scenario directory containing canonical Session fixtures.
412
+ * @param scenarioName Scenario name used in failure diagnostics.
413
+ * @returns Resolves when all selected fixtures satisfy the storage checks.
414
+ */
415
+ export declare function assertSessionFixtureStorage(dir: string, scenarioName: string): Promise<void>;
416
+ /**
417
+ * Register the suite: one test per scenario (the expected-output and log comparisons and
418
+ * the header and prompt uniformity guard) plus the fixture guard block (no orphan
419
+ * scenario dirs, required files present, exactly one pin per header class,
420
+ * shared sidecars unique and well-formed, every JSONL prompt- and
421
+ * schema-scrubbed with a `system/message` before its first request). Must
422
+ * run at vitest collection time — it calls `describe`/`it`. Throws
423
+ * immediately if any header class lacks a pinning scenario or carries two
424
+ * (the uniformity guard needs exactly one comparison anchor per class).
425
+ *
426
+ * @param options The agent, snapshots directory, scenario table, and mode.
427
+ */
428
+ export declare function defineAcpSnapshotSuite(options: SnapshotSuiteOptions): void;
429
+ export {};
430
+ //# sourceMappingURL=suite.d.ts.map
@@ -0,0 +1,58 @@
1
+ /** Capture readable, path-stable workspace state for recorded-session tests. */
2
+ /** Marker that lets Git retain an expected empty directory without becoming expected workspace state. */
3
+ export declare const EMPTY_WORKSPACE_MARKER = ".empty";
4
+ /** One UTF-8 file in a captured workspace. */
5
+ export interface WorkspaceTextFileSnapshot {
6
+ /** Cwd-relative POSIX path. */
7
+ readonly path: string;
8
+ /** Entry discriminator. */
9
+ readonly kind: 'text';
10
+ /** Exact UTF-8 contents. */
11
+ readonly content: string;
12
+ }
13
+ /** One non-text file in a captured workspace. */
14
+ export interface WorkspaceBinaryFileSnapshot {
15
+ /** Cwd-relative POSIX path. */
16
+ readonly path: string;
17
+ /** Entry discriminator. */
18
+ readonly kind: 'binary';
19
+ /** Exact bytes encoded for deterministic diffs. */
20
+ readonly base64: string;
21
+ }
22
+ /** One symbolic link in a captured workspace. */
23
+ export interface WorkspaceSymlinkSnapshot {
24
+ /** Cwd-relative POSIX path. */
25
+ readonly path: string;
26
+ /** Entry discriminator. */
27
+ readonly kind: 'symlink';
28
+ /** Exact link text without resolving the target. */
29
+ readonly target: string;
30
+ }
31
+ /** One empty directory in a captured workspace. */
32
+ export interface WorkspaceEmptyDirectorySnapshot {
33
+ /** Cwd-relative POSIX path. */
34
+ readonly path: string;
35
+ /** Entry discriminator. */
36
+ readonly kind: 'empty-directory';
37
+ }
38
+ /** Stable complete file, link, and empty-directory state below one workspace root. */
39
+ export type WorkspaceSnapshotEntry = WorkspaceTextFileSnapshot | WorkspaceBinaryFileSnapshot | WorkspaceSymlinkSnapshot | WorkspaceEmptyDirectorySnapshot;
40
+ /** Options for excluding harness-owned root entries from a runtime workspace. */
41
+ export interface CaptureWorkspaceSnapshotOptions {
42
+ /** Exact immediate children of the workspace root to omit. */
43
+ readonly ignoredRootEntries?: readonly string[];
44
+ }
45
+ /**
46
+ * Capture one workspace without resolving links or depending on host path separators.
47
+ * @param root - Absolute directory whose user-visible state is captured.
48
+ * @param options - Harness-owned immediate children to omit.
49
+ * @returns Stable entries sorted by relative path.
50
+ */
51
+ export declare function captureWorkspaceSnapshot(root: string, options?: CaptureWorkspaceSnapshotOptions): Promise<WorkspaceSnapshotEntry[]>;
52
+ /**
53
+ * Capture a committed `workspace.expected/` tree, excluding its Git-only empty marker.
54
+ * @param root - Absolute expected-workspace directory.
55
+ * @returns Stable expected entries.
56
+ */
57
+ export declare function captureExpectedWorkspaceSnapshot(root: string): Promise<WorkspaceSnapshotEntry[]>;
58
+ //# sourceMappingURL=workspace.d.ts.map
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@x1a0f3n9/dsh-session-snapshot",
3
+ "description": "Session-log snapshot core with an ACP protocol adapter, expected-output normalization, and fixture invariants",
4
+ "version": "0.1.5-rc.3",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
11
+ "directory": "packages/test-support/session-snapshot"
12
+ },
13
+ "type": "module",
14
+ "main": "lib/index.js",
15
+ "types": "lib/types/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./lib/types/index.d.ts",
19
+ "default": "./lib/index.js"
20
+ },
21
+ "./src/*": "./src/*",
22
+ "./package.json": "./package.json"
23
+ },
24
+ "files": [
25
+ "lib/index.js",
26
+ "lib/types/**/*.d.ts"
27
+ ],
28
+ "license": "MIT",
29
+ "dependencies": {
30
+ "@agentclientprotocol/sdk": "1.4.0",
31
+ "js-yaml": "^4.2.0",
32
+ "vitest": "^4.1.8",
33
+ "@deepseek-ai/cordis-plugin-include": "1.0.7",
34
+ "@x1a0f3n9/dsh-llm-replay": "^0.1.5-rc.3",
35
+ "@x1a0f3n9/dsh-loader-smoke": "0.1.5-rc.3",
36
+ "@x1a0f3n9/dsh-session-format": "^0.1.5-rc.3"
37
+ },
38
+ "peerDependencies": {
39
+ "@deepseek-ai/cordis": "^4.0.2",
40
+ "@x1a0f3n9/dsh-session": "^0.1.5-rc.3",
41
+ "@x1a0f3n9/dsh-http-proxy": "^0.1.5-rc.3"
42
+ },
43
+ "devDependencies": {
44
+ "@types/js-yaml": "^4.0.9",
45
+ "@deepseek-ai/cordis": "^4.0.2",
46
+ "@x1a0f3n9/dsh-agent": "^0.1.5-rc.3",
47
+ "@x1a0f3n9/dsh-agent-loop": "^0.1.5-rc.3",
48
+ "@x1a0f3n9/dsh-compaction": "^0.1.5-rc.3",
49
+ "@x1a0f3n9/dsh-llm": "^0.1.5-rc.3",
50
+ "@x1a0f3n9/dsh-sandbox": "^0.1.5-rc.3",
51
+ "@x1a0f3n9/dsh-sandbox-policy": "^0.1.5-rc.3",
52
+ "@x1a0f3n9/dsh-session": "^0.1.5-rc.3",
53
+ "@x1a0f3n9/dsh-subagent": "^0.1.5-rc.3",
54
+ "@x1a0f3n9/dsh-tools": "^0.1.5-rc.3",
55
+ "@x1a0f3n9/dsh-user-questions": "^0.1.5-rc.3",
56
+ "@x1a0f3n9/dsh-http-proxy": "^0.1.5-rc.3"
57
+ }
58
+ }