wendkeep 0.67.1 → 0.67.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,29 @@ 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.67.3] — 2026-08-02
8
+
9
+ ### Fixed
10
+
11
+ - **Conflitos semânticos agora levam a uma curadoria humana executável.** O doctor diferencia
12
+ candidates em conflito de corrupção estrutural, explica que `memory repair` não escolhe um
13
+ vencedor e mostra `memory candidates --active --vault <vault>` com o Vault resolvido, seguido
14
+ pelos modelos explícitos para promover ou rejeitar a decisão.
15
+ - **A inspeção de candidates deixa de exigir leitura direta do sidecar.** O novo comando read-only
16
+ `memory candidates [--active]` devolve somente `candidate_id`, `reason`, `status`, `memory_key` e
17
+ `event_ids`, sem valores ou conteúdo, em ordem determinística e sem alterar o bundle de memória.
18
+
19
+ ## [0.67.2] — 2026-08-02
20
+
21
+ ### Fixed
22
+
23
+ - **O doctor agora exibe comandos de reparo executáveis no projeto.** Os diagnósticos de memória
24
+ e observabilidade usam `npx --no-install wendkeep`, informam o Vault absoluto resolvido em
25
+ `--vault` e
26
+ mantêm a sequência segura de inspeção (dry-run) antes da variante explícita com `--apply`.
27
+ - **O doctor continua somente leitura.** A correção altera apenas as recomendações exibidas; o
28
+ reparo continua exigindo execução humana do comando `--apply`.
29
+
7
30
  ## [0.67.1] — 2026-08-02
8
31
 
9
32
  ### Fixed
package/README.en.md CHANGED
@@ -346,20 +346,20 @@ Codex uses `session_id`/`turn_id` plus transcript order, with no artificial caus
346
346
  `DIGEST.md` is no longer the operational handoff: it remains the `/brain-recall` bridge and legacy-vault fallback. A vault without SHARED receives CORE+DIGEST with a deprecation warning; migrate during the compatibility window:
347
347
 
348
348
  ```bash
349
- wendkeep memory status --gate --vault .MyApp-vault
350
- wendkeep memory migrate --vault .MyApp-vault # preview, zero writes
351
- wendkeep memory migrate --apply --vault .MyApp-vault # backup + candidates + v2 bundle
349
+ npx --no-install wendkeep memory status --gate --vault .MyApp-vault
350
+ npx --no-install wendkeep memory migrate --vault .MyApp-vault # preview, zero writes
351
+ npx --no-install wendkeep memory migrate --apply --vault .MyApp-vault # backup + candidates + v2 bundle
352
352
  ```
353
353
 
354
354
  ### Health and recovery
355
355
 
356
- Use `wendkeep memory status --gate --vault <vault>` in CI and before `verify`/`archive`. Revision 0
356
+ Use `npx --no-install wendkeep memory status --gate --vault <vault>` in CI and before `verify`/`archive`. Revision 0
357
357
  immediately after valid migration is healthy. The gate correlates `last_memory_attempt`, outbox,
358
358
  ledger, SHARED, and checkpoint: `degraded` with a durable outbox is a warning; an ambiguous attempt,
359
359
  lost publication, or mismatched checkpoint blocks. See [migration](docs/en/commands/memory-migration.md)
360
360
  and [diagnostics](docs/en/commands/maintenance-and-diagnostics.md).
361
361
 
362
- If status blocks, preserve the evidence and run `wendkeep memory repair --vault <vault>` to back up
362
+ If status blocks, preserve the evidence and run `npx --no-install wendkeep memory repair --vault <vault>` to back up
363
363
  the corrupt ledger, retain valid lines, and re-project. Repair remains structural: its only narrow
364
364
  acknowledgement exception covers attempts entirely represented by the outbox consumed by that same
365
365
  run; it does not scan or reclassify historical attempts. Valid pre-0.59 causal checkpoints and
@@ -369,8 +369,10 @@ backup/audit; divergent mirrors fail closed. A demonstrably superseded
369
369
  ambiguity uses `memory reconcile <session> --by-session <successor>
370
370
  --reason <reason>` as a dry run and requires `--apply`; the decision is backed up and audited
371
371
  without rewriting ledger, CORE, or notes. Run `status --gate` again afterwards. Conflicts require
372
- explicit, durable curation: `memory promote <id> --event <event-id>` selects one event from the
373
- candidate, while `memory reject <id>` keeps the current value. The decision is idempotent, and a
372
+ explicit, durable curation: `memory candidates --active --vault <vault>` lists only safe IDs and
373
+ metadata in read-only mode it does not expose memory values or content. After human review,
374
+ `memory promote <id> --event <event-id>` selects one event from the candidate, while
375
+ `memory reject <id>` keeps the current value. `memory repair` does not choose a winner. The decision is idempotent, and a
374
376
  new promotion accepts a later Stop from the same session/activation without recreating a conflict.
375
377
  A promotion written by 0.66.1 remains historical: if the next Stop forms a new candidate, update
376
378
  to 0.66.3 and run `memory repair`. During replay, a transient candidate is re-evaluated against the
package/README.md CHANGED
@@ -346,20 +346,20 @@ Codex uses `session_id`/`turn_id` plus transcript order, with no artificial caus
346
346
  `DIGEST.md` is no longer the operational handoff: it remains the `/brain-recall` bridge and legacy-vault fallback. A vault without SHARED receives CORE+DIGEST with a deprecation warning; migrate during the compatibility window:
347
347
 
348
348
  ```bash
349
- wendkeep memory status --gate --vault .MyApp-vault
350
- wendkeep memory migrate --vault .MyApp-vault # preview, zero writes
351
- wendkeep memory migrate --apply --vault .MyApp-vault # backup + candidates + v2 bundle
349
+ npx --no-install wendkeep memory status --gate --vault .MyApp-vault
350
+ npx --no-install wendkeep memory migrate --vault .MyApp-vault # preview, zero writes
351
+ npx --no-install wendkeep memory migrate --apply --vault .MyApp-vault # backup + candidates + v2 bundle
352
352
  ```
353
353
 
354
354
  ### Health and recovery
355
355
 
356
- Use `wendkeep memory status --gate --vault <vault>` in CI and before `verify`/`archive`. Revision 0
356
+ Use `npx --no-install wendkeep memory status --gate --vault <vault>` in CI and before `verify`/`archive`. Revision 0
357
357
  immediately after valid migration is healthy. The gate correlates `last_memory_attempt`, outbox,
358
358
  ledger, SHARED, and checkpoint: `degraded` with a durable outbox is a warning; an ambiguous attempt,
359
359
  lost publication, or mismatched checkpoint blocks. See [migration](docs/en/commands/memory-migration.md)
