wendkeep 0.69.0 → 0.70.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,20 @@ 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.70.0] — 2026-08-17
8
+
9
+ ### Added
10
+
11
+ - **Observer local multi-projeto.** O WendKeep agora expõe snapshots sanitizados, ingestão HTTP
12
+ loopback, índice append-only reconstruível, CLI `observer`, outbox fail-open nos hooks e um
13
+ compose Docker que persiste somente a projeção; os vaults locais continuam sendo a autoridade.
14
+
15
+ ### Fixed
16
+
17
+ - **Modelos research preview agora são distinguidos de modelos desconhecidos.** O Spark é
18
+ reconhecido sem preço inventado, o `doctor` mantém o custo não estimado e a cobertura de
19
+ preços passa a ser validada por sensor crítico.
20
+
7
21
  ## [0.69.0] — 2026-08-16
8
22
 
9
23
  ### Added
package/README.en.md CHANGED
@@ -85,9 +85,10 @@ Decisions, dead ends, the reason you chose X over Y — gone next session. The p
85
85
  | **Capture** — every turn, on disk | `SessionStart` / `Stop` hooks write each session to a dated Markdown note: prompts, iterations, files touched, wikilinks. |
86
86
  | **Derive** — decisions, bugs, learnings | Pulled from the transcript into their own notes, backlinked to the session. Your history becomes navigable, not archival. |
87
87
  | **Recall** — injected back | Canonical `CORE` + operational `SHARED_MEMORY`, with explicit budgets, are injected on `startup`, `/clear`, and `/compact`; `DIGEST` remains a deep-recall index and legacy fallback. |
88
- | **Cost** — what it all cost | Per‑model, cache‑aware token pricing per session — plus `cost --trend` with a run‑rate projection across the whole vault. |
88
+ | **Cost** — what it all cost | Per‑model, cache‑aware token pricing per session — plus `cost --trend` with a run‑rate projection across the whole vault; research previews without a final rate remain unestimated. |
89
89
  | **Multi‑agent** — one vault, both agents | `init` wires the session hooks into `.claude/settings.json` *and* `.codex/hooks.json`, and every note is tagged with the agent that wrote it: Claude Code is detected from its environment, anything else is recorded as Codex. One shared graph, whichever agent you are in. |
90
90
  | **Local‑first** — no cloud, no account | Everything is plain Markdown on your disk. An optional MCP server (`@bitbonsai/mcpvault`) lets the agent read/write the vault. |
91
+ | **Local Observer** — many projects, one view | `wendkeep observer` keeps a local HTTP index of sanitized, idempotent snapshots; vaults remain local, the container does not mount them, and unavailable hooks use an outbox without blocking the session. |
91
92
 
92
93
  ## Requirements
93
94
 
@@ -110,7 +111,7 @@ npx wendkeep init
110
111
 
111
112
  1. Create the vault folder taxonomy and a templated `README.md` (default vault: `<project>/.<project-name>-vault`, e.g. `.MyApp-vault`; override with `--vault`).
112
113
  2. Write a provider-neutral **`.wendkeep.json`** binding at the project root and a matching `.brain/PROJECT.json` marker in the vault, then merge the session hooks into **`.claude/settings.json`**. The binding is provider-neutral by design: any agent resolves the same vault from its session `cwd`, with no machine-global environment variable. Older registrations already in `.claude/settings.json` are adopted automatically.
