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 +23 -0
- package/README.en.md +9 -7
- package/README.md +9 -7
- package/docs/en/commands/costs-and-observability.md +8 -8
- package/docs/en/commands/maintenance-and-diagnostics.md +21 -14
- package/docs/en/commands/memory.md +12 -0
- package/docs/pt-BR/commands/costs-and-observability.md +8 -8
- package/docs/pt-BR/commands/maintenance-and-diagnostics.md +21 -13
- package/docs/pt-BR/commands/memory.md +12 -0
- package/hooks/harness-doctor.mjs +8 -2
- package/hooks/obsidian-common.mjs +8 -0
- package/hooks/vault-health.mjs +44 -21
- package/package.json +2 -2
- package/packages/cli/src/index.mjs +1 -1
- package/src/memory.mjs +95 -2
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
|
|
373
|
-
|
|
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
|
|
373
|
-
|
|
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
|
|
61
|
-
npx wendkeep cost rebuild --session <id> --
|
|
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;
|
|
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
|
|
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
|
|
61
|
-
npx wendkeep cost rebuild --session <id> --
|
|
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;
|
|
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
|
|
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.
|
package/hooks/harness-doctor.mjs
CHANGED
|
@@ -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 {
|
|
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 =
|
|
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) {
|
package/hooks/vault-health.mjs
CHANGED
|
@@ -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
|
|
102
|
-
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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
|
-
|
|
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: ${
|
|
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.
|
|
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.
|
|
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;
|