360
360
  and [diagnostics](docs/en/commands/maintenance-and-diagnostics.md).
361
361
 
362
- If status blocks, preserve the evidence and run `wendkeep memory repair --vault <vault>` to back up
362
+ If status blocks, preserve the evidence and run `npx --no-install wendkeep memory repair --vault <vault>` to back up
363
363
  the corrupt ledger, retain valid lines, and re-project. Repair remains structural: its only narrow
364
364
  acknowledgement exception covers attempts entirely represented by the outbox consumed by that same
365
365
  run; it does not scan or reclassify historical attempts. Valid pre-0.59 causal checkpoints and
@@ -369,8 +369,10 @@ backup/audit; divergent mirrors fail closed. A demonstrably superseded
369
369
  ambiguity uses `memory reconcile <session> --by-session <successor>
370
370
  --reason <reason>` as a dry run and requires `--apply`; the decision is backed up and audited
371
371
  without rewriting ledger, CORE, or notes. Run `status --gate` again afterwards. Conflicts require
372
- explicit, durable curation: `memory promote <id> --event <event-id>` selects one event from the
373
- candidate, while `memory reject <id>` keeps the current value. The decision is idempotent, and a
372
+ explicit, durable curation: `memory candidates --active --vault <vault>` lists only safe IDs and
373
+ metadata in read-only mode it does not expose memory values or content. After human review,
374
+ `memory promote <id> --event <event-id>` selects one event from the candidate, while
375
+ `memory reject <id>` keeps the current value. `memory repair` does not choose a winner. The decision is idempotent, and a
374
376
  new promotion accepts a later Stop from the same session/activation without recreating a conflict.
375
377
  A promotion written by 0.66.1 remains historical: if the next Stop forms a new candidate, update
376
378
  to 0.66.3 and run `memory repair`. During replay, a transient candidate is re-evaluated against the
@@ -24,9 +24,9 @@ A consistent registry, complete price table, and transcript access for rebuilt s
24
24
  ## Syntax
25
25
 
26
26
  ```bash
27
- npx wendkeep stats [--vault <vault>] [--json]
28
- npx wendkeep cost [--since <date>] [--top [N]] [--trend day|week|month] [--write] [--json]
29
- npx wendkeep cost rebuild [--session <id|file>] [--limit N] [--max-graph-nodes N] [--max-fallback-days N] [--max-fallback-candidates N] [--apply] [--json]
27
+ npx --no-install wendkeep stats [--vault <vault>] [--json]
28
+ npx --no-install wendkeep cost [--since <date>] [--top [N]] [--trend day|week|month] [--write] [--json]
29
+ npx --no-install wendkeep cost rebuild [--session <id|file>] [--vault <vault>] [--limit N] [--max-graph-nodes N] [--max-fallback-days N] [--max-fallback-candidates N] [--apply] [--json]
30
30
  ```
31
31
 
32
32
  ## Options and exit codes