113
- 3. Wire the Codex hooks in **`.codex/hooks.json`** — eight of the twelve: `brain-inject` + `session-start` on `SessionStart`, `session-ensure` + `change-context` on `UserPromptSubmit`, `session-stop` + `change-nag` on `Stop`, `subagent-stop` on `SubagentStop`, and `change-guard` on `PreToolUse` for `Bash`, `exec_command`, `apply_patch`, and mutable MCP tools, always in the `npx wendkeep hook <name>` form. The guard accepts object, raw-string, and argv Codex payloads; before a mutation it compares the session with the project, Git root, remote, branch, and worktree, denying missing or divergent targets. The other four stay out because Codex offers no equivalent payload, tool, or event: `change-warn` (no reliable `tool_input.file_path`), `plan-capture` (no `ExitPlanMode`), `decision-capture` (`AskUserQuestion` is Claude-only), and `task-log` (`TaskCompleted` is not in Codex's event enum). Codex scope blocks use `permissionDecision: "deny"`; `ask` is never emitted in `PreToolUse`. The merge remains non-destructive, preserves third-party hooks, and migrates legacy `timeout` to `timeoutSec`. **Codex enumerates every hook as untrusted and runs none until you approve the “Hooks need review” prompt at startup — `init` cannot pre-approve them**.
114
+ 3. Wire the Codex hooks in **`.codex/hooks.json`** — ten compatible entries: `brain-inject` + `session-start` + `observer-publish` on `SessionStart`, `session-ensure` + `change-context` on `UserPromptSubmit`, `session-stop` + `observer-publish` + `change-nag` on `Stop`, `subagent-stop` on `SubagentStop`, and `change-guard` on `PreToolUse` for `Bash`, `exec_command`, `apply_patch`, and mutable MCP tools, always in the `npx wendkeep hook <name>` form. `observer-publish` only publishes a sanitized, fail-open projection; it does not replace the local lifecycle. The guard accepts object, raw-string, and argv Codex payloads; before a mutation it compares the session with the project, Git root, remote, branch, and worktree, denying missing or divergent targets. The other four stay out because Codex offers no equivalent payload, tool, or event: `change-warn` (no reliable `tool_input.file_path`), `plan-capture` (no `ExitPlanMode`), `decision-capture` (`AskUserQuestion` is Claude-only), and `task-log` (`TaskCompleted` is not in Codex's event enum). Codex scope blocks use `permissionDecision: "deny"`; `ask` is never emitted in `PreToolUse`. The merge remains non-destructive, preserves third-party hooks, and migrates legacy `timeout` to `timeoutSec`. **Codex enumerates every hook as untrusted and runs none until you approve the “Hooks need review” prompt at startup — `init` cannot pre-approve them**.
114
115
  4. Add the **`wendkeep-vault`** MCP server to `.mcp.json` so the agent can read/write the vault. Skip with `--no-mcp` — e.g. when the agent already has a vault MCP. (`--no-mcp` skips *only wendkeep's own* MCP; companion MCPs still follow `--companions`.)
115
116
  5. Offer to pin **companion** plugins/MCP (multi-choice; **none** pre-checked — wendkeep is a neutral harness and presumes no third-party plugin). Each is wired the most agent-agnostic way it supports:
116
117
  - **`context-mode`** — context optimizer + FTS5 memory, wired as a Claude Code plugin. It ships its own MCP server, so wendkeep deliberately adds no `.mcp.json` entry (registering both cold-started two servers at once). On non-Claude agents, add the MCP by hand: `npx -y context-mode`.
@@ -235,6 +236,7 @@ The README is the map; the guides provide syntax, options, exit codes, examples,
235
236
  | **Notes and knowledge** | BUG/APR/ADR, repairs, renumbering, lessons, and dashboard | [Notes and knowledge](https://github.com/rogersialves/wendkeep/blob/main/docs/en/commands/notes-and-knowledge.md) |
236
237
  | **Costs and observability** | safe dry-run, tri-state, aggregation, trends, and historical rebuild | [Costs and observability](https://github.com/rogersialves/wendkeep/blob/main/docs/en/commands/costs-and-observability.md) |
237
238
  | **Maintenance and diagnostics** | doctor, frontier/manifest freshness, drift, version, and help | [Maintenance and diagnostics](https://github.com/rogersialves/wendkeep/blob/main/docs/en/commands/maintenance-and-diagnostics.md) |
239
+ | **Local Observer** | `observer serve`, registration, snapshots, outbox, and multi-project index | [Local Observer](https://github.com/rogersialves/wendkeep/blob/main/docs/en/commands/observer.md) |
238
240
 
239
241
  Operations that deserve step-by-step guidance: [verify and exits 0/1/2](https://github.com/rogersialves/wendkeep/blob/main/docs/en/commands/verify.md),
240
242
  [legacy-memory migration](https://github.com/rogersialves/wendkeep/blob/main/docs/en/commands/memory-migration.md), and
package/README.md CHANGED
@@ -85,9 +85,10 @@ Decisions, dead ends, the reason you chose X over Y — gone next session. The p
85
85
  | **Capture** — every turn, on disk | `SessionStart` / `Stop` hooks write each session to a dated Markdown note: prompts, iterations, files touched, wikilinks. |
86
86
  | **Derive** — decisions, bugs, learnings | Pulled from the transcript into their own notes, backlinked to the session. Your history becomes navigable, not archival. |
87
87
  | **Recall** — injected back | Canonical `CORE` + operational `SHARED_MEMORY`, with explicit budgets, are injected on `startup`, `/clear`, and `/compact`; `DIGEST` remains a deep-recall index and legacy fallback. |
88
- | **Cost** — what it all cost | Per‑model, cache‑aware token pricing per session — plus `cost --trend` with a run‑rate projection across the whole vault. |
88
+ | **Cost** — what it all cost | Per‑model, cache‑aware token pricing per session — plus `cost --trend` with a run‑rate projection across the whole vault; research previews without a final rate remain unestimated. |
89
89
  | **Multi‑agent** — one vault, both agents | `init` wires the session hooks into `.claude/settings.json` *and* `.codex/hooks.json`, and every note is tagged with the agent that wrote it: Claude Code is detected from its environment, anything else is recorded as Codex. One shared graph, whichever agent you are in. |
90
90
  | **Local‑first** — no cloud, no account | Everything is plain Markdown on your disk. An optional MCP server (`@bitbonsai/mcpvault`) lets the agent read/write the vault. |
91
+ | **Local Observer** — many projects, one view | `wendkeep observer` keeps a local HTTP index of sanitized, idempotent snapshots; vaults remain local, the container does not mount them, and unavailable hooks use an outbox without blocking the session. |
91
92
 
92
93
  ## Requirements
93
94
 
@@ -110,7 +111,7 @@ npx wendkeep init
110
111
 
111
112
  1. Create the vault folder taxonomy and a templated `README.md` (default vault: `<project>/.<project-name>-vault`, e.g. `.MyApp-vault`; override with `--vault`).
112
113
  2. Write a provider-neutral **`.wendkeep.json`** binding at the project root and a matching `.brain/PROJECT.json` marker in the vault, then merge the session hooks into **`.claude/settings.json`**. The binding is provider-neutral by design: any agent resolves the same vault from its session `cwd`, with no machine-global environment variable. Older registrations already in `.claude/settings.json` are adopted automatically.
113
- 3. Wire the Codex hooks in **`.codex/hooks.json`** — eight of the twelve: `brain-inject` + `session-start` on `SessionStart`, `session-ensure` + `change-context` on `UserPromptSubmit`, `session-stop` + `change-nag` on `Stop`, `subagent-stop` on `SubagentStop`, and `change-guard` on `PreToolUse` for `Bash`, `exec_command`, `apply_patch`, and mutable MCP tools, always in the `npx wendkeep hook <name>` form. The guard accepts object, raw-string, and argv Codex payloads; before a mutation it compares the session with the project, Git root, remote, branch, and worktree, denying missing or divergent targets. The other four stay out because Codex offers no equivalent payload, tool, or event: `change-warn` (no reliable `tool_input.file_path`), `plan-capture` (no `ExitPlanMode`), `decision-capture` (`AskUserQuestion` is Claude-only), and `task-log` (`TaskCompleted` is not in Codex's event enum). Codex scope blocks use `permissionDecision: "deny"`; `ask` is never emitted in `PreToolUse`. The merge remains non-destructive, preserves third-party hooks, and migrates legacy `timeout` to `timeoutSec`. **Codex enumerates every hook as untrusted and runs none until you approve the “Hooks need review” prompt at startup — `init` cannot pre-approve them**.
114
+ 3. Wire the Codex hooks in **`.codex/hooks.json`** — ten compatible entries: `brain-inject` + `session-start` + `observer-publish` on `SessionStart`, `session-ensure` + `change-context` on `UserPromptSubmit`, `session-stop` + `observer-publish` + `change-nag` on `Stop`, `subagent-stop` on `SubagentStop`, and `change-guard` on `PreToolUse` for `Bash`, `exec_command`, `apply_patch`, and mutable MCP tools, always in the `npx wendkeep hook <name>` form. `observer-publish` only publishes a sanitized, fail-open projection; it does not replace the local lifecycle. The guard accepts object, raw-string, and argv Codex payloads; before a mutation it compares the session with the project, Git root, remote, branch, and worktree, denying missing or divergent targets. The other four stay out because Codex offers no equivalent payload, tool, or event: `change-warn` (no reliable `tool_input.file_path`), `plan-capture` (no `ExitPlanMode`), `decision-capture` (`AskUserQuestion` is Claude-only), and `task-log` (`TaskCompleted` is not in Codex's event enum). Codex scope blocks use `permissionDecision: "deny"`; `ask` is never emitted in `PreToolUse`. The merge remains non-destructive, preserves third-party hooks, and migrates legacy `timeout` to `timeoutSec`. **Codex enumerates every hook as untrusted and runs none until you approve the “Hooks need review” prompt at startup — `init` cannot pre-approve them**.
114
115
  4. Add the **`wendkeep-vault`** MCP server to `.mcp.json` so the agent can read/write the vault. Skip with `--no-mcp` — e.g. when the agent already has a vault MCP. (`--no-mcp` skips *only wendkeep's own* MCP; companion MCPs still follow `--companions`.)
115
116
  5. Offer to pin **companion** plugins/MCP (multi-choice; **none** pre-checked — wendkeep is a neutral harness and presumes no third-party plugin). Each is wired the most agent-agnostic way it supports:
116
117
  - **`context-mode`** — context optimizer + FTS5 memory, wired as a Claude Code plugin. It ships its own MCP server, so wendkeep deliberately adds no `.mcp.json` entry (registering both cold-started two servers at once). On non-Claude agents, add the MCP by hand: `npx -y context-mode`.
@@ -235,6 +236,7 @@ The README is the map; the guides provide syntax, options, exit codes, examples,
235
236
  | **Notes and knowledge** | BUG/APR/ADR, repairs, renumbering, lessons, and dashboard | [Notes and knowledge](https://github.com/rogersialves/wendkeep/blob/main/docs/en/commands/notes-and-knowledge.md) |
236
237
  | **Costs and observability** | safe dry-run, tri-state, aggregation, trends, and historical rebuild | [Costs and observability](https://github.com/rogersialves/wendkeep/blob/main/docs/en/commands/costs-and-observability.md) |
237
238
  | **Maintenance and diagnostics** | doctor, frontier/manifest freshness, drift, version, and help | [Maintenance and diagnostics](https://github.com/rogersialves/wendkeep/blob/main/docs/en/commands/maintenance-and-diagnostics.md) |
239
+ | **Local Observer** | `observer serve`, registration, snapshots, outbox, and multi-project index | [Local Observer](https://github.com/rogersialves/wendkeep/blob/main/docs/en/commands/observer.md) |
238
240
 
239
241
  Operations that deserve step-by-step guidance: [verify and exits 0/1/2](https://github.com/rogersialves/wendkeep/blob/main/docs/en/commands/verify.md),
240
242
  [legacy-memory migration](https://github.com/rogersialves/wendkeep/blob/main/docs/en/commands/memory-migration.md), and
@@ -77,6 +77,10 @@ tool runs, it compares the session, project, Git root, remote, branch, and workt
77
77
  recorded in `SESSION_REGISTRY.json`. Missing, ambiguous, concurrent, or cross-project targets are
78
78
  blocked before the tool.
79
79
 
80
+ The [local Observer](observer.md) is a read-only observability projection: the vault and change
81
+ remain local authorities. Observer queries do not complete, archive, repair, or promote state in a
82
+ vault.
83
+
80
84
  Codex blocks with `permissionDecision: "deny"`; `ask` is not a valid `PreToolUse` decision.
81
85
  `commit`, `push`, `pull`, `merge`, `publish`, and destructive operations remain separate capabilities,
82
86
  including when one command contains multiple actions. Switching projects requires a new explicit
@@ -19,7 +19,8 @@ projects whose registries are mixed.
19
19
 
20
20
  ## Prerequisites
21
21
 
22
- A consistent registry, complete price table, and transcript access for rebuilt sessions.
22
+ A consistent registry, a price table for models with published rates, and transcript access for
23
+ rebuilt sessions. A research preview without a final rate must remain explicitly unestimated.
23
24
 
24
25
  ## Syntax
25
26
 
@@ -64,9 +65,17 @@ returns `complete`, `none`, or `degraded`, plus a frontier, manifest, and saniti
64
65
  Run and review the dry-run before repeating the same command with `--apply`; a semantically
65
66
  identical second apply preserves the note, checkpoint, report, and mtime.
66
67
 
68
+ ## Models without a final rate
69
+
70
+ `doctor` separates a model that is truly missing from the table from a known
71
+ `research-preview` model. The former remains actionable and asks for an update to
72
+ `hooks/pricing.json`; the latter is reported as **final rate not published** and **cost not
73
+ estimated**. Do not copy another model's price into the gap.
74
+
67
75
  ## Common errors and diagnosis
68
76
 
69
- - Model without a price: update the table before accepting totals.
77
+ - Unknown model without a price: update the table before accepting totals.
78
+ - Research preview without a final rate: keep the cost unestimated; do not invent a rate.
70
79
  - Wrong-provider costs: validate the session identity chain.
71
80
  - Missing transcript: do not estimate silently; keep the gap visible.
72
81
  - Duplicated parent/subagent/fork totals: verify registry relationships and deduplication.
@@ -0,0 +1,104 @@
1
+ # Local Observer
2
+
3
+ **English** · [Português](../../pt-BR/commands/observer.md)
4
+
5
+ ## Purpose
6
+
7
+ The Observer consolidates observability for multiple WendKeep projects in a local service without
8
+ copying or taking ownership of their vaults.
9
+
10
+ ## When to use
11
+
12
+ Use it to query summarized changes, sessions, tasks, and health across projects through one local
13
+ projection while each vault remains authoritative.
14
+
15
+ ## When not to use
16
+
17
+ Do not use the Observer to edit, complete, or archive changes, curate memory, store transcripts,
18
+ or replace local hooks. Do not expose it to the network in this version.
19
+
20
+ ## Prerequisites
21
+
22
+ Initialize projects with WendKeep, explicitly register each project, and set a local token before
23
+ starting the HTTP server.
24
+
25
+ ## Syntax
26
+
27
+ ```bash
28
+ npx wendkeep observer status --data-dir <directory> --json
29
+ npx wendkeep observer register --project <project> --vault <vault> --data-dir <directory>
30
+ npx wendkeep observer publish --project <project> --vault <vault> --data-dir <directory>
31
+ npx wendkeep observer serve --host 127.0.0.1 --port 8787 --data-dir <directory>
32
+ ```
33
+
34
+ ## Options and exit codes
35
+
36
+ - `--data-dir` selects the local event and index directory; the default is
37
+ `WENDKEEP_OBSERVER_DATA_DIR` or `~/.wendkeep-observer`.
38
+ - `--project` and `--vault` identify a project only for `register` and `publish`.
39
+ - `--host` accepts only `127.0.0.1`, `localhost`, or `::1`; other hosts are rejected before
40
+ listening.
41
+ - `--token` or `WENDKEEP_OBSERVER_TOKEN` protects `/v1`; `GET /healthz` exposes no project data.
42
+ - Exit `0` means success; exit `1` means configuration or operation failure; the publisher hook
43
+ also returns `0` when the Observer is unavailable.
44
+
45
+ ## Examples
46
+
47
+ ```powershell
48
+ $env:WENDKEEP_OBSERVER_TOKEN = '<local-token>'
49
+ npx wendkeep observer register --project C:\GitHub\WendKeep --vault C:\GitHub\WendKeep\.WendKeep-vault --data-dir C:\WendKeepObserver
50
+ npx wendkeep observer serve --host 127.0.0.1 --port 8787 --data-dir C:\WendKeepObserver
51
+ $env:WENDKEEP_OBSERVER_URL = 'http://127.0.0.1:8787'
52
+ ```
53
+
54
+ For local Docker:
55
+
56
+ ```powershell
57
+ $env:WENDKEEP_OBSERVER_TOKEN = '<local-token>'
58
+ docker compose -f docker/wendkeep-observer/compose.yaml up -d --build
59
+ ```
60
+
61
+ ## Expected result
62
+
63
+ `register` stores only `project_id`, name, version, and registration time. `publish` reads the
64
+ local vault, produces a sanitized snapshot, and sends an idempotent event. The container stores
65
+ only `EVENTS.jsonl` and `INDEX.json` in the `observer-data` volume; it does not mount `C:\GitHub`
66
+ or any `.WendKeep-vault`.
67
+
68
+ `init` projects `observer-publish` into `SessionStart` and `Stop` after the primary hooks. Without
69
+ `WENDKEEP_OBSERVER_URL`, the hook is a no-op. When the server is stopped, it writes to
70
+ `.brain/observer-outbox/` and does not block the session; a later run retries pending events.
71
+
72
+ ## Common errors and diagnosis
73
+
74
+ - `project_not_registered`: run `observer register` before publishing.
75
+ - `unauthorized`: check `Authorization: Bearer <token>` and `WENDKEEP_OBSERVER_TOKEN`.
76
+ - `host loopback`: replace `0.0.0.0` or a LAN address with `127.0.0.1`.
77
+ - Pending outbox: the service was unavailable; preserve `.brain/observer-outbox/` and rerun the
78
+ publisher. Do not delete events manually.
79
+ - The Observer does not read raw content, paths, transcripts, or memory; such rejections are
80
+ expected and should be investigated at the snapshot source.
81
+
82
+ ## Next steps
83
+
84
+ Read the `local-observer` change for the `OBS-1` through `OBS-8` contract. Do not remove the Docker
85
+ volume with `docker compose down -v` during normal operation because it deletes the local
86
+ projection.
87
+
88
+ ## Data authority
89
+
90
+ Each project vault remains authoritative for sessions, changes, tasks, memory, and evidence. The
91
+ Observer is a read-only, rebuildable projection; its queries do not complete, archive, repair, or
92
+ promote state in a vault.
93
+
94
+ ## Minimal API
95
+
96
+ - `GET /healthz` — availability without project data.
97
+ - `GET /v1/projects` — projects with an accepted snapshot.
98
+ - `GET /v1/projects/:project_id` — the latest project snapshot.
99
+ - `GET /v1/projects/:project_id/changes` — change summary from the snapshot.
100
+ - `PUT /v1/projects/:project_id` — authenticated explicit registration.
101
+ - `POST /v1/projects/:project_id/snapshot` — authenticated idempotent ingestion.
102
+
103
+ The `/v1` routes reject oversized bodies and never accept vault paths, transcripts, secrets, or raw
104
+ memory content.
@@ -77,6 +77,10 @@ uma ferramenta de escrita suportada, ele compara sessão, projeto, raiz Git, rem
77
77
  worktree com a lease registrada no `SESSION_REGISTRY.json`. Um alvo ausente, ambíguo, concorrente ou
78
78
  fora do projeto é bloqueado antes da ferramenta.
79
79
 
80
+ O [Observer local](observer.md) é uma projeção read-only da observabilidade: o vault e a change
81
+ continuam autoridades locais. Consultas do Observer não concluem, arquivam, reparam ou promovem
82
+ estado no vault.
83
+
80
84
  No Codex o bloqueio usa `permissionDecision: "deny"`; `ask` não é uma decisão válida de
81
85
  `PreToolUse`. `commit`, `push`, `pull`, `merge`, `publish` e operações destrutivas continuam
82
86
  capacidades separadas, inclusive quando um comando contém mais de uma ação. A troca de projeto
@@ -19,7 +19,9 @@ projetos com registries misturados.
19
19
 
20
20
  ## Pré-requisitos
21
21
 
22
- Registry consistente, tabela de preços completa e acesso aos transcripts das sessões reconstruídas.
22
+ Registry consistente, tabela de preços para os modelos com tarifa publicada e acesso aos transcripts
23
+ das sessões reconstruídas. Um research preview sem tarifa final deve permanecer explicitamente
24
+ como custo não estimado.
23
25
 
24
26
  ## Sintaxe
25
27
 
@@ -64,9 +66,17 @@ tri-state devolve `complete`, `none` ou `degraded`, mais frontier, manifest e di
64
66
  sanitizados. Rode e revise o dry-run antes de repetir o mesmo comando com `--apply`; uma segunda
65
67
  aplicação semanticamente idêntica preserva nota, checkpoint, relatório e mtime.
66
68
 
69
+ ## Modelos sem tarifa final
70
+
71
+ O `doctor` separa um modelo realmente ausente da tabela de um modelo conhecido em
72
+ `research-preview`. O primeiro continua acionável e pede atualização de
73
+ `hooks/pricing.json`; o segundo aparece como **tarifa final não publicada** e **custo não
74
+ estimado**. Não copie o preço de outro modelo para preencher essa lacuna.
75
+
67
76
  ## Erros comuns e diagnóstico
68
77
 
69
- - Modelo sem preço: atualize a tabela antes de aceitar o total.
78
+ - Modelo desconhecido sem preço: atualize a tabela antes de aceitar o total.
79
+ - Research preview sem tarifa final: mantenha o custo não estimado; não invente uma tarifa.
70
80
  - Custos de provider errado: valide a cadeia de identidade da sessão.
71
81
  - Transcript ausente: não estime silenciosamente; mantenha a lacuna visível.
72
82
  - Total duplicado por subagent/fork: confirme relação pai/subagent e deduplicação do registry.
@@ -0,0 +1,105 @@
1
+ # Observer local
2
+
3
+ **PT-BR** · [English](../../en/commands/observer.md)
4
+
5
+ ## Objetivo
6
+
7
+ O Observer consolida a observabilidade de vários projetos WendKeep em um serviço local, sem
8
+ copiar ou assumir a propriedade dos vaults.
9
+
10
+ ## Quando usar
11
+
12
+ Use para consultar changes, sessões, tarefas resumidas e saúde de vários projetos em uma única
13
+ projeção local, mantendo cada vault como fonte oficial.
14
+
15
+ ## Quando não usar
16
+
17
+ Não use o Observer para editar, concluir ou arquivar changes, curar memória, armazenar transcripts
18
+ ou substituir os hooks locais. Não o exponha na rede nesta versão.
19
+
20
+ ## Pré-requisitos
21
+
22
+ Tenha os projetos inicializados com WendKeep, registre explicitamente cada projeto e defina um
23
+ token local antes de iniciar o servidor HTTP.
24
+
25
+ ## Sintaxe
26
+
27
+ ```bash
28
+ npx wendkeep observer status --data-dir <diretório> --json
29
+ npx wendkeep observer register --project <projeto> --vault <vault> --data-dir <diretório>
30
+ npx wendkeep observer publish --project <projeto> --vault <vault> --data-dir <diretório>
31
+ npx wendkeep observer serve --host 127.0.0.1 --port 8787 --data-dir <diretório>
32
+ ```
33
+
34
+ ## Opções e códigos de saída
35
+
36
+ - `--data-dir` escolhe o diretório local de eventos e índice; o padrão é
37
+ `WENDKEEP_OBSERVER_DATA_DIR` ou `~/.wendkeep-observer`.
38
+ - `--project` e `--vault` identificam o projeto somente nos comandos `register` e `publish`.
39
+ - `--host` aceita somente `127.0.0.1`, `localhost` ou `::1`; outros hosts são recusados antes do
40
+ listen.
41
+ - `--token` ou `WENDKEEP_OBSERVER_TOKEN` protege as rotas `/v1`; `GET /healthz` permanece sem
42
+ dados de projeto.
43
+ - Exit `0` indica sucesso; exit `1` indica falha de configuração ou operação; o hook publisher
44
+ também retorna `0` quando o Observer está indisponível.
45
+
46
+ ## Exemplos
47
+
48
+ ```powershell
49
+ $env:WENDKEEP_OBSERVER_TOKEN = '<token-local>'
50
+ npx wendkeep observer register --project C:\GitHub\WendKeep --vault C:\GitHub\WendKeep\.WendKeep-vault --data-dir C:\WendKeepObserver
51
+ npx wendkeep observer serve --host 127.0.0.1 --port 8787 --data-dir C:\WendKeepObserver
52
+ $env:WENDKEEP_OBSERVER_URL = 'http://127.0.0.1:8787'
53
+ ```
54
+
55
+ Para Docker local:
56
+
57
+ ```powershell
58
+ $env:WENDKEEP_OBSERVER_TOKEN = '<token-local>'
59
+ docker compose -f docker/wendkeep-observer/compose.yaml up -d --build
60
+ ```
61
+
62
+ ## Resultado esperado
63
+
64
+ `register` grava apenas `project_id`, nome, versão e data de registro. `publish` lê o vault local,
65
+ produz um snapshot sanitizado e envia um evento idempotente. O container mantém somente
66
+ `EVENTS.jsonl` e `INDEX.json` no volume `observer-data`; não monta `C:\GitHub` nem qualquer
67
+ `.WendKeep-vault`.
68
+
69
+ O `init` projeta `observer-publish` para `SessionStart` e `Stop` depois dos hooks principais. Sem
70
+ `WENDKEEP_OBSERVER_URL`, o hook é no-op. Com o servidor parado, ele grava em
71
+ `.brain/observer-outbox/` e não bloqueia a sessão; uma execução posterior tenta reenviar eventos
72
+ pendentes.
73
+
74
+ ## Erros comuns e diagnóstico
75
+
76
+ - `project_not_registered`: rode `observer register` antes de publicar.
77
+ - `unauthorized`: confira `Authorization: Bearer <token>` e `WENDKEEP_OBSERVER_TOKEN`.
78
+ - `host loopback`: troque `0.0.0.0` ou endereço LAN por `127.0.0.1`.
79
+ - Outbox pendente: o serviço estava indisponível; preserve `.brain/observer-outbox/` e repita o
80
+ publisher. Não apague eventos manualmente.
81
+ - O Observer não lê conteúdo bruto, caminhos, transcripts ou memória; rejeições desse tipo são
82
+ esperadas e devem ser investigadas na origem do snapshot.
83
+
84
+ ## Próximos passos
85
+
86
+ Leia a change `local-observer` para o contrato `OBS-1` a `OBS-8`. O volume Docker não deve ser
87
+ removido com `docker compose down -v` durante a operação normal, pois isso apaga a projeção local.
88
+
89
+ ## Autoridade dos dados
90
+
91
+ O vault de cada projeto continua sendo a fonte oficial de sessões, changes, tarefas, memória e
92
+ evidências. O Observer é uma projeção read-only reconstruível; suas consultas não concluem,
93
+ arquivam, reparam ou promovem estado no vault.
94
+
95
+ ## API mínima
96
+
97
+ - `GET /healthz` — disponibilidade sem dados de projeto.
98
+ - `GET /v1/projects` — projetos com snapshot aceito.
99
+ - `GET /v1/projects/:project_id` — último snapshot do projeto.
100
+ - `GET /v1/projects/:project_id/changes` — resumo das changes do snapshot.
101
+ - `PUT /v1/projects/:project_id` — registro explícito autenticado.
102
+ - `POST /v1/projects/:project_id/snapshot` — ingestão autenticada e idempotente.
103
+
104
+ As rotas `/v1` rejeitam corpo acima do limite e nunca aceitam caminho de vault, transcript, segredo
105
+ ou conteúdo bruto de memória.
@@ -212,9 +212,11 @@ export function checkStackedFrontmatter(vaultBase) {
212
212
  }
213
213
 
214
214
  // Um modelo fora de `pricing.json` faz `priceForModel` devolver null e a parcela dele do custo
215
- // virar zero — sem erro, sem aviso. Modelo novo (claude-opus-5, claude-mythos-5) cai nisso por
216
- // default. A checagem é sobre o vault, não sobre o caminho de cálculo: o cálculo roda em hook a
217
- // cada turno, onde avisar viraria ruído e lançar derrubaria a captura da sessão.
215
+ // virar zero — sem erro, sem aviso. Modelos conhecidos sem tarifa final (por exemplo, um
216
+ // research preview) ficam na tabela com status explícito e são reportados separadamente: isso
217
+ // evita tanto o silêncio quanto a sugestão de inventar um preço. A checagem é sobre o vault, não
218
+ // sobre o caminho de cálculo: o cálculo roda em hook a cada turno, onde avisar viraria ruído e
219
+ // lançar derrubaria a captura da sessão.
218
220
  //
219
221
  // Cada modelo citado na nota é consultado direto em `priceForModel` — NÃO se infere pelo
220
222
  // sintoma "custo zerado". Numa sessão multi-modelo (`modelo: "claude-opus-4.8 + claude-opus-5"`)
@@ -222,6 +224,7 @@ export function checkStackedFrontmatter(vaultBase) {
222
224
  // motivou esta change, a nota fecha com $415 e a fatia do Opus 5 é a única zerada.
223
225
  export function checkUnpricedModels(vaultBase) {
224
226
  const counts = new Map();
227
+ const researchPreview = new Map();
225
228
 
226
229
  const modelsOf = (frontmatter) => {
227
230
  // `modelos:` é a lista canônica; `modelo:` é o rótulo agregado (junta com " + ").
@@ -247,23 +250,34 @@ export function checkUnpricedModels(vaultBase) {
247
250
  for (const raw of modelsOf(fm[1])) {
248
251
  const model = raw.trim().replace(/^["']|["']$/g, '');
249
252
  if (!model || model === 'unknown') continue;
250
- if (priceForModel(model)) continue;
251
- counts.set(model, (counts.get(model) || 0) + 1);
253
+ const price = priceForModel(model);
254
+ if (price?.pricingStatus === 'research-preview') {
255
+ researchPreview.set(model, (researchPreview.get(model) || 0) + 1);
256
+ } else if (!price) {
257
+ counts.set(model, (counts.get(model) || 0) + 1);
258
+ }
252
259
  }
253
260
  }
254
261
  };
255
262
 
256
263
  walk(join(vaultBase, '02-Sessões'));
257
- return { models: [...counts].map(([model, notes]) => ({ model, notes })) };
264
+ return {
265
+ models: [...counts].map(([model, notes]) => ({ model, notes })),
266
+ researchPreview: [...researchPreview].map(([model, notes]) => ({ model, notes })),
267
+ };
258
268
  }
259
269
 
260
270
  export function renderUnpricedModelLines(unpriced) {
271
+ const preview = unpriced.researchPreview || [];
261
272
  const lines = [`[preços] ${unpriced.models.length} modelo(s) sem preço na tabela`];
262
273
  for (const { model, notes } of unpriced.models) {
263
274
  lines.push(` ✗ ${model} (${notes} nota(s) com custo zerado)`);
264
275
  }
265
276
  if (unpriced.models.length) lines.push(' → adicione o modelo em hooks/pricing.json');
266
- else lines.push(' tabela de preços completa ✓');
277
+ else if (!preview.length) lines.push(' tabela de preços completa ✓');
278
+ for (const { model, notes } of preview) {
279
+ lines.push(` ! ${model} (${notes} nota(s)): research preview sem tarifa final; custo não estimado`);
280
+ }
267
281
  return lines;
268
282
  }
269
283
 
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env node
2
+ import { pathToFileURL } from 'node:url';
3
+ import { debugLog, readHookInput, resolveVault } from './obsidian-common.mjs';
4
+ import { publishObserverSnapshot } from '../src/observer-publish.mjs';
5
+
6
+ async function main() {
7
+ const input = readHookInput();
8
+ const resolved = resolveVault(input);
9
+ const result = await publishObserverSnapshot({
10
+ vaultBase: resolved.base,
11
+ projectRoot: resolved.projectRoot,
12
+ });
13
+ if (!result.ok && result.error) debugLog('Observer publish fail-open:', result.error);
14
+ }
15
+
16
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
17
+ main().catch((error) => {
18
+ debugLog('Observer hook falhou de forma fail-open:', error);
19
+ process.exitCode = 0;
20
+ });
21
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "_nota": "Preços API por milhão de tokens. cachedInput = cache read. Cache write aplica multiplicador no código: 5m = 1.25x input, 1h = 2x input. Editar aqui quando o provedor mudar preços (sem mexer no .mjs). Se o arquivo sumir ou ficar inválido, o hook usa a tabela embutida em token-usage.mjs.",
3
- "_fonte": "OpenAI https://openai.com/index/gpt-5-6/ e https://help.openai.com/en/articles/20001325-a-preview-of-gpt-56-sol-terra-and-luna; Anthropic https://www.anthropic.com/pricing — conferido 2026-07-11",
3
+ "_fonte": "OpenAI https://openai.com/index/gpt-5-6/, https://openai.com/index/introducing-gpt-5-3-codex-spark/ e https://help.openai.com/en/articles/20001106-codex-rate-card; Anthropic https://www.anthropic.com/pricing — conferido 2026-08-16",
4
4
  "models": {
5
5
  "gpt-5.6-sol": { "label": "GPT-5.6 Sol API", "provider": "openai", "input": 5, "cachedInput": 0.5, "output": 30 },
6
6
  "gpt-5.6-terra": { "label": "GPT-5.6 Terra API", "provider": "openai", "input": 2.5, "cachedInput": 0.25, "output": 15 },
@@ -12,6 +12,15 @@
12
12
  "cachedInput": 0.5,
13
13
  "output": 30
14
14
  },
15
+ "gpt-5.3-codex-spark": {
16
+ "label": "GPT-5.3-Codex-Spark (research preview)",
17
+ "provider": "openai",
18
+ "pricingStatus": "research-preview",
19
+ "pricingNote": "Tarifa final não publicada; custo não estimado.",
20
+ "input": null,
21
+ "cachedInput": null,
22
+ "output": null
23
+ },
15
24
  "claude-opus-4.7": {
16
25
  "label": "Claude Opus 4.7 API",
17
26
  "provider": "anthropic",
@@ -30,6 +30,15 @@ const DEFAULT_PRICE_REFERENCE = {
30
30
  'gpt-5.6-sol': { label: 'GPT-5.6 Sol API', provider: 'openai', input: 5, cachedInput: 0.5, output: 30 },
31
31
  'gpt-5.6-terra': { label: 'GPT-5.6 Terra API', provider: 'openai', input: 2.5, cachedInput: 0.25, output: 15 },
32
32
  'gpt-5.6-luna': { label: 'GPT-5.6 Luna API', provider: 'openai', input: 1, cachedInput: 0.1, output: 6 },
33
+ 'gpt-5.3-codex-spark': {
34
+ label: 'GPT-5.3-Codex-Spark (research preview)',
35
+ provider: 'openai',
36
+ pricingStatus: 'research-preview',
37
+ pricingNote: 'Tarifa final não publicada; custo não estimado.',
38
+ input: null,
39
+ cachedInput: null,
40
+ output: null,
41
+ },
33
42
  'gpt-5.5': {
34
43
  label: 'GPT-5.5 API',
35
44
  provider: 'openai',
@@ -122,6 +131,10 @@ const MODEL_ALIASES = {
122
131
  'gpt-5.3-codex': 'gpt-5.5',
123
132
  'gpt-5.3': 'gpt-5.5',
124
133
  'openai/gpt-5.4': 'gpt-5.5',
134
+ 'gpt-5.3-codex-spark': 'gpt-5.3-codex-spark',
135
+ 'gpt-5-3-codex-spark': 'gpt-5.3-codex-spark',
136
+ 'openai/gpt-5.3-codex-spark': 'gpt-5.3-codex-spark',
137
+ 'openai/gpt-5-3-codex-spark': 'gpt-5.3-codex-spark',
125
138
  'claude-opus-4.7': 'claude-opus-4.7',
126
139
  'claude-opus-4-7': 'claude-opus-4.7',
127
140
  'anthropic/claude-opus-4.7': 'claude-opus-4.7',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wendkeep",
3
- "version": "0.69.0",
3
+ "version": "0.70.0",
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": [
@@ -40,7 +40,7 @@
40
40
  "node": ">=18"
41
41
  },
42
42
  "scripts": {
43
- "check": "node --check scripts/release.mjs && node --check scripts/release-plan.mjs && node --check bin/wendkeep.mjs && node --check packages/cli/src/index.mjs && node --check src/init.mjs && node --check src/doctor.mjs && node --check src/project-vault.mjs && node --check src/operating-profile.mjs && node --check src/profile.mjs && node --check src/flow.mjs && node --check hooks/operating-profile-runtime.mjs && node --check hooks/operating-profile-task-store.mjs && node --check hooks/flow-core.mjs && node --check hooks/flow-protected-policy.mjs && node --check hooks/git-snapshot.mjs && node --check hooks/vault-path-safety.mjs && node --check hooks/vault-runtime-store.mjs && node --check packages/harness/src/index.mjs && node --check packages/harness/src/flow-store.mjs && node --check packages/harness/src/operating-profile.mjs && node --check packages/harness/src/sensors-core.mjs && node --check packages/integrations/src/host-hooks.mjs && node --check packages/integrations/src/hook-envelope.mjs && node --check packages/integrations/src/prompt-content.mjs && node --check packages/integrations/src/transcript-usage.mjs && node --check packages/integrations/src/transcripts.mjs && node --check packages/integrations/src/session-identity.mjs && node --check packages/integrations/src/index.mjs && node --check packages/mcp/src/config.mjs && node --check packages/mcp/src/index.mjs && node --check packages/vault/src/index.mjs && node --check packages/vault/src/project-vault.mjs && node --check packages/vault/src/vault-path-safety.mjs && node --check packages/vault/src/locale.mjs && node --check packages/vault/src/memory-schema.mjs && node --check packages/vault/src/memory-mode.mjs && node --check packages/vault/src/memory-handoff.mjs && node --check packages/vault/src/memory-store.mjs && node --check packages/vault/src/validate-core.mjs && node --check packages/vault/src/validate-memory.mjs",
43
+ "check": "node --check scripts/release.mjs && node --check scripts/release-plan.mjs && node --check bin/wendkeep.mjs && node --check packages/cli/src/index.mjs && node --check src/init.mjs && node --check src/doctor.mjs && node --check src/project-vault.mjs && node --check src/observer-snapshot.mjs && node --check src/observer-store.mjs && node --check src/observer-server.mjs && node --check src/observer.mjs && node --check src/observer-publish.mjs && node --check src/operating-profile.mjs && node --check src/profile.mjs && node --check src/flow.mjs && node --check hooks/observer-publish.mjs && node --check hooks/operating-profile-runtime.mjs && node --check hooks/operating-profile-task-store.mjs && node --check hooks/flow-core.mjs && node --check hooks/flow-protected-policy.mjs && node --check hooks/git-snapshot.mjs && node --check hooks/vault-path-safety.mjs && node --check hooks/vault-runtime-store.mjs && node --check packages/harness/src/index.mjs && node --check packages/harness/src/flow-store.mjs && node --check packages/harness/src/operating-profile.mjs && node --check packages/harness/src/sensors-core.mjs && node --check packages/integrations/src/host-hooks.mjs && node --check packages/integrations/src/hook-envelope.mjs && node --check packages/integrations/src/prompt-content.mjs && node --check packages/integrations/src/transcript-usage.mjs && node --check packages/integrations/src/transcripts.mjs && node --check packages/integrations/src/session-identity.mjs && node --check packages/integrations/src/index.mjs && node --check packages/mcp/src/config.mjs && node --check packages/mcp/src/index.mjs && node --check packages/vault/src/index.mjs && node --check packages/vault/src/project-vault.mjs && node --check packages/vault/src/vault-path-safety.mjs && node --check packages/vault/src/locale.mjs && node --check packages/vault/src/memory-schema.mjs && node --check packages/vault/src/memory-mode.mjs && node --check packages/vault/src/memory-handoff.mjs && node --check packages/vault/src/memory-store.mjs && node --check packages/vault/src/validate-core.mjs && node --check packages/vault/src/validate-memory.mjs",
44
44
  "test": "node --test --test-concurrency=2",
45
45
  "release": "node scripts/release.mjs",
46
46
  "release:dry": "node scripts/release.mjs --dry-run",
@@ -70,6 +70,6 @@
70
70
  },
71
71
  "devDependencies": {
72
72
  "acorn": "^8.18.0",
73
- "wendkeep": "^0.68.6"
73
+ "wendkeep": "^0.69.0"
74
74
  }
75
75
  }
@@ -49,6 +49,7 @@ Usage:
49
49
  cannot replace itself. · --vault P · --profile <name> · --yes.
50
50
 
51
51
  wendkeep doctor [--vault P] Run a vault health check.
52
+ wendkeep observer <sub> Local multi-project Observer: serve | register | publish | status.
52
53
  wendkeep change <sub> Change lifecycle: new [--simple] | use | bind <slug> --session <id> | continue | list | show |
53
54
  status | done <id> | undone <id> | diff | archive [--force] | abandon | relink | backlink.
54
55
  archive exige verdict (rode verify --deep); abandon descarta sem ADR.
@@ -199,7 +200,7 @@ async function main(argv) {
199
200
  // `sync` starts with `init` and resolves the freshly bound Vault itself. Pre-resolving
200
201
  // here would prevent that repair step from reporting a corrupt binding as its own
201
202
  // first-stage failure (and could never make it as far as the guarded init).
202
- && !['init', 'sync', 'hook', '--version', '-v', '--help', '-h', 'help'].includes(cmd)) {
203
+ && !['init', 'sync', 'hook', 'observer', '--version', '-v', '--help', '-h', 'help'].includes(cmd)) {
203
204
  await preferProjectVault(rest);
204
205
  }
205
206
  switch (cmd) {
@@ -216,6 +217,12 @@ async function main(argv) {
216
217
  process.exit(runDoctor(rest));
217
218
  break;
218
219
  }
220
+ case 'observer': {
221
+ const { runObserver } = await import('../../../src/observer.mjs');
222
+ const observerExitCode = await runObserver(rest);
223
+ if (rest[0] !== 'serve') process.exit(observerExitCode);
224
+ break;
225
+ }
219
226
  case 'sync': {
220
227
  const { runSync } = await import('../../../src/sync.mjs');
221
228
  process.exit(await runSync(rest));
@@ -13,7 +13,11 @@ export const SESSION_HOOKS = [
13
13
  // memory injection for the whole session.
14
14
  { event: 'SessionStart', matcher: 'startup|clear|compact', name: 'brain-inject', timeout: 45, order: -10, codex: true, statusMessage: 'wendkeep: injecting memory + active change' },
15
15
  { event: 'SessionStart', matcher: 'startup', name: 'session-start', timeout: 30, codex: true, statusMessage: 'wendkeep: opening Obsidian session' },
16
+ // Observer publication is a derived, fail-open projection and therefore runs only after
17
+ // the local lifecycle hook has written its authoritative session state.
18
+ { event: 'SessionStart', matcher: 'startup|resume|clear|compact', name: 'observer-publish', timeout: 5, order: 20, codex: true, statusMessage: 'wendkeep: publishing local observer snapshot' },
16
19
  { event: 'Stop', matcher: null, name: 'session-stop', timeout: 60, codex: true, statusMessage: 'wendkeep: writing session checkpoint' },
20
+ { event: 'Stop', matcher: null, name: 'observer-publish', timeout: 5, order: 20, codex: true, statusMessage: 'wendkeep: publishing local observer snapshot' },
17
21
  { event: 'UserPromptSubmit', matcher: null, name: 'session-ensure', timeout: 30, codex: true, statusMessage: 'wendkeep: ensuring active session' },
18
22
  // Capture an interactive decision (AskUserQuestion) — options + the user's choice — into 04-Decisões.
19
23
  // codex: AskUserQuestion is a Claude-only tool; there is nothing to match on.