bantamkit-mcp 0.29.2 → 0.30.0

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.
@@ -29,7 +29,8 @@ import { BantamError } from '../errors.js';
29
29
  import { pyStrip } from './factfile.js';
30
30
  import { countFacts, loadGrants, MEMORY_DIR_ENV, PROJECT_STORE, resolveProjectStore, } from './layers.js';
31
31
  import { pyHome, pyJoin, pyName, pyParent, pyResolve, PyOSError, PyUnicodeDecodeError } from './pyfs.js';
32
- import { DEFAULT_INDEX_BUDGET, MemoryBudgetExceeded, MemoryStore, MemoryValidationError, pyHashKey, pyText, } from './store.js';
32
+ import { DEFAULT_INDEX_BUDGET, MemoryBudgetExceeded, MemoryStore, MemoryValidationError, pyHashKey, pyText, RECALL_MIN_SCORE_RATIO, } from './store.js';
33
+ import { dream as runDream, formatFixed3, SUPERSEDED_HEADING } from './dream.js';
33
34
  /**
34
35
  * The model's spelling adapted to the store's contract: lowercase, `_`/space -> `-`.
35
36
  *
@@ -177,8 +178,8 @@ export class Memory {
177
178
  * fact. The floor is the operator's configured default, not a constant, so a consumer who
178
179
  * really wants top-1 says so once at construction.
179
180
  */