@@ -50,11 +50,11 @@ npx wendkeep cost rebuild [--session <id|file>] [--limit N] [--max-graph-nodes N
50
50
  ## Examples
51
51
 
52
52
  ```bash
53
- npx wendkeep stats --vault .MyApp-vault
54
- npx wendkeep cost --since 2026-07-01 --top 10 --trend week
55
- npx wendkeep cost rebuild --session 019abc --json
56
- npx wendkeep cost rebuild --session 019abc --max-graph-nodes 8192 --json
57
- npx wendkeep cost rebuild --session 019abc --apply
53
+ npx --no-install wendkeep stats --vault .MyApp-vault
54
+ npx --no-install wendkeep cost --since 2026-07-01 --top 10 --trend week
55
+ npx --no-install wendkeep cost rebuild --session 019abc --json --vault .MyApp-vault
56
+ npx --no-install wendkeep cost rebuild --session 019abc --max-graph-nodes 8192 --json --vault .MyApp-vault
57
+ npx --no-install wendkeep cost rebuild --session 019abc --json --vault .MyApp-vault --apply
58
58
  ```
59
59
 
60
60
  ## Expected result
@@ -23,11 +23,11 @@ Run from the project root or provide `--project` and `--vault` explicitly.
23
23
  ## Syntax
24
24
 
25
25
  ```bash
26
- npx wendkeep doctor [--vault <vault>]
27
- npx wendkeep sync-defs [--check|--reseed] --vault <vault> --project <root>
28
- npx wendkeep theme sync --vault <vault>
29
- npx wendkeep --version
30
- npx wendkeep --help
26
+ npx --no-install wendkeep doctor [--vault <vault>]
27
+ npx --no-install wendkeep sync-defs [--check|--reseed] --vault <vault> --project <root>
28
+ npx --no-install wendkeep theme sync --vault <vault>
29
+ npx --no-install wendkeep --version
30
+ npx --no-install wendkeep --help
31
31
  ```
32
32
 
33
33
  ## Options and exit codes
@@ -53,12 +53,13 @@ npx wendkeep --help
53
53
  Post-update checklist:
54
54
 
55
55
  ```bash
56
- npx wendkeep --version
57
- npx wendkeep sync-defs --check --vault .MyApp-vault --project .
58
- npx wendkeep doctor --vault .MyApp-vault
59
- npx wendkeep memory status --gate --vault .MyApp-vault
60
- npx wendkeep cost rebuild --session <id> --json
61
- npx wendkeep cost rebuild --session <id> --apply
56
+ npx --no-install wendkeep --version
57
+ npx --no-install wendkeep sync-defs --check --vault .MyApp-vault --project .
58
+ npx --no-install wendkeep doctor --vault .MyApp-vault
59
+ npx --no-install wendkeep memory status --gate --vault .MyApp-vault
60
+ npx --no-install wendkeep memory candidates --active --vault .MyApp-vault
61
+ npx --no-install wendkeep cost rebuild --session <id> --json --vault .MyApp-vault
62
+ npx --no-install wendkeep cost rebuild --session <id> --json --vault .MyApp-vault --apply
62
63
  ```
63
64
 
64
65
  ## Expected result
@@ -74,14 +75,20 @@ dry-run path before any write.
74
75
 
75
76
  - `no vault`: run from the bound root or pass `--vault`.
76
77
  - `defs stale`: confirm the version and run `sync-defs --reseed`.
77
- - Legacy vault: this is a non-blocking warning; plan `memory migrate --apply` separately.
78
+ - Legacy vault: this is a non-blocking warning; doctor shows
79
+ `npx --no-install wendkeep memory migrate --apply --vault <vault>` with the resolved Vault, but
80
+ migration remains opt-in and must be planned separately.
78
81
  - `degraded` plus an intact outbox: warning; preserve the outbox and allow idempotent replay.
79
82
  - `ambiguous`, lost publication, or a mismatched checkpoint: blocking; preserve registry, ledger,
80
83
  outbox, and SHARED so `last_memory_attempt` can be correlated before repair.
81
84
  - Corrupt bundle: preserve evidence and run `memory status --gate` before `memory repair`.
85
+ - An active semantic conflict requires a human decision: `memory repair` does not choose a winner.
86
+ List safe IDs with `memory candidates --active --vault <vault>`, review the evidence, and then use
87
+ `memory promote <candidate-id> --event <event-id> --vault <vault>` to select an event or
88
+ `memory reject <candidate-id> --vault <vault>` to keep the current operational value.
82
89
  - `legacy`/`degraded`/`stale`/`manifest-unproven` observability: run
83
- `wendkeep cost rebuild --session <id> --json`, review diagnostics, and only then authorize
84
- `--apply`.
90
+ `npx --no-install wendkeep cost rebuild --session <id> --json --vault <vault>`, review diagnostics,
91
+ and only then authorize the second variant with `--apply`.
85
92
 
86
93
  ## Next steps
87
94
 
@@ -24,6 +24,7 @@ Pass the vault explicitly in automation. Preserve backups and evidence before re
24
24
 
25
25
  ```bash
26
26
  npx wendkeep memory status [--gate] --vault <vault>
27
+ npx wendkeep memory candidates [--active] --vault <vault>
27
28
  npx wendkeep memory repair --vault <vault>
28
29
  npx wendkeep memory recover-attempt <session> [--apply] --vault <vault>
29
30
  npx wendkeep memory reconcile <ambiguous-session> --by-session <successor-session> --reason <reason> [--apply] --vault <vault>
@@ -36,6 +37,13 @@ npx wendkeep validate-memory --vault <v2-vault>
36
37
  ## Options and exit codes
37
38
 
38
39
  - `memory status` is read-only; `--gate` exits `1` only for blocking state.
40
+ - `memory candidates` is read-only and prints deterministic JSON containing only `candidate_id`,
41
+ `reason`, `status`, `memory_key`, and `event_ids`; it does not expose memory values or content and
42
+ does not create a lock or mutate the bundle. `--active` omits terminal candidates (`resolved`,
43
+ `rejected`, and `superseded`). A missing status is normalized to `active`.
44
+ - For `memory candidates`, exit `0` means a valid inventory (including empty or conflicted), exit
45
+ `1` means an invalid sidecar/unsafe topology, and exit `2` means a missing `--vault`, unknown or
46
+ duplicate option, extra argument, or an invalid value passed to `--active`.
39
47
  - `Stop` writes events to the outbox before acknowledging `last_memory_attempt: enqueued`, then the
40
48
  projector runs outside the registry lock. Retrying the same attempt reuses its frozen event IDs
41
49
  and can project them at most once.
@@ -105,6 +113,7 @@ npx wendkeep validate-memory --vault <v2-vault>
105
113
 
106
114
  ```bash
107
115
  npx wendkeep memory status --gate --vault .MyApp-vault
116
+ npx wendkeep memory candidates --active --vault .MyApp-vault
108
117
  npx wendkeep memory recover-attempt session-123 --vault .MyApp-vault
109
118
  npx wendkeep memory recover-attempt session-123 --apply --vault .MyApp-vault
110
119
  npx wendkeep memory reconcile old --by-session current --reason "delivery continued" --vault .MyApp-vault
@@ -121,6 +130,9 @@ state of the last attempt. CORE stays hand-curated and canonical; SHARED stays a
121
130
  operational projection. After successful projection, an attempt checkpoint may be a valid prefix
122
131
  of a global projection that has already advanced with concurrent events.
123
132
 
133
+ `memory candidates` returns `status: "ok"` and the sanitized candidate list in stable order; with
134
+ `--active`, the list contains only decisions still open for human curation.
135
+
124
136
  ## Common errors and diagnosis
125
137
 
126
138
  - `legacy`: follow the migration guide; this is not corruption.
@@ -24,9 +24,9 @@ Registry consistente, tabela de preços completa e acesso aos transcripts das se
24
24
  ## Sintaxe
25
25
 
26
26
  ```bash
27
- npx wendkeep stats [--vault <cofre>] [--json]
28
- npx wendkeep cost [--since <data>] [--top [N]] [--trend day|week|month] [--write] [--json]
29
- npx wendkeep cost rebuild [--session <id|arquivo>] [--limit N] [--max-graph-nodes N] [--max-fallback-days N] [--max-fallback-candidates N] [--apply] [--json]
27
+ npx --no-install wendkeep stats [--vault <cofre>] [--json]
28
+ npx --no-install wendkeep cost [--since <data>] [--top [N]] [--trend day|week|month] [--write] [--json]
29
+ npx --no-install wendkeep cost rebuild [--session <id|arquivo>] [--vault <cofre>] [--limit N] [--max-graph-nodes N] [--max-fallback-days N] [--max-fallback-candidates N] [--apply] [--json]
30
30
  ```
31
31
 
32
32
  ## Opções e códigos de saída
@@ -50,11 +50,11 @@ npx wendkeep cost rebuild [--session <id|arquivo>] [--limit N] [--max-graph-node
50
50
  ## Exemplos
51
51
 
52
52
  ```bash
53
- npx wendkeep stats --vault .MeuApp-vault
54
- npx wendkeep cost --since 2026-07-01 --top 10 --trend week
55
- npx wendkeep cost rebuild --session 019abc --json
56
- npx wendkeep cost rebuild --session 019abc --max-graph-nodes 8192 --json
57
- npx wendkeep cost rebuild --session 019abc --apply
53
+ npx --no-install wendkeep stats --vault .MeuApp-vault
54
+ npx --no-install wendkeep cost --since 2026-07-01 --top 10 --trend week
55
+ npx --no-install wendkeep cost rebuild --session 019abc --json --vault .MeuApp-vault
56
+ npx --no-install wendkeep cost rebuild --session 019abc --max-graph-nodes 8192 --json --vault .MeuApp-vault
57
+ npx --no-install wendkeep cost rebuild --session 019abc --json --vault .MeuApp-vault --apply
58
58
  ```
59
59
 
60
60
  ## Resultado esperado
@@ -23,11 +23,11 @@ Execute na raiz do projeto ou informe `--project`/`--vault` explicitamente.
23
23
  ## Sintaxe
24
24
 
25
25
  ```bash
26
- npx wendkeep doctor [--vault <cofre>]
27
- npx wendkeep sync-defs [--check|--reseed] --vault <cofre> --project <raiz>
28
- npx wendkeep theme sync --vault <cofre>
29
- npx wendkeep --version
30
- npx wendkeep --help
26
+ npx --no-install wendkeep doctor [--vault <cofre>]
27
+ npx --no-install wendkeep sync-defs [--check|--reseed] --vault <cofre> --project <raiz>
28
+ npx --no-install wendkeep theme sync --vault <cofre>
29
+ npx --no-install wendkeep --version
30
+ npx --no-install wendkeep --help
31
31
  ```
32
32
 
33
33
  ## Opções e códigos de saída
@@ -53,12 +53,13 @@ npx wendkeep --help
53
53
  Checklist pós-atualização:
54
54
 
55
55
  ```bash
56
- npx wendkeep --version
57
- npx wendkeep sync-defs --check --vault .MeuApp-vault --project .
58
- npx wendkeep doctor --vault .MeuApp-vault
59
- npx wendkeep memory status --gate --vault .MeuApp-vault
60
- npx wendkeep cost rebuild --session <id> --json
61
- npx wendkeep cost rebuild --session <id> --apply
56
+ npx --no-install wendkeep --version
57
+ npx --no-install wendkeep sync-defs --check --vault .MeuApp-vault --project .
58
+ npx --no-install wendkeep doctor --vault .MeuApp-vault
59
+ npx --no-install wendkeep memory status --gate --vault .MeuApp-vault
60
+ npx --no-install wendkeep memory candidates --active --vault .MeuApp-vault
61
+ npx --no-install wendkeep cost rebuild --session <id> --json --vault .MeuApp-vault
62
+ npx --no-install wendkeep cost rebuild --session <id> --json --vault .MeuApp-vault --apply
62
63
  ```
63
64
 
64
65
  ## Resultado esperado
@@ -74,13 +75,20 @@ ou sem manifest comprovado e oferece um caminho dry-run antes de qualquer escrit
74
75
 
75
76
  - `no vault`: execute da raiz vinculada ou passe `--vault`.
76
77
  - `defs stale`: confirme a versão e rode `sync-defs --reseed`.
77
- - Vault legado: é warning não bloqueante; planeje `memory migrate --apply` separadamente.
78
+ - Vault legado: é warning não bloqueante; o doctor mostra
79
+ `npx --no-install wendkeep memory migrate --apply --vault <cofre>` com o Vault resolvido, mas a
80
+ migração continua sendo opt-in e deve ser planejada separadamente.
78
81
  - `degraded` + outbox íntegra: warning; preserve a outbox e permita replay idempotente.
79
82
  - `ambiguous`, publicação perdida ou checkpoint divergente: bloqueante; preserve registry, ledger,
80
83
  outbox e SHARED para correlacionar `last_memory_attempt` antes de reparar.
81
84
  - Bundle corrompido: preserve a evidência e use `memory status --gate` antes de `memory repair`.
85
+ - Conflito semântico ativo exige decisão humana: `memory repair` não escolhe vencedor. Liste os IDs
86
+ seguros com `memory candidates --active --vault <cofre>`, revise a evidência e então use
87
+ `memory promote <candidate-id> --event <event-id> --vault <cofre>` para selecionar um evento ou
88
+ `memory reject <candidate-id> --vault <cofre>` para manter o valor operacional atual.
82
89
  - Observabilidade `legacy`/`degraded`/`stale`/`manifest-unproven`: rode
83
- `wendkeep cost rebuild --session <id> --json`, revise diagnostics e só então autorize `--apply`.
90
+ `npx --no-install wendkeep cost rebuild --session <id> --json --vault <cofre>`, revise diagnostics
91
+ e só então autorize a segunda variante com `--apply`.
84
92
 
85
93
  ## Próximos passos
86
94
 
@@ -24,6 +24,7 @@ Informe o vault explicitamente em automações. Preserve backups e evidências a
24
24
 
25
25
  ```bash
26
26
  npx wendkeep memory status [--gate] --vault <cofre>
27
+ npx wendkeep memory candidates [--active] --vault <cofre>
27
28
  npx wendkeep memory repair --vault <cofre>
28
29
  npx wendkeep memory recover-attempt <sessão> [--apply] --vault <cofre>
29
30
  npx wendkeep memory reconcile <sessão-ambígua> --by-session <sessão-sucessora> --reason <motivo> [--apply] --vault <cofre>
@@ -36,6 +37,13 @@ npx wendkeep validate-memory --vault <cofre-v2>
36
37
  ## Opções e códigos de saída
37
38
 
38
39
  - `memory status` é read-only; `--gate` retorna exit `1` apenas para estado bloqueante.
40
+ - `memory candidates` é read-only e imprime JSON determinístico com somente `candidate_id`,
41
+ `reason`, `status`, `memory_key` e `event_ids`; não expõe valores nem conteúdo da memória e não
42
+ cria lock nem altera o bundle. `--active` omite candidates terminais (`resolved`, `rejected` e
43
+ `superseded`). Status ausente é normalizado para `active`.
44
+ - Em `memory candidates`, exit `0` indica inventário válido (inclusive vazio ou com conflitos),
45
+ exit `1` indica sidecar inválido/topologia insegura e exit `2` indica `--vault` ausente, opção
46
+ desconhecida/duplicada, argumento extra ou valor indevido em `--active`.
39
47
  - O `Stop` grava os eventos na outbox antes de reconhecer `last_memory_attempt: enqueued`; depois o
40
48
  projector roda fora do lock do registry. Retry do mesmo attempt reutiliza os event IDs congelados
41
49
  e pode projetá-los no máximo uma vez.
@@ -103,6 +111,7 @@ npx wendkeep validate-memory --vault <cofre-v2>
103
111
 
104
112
  ```bash
105
113
  npx wendkeep memory status --gate --vault .MeuApp-vault
114
+ npx wendkeep memory candidates --active --vault .MeuApp-vault
106
115
  npx wendkeep memory recover-attempt sessao-123 --vault .MeuApp-vault
107
116
  npx wendkeep memory recover-attempt sessao-123 --apply --vault .MeuApp-vault
108
117
  npx wendkeep memory reconcile antiga --by-session atual --reason "entrega continuada" --vault .MeuApp-vault
@@ -119,6 +128,9 @@ causal do último attempt. CORE permanece canônico e curado à mão; SHARED per
119
128
  operacional verificável. Depois de uma projeção bem-sucedida, o checkpoint do attempt pode ser um
120
129
  prefixo válido de uma projeção global que já avançou com eventos concorrentes.
121
130
 
131
+ `memory candidates` retorna `status: "ok"` e a lista sanitizada de candidates em ordem estável;
132
+ com `--active`, a lista contém somente decisões ainda abertas para curadoria humana.
133
+
122
134
  ## Erros comuns e diagnóstico
123
135
 
124
136
  - `legacy`: siga o guia de migração; não é corrupção.
@@ -8,7 +8,12 @@ import { buildEffectiveRequirementPackage, checkSpecsState, evaluateVerdict, tas
8
8
  import { getLocale } from './locale.mjs';
9
9
  import { priceForModel } from './token-usage.mjs';
10
10
  import { countMissing, indexDerivedBySession, listSessionNotes, missingDerivedLinks } from './derived-sections.mjs';
11
- import { readControl, readSessionRegistry } from './obsidian-common.mjs';
11
+ import {
12
+ quoteCommandArgument,
13
+ readControl,
14
+ readSessionRegistry,
15
+ WENDKEEP_COMMAND,
16
+ } from './obsidian-common.mjs';
12
17
  import { parseObservabilityCheckpoint } from './session-observability-state.mjs';
13
18
  import { readObservabilityStore } from './session-observability-store.mjs';
14
19
  import { assessObservabilityFreshness } from './session-observability-lifecycle.mjs';
@@ -20,6 +25,7 @@ export function checkSessionObservability(vaultBase, deps = {}) {
20
25
  const statSource = deps.statSource || statSync;
21
26
  const registry = readRegistry(vaultBase);
22
27
  const result = { ok: true, scanned: 0, healthy: 0, issues: [] };
28
+ const commandPrefix = `${WENDKEEP_COMMAND} cost rebuild`;
23
29
  const entries = Object.entries(registry?.sessions || {})
24
30
  .map(([sessionId, entry]) => ({ sessionId, ...entry }))
25
31
  .filter((entry) => entry.session_file)
@@ -32,7 +38,7 @@ export function checkSessionObservability(vaultBase, deps = {}) {
32
38
  try { content = readNote(notePath, 'utf8'); } catch { continue; }
33
39
  if (!entry.provider && /^provider:\s*["']?claude/m.test(content)) continue;
34
40
  result.scanned += 1;
35
- const command = `wendkeep cost rebuild --session ${entry.sessionId} --json`;
41
+ const command = `${commandPrefix} --session ${quoteCommandArgument(entry.sessionId)} --json --vault ${quoteCommandArgument(vaultBase)}`;
36
42
  const checkpoint = parseObservabilityCheckpoint(content);
37
43
  if (!checkpoint) {
38
44
  result.issues.push({
@@ -67,6 +67,14 @@ export function getVaultBase(input = {}) {
67
67
  return resolveVault(input).base;
68
68
  }
69
69
 
70
+ // Diagnostic commands must use the project-local package even when `wendkeep` is not installed
71
+ // globally. Quotes preserve resolved Windows paths with spaces in copy/pasteable output.
72
+ export const WENDKEEP_COMMAND = 'npx --no-install wendkeep';
73
+
74
+ export function quoteCommandArgument(value) {
75
+ return `"${String(value ?? '').replaceAll('"', '\\"')}"`;
76
+ }
77
+
70
78
  // Diagnostic logger. No-op unless WENDKEEP_DEBUG is set, so it never pollutes the
71
79
  // stdout hook contract during normal runs but makes fail-open paths debuggable.
72
80
  export function debugLog(...args) {
@@ -6,8 +6,10 @@ import {
6
6
  controlPath,
7
7
  getVaultBase,
8
8
  listMarkdownFiles,
9
+ quoteCommandArgument,
9
10
  readControl,
10
11
  readSessionRegistry,
12
+ WENDKEEP_COMMAND,
11
13
  wikilinkFromRel,
12
14
  } from './obsidian-common.mjs';
13
15
  import { getLocale } from './locale.mjs';
@@ -98,8 +100,24 @@ function linkedNotesFromSession(content) {
98
100
  return notes;
99
101
  }
100
102
 
101
- const MEMORY_STATUS_COMMAND = 'wendkeep memory status --gate --vault <vault>';
102
- const MEMORY_REPAIR_COMMAND = 'wendkeep memory repair --vault <vault>';
103
+ const memoryStatusCommand = (vaultBase) => (
104
+ `${WENDKEEP_COMMAND} memory status --gate --vault ${quoteCommandArgument(vaultBase)}`
105
+ );
106
+ const memoryRepairCommand = (vaultBase) => (
107
+ `${WENDKEEP_COMMAND} memory repair --vault ${quoteCommandArgument(vaultBase)}`
108
+ );
109
+ const memoryMigrateCommand = (vaultBase) => (
110
+ `${WENDKEEP_COMMAND} memory migrate --apply --vault ${quoteCommandArgument(vaultBase)}`
111
+ );
112
+ const memoryCandidatesCommand = (vaultBase) => (
113
+ `${WENDKEEP_COMMAND} memory candidates --active --vault ${quoteCommandArgument(vaultBase)}`
114
+ );
115
+ const memoryPromoteCommand = (vaultBase) => (
116
+ `${WENDKEEP_COMMAND} memory promote <candidate-id> --event <event-id> --vault ${quoteCommandArgument(vaultBase)}`
117
+ );
118
+ const memoryRejectCommand = (vaultBase) => (
119
+ `${WENDKEEP_COMMAND} memory reject <candidate-id> --vault ${quoteCommandArgument(vaultBase)}`
120
+ );
103
121
 
104
122
  function readJsonLines(vaultBase, path, label) {
105
123
  let checked;
@@ -279,13 +297,13 @@ function checkMemoryAttempts(registry, {
279
297
  : [];
280
298
 
281
299
  if (state === 'skipped' && disposition === 'ambiguous') {
282
- failures.push(`Lifecycle de memória v2 ambíguo: Stop pulou a publicação sem identidade causal suficiente. Inspecione com: ${MEMORY_STATUS_COMMAND}.`);
300
+ failures.push(`Lifecycle de memória v2 ambíguo: Stop pulou a publicação sem identidade causal suficiente. Inspecione com: ${memoryStatusCommand(vaultBase)}.`);
283
301
  continue;
284
302
  }
285
303
 
286
304
  if (state === 'skipped' && ['stale_turn', 'superseded'].includes(disposition)) {
287
305
  if (eventIds.length) {
288
- failures.push(`Stop stale/superseded emitiu event_ids apesar da rejeição causal. Inspecione com: ${MEMORY_STATUS_COMMAND}.`);
306
+ failures.push(`Stop stale/superseded emitiu event_ids apesar da rejeição causal. Inspecione com: ${memoryStatusCommand(vaultBase)}.`);
289
307
  } else {
290
308
  warnings.push('Stop stale/superseded foi descartado sem publicar memória.');
291
309
  }
@@ -294,12 +312,12 @@ function checkMemoryAttempts(registry, {
294
312
 
295
313
  if (disposition !== 'applied') {
296
314
  if (state === 'skipped') warnings.push('Attempt de memória v2 foi descartado sem publicação.');
297
- else failures.push(`Attempt de memória v2 possui disposition não reconhecida para o estado informado. Inspecione com: ${MEMORY_STATUS_COMMAND}.`);
315
+ else failures.push(`Attempt de memória v2 possui disposition não reconhecida para o estado informado. Inspecione com: ${memoryStatusCommand(vaultBase)}.`);
298
316
  continue;
299
317
  }
300
318
 
301
319
  if (!eventIds.length) {
302
- failures.push(`Attempt v2 aplicado não declarou event_ids; publicação perdida. Inspecione com: ${MEMORY_STATUS_COMMAND}.`);
320
+ failures.push(`Attempt v2 aplicado não declarou event_ids; publicação perdida. Inspecione com: ${memoryStatusCommand(vaultBase)}.`);
303
321
  continue;
304
322
  }
305
323
 
@@ -317,7 +335,7 @@ function checkMemoryAttempts(registry, {
317
335
  if (typeof attempt.turn_id !== 'string' || !attempt.turn_id) invalidAttemptFields.push('turn_id');
318
336
  if (!Number.isInteger(attempt.turn_sequence) || attempt.turn_sequence < 0) invalidAttemptFields.push('turn_sequence');
319
337
  if (invalidAttemptFields.length) {
320
- failures.push(`Attempt v2 da sessão ${sessionId} possui identidade causal inválida (${invalidAttemptFields.join(', ')}). Inspecione com: ${MEMORY_STATUS_COMMAND}.`);
338
+ failures.push(`Attempt v2 da sessão ${sessionId} possui identidade causal inválida (${invalidAttemptFields.join(', ')}). Inspecione com: ${memoryStatusCommand(vaultBase)}.`);
321
339
  continue;
322
340
  }
323
341
  const causalMismatches = [];
@@ -330,14 +348,14 @@ function checkMemoryAttempts(registry, {
330
348
  if (fields.length) causalMismatches.push(`${eventId}: ${fields.join(', ')}`);
331
349
  }
332
350
  if (causalMismatches.length) {
333
- failures.push(`Attempt v2 da sessão ${sessionId} referencia evento(s) com identidade causal divergente (${causalMismatches.join('; ')}). Inspecione com: ${MEMORY_STATUS_COMMAND}.`);
351
+ failures.push(`Attempt v2 da sessão ${sessionId} referencia evento(s) com identidade causal divergente (${causalMismatches.join('; ')}). Inspecione com: ${memoryStatusCommand(vaultBase)}.`);
334
352
  continue;
335
353
  }
336
354
 
337
355
  if (state === 'enqueued' || state === 'degraded') {
338
356
  const missing = eventIds.filter((eventId) => !ledgerEventIds.has(eventId) && !outboxEventIds.has(eventId));
339
357
  if (missing.length) {
340
- failures.push(`Attempt v2 perdeu ${missing.length} evento(s): ausentes do ledger e da outbox. Inspecione com: ${MEMORY_STATUS_COMMAND}.`);
358
+ failures.push(`Attempt v2 perdeu ${missing.length} evento(s): ausentes do ledger e da outbox. Inspecione com: ${memoryStatusCommand(vaultBase)}.`);
341
359
  } else if (
342
360
  state === 'enqueued'
343
361
  && eventIds.every((eventId) => ledgerEventIds.has(eventId))
@@ -353,14 +371,14 @@ function checkMemoryAttempts(registry, {
353
371
  if (state === 'projected') {
354
372
  const outsideLedger = eventIds.filter((eventId) => !ledgerEventIds.has(eventId));
355
373
  if (outsideLedger.length) {
356
- failures.push(`Attempt projetado perdeu ${outsideLedger.length} evento(s) no ledger. Inspecione com: ${MEMORY_STATUS_COMMAND}.`);
374
+ failures.push(`Attempt projetado perdeu ${outsideLedger.length} evento(s) no ledger. Inspecione com: ${memoryStatusCommand(vaultBase)}.`);
357
375
  } else if (!checkpointMatchesLedgerPrefix(attempt.checkpoint, eventIds, ledgerEvents, vaultBase)) {
358
- failures.push(`Checkpoint do attempt projetado diverge do prefixo rederivado do ledger. Inspecione com: ${MEMORY_STATUS_COMMAND}.`);
376
+ failures.push(`Checkpoint do attempt projetado diverge do prefixo rederivado do ledger. Inspecione com: ${memoryStatusCommand(vaultBase)}.`);
359
377
  }
360
378
  continue;
361
379
  }
362
380
 
363
- failures.push(`Attempt de memória v2 possui state inválido. Inspecione com: ${MEMORY_STATUS_COMMAND}.`);
381
+ failures.push(`Attempt de memória v2 possui state inválido. Inspecione com: ${memoryStatusCommand(vaultBase)}.`);
364
382
  }
365
383
 
366
384
  return { failures, warnings };
@@ -391,7 +409,7 @@ export function checkMemoryBundle(vaultBase, { registry } = {}) {
391
409
  ok: true,
392
410
  status: 'legacy',
393
411
  failures: [],
394
- warnings: [LEGACY_MEMORY_WARNING],
412
+ warnings: [`${LEGACY_MEMORY_WARNING} Comando com Vault resolvido: ${memoryMigrateCommand(vaultBase)}.`],
395
413
  metrics: memoryMetrics(),
396
414
  };
397
415
  }
@@ -410,26 +428,26 @@ export function checkMemoryBundle(vaultBase, { registry } = {}) {
410
428
  const ledgerCorrupt = (bundle.ledger?.errors || []).length > 0;
411
429
  if (ledgerCorrupt) {
412
430
  for (const error of bundle.ledger.errors) {
413
- failures.push(`${error} Execute com segurança: ${MEMORY_REPAIR_COMMAND}.`);
431
+ failures.push(`${error} Execute com segurança: ${memoryRepairCommand(vaultBase)}.`);
414
432
  }
415
433
  }
416
434
  for (const error of bundle.errors || []) {
417
435
  if (error.startsWith('ledger:')) continue;
418
- failures.push(`${error} Inspecione com: ${MEMORY_STATUS_COMMAND}.`);
436
+ failures.push(`${error} Inspecione com: ${memoryStatusCommand(vaultBase)}.`);
419
437
  }
420
438
 
421
439
  if (outbox.errors.length) {
422
- failures.push(`Outbox corrompida (${outbox.errors.join('; ')}). Inspecione com: ${MEMORY_STATUS_COMMAND}.`);
440
+ failures.push(`Outbox corrompida (${outbox.errors.join('; ')}). Inspecione com: ${memoryStatusCommand(vaultBase)}.`);
423
441
  }
424
442
  if (candidates.errors.length) {
425
- failures.push(`${candidates.errors.join('; ')} Execute com segurança: ${MEMORY_REPAIR_COMMAND}.`);
443
+ failures.push(`${candidates.errors.join('; ')} Execute com segurança: ${memoryRepairCommand(vaultBase)}.`);
426
444
  }
427
445
 
428
446
  let replay = null;
429
447
  if (bundle.ledger?.ok) {
430
448
  try { replay = deriveMemoryProjection(vaultBase, bundle.ledger.events); }
431
449
  catch (error) {
432
- failures.push(`Ledger não pode ser reduzido: ${error.message}. Execute com segurança: ${MEMORY_REPAIR_COMMAND}.`);
450
+ failures.push(`Ledger não pode ser reduzido: ${error.message}. Execute com segurança: ${memoryRepairCommand(vaultBase)}.`);
433
451
  }
434
452
  }
435
453
  if (replay && bundle.shared?.ok) {
@@ -438,7 +456,7 @@ export function checkMemoryBundle(vaultBase, { registry } = {}) {
438
456
  if (metadata.event_cursor !== replay.ledgerCursor) divergences.push(`event_cursor ${metadata.event_cursor} != ${replay.ledgerCursor}`);
439
457
  if (metadata.state_hash !== replay.stateHash) divergences.push(`state_hash ${metadata.state_hash} != ${replay.stateHash}`);
440
458
  if (divergences.length) {
441
- failures.push(`Projeção SHARED stale/lag (${divergences.join('; ')}). Inspecione com: ${MEMORY_STATUS_COMMAND}.`);
459
+ failures.push(`Projeção SHARED stale/lag (${divergences.join('; ')}). Inspecione com: ${memoryStatusCommand(vaultBase)}.`);
442
460
  }
443
461
  }
444
462
 
@@ -460,7 +478,12 @@ export function checkMemoryBundle(vaultBase, { registry } = {}) {
460
478
  const activeConflicts = unresolved.filter((item) => item?.reason === 'conflict');
461
479
  const ordinaryCandidates = unresolved.filter((item) => item?.reason !== 'conflict');
462
480
  if (activeConflicts.length) {
463
- failures.push(`${activeConflicts.length} conflito ativo em chave operacional (${activeConflicts.map((item) => item.memory_key || item.candidate_id).join(', ')}). Inspecione com: ${MEMORY_STATUS_COMMAND}.`);
481
+ const keys = [...new Set(activeConflicts.map((item) => item.memory_key || item.candidate_id))]
482
+ .sort((left, right) => String(left).localeCompare(String(right)));
483
+ const label = activeConflicts.length === 1
484
+ ? '1 conflito ativo'
485
+ : `${activeConflicts.length} conflitos ativos`;
486
+ failures.push(`${label} em chave operacional (${keys.join(', ')}). Conflito semântico exige curadoria humana; memory repair não escolhe vencedor. Liste IDs seguros com: ${memoryCandidatesCommand(vaultBase)}. Depois da revisão, use ${memoryPromoteCommand(vaultBase)} ou ${memoryRejectCommand(vaultBase)}.`);
464
487
  }
465
488
  if (outbox.count) warnings.push(`${outbox.count} evento(s) pendente(s) na outbox; execute o projector quando seguro.`);
466
489
  if (ordinaryCandidates.length) warnings.push(`${ordinaryCandidates.length} candidate(s) aguardando curadoria humana.`);
@@ -585,7 +608,7 @@ export function runVaultHealth({ vaultBase, session = '' }) {
585
608
  failures.push(...memory.failures.map((item) => `Memória: ${item}`));
586
609
  warnings.push(...memory.warnings.map((item) => `Memória: ${item}`));
587
610
  } else {
588
- warnings.push(`Bundle de memória v2 ausente (vault legado); inspecione com: ${MEMORY_STATUS_COMMAND}.`);
611
+ warnings.push(`Bundle de memória v2 ausente (vault legado); inspecione com: ${memoryStatusCommand(vaultBase)}.`);
589
612
  }
590
613
 
591
614
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wendkeep",
3
- "version": "0.67.1",
3
+ "version": "0.67.3",
4
4
  "description": "Vault-first persistent memory for AI coding agents, with an optional profile-aware governance runtime: OFF, FLOW, GUIDE, GOVERN, or ASSURE. Local-first and agent-agnostic (Claude Code, Codex, Cursor…).",
5
5
  "type": "module",
6
6
  "workspaces": [
@@ -70,6 +70,6 @@
70
70
  },
71
71
  "devDependencies": {
72
72
  "acorn": "^8.18.0",
73
- "wendkeep": "^0.67.0"
73
+ "wendkeep": "^0.67.2"
74
74
  }
75
75
  }
@@ -99,7 +99,7 @@ Usage:
99
99
  in session notes from the linked derived notes — the body used to lag
100
100
  behind the closing block. Dry-run by default · --apply · --json.
101
101
  wendkeep lesson add "t" "l" Record a project-local lesson (injected at SessionStart).
102
- wendkeep memory <sub> Shared memory v2: status | migrate [--apply] | repair |
102
+ wendkeep memory <sub> Shared memory v2: status | candidates [--active] | migrate [--apply] | repair |
103
103
  recover-attempt <session> [--apply] |
104
104
  reconcile <session> --by-session <session> --reason <text> [--apply] |
105
105
  promote <candidate> [--event <event-id>] | reject <candidate>. --vault P.
package/src/memory.mjs CHANGED
@@ -237,6 +237,48 @@ function readCandidates(vault) {
237
237
  .split('\n').filter(Boolean).map((line) => JSON.parse(line));
238
238
  }
239
239
 
240
+ const TERMINAL_CANDIDATE_STATUSES = new Set(['resolved', 'rejected', 'superseded']);
241
+
242
+ function lexicalCompare(left, right) {
243
+ if (left < right) return -1;
244
+ if (left > right) return 1;
245
+ return 0;
246
+ }
247
+
248
+ function sanitizedCandidate(candidate, index) {
249
+ if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate)) {
250
+ throw new Error(`MEMORY_CANDIDATES.jsonl: candidate ${index + 1} inválido.`);
251
+ }
252
+ const required = ['candidate_id', 'reason', 'memory_key'];
253
+ const missing = required.filter((field) => typeof candidate[field] !== 'string' || !candidate[field]);
254
+ if (missing.length) {
255
+ throw new Error(`MEMORY_CANDIDATES.jsonl: candidate ${index + 1} sem ${missing.join(', ')}.`);
256
+ }
257
+ if (candidate.status !== undefined && (typeof candidate.status !== 'string' || !candidate.status)) {
258
+ throw new Error(`MEMORY_CANDIDATES.jsonl: candidate ${index + 1} possui status inválido.`);
259
+ }
260
+ const eventIds = candidate.event_ids ?? [];
261
+ if (!Array.isArray(eventIds) || eventIds.some((eventId) => typeof eventId !== 'string' || !eventId)) {
262
+ throw new Error(`MEMORY_CANDIDATES.jsonl: candidate ${index + 1} possui event_ids inválidos.`);
263
+ }
264
+ return {
265
+ candidate_id: candidate.candidate_id,
266
+ reason: candidate.reason,
267
+ status: candidate.status || 'active',
268
+ memory_key: candidate.memory_key,
269
+ event_ids: [...eventIds].sort(lexicalCompare),
270
+ };
271
+ }
272
+
273
+ export function listMemoryCandidates(vault, { activeOnly = false } = {}) {
274
+ const candidates = readCandidates(vault)
275
+ .map(sanitizedCandidate)
276
+ .filter((candidate) => !activeOnly || !TERMINAL_CANDIDATE_STATUSES.has(candidate.status))
277
+ .sort((left, right) => lexicalCompare(left.memory_key, right.memory_key)
278
+ || lexicalCompare(left.candidate_id, right.candidate_id));
279
+ return { status: 'ok', candidates };
280
+ }
281
+
240
282
  function priorCandidateDecision(vault, candidateId) {
241
283
  return readMemoryLedger(vault).events.find(
242
284
  (event) => event.candidate_decision?.candidate_id === candidateId,
@@ -1897,10 +1939,49 @@ function parseRecoverAttemptArgs(argv) {
1897
1939
  };
1898
1940
  }
1899
1941
 
1942
+ function parseCandidatesArgs(argv) {
1943
+ const seen = new Set();
1944
+ let activeOnly = false;
1945
+ let vault = '';
1946
+
1947
+ for (let index = 1; index < argv.length; index += 1) {
1948
+ const token = argv[index];
1949
+ if (!token.startsWith('--')) {
1950
+ throw memoryUsageError(`memory candidates recebeu argumento posicional extra: ${token}.`);
1951
+ }
1952
+
1953
+ const equalAt = token.indexOf('=');
1954
+ const name = equalAt >= 0 ? token.slice(0, equalAt) : token;
1955
+ if (name !== '--active' && name !== '--vault') {
1956
+ throw memoryUsageError(`memory candidates recebeu opção desconhecida: ${name}.`);
1957
+ }
1958
+ if (seen.has(name)) {
1959
+ throw memoryUsageError(`memory candidates recebeu opção duplicada: ${name}.`);
1960
+ }
1961
+ seen.add(name);
1962
+
1963
+ if (name === '--active') {
1964
+ if (equalAt >= 0) throw memoryUsageError('--active não aceita valor.');
1965
+ activeOnly = true;
1966
+ continue;
1967
+ }
1968
+
1969
+ const value = equalAt >= 0 ? token.slice(equalAt + 1) : argv[index + 1];
1970
+ if (!value || !value.trim() || value.startsWith('--')) {
1971
+ throw memoryUsageError('--vault requer valor não vazio que não comece com --.');
1972
+ }
1973
+ vault = value;
1974
+ if (equalAt < 0) index += 1;
1975
+ }
1976
+
1977
+ return { activeOnly, vault };
1978
+ }
1979
+
1900
1980
  export function runMemory(argv) {
1901
1981
  const [sub, positional] = argv;
1902
1982
  let reconcileArgs = null;
1903
1983
  let recoverAttemptArgs = null;
1984
+ let candidatesArgs = null;
1904
1985
  if (sub === 'reconcile') {
1905
1986
  try {
1906
1987
  reconcileArgs = parseReconcileArgs(argv);
@@ -1919,14 +2000,26 @@ export function runMemory(argv) {
1919
2000
  return;
1920
2001
  }
1921
2002
  }
2003
+ if (sub === 'candidates') {
2004
+ try {
2005
+ candidatesArgs = parseCandidatesArgs(argv);
2006
+ } catch (error) {
2007
+ process.stderr.write(`wendkeep memory: ${error.message}\n`);
2008
+ process.exitCode = error.code === 'WENDKEEP_MEMORY_USAGE' ? 2 : 1;
2009
+ return;
2010
+ }
2011
+ }
1922
2012
  const vault = (
1923
- recoverAttemptArgs?.vault || reconcileArgs?.vault || option(argv, '--vault')
2013
+ recoverAttemptArgs?.vault || reconcileArgs?.vault || candidatesArgs?.vault || option(argv, '--vault')
1924
2014
  ) || process.env.OBSIDIAN_VAULT_PATH;
1925
2015
  if (!vault) { process.stderr.write('wendkeep memory: passe --vault <path>.\n'); process.exitCode = 2; return; }
1926
2016
  if (!existsSync(vault)) { process.stderr.write(`wendkeep memory: not found: ${vault}\n`); process.exitCode = 2; return; }
1927
2017
  try {
1928
2018
  let result;
1929
2019
  if (sub === 'status') result = memoryStatus(vault);
2020
+ else if (sub === 'candidates') {
2021
+ result = listMemoryCandidates(vault, { activeOnly: candidatesArgs.activeOnly });
2022
+ }
1930
2023
  else if (sub === 'migrate') result = migrateMemory(vault, { apply: argv.includes('--apply') });
1931
2024
  else if (sub === 'repair') result = repairMemory(vault);
1932
2025
  else if (sub === 'reconcile') {
@@ -1950,7 +2043,7 @@ export function runMemory(argv) {
1950
2043
  action: sub, candidateId: positional, ...(eventId ? { eventId } : {}),
1951
2044
  });
1952
2045
  }
1953
- else { process.stderr.write('wendkeep memory: use status | migrate [--apply] | repair | recover-attempt <session> [--apply] | reconcile <session> --by-session <session> --reason <text> [--apply] | promote <candidate> [--event <event-id>] | reject <candidate>.\n'); process.exitCode = 2; return; }
2046
+ else { process.stderr.write('wendkeep memory: use status | candidates [--active] | migrate [--apply] | repair | recover-attempt <session> [--apply] | reconcile <session> --by-session <session> --reason <text> [--apply] | promote <candidate> [--event <event-id>] | reject <candidate>.\n'); process.exitCode = 2; return; }
1954
2047
  process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
1955
2048
  if (sub === 'status' && argv.includes('--gate')) process.exitCode = result.status === 'blocked' ? 1 : 0;
1956
2049
  else if (sub === 'reconcile' && reconcileArgs.apply) process.exitCode = result.health?.status === 'blocked' ? 1 : 0;