wendkeep 0.67.0 → 0.67.2

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,41 @@ 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.2] — 2026-08-02
8
+
9
+ ### Fixed
10
+
11
+ - **O doctor agora exibe comandos de reparo executáveis no projeto.** Os diagnósticos de memória
12
+ e observabilidade usam `npx --no-install wendkeep`, informam o Vault absoluto resolvido em
13
+ `--vault` e
14
+ mantêm a sequência segura de inspeção (dry-run) antes da variante explícita com `--apply`.
15
+ - **O doctor continua somente leitura.** A correção altera apenas as recomendações exibidas; o
16
+ reparo continua exigindo execução humana do comando `--apply`.
17
+
18
+ ## [0.67.1] — 2026-08-02
19
+
20
+ ### Fixed
21
+
22
+ - **Turnos de subagents não bloqueiam mais a captura da sessão-mãe.** `UserPromptSubmit` de rollout
23
+ filho registra o path para observabilidade sem avançar a sequência principal, e `SessionStop`
24
+ prefere o mapeamento causal `turn_sequences[turn_id]` antes da ordem local do transcript. Isso
25
+ preserva os marcadores `wk-turn`, o encerramento e a memória mesmo com filhos intercalados.
26
+ - **`SubagentStop` consome o transcript correto do payload Codex.** O hook resolve o filho por
27
+ `agent_transcript_path`/`agentTranscriptPath`, valida identidade, sessão e `parent_thread_id`
28
+ contra um root comprovado e mantém `transcript_path` como contexto da sessão-mãe, permitindo
29
+ preencher `Subagents e workflows` no último filho sem aceitar sinais de outra cadeia.
30
+ - **O backfill não grava o turno Codex ainda em execução.** `hook session-backfill` exige
31
+ `task_complete`, separa `missingTurns` de `incompleteTurns`, permanece dry-run por padrão e aplica
32
+ somente turnos concluídos de forma idempotente. A CLI agora encaminha `--session`, `--vault`,
33
+ `--write` e demais argumentos ao hook executado. README e guias PT-BR/EN documentam o reparo.
34
+ - **O bloco gerenciado do `AGENTS.md` exige roteamento adaptativo antes de editar.** O harness
35
+ inspeciona o perfil-base, classifica a solicitação, registra uma lease temporária entre `FLOW`,
36
+ `GUIDE`, `GOVERN` e `ASSURE` e confirma o perfil efetivo; `OFF` nunca é escolhido pela LLM e a
37
+ escolha explícita do usuário prevalece.
38
+ - **O scanner de privacidade distingue configuração pública de identificadores privados.** Comandos
39
+ longos legítimos em `wendkeep.sensors.json` deixam de gerar falso positivo, sem permitir que UUID,
40
+ hash, token compacto, campo desconhecido ou path absoluto sejam ocultados pela exceção.
41
+
7
42
  ## [0.67.0] — 2026-08-01
8
43
 
9
44
  ### Added
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
@@ -385,7 +385,7 @@ acknowledgement pending on 0.66.4 or later, first run the targeted dry run
385
385
  registry/checkpoint. See syntax, preconditions, and fail-closed behavior in
386
386
  [memory and curation](docs/en/commands/memory.md).
387
387
 
388
- Session notes use one live `## Agentes, tokens e custos` snapshot. Main-agent and subagent hooks recompose it atomically, with costs, token dimensions, reasoning tokens and effort per model/source. Every hook that rewrites a session note takes a per-file lock and writes through a temp file + rename, so the `SubagentStop` fan-out (one hook run per subagent) can never leave a note half-written; a note whose frontmatter reads back damaged is left untouched rather than patched.
388
+ Session notes use one live `## Agentes, tokens e custos` snapshot. Main-agent and subagent hooks recompose it atomically, with costs, token dimensions, reasoning tokens and effort per model/source. On Codex, subagent prompts register their rollout for observability without advancing the main agent's sequence; `SubagentStop` reads the child from `agent_transcript_path` and persists its signal only when `parent_thread_id` matches a validated session root. The main Stop uses the registry's causal `turn_id` mapping before falling back to local transcript order. To recover missing markers while a conversation is open, `hook session-backfill` is a dry-run by default and never writes a Codex turn without `task_complete`. Every hook that rewrites a session note takes a per-file lock and writes through a temp file + rename, so the `SubagentStop` fan-out (one hook run per subagent) can never leave a note half-written; a note whose frontmatter reads back damaged is left untouched rather than patched.
389
389
 
390
390
  ## Retroactive memory (`import`) — install today, remember yesterday
391
391
 
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
@@ -385,7 +385,7 @@ acknowledgement pending on 0.66.4 or later, first run the targeted dry run
385
385
  registry/checkpoint. See syntax, preconditions, and fail-closed behavior in
386
386
  [memory and curation](docs/en/commands/memory.md).
387
387
 
388
- Session notes use one live `## Agentes, tokens e custos` snapshot. Main-agent and subagent hooks recompose it atomically, with costs, token dimensions, reasoning tokens and effort per model/source. Every hook that rewrites a session note takes a per-file lock and writes through a temp file + rename, so the `SubagentStop` fan-out (one hook run per subagent) can never leave a note half-written; a note whose frontmatter reads back damaged is left untouched rather than patched.
388
+ Session notes use one live `## Agentes, tokens e custos` snapshot. Main-agent and subagent hooks recompose it atomically, with costs, token dimensions, reasoning tokens and effort per model/source. On Codex, subagent prompts register their rollout for observability without advancing the main agent's sequence; `SubagentStop` reads the child from `agent_transcript_path` and persists its signal only when `parent_thread_id` matches a validated session root. The main Stop uses the registry's causal `turn_id` mapping before falling back to local transcript order. To recover missing markers while a conversation is open, `hook session-backfill` is a dry-run by default and never writes a Codex turn without `task_complete`. Every hook that rewrites a session note takes a per-file lock and writes through a temp file + rename, so the `SubagentStop` fan-out (one hook run per subagent) can never leave a note half-written; a note whose frontmatter reads back damaged is left untouched rather than patched.
389
389
 
