wendkeep 0.59.0 → 0.61.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.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,51 @@ All notable changes to **wendkeep** are documented here. Format based on
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this project follows
5
5
  [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.61.0] — 2026-07-28
8
+
9
+ ### Added
10
+
11
+ - **`wendkeep/vault` passa a expor o kernel de memória operacional.** Schema v2, detecção de
12
+ modo, handoff, ledger/projeção e validadores CORE/SHARED ficam disponíveis pela superfície
13
+ pública e são exercitados a partir de um tarball realmente instalado.
14
+
15
+ ### Changed
16
+
17
+ - **A implementação canônica do kernel agora pertence a `packages/vault/src`.** Os paths
18
+ históricos em `hooks/` e `src/` permanecem como fachadas de reexport, inclusive por bare
19
+ specifiers no pacote instalado, preservando identidade de funções, classes, constantes e
20
+ compatibilidade com consumidores existentes.
21
+ - **A extração é estrutural e não migra dados.** Schema 2, ledger, locks, projeção,
22
+ SessionStop e o conteúdo de cada Vault mantêm o mesmo contrato persistente.
23
+
24
+ ### Fixed
25
+
26
+ - **`memory repair` converge checkpoints assert-only históricos pré-0.59 sem editar o ledger.**
27
+ O repair só migra quando prefixo, revision, cursor, hash, identidade e turns reproduzem
28
+ exatamente a semântica antiga e o espelho `memory_checkpoint` é compatível; o caminho preserva
29
+ CORE/ledger, usa CAS do attempt e do espelho, backup e auditoria e continua falhando fechado para
30
+ tuples, operações ou espelhos divergentes.
31
+
32
+ ## [0.60.0] — 2026-07-27
33
+
34
+ ### Added
35
+
36
+ - **O runtime ganha seis fronteiras físicas internas:** `cli`, `harness`, `vault`, `mcp`,
37
+ `integrations` e `pi` passam a existir como workspaces privados `@wendkeep/*`, mantendo
38
+ `wendkeep` como uma única unidade de instalação e publicação.
39
+ - **O Keep Core inaugura a API programática `wendkeep/vault`.** Binding/resolução do Vault e a
40
+ fronteira segura de paths podem ser importados pelo novo subpath público, incluído e validado no
41
+ tarball.
42
+
43
+ ### Changed
44
+
45
+ - **Binding e segurança física agora pertencem a `packages/vault`.** Os paths históricos em
46
+ `src/project-vault.mjs` e `hooks/vault-path-safety.mjs` permanecem como fachadas compatíveis, sem
47
+ alterar CLI, hooks ou dados existentes.
48
+ - **A direção de dependências virou gate estrutural.** O workspace Vault aceita somente built-ins
49
+ Node e módulos internos, impedindo que Keep Core passe a depender de Harness, perfis, changes,
50
+ FLOW, sensores ou CLI.
51
+
7
52
  ## [0.59.0] — 2026-07-27
8
53
 
9
54
  ### Added
package/README.en.md CHANGED
@@ -15,6 +15,14 @@
15
15
 
16
16
  **Persistent memory for AI coding agents, built on your Obsidian vault.** Every Claude Code **and Codex** session is captured turn by turn into local Markdown — `init` wires both (Codex asks you to approve its hooks once; `import` backfills past sessions either way) — with token/cost tracking and automatically extracted decisions, bugs, and learnings. That always-on plane is **Keep Core**. On top of it, **Wend Runtime** provides a native, zero-dependency lifecycle (spec → change → TDD → sensor-gated archive), selected through the `OFF`, `FLOW`, `GUIDE`, `GOVERN`, and `ASSURE` Operating Profiles. 100% local, open-core.
17
17
 
