wendkeep 0.24.0 → 0.26.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,39 @@ 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.26.0] — 2026-07-08
8
+
9
+ ### Fixed
10
+ - **`wendkeep init` output now follows the chosen vault language.** Picking Português left the
11
+ whole summary + `[n/4]` steps + "Next steps" block in English; only the interactive prompts were
12
+ localized. All init output is now driven by a locale message set (pt-BR / en) resolved from the
13
+ language answer — "Próximos passos", "taxonomia do vault", "sensores semeados", etc. `src/init.mjs`.
14
+
15
+ ## [0.25.1] — 2026-07-08
16
+
17
+ ### Added
18
+ - **Landing page in the repo**: a static SVG hero (`docs/assets/wendkeep-hero.svg`, the knowledge
19
+ graph) embedded at the top of the README, plus the self-contained interactive landing at
20
+ `docs/index.html` (live Canvas graph; serve `docs/` via GitHub Pages for a public URL).
21
+
22
+ ### Changed
23
+ - `wendkeep stats` now says **"N dias ativos (first→last)"** — the count is distinct days *with
24
+ activity*, not the calendar span; the old "N dia(s)" read as calendar days.
25
+
26
+ ## [0.25.0] — 2026-07-08
27
+
28
+ Cost trend/projection + shareable stats + launch assets.
29
+
30
+ ### Added
31
+ - **`wendkeep cost --trend [day|week|month]`** — cost bucketed over time plus a run-rate
32
+ **projection** (recent-window daily average × horizon). `wendkeep cost --write` generates a
33
+ `00-Custo.md` trend note in the vault (by-month table + projection + top models). `src/cost.mjs`.
34
+ - **`wendkeep stats`** — one shareable line: sessions · prompts · spend · date span · models
35
+ (`--json` too). For the npm page, a README badge line, or a post. `src/stats.mjs`.
36
+ - **Launch assets** (`docs/`): README hero (tagline, badges, quickstart, screenshot slot),
37
+ Show HN / r/ObsidianMD / X post drafts (`docs/20-launch-posts.md`), and a repeatable
38
+ graph-screenshot guide (`docs/21-graph-screenshot.md`).
39
+
7
40
  ## [0.24.0] — 2026-07-08
8
41
 
9
42
  ### Changed
package/README.md CHANGED
@@ -1,25 +1,47 @@
1
1
  # wendkeep
2
2
 