390
390
  ## Retroactive memory (`import`) — install today, remember yesterday
391
391
 
@@ -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,12 @@ 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 cost rebuild --session <id> --json --vault .MyApp-vault
61
+ npx --no-install wendkeep cost rebuild --session <id> --json --vault .MyApp-vault --apply
62
62
  ```
63
63
 
64
64
  ## Expected result
@@ -80,8 +80,8 @@ dry-run path before any write.
80
80
  outbox, and SHARED so `last_memory_attempt` can be correlated before repair.
81
81
  - Corrupt bundle: preserve evidence and run `memory status --gate` before `memory repair`.
82
82
  - `legacy`/`degraded`/`stale`/`manifest-unproven` observability: run
83
- `wendkeep cost rebuild --session <id> --json`, review diagnostics, and only then authorize
84
- `--apply`.
83
+ `npx --no-install wendkeep cost rebuild --session <id> --json --vault <vault>`, review diagnostics,
84
+ and only then authorize the second variant with `--apply`.
85
85
 
86
86
  ## Next steps
87
87
 
@@ -30,6 +30,7 @@ npx wendkeep hook <name>
30
30
  npx wendkeep session list
31
31
  npx wendkeep session show <id>
32
32
  npx wendkeep session use <id>
33
+ npx wendkeep hook session-backfill --session <id> [--write]
33
34
  npx wendkeep import [options]
34
35
  ```
35
36
 
@@ -38,19 +39,27 @@ npx wendkeep import [options]
38
39
  - `wendkeep hook <name>` reads the agent payload from stdin; valid names are listed by `--help`.
39
40
  - `SessionStart` opens an activation: an epoch that remains active across multiple `Stop` events;
40
41
  only a new `SessionStart` supersedes the previous epoch.
41
- - `UserPromptSubmit` advances the active activation's native turn. If it finds a legacy registry
42
- with a closed epoch, it opens exactly one recovery activation; replaying the same prompt does
43
- not open another one.
44
- - On Codex, `session_id`, the native `turn_id`, and observed transcript order are enough to resolve
45
- the turn. Hook payloads do not need invented `activation_id` or `turn_sequence` fields.
42
+ - Main-agent `UserPromptSubmit` advances the active activation's native turn. A Codex rollout
43
+ prompt with `source.subagent` only registers its path for observability: it does not advance the
44
+ sequence, enter `turn_sequences`, or replace the main transcript. If a main prompt finds a
45
+ legacy registry with a closed epoch, it opens exactly one recovery activation; replaying the
46
+ same prompt does not open another one.
47
+ - On Codex, `session_id` and the native `turn_id` are enough to resolve the turn. Stop proves the
48
+ ID in the transcript and prefers `SESSION_REGISTRY.turn_sequences[turn_id]`; local transcript
49
+ order is the legacy-registry fallback. Interleaved subagent turns therefore cannot make the
50
+ parent's Stop artificially `stale_turn`. Hook payloads need no invented `activation_id` or
51
+ `turn_sequence` fields.
46
52
  - `Stop` accepts only a transcript-proven turn from the compatible active activation. Duplicates
47
53
  are no-ops; stale/superseded Stops neither publish memory nor overwrite a newer epoch's
48
54
  checkpoint.
49
55
  - `Stop` receives an absolute **45 s** deadline from hook entry. Reads check the clock between
50
56
  rollouts and on every chunk; reaching the limit returns `degraded` before the host timeout.
51
- - `SubagentStop` receives an absolute **15 s** deadline. Signals arriving within the **250 ms**
52
- window are coalesced: only the highest sequence recomposes/publishes, without losing the last
53
- child.
57
+ - `SubagentStop` receives an absolute **15 s** deadline and resolves the child rollout from Codex's
58
+ official `agent_transcript_path` field (`agentTranscriptPath` is also accepted);
59
+ `transcript_path` continues to identify the parent session. Before any write, `source.subagent`,
60
+ ID, canonical session, and `parent_thread_id` are validated; the parent must match a proven
61
+ session root. Signals arriving within the **250 ms** window are coalesced: only the highest
62
+ sequence recomposes/publishes, without losing the last child.
54
63
  - Observability is tri-state: `complete` publishes the full snapshot; `none` means zero proven by
55
64
  a causal Stop or stable offline scan; `degraded` preserves the previous snapshot and allowlisted
56
65
  diagnostics. An isolated `SubagentStop` never publishes `none`.
@@ -64,6 +73,10 @@ npx wendkeep import [options]
64
73
  - `import` reconciles observability even when no `wk-turn` is missing: a legacy schema, stale
65
74
  frontier, or unproven manifest triggers recomposition without duplicating iterations. A fresh
66
75
  checkpoint remains byte-identical; `degraded` is reported and does not change the note.
76
+ - `hook session-backfill` recovers missing `wk-turn` markers for the selected session. Without
77
+ `--write`, it only reports. On Codex, `missingTurns` contains only turns with `task_complete`;
78
+ open turns appear under `incompleteTurns` and are never written. `--write` applies only completed
79
+ candidates, and a second run is idempotent.
67
80
  - Exit `0` means consistent processing; non-zero reports invalid source/config/write instead of
68
81
  presenting silent partial success.
69
82
 