18
+ The runtime is being separated into six physical boundaries — `cli`, `harness`, `vault`, `mcp`,
19
+ `integrations`, and `pi` — without fragmenting installation. The private `vault` workspace now
20
+ canonically owns safe binding and the Shared Project Memory v2 kernel (schema, mode, handoff,
21
+ ledger, projection, and validation), exposed by the root package through `wendkeep/vault`.
22
+ Historical imports, including installed bare subpaths such as `wendkeep/hooks/...`, keep working
23
+ through compatibility facades and no session data needs migration;
24
+ see the [modular architecture](docs/en/architecture.md).
25
+
18
26
  ```bash
19
27
  npm i -D wendkeep && npx wendkeep init # captures from the next session on
20
28
  npx wendkeep import # backfill past Claude + Codex sessions
@@ -244,9 +252,11 @@ lost publication, or mismatched checkpoint blocks. See [migration](docs/en/comma
244
252
  and [diagnostics](docs/en/commands/maintenance-and-diagnostics.md).
245
253
 
246
254
  If status blocks, preserve the evidence and run `wendkeep memory repair --vault <vault>` to back up
247
- the corrupt ledger, retain valid lines, and re-project. Repair never reclassifies attempts. A
248
- valid pre-0.59 causal checkpoint is CAS-migrated to the physical boundary with backup/audit. A
249
- demonstrably superseded ambiguity uses `memory reconcile <session> --by-session <successor>
255
+ the corrupt ledger, retain valid lines, and re-project. Repair never reclassifies attempts. Valid
256
+ pre-0.59 causal checkpoints and exactly re-derived assert-only historical prefixes are
257
+ CAS-migrated on both the attempt and `memory_checkpoint` to the correct physical boundary with
258
+ backup/audit; divergent mirrors fail closed. A demonstrably superseded
259
+ ambiguity uses `memory reconcile <session> --by-session <successor>
250
260
  --reason <reason>` as a dry run and requires `--apply`; the decision is backed up and audited
251
261
  without rewriting ledger, CORE, or notes. Run `status --gate` again afterwards. Conflicts require
252
262
  explicit curation with `memory promote <id>` or `memory reject <id>`; doctor only diagnoses.
package/README.md CHANGED
@@ -15,6 +15,14 @@
15
15
 
16
16
  **Persistent memory for AI coding agents, built on your Obsidian vault.** Every Claude Code **and Codex** session is captured turn by turn into local Markdown — `init` wires both (Codex asks you to approve its hooks once; `import` backfills past sessions either way) — with token/cost tracking and automatically extracted decisions, bugs, and learnings. That always-on plane is **Keep Core**. On top of it, **Wend Runtime** provides a native, zero-dependency lifecycle (spec → change → TDD → sensor-gated archive), selected through the `OFF`, `FLOW`, `GUIDE`, `GOVERN`, and `ASSURE` Operating Profiles. 100% local, open-core.
17
17
 
18
+ The runtime is being separated into six physical boundaries — `cli`, `harness`, `vault`, `mcp`,
19
+ `integrations`, and `pi` — without fragmenting installation. The private `vault` workspace now
20
+ canonically owns safe binding and the Shared Project Memory v2 kernel (schema, mode, handoff,
21
+ ledger, projection, and validation), exposed by the root package through `wendkeep/vault`.
22
+ Historical imports, including installed bare subpaths such as `wendkeep/hooks/...`, keep working
23
+ through compatibility facades and no session data needs migration;
24
+ see the [modular architecture](docs/en/architecture.md).
25
+
18
26
  ```bash
19
27
  npm i -D wendkeep && npx wendkeep init # captures from the next session on
20
28
  npx wendkeep import # backfill past Claude + Codex sessions
@@ -244,9 +252,11 @@ lost publication, or mismatched checkpoint blocks. See [migration](docs/en/comma
244
252
  and [diagnostics](docs/en/commands/maintenance-and-diagnostics.md).
245
253
 
246
254
  If status blocks, preserve the evidence and run `wendkeep memory repair --vault <vault>` to back up
247
- the corrupt ledger, retain valid lines, and re-project. Repair never reclassifies attempts. A
248
- valid pre-0.59 causal checkpoint is CAS-migrated to the physical boundary with backup/audit. A
249
- demonstrably superseded ambiguity uses `memory reconcile <session> --by-session <successor>
255
+ the corrupt ledger, retain valid lines, and re-project. Repair never reclassifies attempts. Valid
256
+ pre-0.59 causal checkpoints and exactly re-derived assert-only historical prefixes are
257
+ CAS-migrated on both the attempt and `memory_checkpoint` to the correct physical boundary with
258
+ backup/audit; divergent mirrors fail closed. A demonstrably superseded
259
+ ambiguity uses `memory reconcile <session> --by-session <successor>
250
260
  --reason <reason>` as a dry run and requires `--apply`; the decision is backed up and audited