180
- recall(query, k = null) {
181
- return this.recallOutcome(query, k).reply;
181
+ recall(query, k = null, minRatio = RECALL_MIN_SCORE_RATIO) {
182
+ return this.recallOutcome(query, k, minRatio).reply;
182
183
  }
183
184
  /**
184
185
  * `recall`, carrying the walk it performed as numbers rather than as prose.
@@ -196,8 +197,18 @@ export class Memory {
196
197
  * refuses to list contributes nothing and raises the `unreadable` count instead of being
197
198
  * scored as empty; that distinction is the whole subject of `nothingToReport` below and
198
199
  * must not be undone here.
200
+ *
201
+ * `minRatio` (roadmap #6) rides through to every layer's `MemoryStore.recall` unchanged,
202
+ * so the gate is measured against EACH LAYER's own best score and never across layers: a
203
+ * profile fact does not have to out-score the project store's top hit to be admitted,
204
+ * because the two stores are answering as two stores. Its default is
205
+ * `RECALL_MIN_SCORE_RATIO` = 0.0, which keeps every fact `recall` was going to return,
206
+ * and nothing on the tool path passes anything else today. A ratio outside `[0.0, 1.0]`
207
+ * raises out of the FIRST layer, which is the writable project store, so it surfaces as
208
+ * the error it is rather than as an `unreadable` count — the read-only-layer `catch`
209
+ * below would otherwise file a caller's bad argument as a corrupt grant.
199
210
  */
200
- recallOutcome(query, k = null) {
211
+ recallOutcome(query, k = null, minRatio = RECALL_MIN_SCORE_RATIO) {
201
212
  const budget = k === null ? this.k : Math.max(k, this.k);
202
213
  const picked = [];
203
214
  const seen = new Set();
@@ -210,7 +221,7 @@ export class Memory {
210
221
  reached += 1;
211
222
  let facts;
212
223
  try {
213
- facts = store.recall(query, budget, writable);
224
+ facts = store.recall(query, budget, writable, minRatio);
214
225
  }
215
226
  catch (e) {
216
227
  if (!(e instanceof BantamError || e instanceof PyOSError || e instanceof PyUnicodeDecodeError)) {
@@ -312,6 +323,147 @@ export class Memory {
312
323
  budget: result.budget,
313
324
  };
314
325
  }
326
+ /** Consolidate what the project and profile layers hold under the same name. */
327
+ dream(dryRun = true) {
328
+ return this.dreamOutcome(dryRun).reply;
329
+ }
330
+ /**
331
+ * `dream`, with the decision it took carried beside the sentence it wrote.
332
+ *
333
+ * ONLY THE PROFILE LAYER IS CONSUMED. `layers` also carries read-only GRANTS, and a grant
334
+ * is another operator's store: consolidating a fact out of one is not this person's move
335
+ * to make, so `dream` never looks at them. The label is matched exactly (`profile`), never
336
+ * by prefix, because a grant is labelled `extra:<name>` and a prefix match on a directory
337
+ * called `profile-something` would reach one.
338
+ *
339
+ * A `Memory` constructed directly — not through `layered` — has no profile layer at all,
340
+ * and that is `no-profile-layer` rather than an error: there is nothing to consolidate
341
+ * ACROSS when only one layer is bound.
342
+ *
343
+ * `dryRun` DEFAULTS TO TRUE. This is the only op in this component that writes into the
344
+ * user's home directory, and it is the only one whose effect is machine-wide: a fact
345
+ * archived out of the profile store stops answering for every other project on this
346
+ * machine that has no store of its own. A destructive consolidation nobody can preview is
347
+ * not shippable, so the safe call is the short one.
348
+ */
349
+ dreamOutcome(dryRun = true) {
350
+ const profile = this.layers.find(([label]) => label === 'profile')?.[1] ?? null;
351
+ if (profile === null) {
352
+ return {
353
+ reply: 'nothing to consolidate: no profile layer is bound, so the project ' +
354
+ `store ${this.store.root} is the only layer there is.`,
355
+ status: 'no-profile-layer',
356
+ dryRun,
357
+ merged: 0,
358
+ consumed: 0,
359
+ absolutised: 0,
360
+ superseded: 0,
361
+ indexBefore: 0,
362
+ indexAfter: 0,
363
+ budget: this.store.indexBudget,
364
+ result: null,
365
+ };
366
+ }
367
+ const result = runDream(this.store, profile, dryRun);
368
+ let status;
369
+ if (result.overBudget)
370
+ status = 'refused-budget';
371
+ else if (result.changes === 0)
372
+ status = 'nothing-to-consolidate';
373
+ else if (result.applied)
374
+ status = 'consolidated';
375
+ else
376
+ status = 'previewed';
377
+ return {
378
+ reply: Memory.dreamReply(status, result),
379
+ status,
380
+ dryRun: result.dryRun,
381
+ merged: result.merged.length,
382
+ consumed: result.consumed.length,
383
+ absolutised: result.absolutised.length,
384
+ superseded: result.superseded.length,
385
+ indexBefore: result.indexBefore,
386
+ indexAfter: result.indexAfter,
387
+ budget: result.budget,
388
+ result,
389
+ };
390
+ }
391
+ /**
392
+ * The whole diff as prose: what merged, what was dated, what moved, what it cost.
393
+ *
394
+ * THE HONESTY SENTENCE AT THE END IS NOT DECORATION. Row 5 was planned as a token saving
395
+ * and J45-1 measured that it is not one: the profile store has no `index.md` on disk, its
396
+ * index is derived at read time, and nothing loads it into a prompt — so deduplicating it
397
+ * frees approximately zero prompt bytes. The value is that one ruling now has one copy
398
+ * instead of two that can disagree, and the reply says which of those two things the
399
+ * operator just bought.
400
+ *
401
+ * `formatFixed3` and not `toFixed(3)` for the two similarity figures: see the header of
402
+ * `dream.ts`. CPython's `.3f` rounds ties to EVEN and they are reachable.
403
+ */
404
+ static dreamReply(status, result) {
405
+ const lines = [];
406
+ if (status === 'nothing-to-consolidate') {
407
+ lines.push(`nothing to consolidate: ${result.projectRoot} and ${result.profileRoot} ` +
408
+ 'hold no fact under the same name, and no project fact carries a relative ' +
409
+ 'date this pass will resolve.');
410
+ }
411
+ else {
412
+ const identical = result.merged.filter((merge) => merge.kind === 'identical').length;
413
+ const diverged = result.merged.length - identical;
414
+ const verb = status === 'consolidated' ? 'consolidated' : 'would consolidate';
415
+ lines.push(`${verb} ${result.merged.length} cross-layer name collision(s) — ${identical} ` +
416
+ `byte-identical, ${diverged} diverged and unioned — and rewrote ` +
417
+ `${result.rewritten.length} project fact(s). The survivor stays in the ` +
418
+ `project layer; the profile copy moves to ${result.archiveDir} and is NOT ` +
419
+ 'deleted: restore it by name.');
420
+ }
421
+ for (const merge of result.merged) {
422
+ lines.push(`- ${pyText(merge.name)} (${merge.kind}, name+description similarity ` +
423
+ `${formatFixed3(merge.jaccard)}) — body ${merge.bodyBefore} -> ${merge.bodyAfter} ` +
424
+ `bytes, ${merge.blocksAdded} block(s) kept from the ${merge.consumedLayer} ` +
425
+ `copy, survivor in ${merge.survivorLayer}`);
426
+ }
427
+ for (const record of result.superseded) {
428
+ lines.push(`- superseded '${record.subject}': the ${record.lostLayer} copy ` +
429
+ `(${record.lostDate}) lost to the ${record.keptLayer} copy ` +
430
+ `(${record.keptDate}); the older claim is kept verbatim under ` +
431
+ `'${SUPERSEDED_HEADING}'`);
432
+ }
433
+ for (const hit of result.absolutised) {
434
+ lines.push(`- dated ${pyText(hit.name)} (${hit.layer}): '${hit.term}' -> ${hit.resolved}, ` +
435
+ `resolved against that fact's own mtime ${hit.basis}, not today`);
436
+ }
437
+ for (const hit of result.unresolved) {
438
+ lines.push(`- left alone in ${pyText(hit.name)} (${hit.layer}): '${hit.term}' — no exact day ` +
439
+ 'follows from an mtime, so nothing was substituted');
440
+ }
441
+ for (const [name, reason] of result.refused) {
442
+ lines.push(`- refused ${pyText(name)}: ${reason}`);
443
+ }
444
+ for (const pair of result.similarUnmerged) {
445
+ lines.push(`- similar but NOT merged: ${pyText(pair.projectName)} (project) and ` +
446
+ `${pyText(pair.profileName)} (profile) score ${formatFixed3(pair.jaccard)}; this pass merges ` +
447
+ 'on name equality only, so nothing was done about it');
448
+ }
449
+ lines.push(`project index ${result.indexBefore} -> ${result.indexAfter} bytes against a ` +
450
+ `${result.budget}-byte budget; the profile index (derived, no file on disk) ` +
451
+ `${result.profileIndexBefore} -> ${result.profileIndexAfter}; fact bytes ` +
452
+ `across both layers ${result.factBytesBefore} -> ${result.factBytesAfter}.`);
453
+ if (status === 'refused-budget') {
454
+ lines.push(`NOTHING WAS WRITTEN: the merged index would be ${result.indexAfter} bytes ` +
455
+ `against a ${result.budget}-byte budget. Call \`memory_compact\` to archive ` +
456
+ 'the stalest facts, then run this again.');
457
+ }
458
+ else if (result.dryRun) {
459
+ lines.push('DRY RUN: nothing was written. Re-run with dry_run false to apply it.');
460
+ }
461
+ lines.push('This is a correctness pass, not a token saving: the profile index is derived ' +
462
+ 'at read time and is not loaded from a file, so consolidating it frees ' +
463
+ 'approximately no prompt bytes. What it buys is one copy of a ruling instead ' +
464
+ 'of two that can diverge.');
465
+ return lines.join('\n');
466
+ }
315
467
  /**
316
468
  * `(index bytes, budget)` for the writable project store; bytes may be `null`.
317
469
  *
@@ -0,0 +1,247 @@
1
+ import type { FactValue } from './factfile.js';
2
+ import { MemoryStore } from './store.js';
3
+ /**
4
+ * The two layer names this pass knows. A read-only GRANT is deliberately not one of them: a
5
+ * grant is another operator's store, and consuming a fact out of it is not this person's to
6
+ * do. Grants are neither merged nor scanned.
7
+ */
8
+ export declare const PROJECT_LAYER = "project";
9
+ export declare const PROFILE_LAYER = "profile";
10
+ /**
11
+ * The heading the union writes above claims a contradiction retired. Distinctive on
12
+ * purpose: a body that already contains an ordinary `## superseded` section must not be
13
+ * mistaken for one this pass wrote.
14
+ */
15
+ export declare const SUPERSEDED_HEADING = "## superseded by a dream merge";
16
+ /**
17
+ * One relative term found in a body, and what — if anything — it resolved to.
18
+ *
19
+ * `resolved` is `''` for a term in `UNRESOLVED`: the term was FOUND and REPORTED and the body
20
+ * was not touched. `basis` is always the ISO date the arithmetic ran against, which is the
21
+ * fact file's own mtime and never today's date — a fact written in August that says "today"
22
+ * means a day in August, and resolving it against the day the dream runs is how a
23
+ * consolidation pass invents history.
24
+ */
25
+ export interface DateHit {
26
+ readonly name: FactValue;
27
+ readonly layer: string;
28
+ readonly term: string;
29
+ readonly resolved: string;
30
+ readonly basis: string;
31
+ }
32
+ /** A claim that lost a contradiction, kept verbatim so nothing is silently dropped. */
33
+ export interface Superseded {
34
+ readonly subject: string;
35
+ readonly kept: string;
36
+ readonly keptLayer: string;
37
+ readonly keptDate: string;
38
+ readonly lost: string;
39
+ readonly lostLayer: string;
40
+ readonly lostDate: string;
41
+ }
42
+ /** One consolidated name: what it was, what it became, and where each half went. */
43
+ export interface DreamMerge {
44
+ readonly name: FactValue;
45
+ readonly kind: 'identical' | 'diverged';
46
+ readonly jaccard: number;
47
+ readonly survivorLayer: string;
48
+ readonly consumedLayer: string;
49
+ readonly bodyBefore: number;
50
+ readonly bodyAfter: number;
51
+ readonly blocksAdded: number;
52
+ readonly superseded: readonly Superseded[];
53
+ }
54
+ /**
55
+ * A cross-layer pair similarity would have merged and NAME EQUALITY DID NOT FIND.
56
+ *
57
+ * Reported, never acted on. J45-1 measured this population at ZERO on the live stores. It is
58
+ * carried so a future store that grows a paraphrase is visible rather than silently
59
+ * unmerged, and so that "similarity contributes nothing today" is a number a reader can
60
+ * check instead of a sentence they have to trust.
61
+ */
62
+ export interface SimilarPair {
63
+ readonly projectName: FactValue;
64
+ readonly profileName: FactValue;
65
+ readonly jaccard: number;
66
+ }
67
+ /**
68
+ * The diff. Everything a caller needs to see what a dream did, or would do.
69
+ *
70
+ * `applied` is FALSE for a dry run and for a plan refused by the budget, and it is the only
71
+ * field that says whether anything on disk moved. The byte fields are projections computed
72
+ * the same way in both modes, so a preview and the run it previews report the same
73
+ * arithmetic rather than two numbers a reader has to reconcile.
74
+ *
75
+ * `overBudget`, `changes` and `superseded` are `@property` on the reference and plain fields
76
+ * here — the values are the same and nothing mutates the result.
77
+ */
78
+ export interface DreamResult {
79
+ readonly applied: boolean;
80
+ readonly dryRun: boolean;
81
+ readonly merged: readonly DreamMerge[];
82
+ readonly refused: readonly (readonly [FactValue, string])[];
83
+ readonly absolutised: readonly DateHit[];
84
+ readonly unresolved: readonly DateHit[];
85
+ readonly similarUnmerged: readonly SimilarPair[];
86
+ readonly consumed: readonly FactValue[];
87
+ readonly rewritten: readonly FactValue[];
88
+ readonly archiveDir: string;
89
+ readonly indexBefore: number;
90
+ readonly indexAfter: number;
91
+ readonly budget: number;
92
+ readonly profileIndexBefore: number;
93
+ readonly profileIndexAfter: number;
94
+ readonly factBytesBefore: number;
95
+ readonly factBytesAfter: number;
96
+ readonly projectRoot: string;
97
+ readonly profileRoot: string;
98
+ /** Would the project index not fit after this merge? Then nothing is written. */
99
+ readonly overBudget: boolean;
100
+ /** Merges plus rewritten facts — the count that decides `nothing-to-consolidate`. */
101
+ readonly changes: number;
102
+ readonly superseded: readonly Superseded[];
103
+ }
104
+ /**
105
+ * `int(text)` for a run of `\p{Nd}` digits, which is what CPython's `int()` accepts.
106
+ *
107
+ * A decimal-digit block is exactly ten consecutive codepoints, so the value of a digit is
108
+ * its distance from the block's zero and the block's zero is found by walking back while the
109
+ * previous codepoint is still `Nd` — no table, and correct for every script including the
110
+ * ones assigned after this file was written.
111
+ */
112
+ export declare function pyIntDigits(text: string): number;
113
+ /**
114
+ * `f"{value:.3f}"` — round HALF TO EVEN on the exact binary value of the double.
115
+ *
116
+ * NOT `toFixed(3)`, which rounds ties AWAY from zero. The reachable ties are exactly the
117
+ * odd sixteenths (a tie at three places needs the exact value to be `k/10000` with `k`
118
+ * ending in 5, and only `j/16` is both that and a double), and four of the eight disagree:
119
+ * CPython prints `0.062`, `0.312`, `0.562`, `0.812` where `toFixed` prints `0.063`, `0.313`,
120
+ * `0.563`, `0.813`. A Jaccard of `1/16` is one shared token in a sixteen-token union, which
121
+ * is an ordinary pair and not a contrived one.
122
+ *
123
+ * The arithmetic is exact: the double is decomposed to `mantissa * 2**exponent` over
124
+ * `BigInt`, so nothing here rounds twice.
125
+ */
126
+ export declare function formatFixed3(value: number): string;
127
+ /**
128
+ * `sorted(values)` over what a `Fact.name` can hold — CPython's `listsort` for a single run.
129
+ *
130
+ * Every name a `str` is the total order `cmpCodepoint` gives. Anything else can RAISE, and
131
+ * then WHICH pair CPython happened to probe decides the sentence, so the slow path is
132
+ * `count_run` + `binarysort` exactly as `sortScored` spells it: pivot on the LEFT of every
133
+ * probe, midpoint `l + ((r - l) >> 1)`, `==` short-circuiting `<` so two facts both named
134
+ * `None` order rather than raise.
135
+ *
136
+ * THE EDGE, STATED: the reference sorts a `set`, whose iteration order is hash-derived, so
137
+ * for a list that RAISES the two runtimes can name a different pair of types. Where the
138
+ * order exists — every name a `str`, which is every store `save` has ever written — the
139
+ * answer is identical.
140
+ */
141
+ export declare function sortedNames(values: readonly FactValue[]): FactValue[];
142
+ /** One line, single-spaced. The basis of every comparison key in this module. */
143
+ export declare function collapse(text: string): string;
144
+ /**
145
+ * The identity of a block for set-union purposes: collapsed whitespace, lowercased.
146
+ *
147
+ * Punctuation is deliberately NOT stripped. A rule that ignored it would call two sentences
148
+ * the same because one ends in a question mark, and this pass is allowed to repeat a claim
149
+ * but never to drop one.
150
+ */
151
+ export declare function blockKey(text: string): string;
152
+ /**
153
+ * Paragraphs: non-empty runs separated by a blank line, each stripped.
154
+ *
155
+ * Line endings are normalised first, so the same body blocks identically whether it was
156
+ * written on Windows or on POSIX — the two runtimes must agree on the block list before they
157
+ * can agree on the union of two of them.
158
+ */
159
+ export declare function splitBlocks(text: string): string[];
160
+ /**
161
+ * `[subject key, value key]` when a block is a single-line `Subject: value`, else `null`.
162
+ *
163
+ * See `SLOT` for why this is narrow. It exists only so that a genuine CONTRADICTION — the
164
+ * same slot given two different values — can be told apart from two claims that are merely
165
+ * different, which is the whole of what union already handles.
166
+ */
167
+ export declare function claimSlot(block: string): [string, string] | null;
168
+ /**
169
+ * `[body, resolved hits, unresolved hits]` — a relative date annotated with its day.
170
+ *
171
+ * THE TERM IS KEPT AND THE DATE IS ADDED: `today` becomes `today (2026-08-27)`. Deleting the
172
+ * word would rewrite the sentence a person wrote; appending the day says which date was
173
+ * substituted and leaves the reader able to disagree with it. A term already carrying a
174
+ * stamp is skipped, which is what makes a second dream over the same body a no-op.
175
+ *
176
+ * A term whose arithmetic leaves the calendar (see `addDays`) is REPORTED as unresolved and
177
+ * the body is left alone — the same treatment `recently` and `last month` get, for the same
178
+ * reason: this pass may say which day it substituted, or say it could not, and never invent
179
+ * one. Those hits come FIRST in the unresolved list, in the order the relative scan met
180
+ * them, followed by the `UNRESOLVED` matches in the order the second scan met them, which
181
+ * is the order `dream.py` builds the same list in.
182
+ */
183
+ export declare function absolutise(body: string, basis: string, name: FactValue, layer: string): [string, DateHit[], DateHit[]];
184
+ /**
185
+ * Both descriptions, `; `-joined, unless they are the same line of text.
186
+ *
187
+ * The description is what `recall` SCORES against, so dropping one side's would cost the
188
+ * survivor the query vocabulary that half of the pair used to answer. Equality is on the
189
+ * collapsed, lowercased key — the same key every other comparison here uses — and nothing
190
+ * cleverer: a containment or similarity rule would be a second thing to reproduce byte for
191
+ * byte, and the index budget already bounds the cost of being literal.
192
+ */
193
+ export declare function mergeDescriptions(base: FactValue, other: FactValue): string;
194
+ /** Ordered set union, base first, exact string identity. Order is the port's contract. */
195
+ export declare function mergeLinks(base: readonly FactValue[], other: readonly FactValue[]): FactValue[];
196
+ /**
197
+ * UNION, never a winner. `[body, blocks taken from other, superseded records]`.
198
+ *
199
+ * THE ALGORITHM, stated as the contract both runtimes reproduce exactly:
200
+ *
201
+ * 1. Split both bodies into blocks (`splitBlocks`).
202
+ * 2. Find CONTRADICTIONS: a `claimSlot` subject present on both sides with two different
203
+ * value keys. The side whose FACT FILE has the later mtime wins; a tie goes to `base`,
204
+ * which is the writable project layer. The loser is removed from the body sequence and
205
+ * recorded in a `Superseded` entry — never dropped.
206
+ * 3. Emit every surviving BASE block in source order, skipping any whose `blockKey` has
207
+ * already been emitted.
208
+ * 4. Emit every surviving OTHER block in source order, same skip. Count these.
209
+ * 5. If anything was superseded, append one block: `SUPERSEDED_HEADING`, a blank line, and
210
+ * one `- from <layer> (<date>): <collapsed text>` line per record, sorted by subject.
211
+ * 6. Join with a blank line.
212
+ *
213
+ * WHY MTIME AND NOT `created` DECIDES A CONTRADICTION. J45-1 measured all three obvious
214
+ * clocks on the real diverged pair and every one picks wrong: newest `created` and longest
215
+ * body each pick the profile copy and lose the project copy's amended paragraph,
216
+ * project-layer-wins drops ~1.7 kB of the profile copy, and `last_recalled` is the same date
217
+ * on both. `created` is FIRST-LANDING and the older-created fact there holds the NEWER
218
+ * content, so it is actively backwards. The file's mtime is the only clock on disk that
219
+ * records when the text was last written, and it is the same clock `absolutise` uses.
220
+ *
221
+ * WHY UNION MAY REPEAT A CLAIM. Two paragraphs that say the same thing in different words
222
+ * have different `blockKey`s and both survive. That is the deliberate direction of the
223
+ * error: a repeated claim is something the user can delete in one edit, and a dropped ruling
224
+ * is not recoverable from the merged fact at all.
225
+ */
226
+ export declare function mergeBodies(baseBody: string, otherBody: string, baseLayer: string, otherLayer: string, baseDate: string, otherDate: string): [string, number, Superseded[]];
227
+ /**
228
+ * Consolidate the facts the two layers hold under the same NAME. Reversible, bounded.
229
+ *
230
+ * `dryRun` defaults to TRUE because this pass writes into the user's home directory and a
231
+ * destructive consolidation nobody can preview is not shippable. A dry run performs every
232
+ * read, every merge and every projection and writes nothing.
233
+ *
234
+ * THE BUDGET IS `compact()`'s BUDGET AND NOT A SECOND CAP. The only index this pass can grow
235
+ * is the project one, and it can grow it only by the bytes a unioned DESCRIPTION adds —
236
+ * every merged name is already a line in that index, so no line is ever added. The projected
237
+ * index is measured exactly the way `checkIndexBudget` measures it, and a plan that would
238
+ * not fit is returned unapplied with `overBudget` set, for the caller to report. It is not
239
+ * raised: a preview whose answer is an exception has told the operator nothing about the
240
+ * plan they asked to see.
241
+ *
242
+ * WRITE ORDER IS SURVIVORS FIRST, CONSUMPTION SECOND, and it is chosen for the direction of
243
+ * its failure. If the process dies between the two, the survivor holds everything and the
244
+ * profile copy is still live — a duplicate that the next dream consolidates. The other order
245
+ * would leave a window in which the profile copy is archived and the union has not landed.
246
+ */
247
+ export declare function dream(project: MemoryStore, profile: MemoryStore, dryRun?: boolean): DreamResult;