@@ -72,6 +85,8 @@ npx wendkeep import [options]
72
85
  ```bash
73
86
  npx wendkeep session list
74
87
  npx wendkeep session show 019abc-session-id
88
+ npx wendkeep hook session-backfill --session 019abc-session-id
89
+ npx wendkeep hook session-backfill --session 019abc-session-id --write
75
90
  npx wendkeep import --source codex --since 2026-07-01 --dry-run --json
76
91
  ```
77
92
 
@@ -98,6 +113,10 @@ without creating a new turn block.
98
113
  was found; the attempt remains observable but does not publish memory.
99
114
  - A late Stop reports `stale_turn`/`superseded`: the newer epoch and checkpoint are preserved; do
100
115
  not force the old payload to apply.
116
+ - `session-backfill` lists `incompleteTurns`: wait for that turn's `task_complete`/Stop and retry;
117
+ do not edit the note or force the partial turn. Once that turn receives `task_complete`, the next
118
+ run may treat it as eligible. Claude transcripts preserve their historical contract and do not
119
+ require this Codex event.
101
120
  - Fork duplicates: bound source/date and inspect `forked_from_id`/`source.subagent`.
102
121
  - Codex does not capture: approve hooks and start a new session after `sync`.
103
122
  - Contaminated cost: validate `session_id → session_file → transcript_path → provider`.
@@ -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,12 @@ 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 cost rebuild --session <id> --json --vault .MeuApp-vault
61
+ npx --no-install wendkeep cost rebuild --session <id> --json --vault .MeuApp-vault --apply
62
62
  ```
63
63
 
64
64
  ## Resultado esperado
@@ -80,7 +80,8 @@ ou sem manifest comprovado e oferece um caminho dry-run antes de qualquer escrit
80
80
  outbox e SHARED para correlacionar `last_memory_attempt` antes de reparar.
81
81
  - Bundle corrompido: preserve a evidência e use `memory status --gate` antes de `memory repair`.
82
82
  - Observabilidade `legacy`/`degraded`/`stale`/`manifest-unproven`: rode
83
- `wendkeep cost rebuild --session <id> --json`, revise diagnostics e só então autorize `--apply`.
83
+ `npx --no-install wendkeep cost rebuild --session <id> --json --vault <cofre>`, revise diagnostics
84
+ e só então autorize a segunda variante com `--apply`.
84
85
 
85
86
  ## Próximos passos
86
87
 
@@ -30,6 +30,7 @@ npx wendkeep hook <nome>
30
30
  npx wendkeep session list
31
31
  npx wendkeep session show <id>
32
32
  npx wendkeep session use <id>
33
+ npx wendkeep hook session-backfill --session <id> [--write]
33
34
  npx wendkeep import [opções]
34
35
  ```
35
36
 
@@ -38,18 +39,26 @@ npx wendkeep import [opções]
38
39
  - `wendkeep hook <name>` lê o payload do agente em stdin; nomes válidos aparecem em `--help`.
39
40
  - `SessionStart` abre uma activation, isto é, um epoch que continua ativo por vários `Stop`; só um
40
41
  novo `SessionStart` torna o epoch anterior superseded.
41
- - `UserPromptSubmit` avança o turno nativo da activation ativa. Se encontrar um registry legado
42
- com o epoch fechado, abre exatamente uma activation de recuperação; repetir o mesmo prompt não
43
- abre outra.
44
- - No Codex, `session_id`, o `turn_id` nativo e a ordem observada no transcript bastam para resolver
45
- o turno. O payload do hook não precisa inventar `activation_id` nem `turn_sequence`.
42
+ - `UserPromptSubmit` do agente principal avança o turno nativo da activation ativa. Um prompt de
43
+ rollout Codex com `source.subagent` apenas registra o path para observabilidade: não avança a
44
+ sequência, não entra em `turn_sequences` e não substitui o transcript principal. Se o prompt
45
+ principal encontra um registry legado com o epoch fechado, abre exatamente uma activation de
46
+ recuperação; repetir o mesmo prompt não abre outra.
47
+ - No Codex, `session_id` e o `turn_id` nativo bastam para resolver o turno. O Stop prova o ID no
48
+ transcript e prefere `SESSION_REGISTRY.turn_sequences[turn_id]`; a ordem local do transcript é
49
+ fallback para registry legado. Assim, turnos intercalados de subagents não tornam o Stop da mãe
50
+ artificialmente `stale_turn`. O payload não precisa inventar `activation_id` ou `turn_sequence`.
46
51
  - `Stop` aceita somente o turno comprovado pelo transcript e pela activation ativa compatível.
47
52
  Duplicatas são no-op; Stops stale/superseded não publicam memória nem sobrescrevem o checkpoint
48
53
  de um epoch mais novo.
49
54
  - `Stop` recebe deadline absoluto de **45 s** desde a entrada do hook. A leitura verifica o relógio
50
55
  entre rollouts e a cada chunk; ao atingir o limite, devolve `degraded` antes do timeout do host.
51
- - `SubagentStop` recebe deadline absoluto de **15 s**. Sinais que chegam na janela de **250 ms**
52
- são coalescidos: somente a maior sequência recompõe/publica, sem perder o último filho.
56
+ - `SubagentStop` recebe deadline absoluto de **15 s** e resolve o rollout filho pelo campo oficial
57
+ Codex `agent_transcript_path` (também aceita `agentTranscriptPath`); `transcript_path` continua
58
+ identificando a sessão-mãe. Antes de qualquer escrita, `source.subagent`, ID, sessão canônica e
59
+ `parent_thread_id` são validados; o pai deve corresponder a um root comprovado da sessão. Sinais
60
+ que chegam na janela de **250 ms** são coalescidos: somente a maior sequência
61
+ recompõe/publica, sem perder o último filho.
53
62
  - A observabilidade usa tri-state: `complete` publica o snapshot integral; `none` representa zero