251
261
  without rewriting ledger, CORE, or notes. Run `status --gate` again afterwards. Conflicts require
252
262
  explicit curation with `memory promote <id>` or `memory reject <id>`; doctor only diagnoses.
@@ -47,8 +47,12 @@ npx wendkeep validate-memory --vault <v2-vault>
47
47
  mismatched checkpoint blocks; `degraded` with an intact outbox is a warning.
48
48
  - `memory repair` is structural only: it uses PID/token-owned locks, writes a `.bak`, retains
49
49
  valid events, and reprojects state. When it recognizes a valid pre-0.59 checkpoint whose cursor
50
- is causal, it CAS-migrates it to the physical boundary with backup/audit; it never reclassifies
51
- registry attempts or accepts a tuple that cannot be fully re-derived.
50
+ is causal, it CAS-migrates it to the physical boundary. It also recognizes an assert-only
51
+ historical prefix only when revision, cursor, hash, identity, turns, and the
52
+ `memory_checkpoint` mirror exactly reproduce the old semantics; the target is the current replay
53
+ of that prefix, without absorbing later events. Both paths CAS-check the attempt and mirror and
54
+ record backup/audit. Repair never reclassifies registry attempts or accepts a tuple, operation,
55
+ or mirror that cannot be fully re-derived.
52
56
  - `memory reconcile` is a dry run by default. `--apply` requires two named sessions plus a reason,
53
57
  CAS-checks the exact attempt, backs up the registry, and limits mutation to the ambiguous attempt
54
58
  and its successor. Replay is CORE-aware, checkpoints use the physical ledger cursor, and the
@@ -47,8 +47,12 @@ npx wendkeep validate-memory --vault <cofre-v2>
47
47
  publicação perdida ou checkpoint divergente bloqueiam; `degraded` com outbox íntegra é warning.
48
48
  - `memory repair` é exclusivamente estrutural: trabalha sob locks com owner PID/token, salva
49
49
  `.bak`, retém eventos válidos e reprojeta. Quando reconhece um checkpoint pré-0.59 válido com
50
- cursor causal, migra-o por CAS para a fronteira física e registra backup/auditoria; nunca
51
- reclassifica attempts do registry nem aceita tuple que não seja rederivada integralmente.
50
+ cursor causal, migra-o por CAS para a fronteira física. Também reconhece um prefixo histórico
51
+ assert-only somente quando revision, cursor, hash, identidade, turns e o espelho
52
+ `memory_checkpoint` reproduzem exatamente a semântica antiga; o alvo é o replay atual daquele
53
+ prefixo, sem absorver eventos posteriores. Ambos os casos fazem CAS do attempt e do espelho e
54
+ registram backup/auditoria. O repair nunca reclassifica attempts do registry nem aceita tuple,
55
+ operação ou espelho que não seja rederivado integralmente.
52
56
  - `memory reconcile` é dry-run por padrão. `--apply` exige duas sessões nomeadas e motivo, faz CAS
53
57
  do attempt exato, salva backup do registry e limita a mutação ao attempt ambíguo e à sucessora.
54
58
  O replay é CORE-aware, usa cursor físico do ledger no checkpoint e não reescreve ledger, CORE ou