3
+ [Português](README.pt-BR.md) · **English**
4
+
5
+ > **Your AI coding agent forgets every session. wendkeep makes it remember — in the Obsidian vault you already use.**
6
+
7
+ [![npm](https://img.shields.io/npm/v/wendkeep.svg)](https://www.npmjs.com/package/wendkeep)
3
8
  ![test](https://github.com/rogersialves/wendkeep/actions/workflows/test.yml/badge.svg)
9
+ ![zero deps](https://img.shields.io/badge/runtime%20deps-0-brightgreen)
10
+ ![node](https://img.shields.io/badge/node-%E2%89%A518-blue)
11
+
12
+ [![wendkeep — persistent memory for AI coding agents, shown as a knowledge graph of sessions, decisions, bugs, learnings and changes](docs/assets/wendkeep-hero.svg)](docs/index.html)
13
+
14
+ **In the graph:** 🔵 session · 🟣 decision · 🔴 bug · 🟢 learning · 🟡 change — every note, backlinked.
4
15
 
5
16
  **A persistent‑memory harness for AI coding agents, built on your Obsidian vault.** Every Claude Code / Codex session is captured turn‑by‑turn into local Markdown — with token/cost tracking, auto‑extracted decisions, bugs and learnings, and a curated memory layer injected back at the start of the next session. On top of that memory core sits a native, zero‑dependency **change lifecycle** (spec → change → TDD → sensor‑gated archive) that keeps intent, work and proof wikilinked in one graph. 100% local, open‑core.
6
17
 
18
+ ```bash
19
+ npm i -D wendkeep && npx wendkeep init # captures from the next session on
20
+ npx wendkeep import # backfill past Claude + Codex sessions
21
+ ```
22
+
23
+ **▶ Interactive demo:** [`docs/index.html`](docs/index.html) — a self-contained page with the live knowledge‑graph hero. Open it locally (or serve `docs/` on any static host). The image above is a static render of it.
24
+
25
+ > **From one real production vault** (`npx wendkeep stats`): **308** sessions · **1,696** prompts · **$4,836** captured across **46 active days** (Jan–Jul 2026) · **15** models — every one a note in the graph.
26
+
27
+ <!-- Optional: drop a real Obsidian graph screenshot at docs/assets/graph.png and add it here (see docs/21-graph-screenshot.md). -->
28
+
7
29
  > Extracted from a system in daily production use: the capture engine, cost tracking and graph wiring are battle‑tested; the cross‑platform installer (`wendkeep init`) and the native change loop are the newer parts. See [`docs/`](https://github.com/rogersialves/wendkeep/tree/main/docs) for the project's strategy and decision log.
8
30
 
9
31
  ---
10
32
 
11
- ## Why
33
+ ## The problem: the context dies when the window closes
12
34
 
13
- The pieces to give a coding agent durable memory exist, but fragmented (qmd‑sessions, memsearch, Nexus, hand‑written hooks). wendkeep ships them as one turnkey package that lands the memory **inside the Obsidian graph you already use** — no manual setup, no snapshot to keep in sync.
35
+ Decisions, dead ends, the reason you chose X over Y — gone next session. The pieces to fix that exist but are scattered (qmd‑sessions, memsearch, Nexus, hand‑written hooks). wendkeep ships them as one turnkey package that writes into a knowledge graph **inside the Obsidian vault you already use** — no manual setup, no snapshot to keep in sync.
14
36
 
15
- ## What you get
16
-
17
- - **Automatic session capture** — `SessionStart` / `UserPromptSubmit` / `Stop` hooks write each session to `02-Sessões/<year>/<month>/DIA <dd>/` as Markdown with YAML frontmatter, turn‑by‑turn iterations, and wikilinks.
18
- - **Multi‑agent** — detects the real provider (Claude Code, Codex, Copilot) at runtime; one install covers all.
19
- - **Token & cost tracking** per‑model, cacheaware pricing (`pricing.json`) with a per‑session usage table.
20
- - **Auto‑extracted derived notes** — decisions (ADRstyle), bugs and learnings pulled from the transcript into `04-Decisões/`, `05-Bugs/`, `06-Aprendizados/`, backlinked to the session.
21
- - **Curated memory** — a cold frontmatter index (`.brain/`) plus a budget‑capped `CORE` + `DIGEST` injected at the next `SessionStart`.
22
- - **Local‑first** — everything is plain Markdown on your disk. The optional MCP server (`@bitbonsai/mcpvault`) lets the agent read/write the vault; no cloud, no account.
37
+ | | |
38
+ |---|---|
39
+ | **Capture** — every turn, on disk | `SessionStart` / `Stop` hooks write each session to a dated Markdown note: prompts, iterations, files touched, wikilinks. |
40
+ | **Derive** — decisions, bugs, learnings | Pulled from the transcript into their own notes, backlinked to the session. Your history becomes navigable, not archival. |
41
+ | **Recall** injected back | A budgetcapped `CORE` + `DIGEST` and the active change are fed to the agent at the next `SessionStart`. It resumes where it left off. |
42
+ | **Cost** — what it all cost | Permodel, cache‑aware token pricing per session plus `cost --trend` with a run‑rate projection across the whole vault. |
43
+ | **Multi‑agent** — one install, all agents | Detects the real provider (Claude Code, Codex, Copilot) at runtime. |
44
+ | **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. |
23
45
 
24
46
  ## Requirements
25
47
 
@@ -102,9 +124,9 @@ No re‑copying, no snapshot to re‑sync — the package is the single source o
102
124
  | `wendkeep doctor [--vault P]` | Run a vault health check (integrity of sessions, registry, links). |
103
125
  | `wendkeep --version` / `--help` | Version / usage. |
104
126
 
105
- ## Retroactive memory (`import`)
127
+ ## Retroactive memory (`import`) — install today, remember yesterday
106
128
 
107
- Install wendkeep into an existing project and it only remembers sessions **from now on**. `wendkeep import` fixes that: it reads the project's past **Claude Code and Codex** transcripts and rebuilds each one as a full session note in its **real** date folder — frontmatter (tagged with the transcript's real provider), one iteration block per turn, cost + subagent telemetry, derived decision/bug/learning notes, finalized closing. It is an offline replay of the live capture flow, so an imported note is indistinguishable from a captured one.
129
+ Install wendkeep into an existing project and it only remembers sessions **from now on**. `wendkeep import` fixes that: one command backfills your project's past **Claude & Codex** sessions into the vault — deduped, dated, with cost — so the graph starts full, not empty. It rebuilds each transcript as a full session note in its **real** date folder — frontmatter (tagged with the transcript's real provider), one iteration block per turn, cost + subagent telemetry, derived decision/bug/learning notes, finalized closing. An offline replay of the live capture flow, so an imported note is indistinguishable from a captured one.
108
130
 
109
131
  ```bash
110
132
  wendkeep import --vault .myproject-vault --dry-run # preview what would be imported (both agents)
@@ -131,6 +153,8 @@ explore → propose → apply (TDD) → verify → archive
131
153
  - **Verify** — `wendkeep verify` runs the sensors your tasks declared (from `wendkeep.sensors.json` at the project root) and records `evidencia.json`. A critical red fails the gate; a `warning` red is advisory.
132
154
  - **Archive** — `wendkeep change archive <slug>` **gates** on the evidence (blocks unless every declared critical sensor is green), promotes each capability's spec delta (`ADDED`/`MODIFIED`/`REMOVED`) into the living `07-Specs/<capability>.md`, moves the change to `_arquivo/`, and mints an ADR in `04-Decisões/`.
133
155
 
156
+ > The gate blocks unless the scaffold is filled, no task is open, evidence is fresh, and every declared requirement is covered. **`--force` is the human's call — never the agent's.**
157
+
134
158
  `wendkeep init` also seeds **native process skills** (`wk-workflow`, `wk-tdd`, `wk-debugging`, `wk-brainstorming`, `wk-planning`, `wk-verify`) into `.brain/skills` and delivers them to `.claude/skills` — the *how* layer, zero‑dep. Optional companions (`context-mode`, `dotcontext`, `understand-anything`, `caveman`) remain an opt‑in extra layer.
135
159
 
136
160
  ### The loop in five minutes
@@ -181,6 +205,16 @@ The archive **gate** blocks unless: the change scaffold is filled (G0), no task
181
205
  - **Transcript formats are agent‑internal** and can change between agent versions; parsing is isolated but may need updates.
182
206
  - Installer wires **Claude Code** settings + `.mcp.json`. Codex hooks run on the same scripts but are not auto‑wired yet (import already covers past Codex sessions via `--source codex`).
183
207
 
208
+ ---
209
+
210
+ ## Stop re‑explaining your codebase every morning
211
+
212
+ ```bash
213
+ npm i -D wendkeep && npx wendkeep init
214
+ ```
215
+
216
+ **[Install from npm](https://www.npmjs.com/package/wendkeep)** · **[Star on GitHub](https://github.com/rogersialves/wendkeep)** — MIT · open‑core · your data never leaves your disk.
217
+
184
218
  ## License
185
219
 
186
220
  MIT
@@ -0,0 +1,189 @@
1
+ # wendkeep
2
+
3
+ **Português** · [English](README.md)
4
+
5
+ > **Seu agente de código esquece cada sessão. O wendkeep faz ele lembrar — no cofre Obsidian que você já usa.**
6
+
7
+ [![npm](https://img.shields.io/npm/v/wendkeep.svg)](https://www.npmjs.com/package/wendkeep)
8
+ ![test](https://github.com/rogersialves/wendkeep/actions/workflows/test.yml/badge.svg)
9
+ ![zero deps](https://img.shields.io/badge/deps%20de%20runtime-0-brightgreen)
10
+ ![node](https://img.shields.io/badge/node-%E2%89%A518-blue)
11
+
12
+ [![wendkeep — memória persistente para agentes de código, mostrada como um grafo de sessões, decisões, bugs, aprendizados e mudanças](docs/assets/wendkeep-hero.pt.svg)](docs/index.pt.html)
13
+
14
+ **No grafo:** 🔵 sessão · 🟣 decisão · 🔴 bug · 🟢 aprendizado · 🟡 mudança — cada nota, com backlink.
15
+
16
+ **Um harness de memória persistente para agentes de código, construído sobre o seu cofre Obsidian.** Cada sessão do Claude Code / Codex é capturada turno a turno em Markdown local — com rastreio de tokens/custo, decisões, bugs e aprendizados extraídos automaticamente, e uma camada de memória curada injetada de volta no início da próxima sessão. Sobre esse núcleo de memória fica um **ciclo de mudança** nativo e sem dependências (spec → change → TDD → archive com gate por sensor) que mantém intenção, trabalho e prova wikilinkados num só grafo. 100% local, open‑core.
17
+
18
+ ```bash
19
+ npm i -D wendkeep && npx wendkeep init # captura a partir da próxima sessão
20
+ npx wendkeep import # importa sessões passadas do Claude + Codex
21
+ ```
22
+
23
+ **▶ Demo interativo:** [`docs/index.pt.html`](docs/index.pt.html) — uma página autocontida com o herói de grafo vivo. Abra local (ou sirva `docs/` em qualquer host estático). A imagem acima é um render estático dele.
24
+
25
+ > **De um cofre de produção real** (`npx wendkeep stats`): **308** sessões · **1.696** prompts · **US$ 4.836** capturados em **46 dias ativos** (jan–jul 2026) · **15** modelos — cada uma delas uma nota no grafo.
26
+
27
+ > Extraído de um sistema em uso diário de produção: o motor de captura, o rastreio de custo e a fiação do grafo são testados em batalha; o instalador multiplataforma (`wendkeep init`) e o ciclo de mudança nativo são as partes mais novas. Veja [`docs/`](https://github.com/rogersialves/wendkeep/tree/main/docs) para a estratégia e o log de decisões do projeto.
28
+
29
+ ---
30
+
31
+ ## O problema: o contexto morre quando a janela fecha
32
+
33
+ Decisões, becos sem saída, o motivo de você ter escolhido X em vez de Y — some na próxima sessão. As peças pra resolver existem, mas espalhadas (qmd‑sessions, memsearch, Nexus, hooks feitos à mão). O wendkeep entrega tudo num pacote turnkey que escreve num grafo de conhecimento **dentro do cofre Obsidian que você já usa** — sem setup manual, sem snapshot pra manter sincronizado.
34
+
35
+ | | |
36
+ |---|---|
37
+ | **Captura** — cada turno, no disco | Os hooks `SessionStart` / `Stop` escrevem cada sessão numa nota Markdown datada: prompts, iterações, arquivos tocados, wikilinks. |
38
+ | **Deriva** — decisões, bugs, aprendizados | Puxados do transcript pra notas próprias, com backlink pra sessão. Seu histórico fica navegável, não arquivístico. |
39
+ | **Recall** — injetado de volta | Um `CORE` + `DIGEST` com budget capado e a change ativa são injetados no agente no próximo `SessionStart`. Ele retoma de onde parou. |
40
+ | **Custo** — quanto tudo custou | Preço por modelo, ciente de cache, por sessão — mais `cost --trend` com projeção run‑rate no cofre inteiro. |
41
+ | **Multi‑agente** — uma instalação, todos os agentes | Detecta o provedor real (Claude Code, Codex, Copilot) em runtime. |
42
+ | **Local‑first** — sem nuvem, sem conta | Tudo é Markdown puro no seu disco. Um MCP opcional (`@bitbonsai/mcpvault`) deixa o agente ler/escrever o cofre. |
43
+
44
+ ## Requisitos
45
+
46
+ - Node.js ≥ 18
47
+ - Um agente de código com hooks (Claude Code hoje; Codex atendido pelos mesmos hooks)
48
+ - Obsidian (pra ver o grafo) — opcional, mas é o ponto
49
+
50
+ ## Instalar & configurar
51
+
52
+ ```bash
53
+ # no seu projeto
54
+ npm install --save-dev wendkeep # ou: npm install -g wendkeep
55
+ npx wendkeep init
56
+ ```
57
+
58
+ O `wendkeep init` é interativo e **idempotente**. Ele:
59
+
60
+ 1. Cria a taxonomia de pastas do cofre e um `README.md` templado (cofre padrão: `<projeto>/.<nome-do-projeto>-vault`, ex.: `.MeuApp-vault`; sobrescreva com `--vault`).
61
+ 2. **Mescla** os três hooks de sessão e o `OBSIDIAN_VAULT_PATH` no `.claude/settings.json` — sem atropelar suas configs (salva um `.bak`; arquivo ilegível fica intocado e um `.new` é escrito pra você mesclar).
62
+ 3. Adiciona o servidor MCP **`wendkeep-vault`** ao `.mcp.json` pro agente ler/escrever o cofre. Pule com `--no-mcp` — ex.: quando o agente já tem um MCP de cofre. (`--no-mcp` pula *só o MCP do próprio wendkeep*; os MCPs de companion seguem `--companions`.)
63
+ 4. Oferece fixar plugins/MCP **companion** (múltipla escolha; **nenhum** pré-marcado). Cada um é wirado do jeito mais agnóstico que suporta:
64
+ - **`context-mode`** — otimizador de contexto + memória FTS5, como servidor MCP no `.mcp.json` (qualquer agente).
65
+ - **`understand-anything`** — grafo de domínio do projeto, via um hook `understand-inject` no SessionStart que injeta o grafo quando gerado.
66
+ - **`caveman`** — modo de compressão de tokens; roda seu próprio instalador cross‑agent em agentes não‑Claude.
67
+ - **`dotcontext`** — *legado, não recomendado.* O loop a2 nativo do wendkeep (`change` / `verify` / gate) já faz o trabalho dele, então instalar **duplica o harness**. Ainda selecionável via `--companions dotcontext` pra quem já usa (ajuste com `--dotcontext-mcp` / `--dotcontext-hooks`), mas off por padrão.
68
+
69
+ Controle com `--companions <csv>` ou `--no-companions`. A camada de plugin do Claude Code (`extraKnownMarketplaces` + `enabledPlugins`) é wirada como bônus onde o companion tiver uma.
70
+ 5. Instala um **sistema de cores** no `.obsidian/` do cofre: um snippet CSS que colore notas por tipo (sessão/decisão/bug/aprendizado, via as `cssclasses` que os hooks emitem) mais grupos de cor do grafo por pasta. Merge não‑destrutivo em `appearance.json`/`graph.json`; pule com `--no-colors`.
71
+ 6. Semeia a **camada de memória curada**: `.brain/CORE.md` (a camada quente curada à mão, com as 3 seções obrigatórias) e `.brain/COMPACTION_PROTOCOL.md` (o guia do protocolo). As camadas automáticas (`DIGEST.md`, `index.jsonl`) são geradas pelos hooks. Valide a camada curada com `wendkeep validate-memory` (cap 25 linhas, 3 seções, sem segredos/PII).
72
+ 7. Semeia a **camada de definições + skills**: `.brain/agents/` + `.brain/skills/` (fonte da verdade versionada), incluindo as skills de processo nativas `wk-workflow` / `wk-tdd` / `wk-debugging` / `wk-brainstorming` / `wk-planning` / `wk-verify` (algumas trazem templates, ex.: o `verdict-template.json` + prompt de revisor da `wk-verify`). O `init` roda o `wendkeep sync-defs` pra você, entregando em `.codex/agents/` + `.claude/skills/` (rode `sync-defs` de novo após editar o `.brain`).
73
+ 8. Semeia o **ciclo change/spec**: as pastas `07-Specs/` + `08-Mudanças/` e um `wendkeep.sensors.json` nativo (um sensor `validate-memory` mais um por script detectado no `package.json`). Move o `wendkeep change` / `wendkeep verify` — veja **Ciclo de mudança** abaixo.
74
+
75
+ ```bash
76
+ npx wendkeep init --vault "~/vaults/work" --project . --yes # não-interativo
77
+ npx wendkeep init --companions "context-mode,understand-anything" --yes
78
+ npx wendkeep init --no-companions --no-mcp --yes # zero companions, sem MCP do wendkeep
79
+ ```
80
+
81
+ ### Opções do `init`
82
+
83
+ | Flag | O que faz |
84
+ |---|---|
85
+ | `--vault <path>` | Pasta do cofre. Padrão `<projeto>/.<nome-do-projeto>-vault`; o init interativo pergunta. Aponte pra um cofre existente pra instalar nele. |
86
+ | `--project <path>` | Raiz do projeto a wirar (padrão: diretório atual). |
87
+ | `--locale <pt-BR\|en>` | Idioma do cofre — nomes das pastas, scaffold, skills. O init interativo pergunta; travado no init. |
88
+ | `--companions <csv>` | Companions a fixar: `context-mode,caveman,understand-anything` (padrão: **nenhum** — opte explicitamente; `dotcontext` é legado). |
89
+ | `--no-companions` | Não fixa nenhum companion. |
90
+ | `--no-mcp` | Pula o MCP de cofre **do próprio wendkeep** (`wendkeep-vault`). Os MCPs de companion seguem `--companions`. |
91
+ | `--no-colors` | Pula o sistema de cores do Obsidian (snippet `.obsidian` + grupos do grafo). |
92
+ | `--yes`, `-y` | Não-interativo; aceita os padrões (pula os prompts de idioma / cofre / companion). |
93
+ | `--force` | Sobrescreve os blocos de config do wendkeep existentes. |
94
+
95
+ Depois abra o cofre no Obsidian, mande um prompt de teste no seu agente e confirme que uma nota aparece em `02-Sessões/…` (ou `02-Sessions/…` num cofre `en`).
96
+
97
+ ## Atualizar
98
+
99
+ Como os hooks vivem dentro do pacote instalado (o settings.json chama `npx wendkeep hook <name>`), atualizar é só:
100
+
101
+ ```bash
102
+ npm update wendkeep
103
+ ```
104
+
105
+ Sem recopiar, sem snapshot pra re‑sincronizar — o pacote é a única fonte da verdade.
106
+
107
+ ## Comandos
108
+
109
+ | Comando | O que faz |
110
+ |---|---|
111
+ | `wendkeep init` | Configura o wendkeep num projeto (taxonomia do cofre + settings + MCP + skills). |
112
+ | `wendkeep hook <name>` | Roda um hook de sessão; invocado pelo `settings.json` (lê o JSON do agente no stdin). |
113
+ | `wendkeep change <sub>` | Ciclo de mudança: `new [--simple]` / `list` / `show` / `status` / `done <id>` / `undone <id>` / `diff` / `archive [--force]`. |
114
+ | `wendkeep verify [--deep]` | Roda os sensores das tarefas da change; `--deep` monta o pacote de verificação independente. |
115
+ | `wendkeep spec <sub>` | Specs vivos: `list` / `show <capability>`. |
116
+ | `wendkeep sensors <sub>` | `list` / `add <id> "<comando>"` — vê/edita `wendkeep.sensors.json` (JSON Schema incluso). |
117
+ | `wendkeep cost [opts]` | Agrega o gasto de IA nas sessões do cofre — total, por modelo, por dia · `--top [N]` · `--trend [day\|week\|month]` (+ projeção) · `--write` (gera `00-Custo.md`) · `--json`. |
118
+ | `wendkeep stats [--vault P]` | Uma linha compartilhável: sessões · prompts · gasto · período · modelos (`--json`). |
119
+ | `wendkeep import [opts]` | **Memória retroativa** — importa sessões passadas de **Claude + Codex** pro cofre (dedup por `session_id`). `--source all\|claude\|codex` / `--from <dir>` / `--codex-from <dir>` / `--stamp-ids` / `--since d` / `--limit n` / `--dry-run` / `--json`. |
120
+ | `wendkeep dashboard [--force]` | (Re)gera os Bases filtrados por pasta + o MOC `00-Dashboard`. |
121
+ | `wendkeep lesson add "t" "l"` | Registra uma lição local do projeto (injetada no próximo SessionStart). |
122
+ | `wendkeep sync-defs` | Copia `.brain/agents\|skills` pro projeto (`.codex/agents`, `.claude/skills`). |
123
+ | `wendkeep validate-memory [path]` | Valida `.brain/CORE.md` (cap 25, 3 seções, sem segredos/PII). |
124
+ | `wendkeep doctor [--vault P]` | Roda um check de saúde do cofre (integridade de sessões, registry, links). |
125
+ | `wendkeep --version` / `--help` | Versão / uso. |
126
+
127
+ ## Memória retroativa (`import`) — instale hoje, lembre de ontem
128
+
129
+ Instale o wendkeep num projeto existente e ele só lembra sessões **a partir de agora**. O `wendkeep import` conserta isso: um comando importa as sessões passadas de **Claude & Codex** do projeto pro cofre — dedup, datadas, com custo — então o grafo começa cheio, não vazio. Reconstrói cada transcript como uma nota de sessão completa na pasta datada **real** — frontmatter (taggeado com o provedor real), um bloco de iteração por turno, custo + telemetria de subagents, notas derivadas de decisão/bug/aprendizado, encerramento finalizado. Um replay offline do fluxo de captura vivo, então uma nota importada é indistinguível de uma capturada.
130
+
131
+ ```bash
132
+ wendkeep import --vault .meuprojeto-vault --dry-run # prévia do que seria importado (os dois agentes)
133
+ wendkeep import --vault .meuprojeto-vault # escreve as notas
134
+ wendkeep import --vault .meuprojeto-vault --source codex # só Codex
135
+ ```
136
+
137
+ - **Os dois agentes por padrão** (`--source all`). As sessões do Claude vêm de `~/.claude/projects/<slug>/`; os rollouts do Codex de `~/.codex/sessions/**`, escopados pro projeto pelo `cwd` gravado em cada sessão (insensível a case e separador, subpastas inclusas). Estreite com `--source claude` / `--source codex`.
138
+ - Toda nota grava o **`session_id`** e o **`provider`** no frontmatter (captura live e import iguais). Carimbe notas antigas com `wendkeep import --stamp-ids` (preenche o id a partir do registry; idempotente).
139
+ - **Dedup** por `session_id` contra o `SESSION_REGISTRY` do cofre **e** o frontmatter das notas existentes — só importa sessões ausentes e nunca sobrescreve. Rodar de novo é no‑op.
140
+ - Depois de importar, o `wendkeep cost` agrega seu histórico inteiro — retroativamente, nos dois agentes.
141
+
142
+ ## Ciclo de mudança — o loop a2 (spec‑driven, nativo)
143
+
144
+ Além de capturar sessões, o wendkeep é um **harness**: um loop nativo e sem dependências que mantém *intenção* (specs), *trabalho* (changes) e *prova* (sensores) juntos no cofre, wikilinkados no grafo Obsidian.
145
+
146
+ ```
147
+ explore → propose → apply (TDD) → verify → archive
148
+ ```
149
+
150
+ - **Propose** — `wendkeep change new <slug>` faz o scaffold de `08-Mudanças/<slug>/` (`proposta.md`, `design.md`, `tarefas.md` e um delta `specs/`). A change vira *ativa* e é injetada no próximo `SessionStart`, então o agente retoma o trabalho em curso.
151
+ - **Apply** — implemente cada tarefa de `tarefas.md`. Taggeie a tarefa que precisa de prova de máquina com `[sensor:<id>]`.
152
+ - **Verify** — `wendkeep verify` roda os sensores que suas tarefas declararam (do `wendkeep.sensors.json` na raiz do projeto) e grava `evidencia.json`. Um vermelho crítico falha o gate; um vermelho `warning` é aviso.
153
+ - **Archive** — `wendkeep change archive <slug>` faz **gate** na evidência (bloqueia a não ser que todo sensor crítico declarado esteja verde), promove o delta de cada capability (`ADDED`/`MODIFIED`/`REMOVED`) pro `07-Specs/<capability>.md` vivo, move a change pro `_arquivo/` e cunha um ADR em `04-Decisões/`.
154
+
155
+ > O gate bloqueia a não ser que o scaffold esteja preenchido, nenhuma tarefa aberta, evidência fresca e todo requisito declarado coberto. **`--force` é decisão do humano — nunca do agente.**
156
+
157
+ O `wendkeep init` também semeia **skills de processo nativas** (`wk-workflow`, `wk-tdd`, `wk-debugging`, `wk-brainstorming`, `wk-planning`, `wk-verify`) em `.brain/skills` e as entrega em `.claude/skills` — a camada do *como*, zero‑dep. Companions opcionais (`context-mode`, `dotcontext`, `understand-anything`, `caveman`) ficam como camada extra opt‑in.
158
+
159
+ ## Como funciona
160
+
161
+ ```
162
+ sessão do agente ──hooks──▶ wendkeep ──▶ Markdown no cofre ──▶ índice .brain + grafo Obsidian
163
+ (Claude/Codex) (Node) (02-Sessões/…) (CORE+DIGEST, backlinks)
164
+ ```
165
+
166
+ O settings.json do agente aponta cada hook pra `npx wendkeep hook …`. No `Stop`, o wendkeep parseia o transcript, anexa o turno, atualiza a tabela de tokens/custo e (idempotentemente) emite qualquer nota de decisão/bug/aprendizado. Em todo `SessionStart`, o `brain-inject` injeta de volta a memória curada (CORE + DIGEST), a change ativa, as lições do projeto e um roteador `<wk_process>` que roteia qualquer tarefa não‑trivial pelo loop a2 (planejar → `change new` + preencher o scaffold → TDD → verify → archive).
167
+
168
+ O **gate** do archive bloqueia a não ser que: o scaffold da change esteja preenchido (G0), nenhuma tarefa esteja aberta (G1), todo sensor crítico declarado esteja verde (com evidência fresca) e — quando a change declara `[req:]` — um `verdict.json` independente cubra eles. O `--force` é a saída de emergência humana; o agente é instruído a nunca usar por conta própria.
169
+
170
+ ## Notas & roadmap
171
+
172
+ - **Nomes das pastas do cofre são em Português por padrão** (`02-Sessões`, `04-Decisões`, …). Passe `wendkeep init --locale en` pra um cofre em inglês (`02-Sessions`, `04-Decisions`, scaffold/skills em inglês). O locale é uma propriedade do cofre, travada no init; os parsers são bilíngues, então conteúdo misto nunca quebra.
173
+ - **Busca é scoring por keyword/frontmatter**, não embeddings on‑device (isso está no roadmap).
174
+ - **Formatos de transcript são internos ao agente** e podem mudar entre versões; o parsing é isolado mas pode precisar de atualizações.
175
+ - O instalador wira settings do **Claude Code** + `.mcp.json`. Os hooks do Codex rodam nos mesmos scripts mas ainda não são auto‑wirados (o import já cobre sessões Codex passadas via `--source codex`).
176
+
177
+ ---
178
+
179
+ ## Pare de reexplicar seu código toda manhã
180
+
181
+ ```bash
182
+ npm i -D wendkeep && npx wendkeep init
183
+ ```
184
+
185
+ **[Instalar do npm](https://www.npmjs.com/package/wendkeep)** · **[Deixar uma star no GitHub](https://github.com/rogersialves/wendkeep)** — MIT · open‑core · seus dados nunca saem do seu disco.
186
+
187
+ ## Licença
188
+
189
+ MIT
package/bin/wendkeep.mjs CHANGED
@@ -48,7 +48,9 @@ Usage:
48
48
  wendkeep spec <sub> Living specs: list | show <capability>.
49
49
  wendkeep sensors <sub> list | add <id> "<command>" [--severity --type --report].
50
50
  wendkeep cost [opts] Aggregate AI-coding spend across the vault's sessions.
51
- --since <date> · --top [N] (priciest sessions) · --json.
51
+ --since <date> · --top [N] (priciest) · --trend [day|week|month]
52
+ (+ run-rate projection) · --write (generate 00-Custo.md) · --json.
53
+ wendkeep stats [--vault P] One shareable line: sessions · prompts · spend · span · models (--json).
52
54
  wendkeep import [opts] Backfill: import this project's past Claude + Codex sessions into
53
55
  the vault (deduped by session_id). --source all|claude|codex (default
54
56
  all) · --stamp-ids (backfill session_id in existing notes) ·
@@ -144,6 +146,11 @@ async function main() {
144
146
  runCost(rest);
145
147
  break;
146
148
  }
149
+ case 'stats': {
150
+ const { runStats } = await import('../src/stats.mjs');
151
+ runStats(rest);
152
+ break;
153
+ }
147
154
  case 'import': {
148
155
  const { runImportCli } = await import('../src/import.mjs');
149
156
  runImportCli(rest);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wendkeep",
3
- "version": "0.24.0",
3
+ "version": "0.26.0",
4
4
  "description": "A persistent-memory harness for AI coding agents on your Obsidian vault: turn-by-turn session capture plus a native, zero-dependency spec→change→verify→archive loop (sensor-gated, independent verdict, mutation discrimination). Local-first, agent-agnostic (Claude Code, Codex, Cursor…).",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cost.mjs CHANGED
@@ -1,12 +1,16 @@
1
1
  // `wendkeep cost` — aggregate AI-coding spend across every session note in the vault.
2
2
  // Each session note carries cost in its frontmatter (main + subagents since 0.10.0); this
3
3
  // rolls the whole vault up: total, by day, by model. Pure aggregation + a thin CLI.
4
- import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
4
+ import { existsSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
5
5
  import { isAbsolute, join, resolve } from 'node:path';
6
6
  import { getLocale } from '../hooks/locale.mjs';
7
7
 
8
8
  const round4 = (n) => Math.round((Number(n) || 0) * 10000) / 10000;
9
9
  const usd = (n) => `$${(Number(n) || 0).toFixed(4)}`;
10
+ const today = () => {
11
+ const d = new Date();
12
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
13
+ };
10
14
 
11
15
  function fmValue(content, key) {
12
16
  const m = content.match(new RegExp(`^${key}:\\s*(.+)$`, 'm'));
@@ -24,6 +28,7 @@ export function parseSessionCost(content) {
24
28
  wasted: Number(fmValue(content, 'subagents_wasted_usd')) || 0,
25
29
  tokens: Number(fmValue(content, 'tokens_total')) || 0,
26
30
  subTokens: Number(fmValue(content, 'subagents_tokens_total')) || 0,
31
+ prompts: Number(fmValue(content, 'prompts')) || 0,
27
32
  };
28
33
  }
29
34
 
@@ -35,8 +40,9 @@ export function aggregateCosts(entries) {
35
40
  let wasted = 0;
36
41
  let tokens = 0;
37
42
  let subTokens = 0;
43
+ let prompts = 0;
38
44
  for (const e of entries) {
39
- main += e.mainCost; sub += e.subCost; wasted += e.wasted || 0; tokens += e.tokens; subTokens += e.subTokens;
45
+ main += e.mainCost; sub += e.subCost; wasted += e.wasted || 0; tokens += e.tokens; subTokens += e.subTokens; prompts += e.prompts || 0;
40
46
  const d = e.date || '?';
41
47
  (byDay[d] = byDay[d] || { cost: 0, count: 0 }).cost += e.mainCost + e.subCost;
42
48
  byDay[d].count += 1;
@@ -48,12 +54,93 @@ export function aggregateCosts(entries) {
48
54
  count: entries.length,
49
55
  main: round4(main), sub: round4(sub), total: round4(total), wasted: round4(wasted),
50
56
  avg: round4(entries.length ? total / entries.length : 0),
51
- tokens, subTokens,
57
+ tokens, subTokens, prompts,
52
58
  byDay: Object.entries(byDay).sort().map(([date, v]) => ({ date, cost: round4(v.cost), count: v.count })),
53
59
  byModel: Object.entries(byModel).sort((a, b) => b[1].cost - a[1].cost).map(([model, v]) => ({ model, cost: round4(v.cost), count: v.count })),
54
60
  };
55
61
  }
56
62
 
63
+ // Shift a 'YYYY-MM-DD' string by `delta` days (UTC, pure).
64
+ function shiftDate(dateStr, delta) {
65
+ const d = new Date(`${dateStr}T00:00:00Z`);
66
+ d.setUTCDate(d.getUTCDate() + delta);
67
+ return d.toISOString().slice(0, 10);
68
+ }
69
+
70
+ // ISO-ish week key 'YYYY-Www' for a 'YYYY-MM-DD' string (pure).
71
+ function isoWeek(dateStr) {
72
+ const d = new Date(`${dateStr}T00:00:00Z`);
73
+ const day = (d.getUTCDay() + 6) % 7; // Mon=0
74
+ d.setUTCDate(d.getUTCDate() - day + 3); // nearest Thursday
75
+ const firstThu = new Date(Date.UTC(d.getUTCFullYear(), 0, 4));
76
+ const week = 1 + Math.round(((d - firstThu) / 86400000 - 3 + ((firstThu.getUTCDay() + 6) % 7)) / 7);
77
+ return `${d.getUTCFullYear()}-W${String(week).padStart(2, '0')}`;
78
+ }
79
+
80
+ // Group the per-day cost series (agg.byDay) into day|week|month buckets. Pure.
81
+ export function trendBuckets(byDay, bucket = 'month') {
82
+ const keyOf = (date) => (bucket === 'day' ? date : bucket === 'week' ? isoWeek(date) : String(date).slice(0, 7));
83
+ const map = {};
84
+ for (const d of byDay || []) {
85
+ if (!d.date || d.date === '?') continue;
86
+ const k = keyOf(d.date);
87
+ (map[k] = map[k] || { period: k, cost: 0, count: 0 }).cost += d.cost;
88
+ map[k].count += d.count;
89
+ }
90
+ return Object.values(map).sort((a, b) => a.period.localeCompare(b.period)).map((b) => ({ ...b, cost: round4(b.cost) }));
91
+ }
92
+
93
+ // Run-rate projection from the last `windowDays` of activity. Pure; nowStr = 'YYYY-MM-DD'
94
+ // (defaults to the latest day seen). Honest: a flat run-rate, not a fitted forecast.
95
+ export function projectSpend(byDay, { nowStr, windowDays = 30, horizonDays = 30 } = {}) {
96
+ const days = (byDay || []).filter((d) => d.date && d.date !== '?');
97
+ if (!days.length) return { dailyRate: 0, projected: 0, windowDays, horizonDays, basisDays: 0, basisTotal: 0 };
98
+ const now = nowStr || days[days.length - 1].date;
99
+ const cutoff = shiftDate(now, -windowDays);
100
+ const recent = days.filter((d) => d.date > cutoff);
101
+ const basisTotal = recent.reduce((s, d) => s + d.cost, 0);
102
+ const dailyRate = basisTotal / windowDays;
103
+ return { dailyRate: round4(dailyRate), projected: round4(dailyRate * horizonDays), windowDays, horizonDays, basisDays: recent.length, basisTotal: round4(basisTotal) };
104
+ }
105
+
106
+ // A generated vault note: cost by month + projection + top models. Overwrites (it is generated).
107
+ export function renderTrendNote(agg, proj, dateStr) {
108
+ const rows = trendBuckets(agg.byDay, 'month').map((b) => `| ${b.period} | ${usd(b.cost)} | ${b.count} |`).join('\n');
109
+ const models = agg.byModel.slice(0, 8).map((m) => `| ${m.model} | ${usd(m.cost)} | ${m.count} |`).join('\n');
110
+ return `---
111
+ type: cost-trend
112
+ date: ${dateStr}
113
+ cssclasses:
114
+ - topic-dashboard
115
+ tags:
116
+ - custo
117
+ ---
118
+
119
+ # Custo — tendência
120
+
121
+ > Gerado por \`wendkeep cost --write\`. ${agg.count} sessão(ões) · total ${usd(agg.total)} · ${usd(agg.avg)}/sessão.
122
+
123
+ ## Por mês
124
+
125
+ | Mês | Custo | Sessões |
126
+ |---|---|---|
127
+ ${rows || '| — | — | — |'}
128
+
129
+ ## Projeção (run-rate ${proj.windowDays}d)
130
+
131
+ Base: ${usd(proj.basisTotal)} nos últimos ${proj.windowDays} dias (${proj.basisDays} dia(s) com atividade) → **${usd(proj.dailyRate)}/dia**.
132
+ Projeção próximos ${proj.horizonDays} dias: **${usd(proj.projected)}**.
133
+
134
+ > Run-rate simples (média diária × horizonte), não previsão ajustada.
135
+
136
+ ## Por modelo
137
+
138
+ | Modelo | Custo | Sessões |
139
+ |---|---|---|
140
+ ${models || '| — | — | — |'}
141
+ `;
142
+ }
143
+
57
144
  function walkNotes(dir) {
58
145
  const out = [];
59
146
  let names;
@@ -111,6 +198,24 @@ export function runCost(argv) {
111
198
  process.exit(0);
112
199
  }
113
200
 
201
+ const trendIdx = argv.indexOf('--trend');
202
+ if (trendIdx >= 0) {
203
+ const bucket = ['day', 'week', 'month'].includes(argv[trendIdx + 1]) ? argv[trendIdx + 1] : 'month';
204
+ const proj = projectSpend(agg.byDay);
205
+ process.stdout.write(`Tendência de custo (por ${bucket}):\n`);
206
+ for (const b of trendBuckets(agg.byDay, bucket)) process.stdout.write(` ${b.period} ${usd(b.cost).padStart(11)} (${b.count})\n`);
207
+ process.stdout.write(`\nRun-rate ${proj.windowDays}d: ${usd(proj.dailyRate)}/dia · projeção ${proj.horizonDays}d: ${usd(proj.projected)} (base ${usd(proj.basisTotal)} em ${proj.basisDays} dia(s))\n`);
208
+ process.exit(0);
209
+ }
210
+
211
+ if (argv.includes('--write')) {
212
+ const proj = projectSpend(agg.byDay);
213
+ const rel = '00-Custo.md';
214
+ writeFileSync(join(vaultBase, rel), renderTrendNote(agg, proj, today()), 'utf8');
215
+ process.stdout.write(`cost --write: ${rel} (nota de tendência gerada)\n`);
216
+ process.exit(0);
217
+ }
218
+
114
219
  process.stdout.write(`Custo total (vault): ${usd(agg.total)} — ${agg.count} sessão(ões) · ${usd(agg.avg)}/sessão\n`);
115
220
  process.stdout.write(` main: ${usd(agg.main)} · subagents: ${usd(agg.sub)}\n`);
116
221
  if (agg.wasted) process.stdout.write(` desperdiçado (runs killed/failed): ${usd(agg.wasted)}\n`);
package/src/init.mjs CHANGED
@@ -223,6 +223,63 @@ export function promptStrings(localeId) {
223
223
  return PROMPTS[localeId] || PROMPTS['pt-BR'];
224
224
  }
225
225
 
226
+ // Output messages by locale — the summary, the [n/4] steps and the next-steps block follow the
227
+ // chosen vault language (before this, a pt-BR install still printed English output).
228
+ const MESSAGES = {
229
+ 'pt-BR': {
230
+ header: '\nwendkeep init',
231
+ lProject: ' projeto ', lVault: ' vault ', lMcp: ' mcp ',
232
+ lCompanions: ' companions ', lColors: ' cores ',
233
+ skipped: 'ignorado', none: 'nenhum',
234
+ colorsOn: 'wendkeep-colors (snippet + grupos do grafo)',
235
+ taxonomy: (n, c, loc, readme, views) => ` [1/4] taxonomia do vault: ${n} pastas (${c} criadas, locale ${loc})${readme}, .brain + change/spec + sensores semeados${views}`,
236
+ readmeCreated: ', README.md criado', viewsNote: (n) => `, ${n} view(s) + dashboard`,
237
+ defs: (s, a) => ` defs entregues: ${s} skill(s) -> .claude/skills, ${a} agent(s) -> .codex/agents`,
238
+ settingsBadJson: (p) => ` [2/4] settings.json existe mas não é JSON válido -> escrevi ${p}.new (mescle à mão)`,
239
+ settings: (verb, added, bak) => ` [2/4] settings.json ${verb} (${added} hook(s) wirados, OBSIDIAN_VAULT_PATH setado${bak})`,
240
+ mcpBadJson: (p) => ` [3/4] .mcp.json existe mas não é JSON válido -> escrevi ${p}.new (mescle à mão)`,
241
+ mcp: (verb, names, bak) => ` [3/4] .mcp.json ${verb} (${names}${bak})`,
242
+ mcpSkipped: ' [3/4] .mcp.json ignorado (--no-mcp, sem companions MCP)',
243
+ colorsSkipped: ' [4/4] cores ignoradas (--no-colors)',
244
+ colors: (r) => ` [4/4] cores: ${r}`,
245
+ merged: 'mesclado', created: 'criado', bakSaved: ', .bak salvo',
246
+ nextSteps: '\nPróximos passos:',
247
+ step1: (v) => ` 1. Abra o vault no Obsidian: "Abrir pasta como cofre" -> ${v}`,
248
+ step2: ' 2. Garanta que o wendkeep está instalado onde o agente roda (npm i -D wendkeep, ou -g).',
249
+ step3: ' 3. Abra o Claude Code neste projeto e envie um prompt de teste.',
250
+ step4: ' 4. Confirme que uma nota aparece em 02-Sessões/<ano>/<mês>/DIA <dd>/ no vault.',
251
+ updateLater: ' Atualize depois com: npm update wendkeep (sem recopiar — os hooks vivem no pacote).\n',
252
+ },
253
+ en: {
254
+ header: '\nwendkeep init',
255
+ lProject: ' project ', lVault: ' vault ', lMcp: ' mcp ',
256
+ lCompanions: ' companions ', lColors: ' colors ',
257
+ skipped: 'skipped', none: 'none',
258
+ colorsOn: 'wendkeep-colors (snippet + graph groups)',
259
+ taxonomy: (n, c, loc, readme, views) => ` [1/4] vault taxonomy: ${n} folders (${c} created, locale ${loc})${readme}, .brain + change/spec + sensors seeded${views}`,
260
+ readmeCreated: ', README.md created', viewsNote: (n) => `, ${n} view(s) + dashboard`,
261
+ defs: (s, a) => ` defs delivered: ${s} skill(s) -> .claude/skills, ${a} agent(s) -> .codex/agents`,
262
+ settingsBadJson: (p) => ` [2/4] settings.json exists but is not valid JSON -> wrote ${p}.new (merge by hand)`,
263
+ settings: (verb, added, bak) => ` [2/4] settings.json ${verb} (${added} hook(s) wired, OBSIDIAN_VAULT_PATH set${bak})`,
264
+ mcpBadJson: (p) => ` [3/4] .mcp.json exists but is not valid JSON -> wrote ${p}.new (merge by hand)`,
265
+ mcp: (verb, names, bak) => ` [3/4] .mcp.json ${verb} (${names}${bak})`,
266
+ mcpSkipped: ' [3/4] .mcp.json skipped (--no-mcp, no MCP companions)',
267
+ colorsSkipped: ' [4/4] colors skipped (--no-colors)',
268
+ colors: (r) => ` [4/4] colors: ${r}`,
269
+ merged: 'merged', created: 'created', bakSaved: ', .bak saved',
270
+ nextSteps: '\nNext steps:',
271
+ step1: (v) => ` 1. Open the vault in Obsidian: "Open folder as vault" -> ${v}`,
272
+ step2: ' 2. Make sure wendkeep is installed where the agent runs (npm i -D wendkeep, or -g).',
273
+ step3: ' 3. Open Claude Code in this project and send a test prompt.',
274
+ step4: ' 4. Confirm a note appears under 02-Sessões/<year>/<month>/DIA <dd>/ in the vault.',
275
+ updateLater: ' Update later with: npm update wendkeep (no re-copying — hooks live in the package).\n',
276
+ },
277
+ };
278
+
279
+ export function initMessages(localeId) {
280
+ return MESSAGES[localeId] || MESSAGES['pt-BR'];
281
+ }
282
+
226
283
  // Map the language answer to a locale id. 2/en → en; 1/pt/empty/unknown → pt-BR.
227
284
  export function parseLocaleAnswer(ans) {
228
285
  const a = String(ans || '').trim().toLowerCase();
@@ -243,7 +300,9 @@ export async function runInit(argv) {
243
300
  rl.close();
244
301
  args.locale = parseLocaleAnswer(ans);
245
302
  }
246
- const P = promptStrings(args.locale && LOCALES[args.locale] ? args.locale : DEFAULT_LOCALE);
303
+ const resolvedLocale = args.locale && LOCALES[args.locale] ? args.locale : DEFAULT_LOCALE;
304
+ const P = promptStrings(resolvedLocale);
305
+ const M = initMessages(resolvedLocale);
247
306
 
248
307
  let vaultPath = args.vault;
249
308
  if (!vaultPath) {
@@ -285,12 +344,12 @@ export async function runInit(argv) {
285
344
  companions = resolveCompanions({});
286
345
  }
287
346
 
288
- log('\nwendkeep init');
289
- log(` project : ${projectPath}`);
290
- log(` vault : ${vaultPath}`);
291
- log(` mcp : ${args.mcp ? 'mcpvault (wendkeep-vault)' : 'skipped'}`);
292
- log(` companions : ${companions.length ? companions.join(', ') : 'none'}`);
293
- log(` colors : ${args.noColors ? 'skipped' : 'wendkeep-colors (snippet + graph groups)'}\n`);
347
+ log(M.header);
348
+ log(`${M.lProject}: ${projectPath}`);
349
+ log(`${M.lVault}: ${vaultPath}`);
350
+ log(`${M.lMcp}: ${args.mcp ? 'mcpvault (wendkeep-vault)' : M.skipped}`);
351
+ log(`${M.lCompanions}: ${companions.length ? companions.join(', ') : M.none}`);
352
+ log(`${M.lColors}: ${args.noColors ? M.skipped : M.colorsOn}\n`);
294
353
 
295
354
  // dotcontext controls (only relevant when selected): hook level + MCP placement.
296
355
  // Skip the project MCP entry when dotcontext is already global (avoids a duplicate
@@ -329,7 +388,7 @@ export async function runInit(argv) {
329
388
  let readmeNote = '';
330
389
  if (!existsSync(readmePath)) {
331
390
  writeFileSync(readmePath, renderVaultReadme({ projectName: basename(projectPath), vaultPath, withMcp: args.mcp, locale: loc.id }), 'utf8');
332
- readmeNote = ', README.md created';
391
+ readmeNote = M.readmeCreated;
333
392
  }
334
393
  // Seed the curated memory layer (CORE.md) + the compaction-protocol doc. The
335
394
  // DIGEST/index are auto-generated by the hooks; CORE is hand-curated, so we
@@ -369,14 +428,14 @@ export async function runInit(argv) {
369
428
  let viewsNote = '';
370
429
  try {
371
430
  const views = seedVaultViews(vaultPath);
372
- if (views.length) viewsNote = `, ${views.length} view(s) + dashboard`;
431
+ if (views.length) viewsNote = M.viewsNote(views.length);
373
432
  } catch { /* views são bônus — nunca derrubam o init */ }
374
- log(` [1/4] vault taxonomy: ${folders.length} folders (${created} created, locale ${loc.id})${readmeNote}, .brain + change/spec + sensors seeded${viewsNote}`);
433
+ log(M.taxonomy(folders.length, created, loc.id, readmeNote, viewsNote));
375
434
  // Deliver the seeded defs (agents + wk process skills) to the project so they're
376
435
  // usable immediately — no separate `wendkeep sync-defs` step needed.
377
436
  const synced = syncDefs(vaultPath, projectPath);
378
437
  if (synced.skills.length || synced.agents.length) {
379
- log(` defs delivered: ${synced.skills.length} skill(s) -> .claude/skills, ${synced.agents.length} agent(s) -> .codex/agents`);
438
+ log(M.defs(synced.skills.length, synced.agents.length));
380
439
  }
381
440
 
382
441
  // 2. .claude/settings.json --------------------------------------------------
@@ -386,13 +445,13 @@ export async function runInit(argv) {
386
445
  // Unparseable existing file — never clobber. Drop a .new for manual merge.
387
446
  const fresh = mergeSettings(null, { vaultPath, withMcp: args.mcp, force: true, companions, skipMcp, dotcontextHookLevel }).settings;
388
447
  writeJson(`${settingsPath}.new`, fresh);
389
- log(` [2/4] settings.json exists but is not valid JSON -> wrote ${settingsPath}.new (merge by hand)`);
448
+ log(M.settingsBadJson(settingsPath));
390
449
  } else {
391
450
  const hadFile = settingsRead.data !== null;
392
451
  const { settings, added } = mergeSettings(settingsRead.data, { vaultPath, withMcp: args.mcp, force: args.force, companions, skipMcp, dotcontextHookLevel });
393
452
  if (hadFile) backup(settingsPath);
394
453
  writeJson(settingsPath, settings);
395
- log(` [2/4] settings.json ${hadFile ? 'merged' : 'created'} (${added} hook(s) wired, OBSIDIAN_VAULT_PATH set${hadFile ? ', .bak saved' : ''})`);
454
+ log(M.settings(hadFile ? M.merged : M.created, added, hadFile ? M.bakSaved : ''));
396
455
  }
397
456
 
398
457
  // 3. .mcp.json --------------------------------------------------------------
@@ -405,22 +464,22 @@ export async function runInit(argv) {
405
464
  const names = [args.mcp && MCP_SERVER_KEY, ...Object.keys(companionMcp)].filter(Boolean).join(', ');
406
465
  if (!mcpRead.ok) {
407
466
  writeJson(`${mcpPath}.new`, mergeMcp(null, mcpOpts));
408
- log(` [3/4] .mcp.json exists but is not valid JSON -> wrote ${mcpPath}.new (merge by hand)`);
467
+ log(M.mcpBadJson(mcpPath));
409
468
  } else {
410
469
  const hadFile = mcpRead.data !== null;
411
470
  if (hadFile) backup(mcpPath);
412
471
  writeJson(mcpPath, mergeMcp(mcpRead.data, mcpOpts));
413
- log(` [3/4] .mcp.json ${hadFile ? 'merged' : 'created'} (${names}${hadFile ? ', .bak saved' : ''})`);
472
+ log(M.mcp(hadFile ? M.merged : M.created, names, hadFile ? M.bakSaved : ''));
414
473
  }
415
474
  } else {
416
- log(' [3/4] .mcp.json skipped (--no-mcp, no MCP companions)');
475
+ log(M.mcpSkipped);
417
476
  }
418
477
 
419
478
  // 4. Vault color system (.obsidian) -----------------------------------------
420
479
  if (args.noColors) {
421
- log(' [4/4] colors skipped (--no-colors)');
480
+ log(M.colorsSkipped);
422
481
  } else {
423
- log(` [4/4] colors: ${installVaultColors(vaultPath)}`);
482
+ log(M.colors(installVaultColors(vaultPath)));
424
483
  }
425
484
 
426
485
  // caveman has no MCP / agnostic-hook path: on non-Claude agents its skills come
@@ -437,10 +496,10 @@ export async function runInit(argv) {
437
496
  log(` [!] add to .gitignore (wendkeep não toca git): ${DOTCONTEXT_GITIGNORE.join(' ')}`);
438
497
  }
439
498
 
440
- log('\nNext steps:');
441
- log(` 1. Open the vault in Obsidian: "Open folder as vault" -> ${vaultPath}`);
442
- log(' 2. Make sure wendkeep is installed where the agent runs (npm i -D wendkeep, or -g).');
443
- log(' 3. Open Claude Code in this project and send a test prompt.');
444
- log(' 4. Confirm a note appears under 02-Sessões/<year>/<month>/DIA <dd>/ in the vault.');
445
- log(' Update later with: npm update wendkeep (no re-copying — hooks live in the package).\n');
499
+ log(M.nextSteps);
500
+ log(M.step1(vaultPath));
501
+ log(M.step2);
502
+ log(M.step3);
503
+ log(M.step4);
504
+ log(M.updateLater);
446
505
  }
package/src/stats.mjs ADDED
@@ -0,0 +1,46 @@
1
+ // `wendkeep stats` — one shareable line about what the vault has captured. For the npm page,
2
+ // a README badge line, or a tweet. Read-only; reuses the cost aggregation.
3
+ import { existsSync } from 'node:fs';
4
+ import { isAbsolute, resolve } from 'node:path';
5
+ import { collectVaultCost } from './cost.mjs';
6
+
7
+ const usd = (n) => `$${(Number(n) || 0).toFixed(2)}`;
8
+ const num = (n) => String(Math.round(Number(n) || 0)).replace(/\B(?=(\d{3})+(?!\d))/g, '.');
9
+
10
+ // Pure: derive the shareable stats from a collectVaultCost aggregate.
11
+ export function statsFrom(agg) {
12
+ const days = (agg.byDay || []).map((d) => d.date).filter((d) => d && d !== '?').sort();
13
+ return {
14
+ sessions: agg.count,
15
+ prompts: agg.prompts || 0,
16
+ cost: agg.total,
17
+ models: (agg.byModel || []).length,
18
+ firstDay: days[0] || '',
19
+ lastDay: days[days.length - 1] || '',
20
+ spanDays: days.length,
21
+ };
22
+ }
23
+
24
+ export function statsLine(s) {
25
+ // "dias ativos" = distinct days WITH activity, not the calendar span (shown in parens).
26
+ const span = s.firstDay && s.lastDay ? ` · ${s.spanDays} dias ativos (${s.firstDay}→${s.lastDay})` : '';
27
+ return `wendkeep: ${num(s.sessions)} sessão(ões) · ${num(s.prompts)} prompts · ${usd(s.cost)} capturado${span} · ${s.models} modelo(s)`;
28
+ }
29
+
30
+ function opt(argv, name) {
31
+ const i = argv.indexOf(name);
32
+ if (i >= 0) return argv[i + 1];
33
+ const eq = argv.find((a) => a.startsWith(`${name}=`));
34
+ return eq ? eq.slice(name.length + 1) : undefined;
35
+ }
36
+
37
+ export function runStats(argv) {
38
+ const vaultRaw = opt(argv, '--vault') || process.env.OBSIDIAN_VAULT_PATH;
39
+ if (!vaultRaw) { process.stderr.write('wendkeep stats: no vault (--vault or OBSIDIAN_VAULT_PATH).\n'); process.exit(2); }
40
+ const vaultBase = isAbsolute(vaultRaw) ? vaultRaw : resolve(process.cwd(), vaultRaw);
41
+ if (!existsSync(vaultBase)) { process.stderr.write(`wendkeep stats: vault not found: ${vaultBase}\n`); process.exit(2); }
42
+ const s = statsFrom(collectVaultCost(vaultBase));
43
+ if (argv.includes('--json')) { process.stdout.write(`${JSON.stringify(s, null, 2)}\n`); process.exit(0); }
44
+ process.stdout.write(`${statsLine(s)}\n`);
45
+ process.exit(0);
46
+ }