54
63
  comprovado por Stop causal ou scan offline estável; `degraded` preserva o snapshot anterior e
55
64
  diagnostics allowlisted. `SubagentStop` isolado nunca publica `none`.
@@ -63,6 +72,10 @@ npx wendkeep import [opções]
63
72
  - `import` reconcilia a observabilidade mesmo quando nenhum `wk-turn` está ausente: schema legado,
64
73
  frontier stale ou manifest não comprovado disparam recomposição sem duplicar iterações. Um
65
74
  checkpoint fresco permanece byte-idêntico; `degraded` é reportado e não altera a nota.
75
+ - `hook session-backfill` recupera `wk-turn` ausente da sessão selecionada. Sem `--write`, apenas
76
+ relata. Em Codex, `missingTurns` contém somente turnos com `task_complete`; turnos ainda abertos
77
+ aparecem em `incompleteTurns` e nunca são gravados. `--write` aplica apenas os candidatos
78
+ concluídos e uma segunda execução é idempotente.
66
79
  - Exit `0` indica processamento consistente; exit não zero indica configuração, fonte ou escrita
67
80
  inválida sem transformar isso em sucesso parcial silencioso.
68
81
 
@@ -71,6 +84,8 @@ npx wendkeep import [opções]
71
84
  ```bash
72
85
  npx wendkeep session list
73
86
  npx wendkeep session show 019abc-session-id
87
+ npx wendkeep hook session-backfill --session 019abc-session-id
88
+ npx wendkeep hook session-backfill --session 019abc-session-id --write
74
89
  npx wendkeep import --source codex --since 2026-07-01 --dry-run --json
75
90
  ```
76
91
 
@@ -97,6 +112,10 @@ sem criar um novo bloco de turno.
97
112
  compatível foi encontrada; o attempt fica observável, mas não publica memória.
98
113
  - Stop atrasado aparece como `stale_turn`/`superseded`: o epoch mais novo e seu checkpoint são
99
114
  preservados; não force a reaplicação do payload antigo.
115
+ - `session-backfill` lista `incompleteTurns`: aguarde o `task_complete`/Stop desse turno e repita;
116
+ não edite a nota nem force o turno parcial. Quando o mesmo turno recebe `task_complete`, a
117
+ próxima execução passa a considerá-lo elegível. Transcripts Claude preservam o contrato
118
+ histórico e não exigem esse evento Codex.
100
119
  - Duplicatas de forks: limite por fonte/data e revise `forked_from_id`/`source.subagent`.
101
120
  - Codex não captura: aprove os hooks e reinicie a sessão após `sync`.
102
121
  - Custo contaminado: valide `session_id → session_file → transcript_path → provider`.
