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 +35 -0
- package/README.en.md +6 -6
- package/README.md +6 -6
- package/docs/en/commands/costs-and-observability.md +8 -8
- package/docs/en/commands/maintenance-and-diagnostics.md +13 -13
- package/docs/en/commands/sessions-and-import.md +27 -8
- package/docs/pt-BR/commands/costs-and-observability.md +8 -8
- package/docs/pt-BR/commands/maintenance-and-diagnostics.md +13 -12
- package/docs/pt-BR/commands/sessions-and-import.md +26 -7
- package/hooks/harness-doctor.mjs +8 -2
- package/hooks/obsidian-common.mjs +15 -0
- package/hooks/session-backfill.mjs +26 -1
- package/hooks/session-ensure.mjs +22 -0
- package/hooks/session-stop.mjs +8 -1
- package/hooks/subagent-stop.mjs +35 -3
- package/hooks/vault-health.mjs +26 -20
- package/package.json +2 -2
- package/packages/cli/src/index.mjs +3 -3
- package/packages/integrations/src/transcripts.mjs +10 -0
- package/src/sync-defs.mjs +14 -4
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
|
|
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.
|
|
42
|
-
with
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
|
52
|
-
|
|
53
|
-
|
|
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
|
|
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.
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
|
52
|
-
|
|
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`.
|
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) {
|
|
@@ -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
|
|
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
|
|
package/hooks/session-ensure.mjs
CHANGED
|
@@ -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);
|
package/hooks/session-stop.mjs
CHANGED
|
@@ -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) {
|
package/hooks/subagent-stop.mjs
CHANGED
|
@@ -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
|
|
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,
|
|
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,
|
|
232
|
+
: resolveEntry(vaultBase, identityInput, provider);
|
|
201
233
|
if (currentResolved.identity?.state !== 'resolved'
|
|
202
234
|
|| currentResolved.identity.canonicalConversationId !== identity.canonicalConversationId
|
|
203
235
|
|| !currentResolved.entry) {
|
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,12 @@ 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
|
+
);
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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.
|
|
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.
|
|
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
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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.
|