@@ -1,199 +1 @@
1
- import { createHash } from 'node:crypto';
2
- import { existsSync, readFileSync, readdirSync } from 'node:fs';
3
- import { basename, join, relative } from 'node:path';
4
-
5
- import { sanitizeMemoryText } from './memory-schema.mjs';
6
-
7
- function canonicalValue(value) {
8
- if (Array.isArray(value)) return value.map(canonicalValue);
9
- if (!value || typeof value !== 'object') return value;
10
- return Object.fromEntries(Object.keys(value).sort().map((key) => [key, canonicalValue(value[key])]));
11
- }
12
-
13
- function eventId(context, memoryKey, value) {
14
- const digest = createHash('sha256')
15
- .update(JSON.stringify([
16
- context.projectId,
17
- context.identity?.canonicalConversationId,
18
- context.activation?.id,
19
- context.turn?.id,
20
- memoryKey,
21
- canonicalValue(value),
22
- ]))
23
- .digest('hex')
24
- .slice(0, 24);
25
- return `mem-${digest}`;
26
- }
27
-
28
- function makeEvent(context, { memoryKey, value, authority, evidence }) {
29
- const cleanValue = typeof value === 'string' ? sanitizeMemoryText(value) : canonicalValue(value);
30
- return {
31
- v: 1,
32
- event_id: eventId(context, memoryKey, cleanValue),
33
- project_id: String(context.projectId || ''),
34
- memory_key: memoryKey,
35
- operation: 'assert',
36
- value: cleanValue,
37
- authority,
38
- canonical_session_id: String(context.identity?.canonicalConversationId || ''),
39
- activation_id: String(context.activation?.id || ''),
40
- activation_epoch: Number(context.activation?.epoch || 0),
41
- turn_sequence: Number(context.turn?.sequence || 0),
42
- source_turn_id: String(context.turn?.id || ''),
43
- observed_at: context.observedAt,
44
- evidence: (evidence || []).filter(Boolean).map((item) => sanitizeMemoryText(item)),
45
- };
46
- }
47
-
48
- function readJson(path) {
49
- try { return JSON.parse(readFileSync(path, 'utf8')); } catch { return null; }
50
- }
51
-
52
- function filesBelow(dir, accept, found = []) {
53
- let entries = [];
54
- try { entries = readdirSync(dir, { withFileTypes: true }); } catch { return found; }
55
- for (const entry of entries) {
56
- const path = join(dir, entry.name);
57
- if (entry.isDirectory()) filesBelow(path, accept, found);
58
- else if (accept(entry.name)) found.push(path);
59
- }
60
- return found;
61
- }
62
-
63
- function vaultRel(vaultBase, path) {
64
- return relative(vaultBase, path).replaceAll('\\', '/');
65
- }
66
-
67
- function nextActionFrom(summary) {
68
- const match = String(summary || '').match(/(?:a\s+)?pr[oó]xima\s+(?:change\s+)?(?:ser[aá]|[ée]|:)\s+(?:a\s+)?([^.!?\n]+)/i);
69
- if (!match) return null;
70
- const text = sanitizeMemoryText(match[1].trim());
71
- const id = text.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase()
72
- .replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, 64);
73
- return id && text ? { id, summary: text } : null;
74
- }
75
-
76
- export function collectLifecycleEvidence(vaultBase, { changeSlug = '', summary = '', noteRel = '' } = {}) {
77
- const evidence = {};
78
- const slug = String(changeSlug || '').trim();
79
- if (slug) {
80
- const changeRoots = ['08-Mudanças', '08-Changes'];
81
- let archivedDir = '';
82
- for (const root of changeRoots) {
83
- const archive = join(vaultBase, root, '_arquivo');
84
- let names = [];
85
- try { names = readdirSync(archive, { withFileTypes: true }); } catch { /* absent locale */ }
86
- const match = names.find((entry) => entry.isDirectory() && (entry.name === slug || entry.name.endsWith(`-${slug}`)));
87
- if (match) { archivedDir = join(archive, match.name); break; }
88
- }
89
-
90
- const adrPattern = new RegExp(`^ADR-(\\d{4})-${slug.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\.md$`, 'i');
91
- const adrPath = filesBelow(vaultBase, (name) => adrPattern.test(name))[0];
92
- if (archivedDir && adrPath) {
93
- const adr = (basename(adrPath).match(/^ADR-\d{4}/i) || [''])[0].toUpperCase();
94
- evidence.change = { slug, status: 'archived', adr, path: vaultRel(vaultBase, adrPath) };
95
- const verdictPath = join(archivedDir, 'verdict.json');
96
- const verdict = readJson(verdictPath);
97
- if (verdict && typeof verdict.ok === 'boolean' && Array.isArray(verdict.coverage)) {
98
- evidence.verdict = {
99
- ok: verdict.ok,
100
- covered: verdict.coverage.filter((item) => item?.covered === true).length,
101
- total: verdict.coverage.length,
102
- path: vaultRel(vaultBase, verdictPath),
103
- };
104
- }
105
- const sensorPath = join(archivedDir, 'evidencia.json');
106
- const sensors = readJson(sensorPath);
107
- if (Array.isArray(sensors) && sensors.length && sensors.every((item) => item?.status === 'green')) {
108
- evidence.sensors = [...new Set(sensors.map((item) => String(item.id || '')).filter(Boolean))].sort();
109
- }
110
- }
111
- }
112
-
113
- const nextAction = nextActionFrom(summary);
114
- if (nextAction) evidence.nextAction = nextAction;
115
- const commit = String(summary || '').match(/\b[0-9a-f]{40}\b/i)?.[0];
116
- if (commit) {
117
- evidence.git = {
118
- commit: commit.toLowerCase(),
119
- pushed: !/(?:nenhum|sem)\s+push/i.test(String(summary || '')),
120
- verified: false,
121
- path: noteRel,
122
- };
123
- }
124
- return evidence;
125
- }
126
-
127
- export function buildSessionMemoryEvents({
128
- projectId,
129
- identity,
130
- activation,
131
- turn,
132
- noteRel,
133
- observedAt,
134
- summary,
135
- evidence = {},
136
- }) {
137
- const context = { projectId, identity, activation, turn, observedAt };
138
- const events = [makeEvent(context, {
139
- memoryKey: 'handoff.latest',
140
- value: sanitizeMemoryText(summary),
141
- authority: 'reported',
142
- evidence: [noteRel],
143
- })];
144
-
145
- if (evidence.change?.slug && evidence.change?.status && evidence.change?.adr) {
146
- events.push(makeEvent(context, {
147
- memoryKey: `change.${evidence.change.slug}.status`,
148
- value: { status: evidence.change.status, adr: evidence.change.adr },
149
- authority: 'verified',
150
- evidence: [evidence.change.path || evidence.change.adr],
151
- }));
152
- }
153
-
154
- if (evidence.verdict?.path && typeof evidence.verdict.ok === 'boolean') {
155
- events.push(makeEvent(context, {
156
- memoryKey: 'quality.latest-verdict',
157
- value: {
158
- ok: evidence.verdict.ok,
159
- covered: Number(evidence.verdict.covered || 0),
160
- total: Number(evidence.verdict.total || 0),
161
- },
162
- authority: 'verified',
163
- evidence: [evidence.verdict.path],
164
- }));
165
- }
166
-
167
- if (Array.isArray(evidence.sensors) && evidence.sensors.length) {
168
- events.push(makeEvent(context, {
169
- memoryKey: 'quality.latest-sensors',
170
- value: [...new Set(evidence.sensors.map(String))].sort(),
171
- authority: 'verified',
172
- evidence: evidence.sensors,
173
- }));
174
- }
175
-
176
- if (evidence.git?.commit) {
177
- events.push(makeEvent(context, {
178
- memoryKey: 'git.local-head',
179
- value: {
180
- commit: evidence.git.commit,
181
- pushed: Boolean(evidence.git.pushed),
182
- push_status: evidence.git.pushed ? 'pushed' : 'nenhum push',
183
- },
184
- authority: evidence.git.verified === false ? 'reported' : 'verified',
185
- evidence: [evidence.git.path || evidence.git.commit],
186
- }));
187
- }
188
-
189
- if (evidence.nextAction?.id && evidence.nextAction?.summary) {
190
- events.push(makeEvent(context, {
191
- memoryKey: `next.${evidence.nextAction.id}`,
192
- value: sanitizeMemoryText(evidence.nextAction.summary),
193
- authority: 'verified',
194
- evidence: [noteRel],
195
- }));
196
- }
197
-
198
- return events;
199
- }
1
+ export * from '../packages/vault/src/memory-handoff.mjs';
@@ -1,89 +1 @@
1
- import { readFileSync, readdirSync } from 'node:fs';
2
- import { join } from 'node:path';
3
-
4
- import { classifySharedMemory } from './memory-schema.mjs';
5
- import { assertVaultPathSafe } from './vault-path-safety.mjs';
6
-
7
- export const LEGACY_MEMORY_WARNING = 'Vault legado: CORE+DIGEST permanece ativo; execute `wendkeep memory migrate --apply` quando a curadoria estiver pronta.';
8
-
9
- function aliasBoundaryError(error) {
10
- return error?.code === 'VAULT_PATH_UNSAFE'
11
- && /link simbólico|junction|reparse|hardlink|nlink|redirecion|escapa logicamente/i
12
- .test(String(error?.message || error));
13
- }
14
-
15
- function readText(vaultBase, path, label) {
16
- try {
17
- let checked = assertVaultPathSafe(vaultBase, path, { expectedType: 'file', label });
18
- if (!checked.exists) return { exists: false, content: '', error: null };
19
- checked = assertVaultPathSafe(vaultBase, checked.target, {
20
- allowMissing: false, expectedType: 'file', label,
21
- });
22
- return { exists: true, content: readFileSync(checked.target, 'utf8'), error: null };
23
- }
24
- catch (error) {
25
- if (aliasBoundaryError(error)) throw error;
26
- return { exists: true, content: '', error };
27
- }
28
- }
29
-
30
- function hasOutboxEvidence(vaultBase, path) {
31
- try {
32
- let checked = assertVaultPathSafe(vaultBase, path, {
33
- expectedType: 'directory', label: 'outbox de memória',
34
- });
35
- if (!checked.exists) return false;
36
- checked = assertVaultPathSafe(vaultBase, checked.target, {
37
- allowMissing: false, expectedType: 'directory', label: 'outbox de memória',
38
- });
39
- const entries = readdirSync(checked.target, { withFileTypes: true });
40
- for (const entry of entries) {
41
- assertVaultPathSafe(vaultBase, join(checked.target, entry.name), {
42
- allowMissing: false, label: `entrada ${entry.name} da outbox de memória`,
43
- });
44
- }
45
- return entries.some((entry) => entry.isFile());
46
- }
47
- catch (error) {
48
- if (aliasBoundaryError(error)) throw error;
49
- return true;
50
- }
51
- }
52
-
53
- function preflightOptionalMemoryFile(vaultBase, path, label) {
54
- try { assertVaultPathSafe(vaultBase, path, { expectedType: 'file', label }); }
55
- catch (error) {
56
- if (aliasBoundaryError(error)) throw error;
57
- // Ordinary unreadable/wrong-type layers preserve the existing degraded-mode contract.
58
- }
59
- }
60
-
61
- /** A single, read-only mode decision shared by injection, health and SessionStop. */
62
- export function detectMemoryMode(vaultBase) {
63
- const brain = join(vaultBase, '.brain');
64
- assertVaultPathSafe(vaultBase, brain, {
65
- expectedType: 'directory', label: 'raiz .brain da memória',
66
- });
67
- // Preflight the layers consumed immediately after mode detection by injection and Stop.
68
- for (const name of ['CORE.md', 'PROJECT.json', 'DIGEST.md']) {
69
- preflightOptionalMemoryFile(vaultBase, join(brain, name), `camada de memória ${name}`);
70
- }
71
- const shared = readText(vaultBase, join(brain, 'SHARED_MEMORY.md'), 'camada SHARED_MEMORY.md');
72
- if (shared.error) return { mode: 'v2', reason: 'shared-unreadable' };
73
- const classified = classifySharedMemory(shared.content);
74
- if (classified.mode === 'v2') return classified;
75
-
76
- const ledger = readText(vaultBase, join(brain, 'MEMORY_EVENTS.jsonl'), 'ledger MEMORY_EVENTS.jsonl');
77
- if (ledger.error) return { mode: 'v2', reason: 'ledger-unreadable' };
78
- const candidates = readText(
79
- vaultBase, join(brain, 'MEMORY_CANDIDATES.jsonl'), 'sidecar MEMORY_CANDIDATES.jsonl',
80
- );
81
- if (candidates.error) return { mode: 'v2', reason: 'candidates-unreadable' };
82
- const ledgerHasEvents = ledger.content.trim().length > 0;
83
- const candidatesHaveEntries = candidates.content.trim().length > 0;
84
- const outboxHasEvents = hasOutboxEvidence(vaultBase, join(brain, 'memory-outbox'));
85
- if (ledgerHasEvents || candidatesHaveEntries || outboxHasEvents) {
86
- return { mode: 'v2', reason: 'operational-evidence' };
87
- }
88
- return classified;
89
- }
1
+ export * from '../packages/vault/src/memory-mode.mjs';