@@ -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) {
@@ -321,6 +329,13 @@ function nonNegativeSequence(value, fallback = null) {
321
329
  return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : fallback;
322
330
  }
323
331
 
332
+ export function resolveRegisteredTurnSequence(entry = {}, turnId = '', fallback = 0) {
333
+ const registered = turnId
334
+ ? nonNegativeSequence(entry?.turn_sequences?.[String(turnId)])
335
+ : null;
336
+ return registered === null ? nonNegativeSequence(fallback, 0) : registered;
337
+ }
338
+
324
339
  function cloneRegistry(registry = {}) {
325
340
  const sessions = Object.fromEntries(Object.entries(registry.sessions || {}).map(([id, entry]) => [
326
341
  id,
@@ -7,6 +7,7 @@ import {
7
7
  insertIteration,
8
8
  parseTranscript,
9
9
  } from './session-stop.mjs';
10
+ import { completedCodexTurnIdsContent } from '../packages/integrations/src/transcripts.mjs';
10
11
  import {
11
12
  getVaultBase,
12
13
  readControl,
@@ -63,6 +64,7 @@ export function backfillSessions({ vaultBase, write = false, limit = 0, session
63
64
  scanned: 0,
64
65
  candidates: 0,
65
66
  inserted: 0,
67
+ incomplete: 0,
66
68
  skipped: 0,
67
69
  missing: [],
68
70
  sessions: [],
@@ -85,13 +87,35 @@ export function backfillSessions({ vaultBase, write = false, limit = 0, session
85
87
  continue;
86
88
  }
87
89
 
90
+ const transcriptContent = readFileSync(entry.transcript_path, 'utf-8');
88
91
  const tx = parseTranscript(entry.transcript_path);
89
- const turns = tx.turns.filter((turn) => turn.turnId && turn.userPrompts.length);
92
+ const allTurns = tx.turns.filter((turn) => turn.turnId && turn.userPrompts.length);
93
+ const completedCodexTurns = tx.provider === 'codex'
94
+ ? completedCodexTurnIdsContent(transcriptContent)
95
+ : null;
96
+ const turns = completedCodexTurns
97
+ ? allTurns.filter((turn) => completedCodexTurns.has(turn.turnId))
98
+ : allTurns;
90
99
  const content = readFileSync(sessionPath, 'utf-8');
91
100
  const missingTurns = turns.filter((turn) => !hasTurnMarker(content, turn.turnId));
101
+ const incompleteTurns = completedCodexTurns
102
+ ? allTurns.filter((turn) => (
103
+ !completedCodexTurns.has(turn.turnId) && !hasTurnMarker(content, turn.turnId)
104
+ ))
105
+ : [];
106
+ report.incomplete += incompleteTurns.length;
92
107
 
93
108
  if (!missingTurns.length) {
94
109
  report.skipped += 1;
110
+ if (incompleteTurns.length) {
111
+ report.sessions.push({
112
+ session: entry.session_file,
113
+ transcript: entry.transcript_path,
114
+ missingTurns: [],
115
+ incompleteTurns: incompleteTurns.map((turn) => turn.turnId),
116
+ inserted: 0,
117
+ });
118
+ }
95
119
  continue;
96
120
  }
97
121
 
@@ -100,6 +124,7 @@ export function backfillSessions({ vaultBase, write = false, limit = 0, session
100
124
  session: entry.session_file,
101
125
  transcript: entry.transcript_path,
102
126
  missingTurns: missingTurns.map((turn) => turn.turnId),
127
+ incompleteTurns: incompleteTurns.map((turn) => turn.turnId),
103
128
  inserted: 0,
104
129
  };
105
130
 
@@ -32,6 +32,7 @@ import {
32
32
  yamlQuote,
33
33
  } from './obsidian-common.mjs';
34
34
  import { resolveSessionIdentity } from './session-identity.mjs';
35
+ import { readCodexRolloutMeta } from './codex-rollout-meta.mjs';
35
36
  import { mutateSessionNote } from './session-note-io.mjs';
36
37
 
37
38
  function sessionIdFromInput(input) {
@@ -54,6 +55,12 @@ function causalTurnPatch(input, now) {
54
55
  };
55
56
  }
56
57
 
58
+ function isCodexSubagentTranscript(identity) {
59
+ if (identity?.provider !== 'codex' || !identity.transcriptPath) return false;
60
+ const inspected = readCodexRolloutMeta(identity.transcriptPath);
61
+ return Boolean(inspected?.ok && inspected.meta?.source?.subagent);
62
+ }
63
+
57
64
  function buildSessionContent({ relPath, now, summary = 'session', sessionId = '', reason = 'Sessão criada automaticamente pelo hook UserPromptSubmit.' }) {
58
65
  const date = formatDate(now);
59
66
  const startedAt = formatLocalIso(now);
@@ -341,6 +348,21 @@ function main() {
341
348
  }
342
349
  const sessionId = identity.canonicalConversationId;
343
350
 
351
+ // UserPromptSubmit also fires inside Codex subagents. The child belongs to the parent's
352
+ // observability graph, but it is not a new main turn: registering it through causalTurnPatch
353
+ // permanently inflated the parent's sequence and made every later main Stop stale.
354
+ if (isCodexSubagentTranscript(identity)) {
355
+ const registered = readSessionRegistry(vaultBase).sessions?.[sessionId];
356
+ if (registered) {
357
+ upsertSessionRegistry(vaultBase, sessionId, {
358
+ transcript_paths: [identity.transcriptPath],
359
+ provider: identity.provider,
360
+ });
361
+ }
362
+ writeHookOutput({});
363
+ return;
364
+ }
365
+
344
366
  // Fast path: skip all writes if control file touched < 5 min ago and session matches
345
367
  try {
346
368
  const ctrlPath = controlPath(vaultBase);
@@ -61,6 +61,7 @@ import {
61
61
  hasTurnMarker,
62
62
  normalizeTurnMarkers,
63
63
  mutateSessionRegistry,
64
+ resolveRegisteredTurnSequence,
64
65
  resolveStopActivation,
65
66
  applyStopActivation,
66
67
  } from './obsidian-common.mjs';
@@ -1180,9 +1181,13 @@ export async function main({
1180
1181
  const turnId = turnIdentity.id;
1181
1182
  const now = finalizing ? new Date() : null;
1182
1183
  const endedAt = finalizing ? formatLocalIso(now) : '';
1183
- const stopTurnSequence = turnIdentity.order;
1184
1184
  const causalStop = finalizing
1185
1185
  ? mutateSessionRegistry(vaultBase, (registry) => {
1186
+ const stopTurnSequence = resolveRegisteredTurnSequence(
1187
+ registry.sessions?.[sessionId],
1188
+ turnId,
1189
+ turnIdentity.order,
1190
+ );
1186
1191
  const activationId = resolveStopActivation(registry, {
1187
1192
  session_id: sessionId,
1188
1193
  activation_id: input.activation_id || input.activationId || '',
@@ -1214,9 +1219,11 @@ export async function main({
1214
1219
  activation,
1215
1220
  stopDisposition: cas.stopDisposition,
1216
1221
  canPromoteMemory: cas.canPromoteMemory,
1222
+ turnSequence: stopTurnSequence,
1217
1223
  };
1218
1224
  })
1219
1225
  : null;
1226
+ const stopTurnSequence = causalStop?.turnSequence ?? turnIdentity.order;
1220
1227
  let memoryHandoff = null;
1221
1228
  let memoryAttempt = null;
1222
1229
  if (finalizing) {
@@ -82,6 +82,32 @@ function claudeRoots(entry) {
82
82
  return { state: 'complete', rootPaths: [...paths], descendantPaths: [], diagnostics: [] };
83
83
  }
84
84
 
85
+ export function subagentIdentityInput(input = {}) {
86
+ const agentTranscriptPath = input.agent_transcript_path || input.agentTranscriptPath || '';
87
+ if (!agentTranscriptPath) return input;
88
+ return {
89
+ ...input,
90
+ transcript_path: agentTranscriptPath,
91
+ transcriptPath: agentTranscriptPath,
92
+ };
93
+ }
94
+
95
+ function validatedCodexRootIds(entry, canonicalConversationId, { resolveRoots, readMeta }) {
96
+ const roots = resolveRoots(entry, { readMeta });
97
+ if (roots?.state !== 'complete' || !roots.rootPaths?.length) return null;
98
+
99
+ const ids = new Set();
100
+ for (const rootPath of roots.rootPaths) {
101
+ const result = readMeta(rootPath);
102
+ const meta = result?.meta;
103
+ const rootId = String(meta?.id || '');
104
+ if (!result?.ok || !rootId || meta?.source?.subagent) return null;
105
+ if (meta.session_id && meta.session_id !== canonicalConversationId) return null;
106
+ ids.add(rootId);
107
+ }
108
+ return ids;
109
+ }
110
+
85
111
  export async function refreshSubagents(vaultBase, input, {
86
112
  now = Date.now,
87
113
  hookStartedAt = now(),
@@ -100,7 +126,8 @@ export async function refreshSubagents(vaultBase, input, {
100
126
  } = {}) {
101
127
  const deadlineAt = hookStartedAt + deadlineMs;
102
128
  const provider = providerMeta(input.provider).id;
103
- const { identity, entry } = resolveEntry(vaultBase, input, provider);
129
+ const identityInput = subagentIdentityInput(input);
130
+ const { identity, entry } = resolveEntry(vaultBase, identityInput, provider);
104
131
  if (identity.state !== 'resolved') return false;
105
132
  const childTranscriptPath = identity.transcriptPath;
106
133
  const sessionRel = entry?.session_file || '';
@@ -118,6 +145,11 @@ export async function refreshSubagents(vaultBase, input, {
118
145
  || childMeta.meta.source?.subagent?.thread_spawn?.parent_thread_id
119
146
  || '',
120
147
  );
148
+ const rootIds = validatedCodexRootIds(entry, identity.canonicalConversationId, {
149
+ resolveRoots,
150
+ readMeta,
151
+ });
152
+ if (!childParentThreadId || !rootIds?.has(childParentThreadId)) return false;
121
153
  }
122
154
 
123
155
  const observed = causalSnapshot(entry);
@@ -168,7 +200,7 @@ export async function refreshSubagents(vaultBase, input, {
168
200
 
169
201
  try {
170
202
  if (now() >= deadlineAt) return true;
171
- const fresh = resolveEntry(vaultBase, input, provider);
203
+ const fresh = resolveEntry(vaultBase, identityInput, provider);
172
204
  if (fresh.identity?.state !== 'resolved'
173
205
  || fresh.identity.canonicalConversationId !== identity.canonicalConversationId
174
206
  || !fresh.entry?.session_file
@@ -197,7 +229,7 @@ export async function refreshSubagents(vaultBase, input, {
197
229
  const currentEntry = guardContext?.entry;
198
230
  const currentResolved = currentEntry
199
231
  ? { identity: { state: 'resolved', canonicalConversationId: identity.canonicalConversationId }, entry: currentEntry }
200
- : resolveEntry(vaultBase, input, provider);
232
+ : resolveEntry(vaultBase, identityInput, provider);
201
233
  if (currentResolved.identity?.state !== 'resolved'
202
234
  || currentResolved.identity.canonicalConversationId !== identity.canonicalConversationId
203
235
  || !currentResolved.entry) {
@@ -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,12 @@ 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
+ );
103
109
 
104
110
  function readJsonLines(vaultBase, path, label) {
105
111
  let checked;
@@ -279,13 +285,13 @@ function checkMemoryAttempts(registry, {
279
285
  : [];
280
286
 
281
287
  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}.`);
288
+ failures.push(`Lifecycle de memória v2 ambíguo: Stop pulou a publicação sem identidade causal suficiente. Inspecione com: ${memoryStatusCommand(vaultBase)}.`);
283
289
  continue;
284
290
  }
285
291
 
286
292
  if (state === 'skipped' && ['stale_turn', 'superseded'].includes(disposition)) {
287
293
  if (eventIds.length) {
288
- failures.push(`Stop stale/superseded emitiu event_ids apesar da rejeição causal. Inspecione com: ${MEMORY_STATUS_COMMAND}.`);
294
+ failures.push(`Stop stale/superseded emitiu event_ids apesar da rejeição causal. Inspecione com: ${memoryStatusCommand(vaultBase)}.`);
289
295
  } else {
290
296
  warnings.push('Stop stale/superseded foi descartado sem publicar memória.');
291
297
  }
@@ -294,12 +300,12 @@ function checkMemoryAttempts(registry, {
294
300
 
295
301
  if (disposition !== 'applied') {
296
302
  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}.`);
303
+ else failures.push(`Attempt de memória v2 possui disposition não reconhecida para o estado informado. Inspecione com: ${memoryStatusCommand(vaultBase)}.`);
298
304
  continue;
299
305
  }
300
306
 
301
307
  if (!eventIds.length) {
302
- failures.push(`Attempt v2 aplicado não declarou event_ids; publicação perdida. Inspecione com: ${MEMORY_STATUS_COMMAND}.`);
308
+ failures.push(`Attempt v2 aplicado não declarou event_ids; publicação perdida. Inspecione com: ${memoryStatusCommand(vaultBase)}.`);
303
309
  continue;
304
310
  }
305
311
 
@@ -317,7 +323,7 @@ function checkMemoryAttempts(registry, {
317
323
  if (typeof attempt.turn_id !== 'string' || !attempt.turn_id) invalidAttemptFields.push('turn_id');
318
324
  if (!Number.isInteger(attempt.turn_sequence) || attempt.turn_sequence < 0) invalidAttemptFields.push('turn_sequence');
319
325
  if (invalidAttemptFields.length) {
320
- failures.push(`Attempt v2 da sessão ${sessionId} possui identidade causal inválida (${invalidAttemptFields.join(', ')}). Inspecione com: ${MEMORY_STATUS_COMMAND}.`);
326
+ failures.push(`Attempt v2 da sessão ${sessionId} possui identidade causal inválida (${invalidAttemptFields.join(', ')}). Inspecione com: ${memoryStatusCommand(vaultBase)}.`);
321
327
  continue;
322
328
  }
323
329
  const causalMismatches = [];
@@ -330,14 +336,14 @@ function checkMemoryAttempts(registry, {
330
336
  if (fields.length) causalMismatches.push(`${eventId}: ${fields.join(', ')}`);
331
337
  }
332
338
  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}.`);
339
+ failures.push(`Attempt v2 da sessão ${sessionId} referencia evento(s) com identidade causal divergente (${causalMismatches.join('; ')}). Inspecione com: ${memoryStatusCommand(vaultBase)}.`);
334
340
  continue;
335
341
  }
336
342
 
337
343
  if (state === 'enqueued' || state === 'degraded') {
338
344
  const missing = eventIds.filter((eventId) => !ledgerEventIds.has(eventId) && !outboxEventIds.has(eventId));
339
345
  if (missing.length) {
340
- failures.push(`Attempt v2 perdeu ${missing.length} evento(s): ausentes do ledger e da outbox. Inspecione com: ${MEMORY_STATUS_COMMAND}.`);
346
+ failures.push(`Attempt v2 perdeu ${missing.length} evento(s): ausentes do ledger e da outbox. Inspecione com: ${memoryStatusCommand(vaultBase)}.`);
341
347
  } else if (
342
348
  state === 'enqueued'
343
349
  && eventIds.every((eventId) => ledgerEventIds.has(eventId))
@@ -353,14 +359,14 @@ function checkMemoryAttempts(registry, {
353
359
  if (state === 'projected') {
354
360
  const outsideLedger = eventIds.filter((eventId) => !ledgerEventIds.has(eventId));
355
361
  if (outsideLedger.length) {
356
- failures.push(`Attempt projetado perdeu ${outsideLedger.length} evento(s) no ledger. Inspecione com: ${MEMORY_STATUS_COMMAND}.`);
362
+ failures.push(`Attempt projetado perdeu ${outsideLedger.length} evento(s) no ledger. Inspecione com: ${memoryStatusCommand(vaultBase)}.`);
357
363
  } 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}.`);
364
+ failures.push(`Checkpoint do attempt projetado diverge do prefixo rederivado do ledger. Inspecione com: ${memoryStatusCommand(vaultBase)}.`);
359
365
  }
360
366
  continue;
361
367
  }
362
368
 
363
- failures.push(`Attempt de memória v2 possui state inválido. Inspecione com: ${MEMORY_STATUS_COMMAND}.`);
369
+ failures.push(`Attempt de memória v2 possui state inválido. Inspecione com: ${memoryStatusCommand(vaultBase)}.`);
364
370
  }
365
371
 
366
372
  return { failures, warnings };
@@ -410,26 +416,26 @@ export function checkMemoryBundle(vaultBase, { registry } = {}) {
410
416
  const ledgerCorrupt = (bundle.ledger?.errors || []).length > 0;
411
417
  if (ledgerCorrupt) {
412
418
  for (const error of bundle.ledger.errors) {
413
- failures.push(`${error} Execute com segurança: ${MEMORY_REPAIR_COMMAND}.`);
419
+ failures.push(`${error} Execute com segurança: ${memoryRepairCommand(vaultBase)}.`);
414
420
  }
415
421
  }
416
422
  for (const error of bundle.errors || []) {
417
423
  if (error.startsWith('ledger:')) continue;
418
- failures.push(`${error} Inspecione com: ${MEMORY_STATUS_COMMAND}.`);
424
+ failures.push(`${error} Inspecione com: ${memoryStatusCommand(vaultBase)}.`);
419
425
  }
420
426
 
421
427
  if (outbox.errors.length) {
422
- failures.push(`Outbox corrompida (${outbox.errors.join('; ')}). Inspecione com: ${MEMORY_STATUS_COMMAND}.`);
428
+ failures.push(`Outbox corrompida (${outbox.errors.join('; ')}). Inspecione com: ${memoryStatusCommand(vaultBase)}.`);
423
429
  }
424
430
  if (candidates.errors.length) {
425
- failures.push(`${candidates.errors.join('; ')} Execute com segurança: ${MEMORY_REPAIR_COMMAND}.`);
431
+ failures.push(`${candidates.errors.join('; ')} Execute com segurança: ${memoryRepairCommand(vaultBase)}.`);
426
432
  }
427
433
 
428
434
  let replay = null;
429
435
  if (bundle.ledger?.ok) {
430
436
  try { replay = deriveMemoryProjection(vaultBase, bundle.ledger.events); }
431
437
  catch (error) {
432
- failures.push(`Ledger não pode ser reduzido: ${error.message}. Execute com segurança: ${MEMORY_REPAIR_COMMAND}.`);
438
+ failures.push(`Ledger não pode ser reduzido: ${error.message}. Execute com segurança: ${memoryRepairCommand(vaultBase)}.`);
433
439
  }
434
440
  }
435
441
  if (replay && bundle.shared?.ok) {
@@ -438,7 +444,7 @@ export function checkMemoryBundle(vaultBase, { registry } = {}) {
438
444
  if (metadata.event_cursor !== replay.ledgerCursor) divergences.push(`event_cursor ${metadata.event_cursor} != ${replay.ledgerCursor}`);
439
445
  if (metadata.state_hash !== replay.stateHash) divergences.push(`state_hash ${metadata.state_hash} != ${replay.stateHash}`);
440
446
  if (divergences.length) {
441
- failures.push(`Projeção SHARED stale/lag (${divergences.join('; ')}). Inspecione com: ${MEMORY_STATUS_COMMAND}.`);
447
+ failures.push(`Projeção SHARED stale/lag (${divergences.join('; ')}). Inspecione com: ${memoryStatusCommand(vaultBase)}.`);
442
448
  }
443
449
  }
444
450
 
@@ -460,7 +466,7 @@ export function checkMemoryBundle(vaultBase, { registry } = {}) {
460
466
  const activeConflicts = unresolved.filter((item) => item?.reason === 'conflict');
461
467
  const ordinaryCandidates = unresolved.filter((item) => item?.reason !== 'conflict');
462
468
  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}.`);
469
+ failures.push(`${activeConflicts.length} conflito ativo em chave operacional (${activeConflicts.map((item) => item.memory_key || item.candidate_id).join(', ')}). Inspecione com: ${memoryStatusCommand(vaultBase)}.`);
464
470
  }
465
471
  if (outbox.count) warnings.push(`${outbox.count} evento(s) pendente(s) na outbox; execute o projector quando seguro.`);
466
472
  if (ordinaryCandidates.length) warnings.push(`${ordinaryCandidates.length} candidate(s) aguardando curadoria humana.`);
@@ -585,7 +591,7 @@ export function runVaultHealth({ vaultBase, session = '' }) {
585
591
  failures.push(...memory.failures.map((item) => `Memória: ${item}`));
586
592
  warnings.push(...memory.warnings.map((item) => `Memória: ${item}`));
587
593
  } else {
588
- warnings.push(`Bundle de memória v2 ausente (vault legado); inspecione com: ${MEMORY_STATUS_COMMAND}.`);
594
+ warnings.push(`Bundle de memória v2 ausente (vault legado); inspecione com: ${memoryStatusCommand(vaultBase)}.`);
589
595
  }
590
596
 
591
597
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wendkeep",
3
- "version": "0.67.0",
3
+ "version": "0.67.2",
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.66.5"
73
+ "wendkeep": "^0.67.1"
74
74
  }
75
75
  }
@@ -117,7 +117,7 @@ Usage:
117
117
  wendkeep --help Show this help.
118
118
  `;
119
119
 
120
- function runHook(name) {
120
+ function runHook(name, args = []) {
121
121
  if (!name) {
122
122
  process.stderr.write('wendkeep hook: missing hook name\n');
123
123
  process.exit(2);
@@ -133,7 +133,7 @@ function runHook(name) {
133
133
  }
134
134
  // Spawn exactly as the agent would run `node <hook>.mjs`: stdio inherited so the
135
135
  // hook's stdin (agent JSON) and stdout (hookSpecificOutput) pass through untouched.
136
- const r = spawnSync(process.execPath, [file], { stdio: 'inherit' });
136
+ const r = spawnSync(process.execPath, [file, ...args], { stdio: 'inherit' });
137
137
  process.exit(r.status ?? 0);
138
138
  }
139
139
 
@@ -207,7 +207,7 @@ async function main(argv) {
207
207
  break;
208
208
  }
209
209
  case 'hook':
210
- runHook(rest[0]);
210
+ runHook(rest[0], rest.slice(1));
211
211
  break;
212
212
  case 'doctor': {
213
213
  const { runDoctor } = await import('../../../src/doctor.mjs');
@@ -170,6 +170,16 @@ function jsonLines(content) {
170
170
  }).filter(Boolean);
171
171
  }
172
172
 
173
+ export function completedCodexTurnIdsContent(content = '') {
174
+ const completed = new Set();
175
+ for (const event of jsonLines(content)) {
176
+ if (event.type !== 'event_msg' || event.payload?.type !== 'task_complete') continue;
177
+ const turnId = String(event.payload?.turn_id || event.turn_id || '').trim();
178
+ if (turnId) completed.add(turnId);
179
+ }
180
+ return completed;
181
+ }
182
+
173
183
  export function parseCodexTranscriptContent(content, options = {}) {
174
184
  const result = createResult('codex');
175
185
  const eventUserPrompts = [];
package/src/sync-defs.mjs CHANGED
@@ -64,10 +64,20 @@ This project uses the [wendkeep](https://github.com/rogersialves/wendkeep) harne
64
64
  in every profile: Vault, session, identity, memory, lessons, and persistence integrations.
65
65
  The persistent profile is selected explicitly; missing or invalid configuration uses **GOVERN as the default**.
66
66
 
67
- Before an implementation, the native LLM harness classifies the current request and may create a
68
- task-scoped lease with \`wendkeep profile route <FLOW|GUIDE|GOVERN|ASSURE> --session <id> --reason <text>\`.
69
- The lease expires when that request ends and the persistent session/project profile becomes
70
- effective again. **OFF is never selected adaptively**; only a human can persist \`profile use OFF\`.
67
+ Before every implementation, the native LLM harness **MUST run the routing gate**:
68
+ 1. Inspect \`wendkeep profile status\`.
69
+ 2. Classify the request and choose FLOW, GUIDE, GOVERN, or ASSURE from its scope and risk.
70
+ 3. Register the choice with
71
+ \`wendkeep profile route <FLOW|GUIDE|GOVERN|ASSURE> --session <id> --reason <text>\`.
72
+ 4. Re-check \`wendkeep profile status\` and follow the effective profile before editing.
73
+
74
+ The persistent profile is only the base restored after the task lease; it is not an immutable
75
+ decision for the current task. A base OFF does not block a temporary FLOW/GUIDE/GOVERN/ASSURE
76
+ lease, while a base GOVERN does not prevent choosing FLOW or GUIDE for bounded work. An explicit
77
+ user choice for the request takes precedence. If no causal session is available or routing fails,
78
+ do not invent lease state: follow the effective profile and use GOVERN only as the conservative
79
+ fallback when configuration is missing or invalid. The lease expires when the request ends.
80
+ **OFF is never selected adaptively**; only a human can persist \`profile use OFF\`.
71
81
 
72
82
  Route work by the effective profile:
73
83
  - **OFF** — Wend Runtime is disabled and governance belongs to the native LLM harness; Keep Core stays active.