wendkeep 0.60.0 → 0.62.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 +44 -0
- package/README.en.md +14 -7
- package/README.md +14 -7
- package/docs/en/commands/memory.md +6 -2
- package/docs/en/commands/operating-profiles.md +26 -4
- package/docs/pt-BR/commands/memory.md +6 -2
- package/docs/pt-BR/commands/operating-profiles.md +26 -3
- package/hooks/memory-handoff.mjs +1 -199
- package/hooks/memory-mode.mjs +1 -89
- package/hooks/memory-schema.mjs +1 -310
- package/hooks/memory-store.mjs +1 -900
- package/hooks/sensors-core.mjs +1 -102
- package/package.json +6 -3
- package/packages/harness/package.json +2 -1
- package/packages/harness/src/index.mjs +2 -0
- package/packages/harness/src/operating-profile.mjs +133 -0
- package/packages/harness/src/sensors-core.mjs +102 -0
- package/packages/vault/src/index.mjs +6 -0
- package/packages/vault/src/memory-handoff.mjs +199 -0
- package/packages/vault/src/memory-mode.mjs +89 -0
- package/packages/vault/src/memory-schema.mjs +310 -0
- package/packages/vault/src/memory-store.mjs +900 -0
- package/packages/vault/src/validate-core.mjs +181 -0
- package/packages/vault/src/validate-memory.mjs +128 -0
- package/src/memory.mjs +77 -11
- package/src/operating-profile.mjs +1 -133
- package/src/validate-core.mjs +1 -181
- package/src/validate-memory.mjs +1 -128
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,50 @@ 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.62.0] — 2026-07-28
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **`wendkeep/harness` passa a expor o policy kernel reutilizável.** Os contratos dos cinco
|
|
12
|
+
Perfis de Operação e a engine de sensores podem ser importados pela superfície pública do pacote
|
|
13
|
+
raiz, com identidade validada a partir de um tarball realmente instalado.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- **As implementações canônicas de perfis e sensores agora pertencem a
|
|
18
|
+
`packages/harness/src`.** `src/operating-profile.mjs` e `hooks/sensors-core.mjs` permanecem como
|
|
19
|
+
fachadas de reexport, preservando funções, constantes, objetos congelados e consumidores atuais.
|
|
20
|
+
- **A direção modular do Harness virou contrato estrutural.** O workspace aceita somente built-ins,
|
|
21
|
+
módulos internos e a superfície pública do Vault; Vault continua proibido de depender do
|
|
22
|
+
Harness, e CLI, MCP, Integrations e Pi permanecem adaptadores superiores.
|
|
23
|
+
- **OFF preserva a semântica publicada:** Keep Core/Vault continua ativo e a governança automática
|
|
24
|
+
fica desligada; comandos explícitos do operador não ganham bloqueios novos nesta extração.
|
|
25
|
+
|
|
26
|
+
## [0.61.0] — 2026-07-28
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- **`wendkeep/vault` passa a expor o kernel de memória operacional.** Schema v2, detecção de
|
|
31
|
+
modo, handoff, ledger/projeção e validadores CORE/SHARED ficam disponíveis pela superfície
|
|
32
|
+
pública e são exercitados a partir de um tarball realmente instalado.
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
|
|
36
|
+
- **A implementação canônica do kernel agora pertence a `packages/vault/src`.** Os paths
|
|
37
|
+
históricos em `hooks/` e `src/` permanecem como fachadas de reexport, inclusive por bare
|
|
38
|
+
specifiers no pacote instalado, preservando identidade de funções, classes, constantes e
|
|
39
|
+
compatibilidade com consumidores existentes.
|
|
40
|
+
- **A extração é estrutural e não migra dados.** Schema 2, ledger, locks, projeção,
|
|
41
|
+
SessionStop e o conteúdo de cada Vault mantêm o mesmo contrato persistente.
|
|
42
|
+
|
|
43
|
+
### Fixed
|
|
44
|
+
|
|
45
|
+
- **`memory repair` converge checkpoints assert-only históricos pré-0.59 sem editar o ledger.**
|
|
46
|
+
O repair só migra quando prefixo, revision, cursor, hash, identidade e turns reproduzem
|
|
47
|
+
exatamente a semântica antiga e o espelho `memory_checkpoint` é compatível; o caminho preserva
|
|
48
|
+
CORE/ledger, usa CAS do attempt e do espelho, backup e auditoria e continua falhando fechado para
|
|
49
|
+
tuples, operações ou espelhos divergentes.
|
|
50
|
+
|
|
7
51
|
## [0.60.0] — 2026-07-27
|
|
8
52
|
|
|
9
53
|
### Added
|
package/README.en.md
CHANGED
|
@@ -16,8 +16,12 @@
|
|
|
16
16
|
**Persistent memory for AI coding agents, built on your Obsidian vault.** Every Claude Code **and Codex** session is captured turn by turn into local Markdown — `init` wires both (Codex asks you to approve its hooks once; `import` backfills past sessions either way) — with token/cost tracking and automatically extracted decisions, bugs, and learnings. That always-on plane is **Keep Core**. On top of it, **Wend Runtime** provides a native, zero-dependency lifecycle (spec → change → TDD → sensor-gated archive), selected through the `OFF`, `FLOW`, `GUIDE`, `GOVERN`, and `ASSURE` Operating Profiles. 100% local, open-core.
|
|
17
17
|
|
|
18
18
|
The runtime is being separated into six physical boundaries — `cli`, `harness`, `vault`, `mcp`,
|
|
19
|
-
`integrations`, and `pi` — without fragmenting installation.
|
|
20
|
-
|
|
19
|
+
`integrations`, and `pi` — without fragmenting installation. The private `harness` and `vault`
|
|
20
|
+
workspaces now canonically own Operating Profiles/the sensor engine and safe binding/the Shared
|
|
21
|
+
Project Memory v2 kernel, respectively. The root package exposes those surfaces through
|
|
22
|
+
`wendkeep/harness` and `wendkeep/vault`; historical imports keep working through compatibility
|
|
23
|
+
facades and no session data needs migration;
|
|
24
|
+
see the [modular architecture](docs/en/architecture.md).
|
|
21
25
|
|
|
22
26
|
```bash
|
|
23
27
|
npm i -D wendkeep && npx wendkeep init # captures from the next session on
|
|
@@ -185,8 +189,9 @@ Operations that deserve step-by-step guidance: [verify and exits 0/1/2](https://
|
|
|
185
189
|
## Operating Profiles — Keep Core is always active
|
|
186
190
|
|
|
187
191
|
**Keep Core is always active**: the Vault, sessions, identity, CORE/SHARED, lessons, costs,
|
|
188
|
-
and persistence do not turn off with the harness. **Wend Runtime** controls only the
|
|
189
|
-
layer
|
|
192
|
+
and persistence do not turn off with the harness. **Wend Runtime** controls only the automatic
|
|
193
|
+
governance layer. Even under `OFF`, explicit WendKeep commands remain available; invoking one is a
|
|
194
|
+
deliberate opt-in and runs that command's own validations:
|
|
190
195
|
|
|
191
196
|
| Profile | Route | Use |
|
|
192
197
|
|---|---|---|
|
|
@@ -248,9 +253,11 @@ lost publication, or mismatched checkpoint blocks. See [migration](docs/en/comma
|
|
|
248
253
|
and [diagnostics](docs/en/commands/maintenance-and-diagnostics.md).
|
|
249
254
|
|
|
250
255
|
If status blocks, preserve the evidence and run `wendkeep memory repair --vault <vault>` to back up
|
|
251
|
-
the corrupt ledger, retain valid lines, and re-project. Repair never reclassifies attempts.
|
|
252
|
-
|
|
253
|
-
|
|
256
|
+
the corrupt ledger, retain valid lines, and re-project. Repair never reclassifies attempts. Valid
|
|
257
|
+
pre-0.59 causal checkpoints and exactly re-derived assert-only historical prefixes are
|
|
258
|
+
CAS-migrated on both the attempt and `memory_checkpoint` to the correct physical boundary with
|
|
259
|
+
backup/audit; divergent mirrors fail closed. A demonstrably superseded
|
|
260
|
+
ambiguity uses `memory reconcile <session> --by-session <successor>
|
|
254
261
|
--reason <reason>` as a dry run and requires `--apply`; the decision is backed up and audited
|
|
255
262
|
without rewriting ledger, CORE, or notes. Run `status --gate` again afterwards. Conflicts require
|
|
256
263
|
explicit curation with `memory promote <id>` or `memory reject <id>`; doctor only diagnoses.
|
package/README.md
CHANGED
|
@@ -16,8 +16,12 @@
|
|
|
16
16
|
**Persistent memory for AI coding agents, built on your Obsidian vault.** Every Claude Code **and Codex** session is captured turn by turn into local Markdown — `init` wires both (Codex asks you to approve its hooks once; `import` backfills past sessions either way) — with token/cost tracking and automatically extracted decisions, bugs, and learnings. That always-on plane is **Keep Core**. On top of it, **Wend Runtime** provides a native, zero-dependency lifecycle (spec → change → TDD → sensor-gated archive), selected through the `OFF`, `FLOW`, `GUIDE`, `GOVERN`, and `ASSURE` Operating Profiles. 100% local, open-core.
|
|
17
17
|
|
|
18
18
|
The runtime is being separated into six physical boundaries — `cli`, `harness`, `vault`, `mcp`,
|
|
19
|
-
`integrations`, and `pi` — without fragmenting installation.
|
|
20
|
-
|
|
19
|
+
`integrations`, and `pi` — without fragmenting installation. The private `harness` and `vault`
|
|
20
|
+
workspaces now canonically own Operating Profiles/the sensor engine and safe binding/the Shared
|
|
21
|
+
Project Memory v2 kernel, respectively. The root package exposes those surfaces through
|
|
22
|
+
`wendkeep/harness` and `wendkeep/vault`; historical imports keep working through compatibility
|
|
23
|
+
facades and no session data needs migration;
|
|
24
|
+
see the [modular architecture](docs/en/architecture.md).
|
|
21
25
|
|
|
22
26
|
```bash
|
|
23
27
|
npm i -D wendkeep && npx wendkeep init # captures from the next session on
|
|
@@ -185,8 +189,9 @@ Operations that deserve step-by-step guidance: [verify and exits 0/1/2](https://
|
|
|
185
189
|
## Operating Profiles — Keep Core is always active
|
|
186
190
|
|
|
187
191
|
**Keep Core is always active**: the Vault, sessions, identity, CORE/SHARED, lessons, costs,
|
|
188
|
-
and persistence do not turn off with the harness. **Wend Runtime** controls only the
|
|
189
|
-
layer
|
|
192
|
+
and persistence do not turn off with the harness. **Wend Runtime** controls only the automatic
|
|
193
|
+
governance layer. Even under `OFF`, explicit WendKeep commands remain available; invoking one is a
|
|
194
|
+
deliberate opt-in and runs that command's own validations:
|
|
190
195
|
|
|
191
196
|
| Profile | Route | Use |
|
|
192
197
|
|---|---|---|
|
|
@@ -248,9 +253,11 @@ lost publication, or mismatched checkpoint blocks. See [migration](docs/en/comma
|
|
|
248
253
|
and [diagnostics](docs/en/commands/maintenance-and-diagnostics.md).
|
|
249
254
|
|
|
250
255
|
If status blocks, preserve the evidence and run `wendkeep memory repair --vault <vault>` to back up
|
|
251
|
-
the corrupt ledger, retain valid lines, and re-project. Repair never reclassifies attempts.
|
|
252
|
-
|
|
253
|
-
|
|
256
|
+
the corrupt ledger, retain valid lines, and re-project. Repair never reclassifies attempts. Valid
|
|
257
|
+
pre-0.59 causal checkpoints and exactly re-derived assert-only historical prefixes are
|
|
258
|
+
CAS-migrated on both the attempt and `memory_checkpoint` to the correct physical boundary with
|
|
259
|
+
backup/audit; divergent mirrors fail closed. A demonstrably superseded
|
|
260
|
+
ambiguity uses `memory reconcile <session> --by-session <successor>
|
|
254
261
|
--reason <reason>` as a dry run and requires `--apply`; the decision is backed up and audited
|
|
255
262
|
without rewriting ledger, CORE, or notes. Run `status --gate` again afterwards. Conflicts require
|
|
256
263
|
explicit curation with `memory promote <id>` or `memory reject <id>`; doctor only diagnoses.
|
|
@@ -47,8 +47,12 @@ npx wendkeep validate-memory --vault <v2-vault>
|
|
|
47
47
|
mismatched checkpoint blocks; `degraded` with an intact outbox is a warning.
|
|
48
48
|
- `memory repair` is structural only: it uses PID/token-owned locks, writes a `.bak`, retains
|
|
49
49
|
valid events, and reprojects state. When it recognizes a valid pre-0.59 checkpoint whose cursor
|
|
50
|
-
is causal, it CAS-migrates it to the physical boundary
|
|
51
|
-
|
|
50
|
+
is causal, it CAS-migrates it to the physical boundary. It also recognizes an assert-only
|
|
51
|
+
historical prefix only when revision, cursor, hash, identity, turns, and the
|
|
52
|
+
`memory_checkpoint` mirror exactly reproduce the old semantics; the target is the current replay
|
|
53
|
+
of that prefix, without absorbing later events. Both paths CAS-check the attempt and mirror and
|
|
54
|
+
record backup/audit. Repair never reclassifies registry attempts or accepts a tuple, operation,
|
|
55
|
+
or mirror that cannot be fully re-derived.
|
|
52
56
|
- `memory reconcile` is a dry run by default. `--apply` requires two named sessions plus a reason,
|
|
53
57
|
CAS-checks the exact attempt, backs up the registry, and limits mutation to the ambiguous attempt
|
|
54
58
|
and its successor. Replay is CORE-aware, checkpoints use the physical ledger cursor, and the
|
|
@@ -8,6 +8,10 @@ Choose how much Wend Runtime governance an execution needs without disabling **K
|
|
|
8
8
|
Keep Core is always active: the Vault, session, identity, CORE/SHARED, lessons, costs, and
|
|
9
9
|
persistence integrations continue working under every profile.
|
|
10
10
|
|
|
11
|
+
The `OFF` profile disables automatic governance activation, not the CLI: explicit commands such as
|
|
12
|
+
`profile`, `flow`, `change`, `verify`, and `sensors` remain available. Invoking one is a deliberate
|
|
13
|
+
opt-in and runs that command's own validations and gates.
|
|
14
|
+
|
|
11
15
|
## When to use
|
|
12
16
|
|
|
13
17
|
Use `profile` to inspect or explicitly select an Operating Profile. Use `FLOW` for local,
|
|
@@ -44,11 +48,28 @@ Every FLOW subcommand also accepts `--project <path>`, `--vault <path>`, and `--
|
|
|
44
48
|
provided, `--session` scopes ID-based reads and mutations to the session that owns the FLOW; an
|
|
45
49
|
ID from another session fails without mutation.
|
|
46
50
|
|
|
51
|
+
## Ownership and programmatic surface
|
|
52
|
+
|
|
53
|
+
The private `packages/harness` workspace canonically owns Operating Profile resolution/policy and
|
|
54
|
+
the sensor engine. Programmatic consumers use the public root-package subpath:
|
|
55
|
+
|
|
56
|
+
```js
|
|
57
|
+
import {
|
|
58
|
+
resolveOperatingProfile,
|
|
59
|
+
runSensors,
|
|
60
|
+
evaluateGate,
|
|
61
|
+
} from 'wendkeep/harness';
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`src/operating-profile.mjs` and `hooks/sensors-core.mjs` are compatibility facades only. Dependency
|
|
65
|
+
direction is `adapters (cli/mcp/integrations/pi) -> Harness -> Vault`; Vault never depends on
|
|
66
|
+
Harness. The workspaces remain private and are not published as independent npm packages.
|
|
67
|
+
|
|
47
68
|
## Options and exit codes
|
|
48
69
|
|
|
49
70
|
| Profile | Route | Contract |
|
|
50
71
|
|---|---|---|
|
|
51
|
-
| `OFF` | LLM-native harness |
|
|
72
|
+
| `OFF` | LLM-native harness | Automatic governance off; Keep Core and explicit commands available. |
|
|
52
73
|
| `FLOW` | E → V | Microcontract with Git baseline, allowlist, sensors, and receipt, without a change. |
|
|
53
74
|
| `GUIDE` | P → E → V | Compact change; policy recognized for compatible evolution. |
|
|
54
75
|
| `GOVERN` | P → R → E → V | Current a2 loop and conservative fallback. |
|
|
@@ -145,9 +166,10 @@ npx wendkeep flow promote $flowId --change-slug another-slug
|
|
|
145
166
|
## Expected result
|
|
146
167
|
|
|
147
168
|
Changing profile neither creates a new session nor interrupts the Vault. In `OFF`, memory and
|
|
148
|
-
lessons are still injected and Stop still persists the session/memory lifecycle, while
|
|
149
|
-
router, skill gate, change context/warn/nag/guard, and plan capture are inactive.
|
|
150
|
-
|
|
169
|
+
lessons are still injected and Stop still persists the session/memory lifecycle, while automatic
|
|
170
|
+
router, skill gate, change context/warn/nag/guard, and plan capture are inactive. Explicit commands
|
|
171
|
+
remain available and run their own contracts. A completed FLOW leaves a durable, inspectable
|
|
172
|
+
receipt; a promoted FLOW enters the normal change lifecycle.
|
|
151
173
|
|
|
152
174
|
## Common errors and diagnosis
|
|
153
175
|
|
|
@@ -47,8 +47,12 @@ npx wendkeep validate-memory --vault <cofre-v2>
|
|
|
47
47
|
publicação perdida ou checkpoint divergente bloqueiam; `degraded` com outbox íntegra é warning.
|
|
48
48
|
- `memory repair` é exclusivamente estrutural: trabalha sob locks com owner PID/token, salva
|
|
49
49
|
`.bak`, retém eventos válidos e reprojeta. Quando reconhece um checkpoint pré-0.59 válido com
|
|
50
|
-
cursor causal, migra-o por CAS para a fronteira física
|
|
51
|
-
|
|
50
|
+
cursor causal, migra-o por CAS para a fronteira física. Também reconhece um prefixo histórico
|
|
51
|
+
assert-only somente quando revision, cursor, hash, identidade, turns e o espelho
|
|
52
|
+
`memory_checkpoint` reproduzem exatamente a semântica antiga; o alvo é o replay atual daquele
|
|
53
|
+
prefixo, sem absorver eventos posteriores. Ambos os casos fazem CAS do attempt e do espelho e
|
|
54
|
+
registram backup/auditoria. O repair nunca reclassifica attempts do registry nem aceita tuple,
|
|
55
|
+
operação ou espelho que não seja rederivado integralmente.
|
|
52
56
|
- `memory reconcile` é dry-run por padrão. `--apply` exige duas sessões nomeadas e motivo, faz CAS
|
|
53
57
|
do attempt exato, salva backup do registry e limita a mutação ao attempt ambíguo e à sucessora.
|
|
54
58
|
O replay é CORE-aware, usa cursor físico do ledger no checkpoint e não reescreve ledger, CORE ou
|
|
@@ -8,6 +8,10 @@ Escolher quanta governança do Wend Runtime uma execução precisa sem desligar
|
|
|
8
8
|
O Keep Core permanece sempre ativo: Vault, sessão, identidade, CORE/SHARED, lessons, custos e
|
|
9
9
|
integrações de persistência continuam funcionando em todos os perfis.
|
|
10
10
|
|
|
11
|
+
O perfil `OFF` desativa a ativação automática da governança, não a CLI: comandos explícitos como
|
|
12
|
+
`profile`, `flow`, `change`, `verify` e `sensors` continuam disponíveis. Invocá-los é um opt-in
|
|
13
|
+
deliberado e executa as validações e gates próprios daquele comando.
|
|
14
|
+
|
|
11
15
|
## Quando usar
|
|
12
16
|
|
|
13
17
|
Use `profile` para consultar ou selecionar explicitamente um Perfil de Operação. Use `FLOW` para
|
|
@@ -44,11 +48,29 @@ Todos os subcomandos FLOW também aceitam `--project <path>`, `--vault <path>` e
|
|
|
44
48
|
Quando informado, `--session` restringe inclusive consultas e mutações por ID à sessão dona do
|
|
45
49
|
FLOW; um ID de outra sessão falha sem mutação.
|
|
46
50
|
|
|
51
|
+
## Ownership e superfície programática
|
|
52
|
+
|
|
53
|
+
O workspace privado `packages/harness` é o dono canônico da resolução/política dos Perfis de
|
|
54
|
+
Operação e da engine de sensores. Consumidores programáticos usam o subpath público do pacote raiz:
|
|
55
|
+
|
|
56
|
+
```js
|
|
57
|
+
import {
|
|
58
|
+
resolveOperatingProfile,
|
|
59
|
+
runSensors,
|
|
60
|
+
evaluateGate,
|
|
61
|
+
} from 'wendkeep/harness';
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`src/operating-profile.mjs` e `hooks/sensors-core.mjs` são somente fachadas de compatibilidade. A
|
|
65
|
+
direção de dependências é `adapters (cli/mcp/integrations/pi) -> Harness -> Vault`; o Vault nunca
|
|
66
|
+
depende do Harness. Os workspaces continuam privados e não são publicados como pacotes npm
|
|
67
|
+
independentes.
|
|
68
|
+
|
|
47
69
|
## Opções e códigos de saída
|
|
48
70
|
|
|
49
71
|
| Perfil | Rota | Contrato |
|
|
50
72
|
|---|---|---|
|
|
51
|
-
| `OFF` | harness nativo da LLM |
|
|
73
|
+
| `OFF` | harness nativo da LLM | Governança automática desligada; Keep Core e comandos explícitos disponíveis. |
|
|
52
74
|
| `FLOW` | E → V | Microcontrato com baseline Git, allowlist, sensores e recibo, sem change. |
|
|
53
75
|
| `GUIDE` | P → E → V | Change compacta; política reconhecida para evolução compatível. |
|
|
54
76
|
| `GOVERN` | P → R → E → V | Loop a2 atual e fallback conservador. |
|
|
@@ -144,8 +166,9 @@ npx wendkeep flow promote $flowId --change-slug outro-slug
|
|
|
144
166
|
|
|
145
167
|
Trocar o perfil não cria outra sessão nem interrompe o Vault. Em `OFF`, a memória e as lessons
|
|
146
168
|
continuam injetadas e o Stop continua persistindo sessão/memória, mas router, skill gate,
|
|
147
|
-
change context/warn/nag/guard e captura de plano ficam inativos.
|
|
148
|
-
|
|
169
|
+
change context/warn/nag/guard e captura de plano automáticos ficam inativos. Os comandos explícitos
|
|
170
|
+
continuam disponíveis e executam seus próprios contratos. Um FLOW concluído deixa recibo durável e
|
|
171
|
+
consultável; um FLOW promovido passa a seguir o lifecycle normal de change.
|
|
149
172
|
|
|
150
173
|
## Erros comuns e diagnóstico
|
|
151
174
|
|
package/hooks/memory-handoff.mjs
CHANGED
|
@@ -1,199 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
3
|
-
import { basename, join, relative } from 'node:path';
|
|
4
|
-
|
|
5
|
-
import { sanitizeMemoryText } from './memory-schema.mjs';
|
|
6
|
-
|
|
7
|
-
function canonicalValue(value) {
|
|
8
|
-
if (Array.isArray(value)) return value.map(canonicalValue);
|
|
9
|
-
if (!value || typeof value !== 'object') return value;
|
|
10
|
-
return Object.fromEntries(Object.keys(value).sort().map((key) => [key, canonicalValue(value[key])]));
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function eventId(context, memoryKey, value) {
|
|
14
|
-
const digest = createHash('sha256')
|
|
15
|
-
.update(JSON.stringify([
|
|
16
|
-
context.projectId,
|
|
17
|
-
context.identity?.canonicalConversationId,
|
|
18
|
-
context.activation?.id,
|
|
19
|
-
context.turn?.id,
|
|
20
|
-
memoryKey,
|
|
21
|
-
canonicalValue(value),
|
|
22
|
-
]))
|
|
23
|
-
.digest('hex')
|
|
24
|
-
.slice(0, 24);
|
|
25
|
-
return `mem-${digest}`;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function makeEvent(context, { memoryKey, value, authority, evidence }) {
|
|
29
|
-
const cleanValue = typeof value === 'string' ? sanitizeMemoryText(value) : canonicalValue(value);
|
|
30
|
-
return {
|
|
31
|
-
v: 1,
|
|
32
|
-
event_id: eventId(context, memoryKey, cleanValue),
|
|
33
|
-
project_id: String(context.projectId || ''),
|
|
34
|
-
memory_key: memoryKey,
|
|
35
|
-
operation: 'assert',
|
|
36
|
-
value: cleanValue,
|
|
37
|
-
authority,
|
|
38
|
-
canonical_session_id: String(context.identity?.canonicalConversationId || ''),
|
|
39
|
-
activation_id: String(context.activation?.id || ''),
|
|
40
|
-
activation_epoch: Number(context.activation?.epoch || 0),
|
|
41
|
-
turn_sequence: Number(context.turn?.sequence || 0),
|
|
42
|
-
source_turn_id: String(context.turn?.id || ''),
|
|
43
|
-
observed_at: context.observedAt,
|
|
44
|
-
evidence: (evidence || []).filter(Boolean).map((item) => sanitizeMemoryText(item)),
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function readJson(path) {
|
|
49
|
-
try { return JSON.parse(readFileSync(path, 'utf8')); } catch { return null; }
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function filesBelow(dir, accept, found = []) {
|
|
53
|
-
let entries = [];
|
|
54
|
-
try { entries = readdirSync(dir, { withFileTypes: true }); } catch { return found; }
|
|
55
|
-
for (const entry of entries) {
|
|
56
|
-
const path = join(dir, entry.name);
|
|
57
|
-
if (entry.isDirectory()) filesBelow(path, accept, found);
|
|
58
|
-
else if (accept(entry.name)) found.push(path);
|
|
59
|
-
}
|
|
60
|
-
return found;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function vaultRel(vaultBase, path) {
|
|
64
|
-
return relative(vaultBase, path).replaceAll('\\', '/');
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function nextActionFrom(summary) {
|
|
68
|
-
const match = String(summary || '').match(/(?:a\s+)?pr[oó]xima\s+(?:change\s+)?(?:ser[aá]|[ée]|:)\s+(?:a\s+)?([^.!?\n]+)/i);
|
|
69
|
-
if (!match) return null;
|
|
70
|
-
const text = sanitizeMemoryText(match[1].trim());
|
|
71
|
-
const id = text.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase()
|
|
72
|
-
.replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, 64);
|
|
73
|
-
return id && text ? { id, summary: text } : null;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export function collectLifecycleEvidence(vaultBase, { changeSlug = '', summary = '', noteRel = '' } = {}) {
|
|
77
|
-
const evidence = {};
|
|
78
|
-
const slug = String(changeSlug || '').trim();
|
|
79
|
-
if (slug) {
|
|
80
|
-
const changeRoots = ['08-Mudanças', '08-Changes'];
|
|
81
|
-
let archivedDir = '';
|
|
82
|
-
for (const root of changeRoots) {
|
|
83
|
-
const archive = join(vaultBase, root, '_arquivo');
|
|
84
|
-
let names = [];
|
|
85
|
-
try { names = readdirSync(archive, { withFileTypes: true }); } catch { /* absent locale */ }
|
|
86
|
-
const match = names.find((entry) => entry.isDirectory() && (entry.name === slug || entry.name.endsWith(`-${slug}`)));
|
|
87
|
-
if (match) { archivedDir = join(archive, match.name); break; }
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const adrPattern = new RegExp(`^ADR-(\\d{4})-${slug.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\.md$`, 'i');
|
|
91
|
-
const adrPath = filesBelow(vaultBase, (name) => adrPattern.test(name))[0];
|
|
92
|
-
if (archivedDir && adrPath) {
|
|
93
|
-
const adr = (basename(adrPath).match(/^ADR-\d{4}/i) || [''])[0].toUpperCase();
|
|
94
|
-
evidence.change = { slug, status: 'archived', adr, path: vaultRel(vaultBase, adrPath) };
|
|
95
|
-
const verdictPath = join(archivedDir, 'verdict.json');
|
|
96
|
-
const verdict = readJson(verdictPath);
|
|
97
|
-
if (verdict && typeof verdict.ok === 'boolean' && Array.isArray(verdict.coverage)) {
|
|
98
|
-
evidence.verdict = {
|
|
99
|
-
ok: verdict.ok,
|
|
100
|
-
covered: verdict.coverage.filter((item) => item?.covered === true).length,
|
|
101
|
-
total: verdict.coverage.length,
|
|
102
|
-
path: vaultRel(vaultBase, verdictPath),
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
const sensorPath = join(archivedDir, 'evidencia.json');
|
|
106
|
-
const sensors = readJson(sensorPath);
|
|
107
|
-
if (Array.isArray(sensors) && sensors.length && sensors.every((item) => item?.status === 'green')) {
|
|
108
|
-
evidence.sensors = [...new Set(sensors.map((item) => String(item.id || '')).filter(Boolean))].sort();
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const nextAction = nextActionFrom(summary);
|
|
114
|
-
if (nextAction) evidence.nextAction = nextAction;
|
|
115
|
-
const commit = String(summary || '').match(/\b[0-9a-f]{40}\b/i)?.[0];
|
|
116
|
-
if (commit) {
|
|
117
|
-
evidence.git = {
|
|
118
|
-
commit: commit.toLowerCase(),
|
|
119
|
-
pushed: !/(?:nenhum|sem)\s+push/i.test(String(summary || '')),
|
|
120
|
-
verified: false,
|
|
121
|
-
path: noteRel,
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
return evidence;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
export function buildSessionMemoryEvents({
|
|
128
|
-
projectId,
|
|
129
|
-
identity,
|
|
130
|
-
activation,
|
|
131
|
-
turn,
|
|
132
|
-
noteRel,
|
|
133
|
-
observedAt,
|
|
134
|
-
summary,
|
|
135
|
-
evidence = {},
|
|
136
|
-
}) {
|
|
137
|
-
const context = { projectId, identity, activation, turn, observedAt };
|
|
138
|
-
const events = [makeEvent(context, {
|
|
139
|
-
memoryKey: 'handoff.latest',
|
|
140
|
-
value: sanitizeMemoryText(summary),
|
|
141
|
-
authority: 'reported',
|
|
142
|
-
evidence: [noteRel],
|
|
143
|
-
})];
|
|
144
|
-
|
|
145
|
-
if (evidence.change?.slug && evidence.change?.status && evidence.change?.adr) {
|
|
146
|
-
events.push(makeEvent(context, {
|
|
147
|
-
memoryKey: `change.${evidence.change.slug}.status`,
|
|
148
|
-
value: { status: evidence.change.status, adr: evidence.change.adr },
|
|
149
|
-
authority: 'verified',
|
|
150
|
-
evidence: [evidence.change.path || evidence.change.adr],
|
|
151
|
-
}));
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
if (evidence.verdict?.path && typeof evidence.verdict.ok === 'boolean') {
|
|
155
|
-
events.push(makeEvent(context, {
|
|
156
|
-
memoryKey: 'quality.latest-verdict',
|
|
157
|
-
value: {
|
|
158
|
-
ok: evidence.verdict.ok,
|
|
159
|
-
covered: Number(evidence.verdict.covered || 0),
|
|
160
|
-
total: Number(evidence.verdict.total || 0),
|
|
161
|
-
},
|
|
162
|
-
authority: 'verified',
|
|
163
|
-
evidence: [evidence.verdict.path],
|
|
164
|
-
}));
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
if (Array.isArray(evidence.sensors) && evidence.sensors.length) {
|
|
168
|
-
events.push(makeEvent(context, {
|
|
169
|
-
memoryKey: 'quality.latest-sensors',
|
|
170
|
-
value: [...new Set(evidence.sensors.map(String))].sort(),
|
|
171
|
-
authority: 'verified',
|
|
172
|
-
evidence: evidence.sensors,
|
|
173
|
-
}));
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
if (evidence.git?.commit) {
|
|
177
|
-
events.push(makeEvent(context, {
|
|
178
|
-
memoryKey: 'git.local-head',
|
|
179
|
-
value: {
|
|
180
|
-
commit: evidence.git.commit,
|
|
181
|
-
pushed: Boolean(evidence.git.pushed),
|
|
182
|
-
push_status: evidence.git.pushed ? 'pushed' : 'nenhum push',
|
|
183
|
-
},
|
|
184
|
-
authority: evidence.git.verified === false ? 'reported' : 'verified',
|
|
185
|
-
evidence: [evidence.git.path || evidence.git.commit],
|
|
186
|
-
}));
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
if (evidence.nextAction?.id && evidence.nextAction?.summary) {
|
|
190
|
-
events.push(makeEvent(context, {
|
|
191
|
-
memoryKey: `next.${evidence.nextAction.id}`,
|
|
192
|
-
value: sanitizeMemoryText(evidence.nextAction.summary),
|
|
193
|
-
authority: 'verified',
|
|
194
|
-
evidence: [noteRel],
|
|
195
|
-
}));
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
return events;
|
|
199
|
-
}
|
|
1
|
+
export * from '../packages/vault/src/memory-handoff.mjs';
|
package/hooks/memory-mode.mjs
CHANGED
|
@@ -1,89 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
|
|
4
|
-
import { classifySharedMemory } from './memory-schema.mjs';
|
|
5
|
-
import { assertVaultPathSafe } from './vault-path-safety.mjs';
|
|
6
|
-
|
|
7
|
-
export const LEGACY_MEMORY_WARNING = 'Vault legado: CORE+DIGEST permanece ativo; execute `wendkeep memory migrate --apply` quando a curadoria estiver pronta.';
|
|
8
|
-
|
|
9
|
-
function aliasBoundaryError(error) {
|
|
10
|
-
return error?.code === 'VAULT_PATH_UNSAFE'
|
|
11
|
-
&& /link simbólico|junction|reparse|hardlink|nlink|redirecion|escapa logicamente/i
|
|
12
|
-
.test(String(error?.message || error));
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function readText(vaultBase, path, label) {
|
|
16
|
-
try {
|
|
17
|
-
let checked = assertVaultPathSafe(vaultBase, path, { expectedType: 'file', label });
|
|
18
|
-
if (!checked.exists) return { exists: false, content: '', error: null };
|
|
19
|
-
checked = assertVaultPathSafe(vaultBase, checked.target, {
|
|
20
|
-
allowMissing: false, expectedType: 'file', label,
|
|
21
|
-
});
|
|
22
|
-
return { exists: true, content: readFileSync(checked.target, 'utf8'), error: null };
|
|
23
|
-
}
|
|
24
|
-
catch (error) {
|
|
25
|
-
if (aliasBoundaryError(error)) throw error;
|
|
26
|
-
return { exists: true, content: '', error };
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function hasOutboxEvidence(vaultBase, path) {
|
|
31
|
-
try {
|
|
32
|
-
let checked = assertVaultPathSafe(vaultBase, path, {
|
|
33
|
-
expectedType: 'directory', label: 'outbox de memória',
|
|
34
|
-
});
|
|
35
|
-
if (!checked.exists) return false;
|
|
36
|
-
checked = assertVaultPathSafe(vaultBase, checked.target, {
|
|
37
|
-
allowMissing: false, expectedType: 'directory', label: 'outbox de memória',
|
|
38
|
-
});
|
|
39
|
-
const entries = readdirSync(checked.target, { withFileTypes: true });
|
|
40
|
-
for (const entry of entries) {
|
|
41
|
-
assertVaultPathSafe(vaultBase, join(checked.target, entry.name), {
|
|
42
|
-
allowMissing: false, label: `entrada ${entry.name} da outbox de memória`,
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
return entries.some((entry) => entry.isFile());
|
|
46
|
-
}
|
|
47
|
-
catch (error) {
|
|
48
|
-
if (aliasBoundaryError(error)) throw error;
|
|
49
|
-
return true;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function preflightOptionalMemoryFile(vaultBase, path, label) {
|
|
54
|
-
try { assertVaultPathSafe(vaultBase, path, { expectedType: 'file', label }); }
|
|
55
|
-
catch (error) {
|
|
56
|
-
if (aliasBoundaryError(error)) throw error;
|
|
57
|
-
// Ordinary unreadable/wrong-type layers preserve the existing degraded-mode contract.
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/** A single, read-only mode decision shared by injection, health and SessionStop. */
|
|
62
|
-
export function detectMemoryMode(vaultBase) {
|
|
63
|
-
const brain = join(vaultBase, '.brain');
|
|
64
|
-
assertVaultPathSafe(vaultBase, brain, {
|
|
65
|
-
expectedType: 'directory', label: 'raiz .brain da memória',
|
|
66
|
-
});
|
|
67
|
-
// Preflight the layers consumed immediately after mode detection by injection and Stop.
|
|
68
|
-
for (const name of ['CORE.md', 'PROJECT.json', 'DIGEST.md']) {
|
|
69
|
-
preflightOptionalMemoryFile(vaultBase, join(brain, name), `camada de memória ${name}`);
|
|
70
|
-
}
|
|
71
|
-
const shared = readText(vaultBase, join(brain, 'SHARED_MEMORY.md'), 'camada SHARED_MEMORY.md');
|
|
72
|
-
if (shared.error) return { mode: 'v2', reason: 'shared-unreadable' };
|
|
73
|
-
const classified = classifySharedMemory(shared.content);
|
|
74
|
-
if (classified.mode === 'v2') return classified;
|
|
75
|
-
|
|
76
|
-
const ledger = readText(vaultBase, join(brain, 'MEMORY_EVENTS.jsonl'), 'ledger MEMORY_EVENTS.jsonl');
|
|
77
|
-
if (ledger.error) return { mode: 'v2', reason: 'ledger-unreadable' };
|
|
78
|
-
const candidates = readText(
|
|
79
|
-
vaultBase, join(brain, 'MEMORY_CANDIDATES.jsonl'), 'sidecar MEMORY_CANDIDATES.jsonl',
|
|
80
|
-
);
|
|
81
|
-
if (candidates.error) return { mode: 'v2', reason: 'candidates-unreadable' };
|
|
82
|
-
const ledgerHasEvents = ledger.content.trim().length > 0;
|
|
83
|
-
const candidatesHaveEntries = candidates.content.trim().length > 0;
|
|
84
|
-
const outboxHasEvents = hasOutboxEvidence(vaultBase, join(brain, 'memory-outbox'));
|
|
85
|
-
if (ledgerHasEvents || candidatesHaveEntries || outboxHasEvents) {
|
|
86
|
-
return { mode: 'v2', reason: 'operational-evidence' };
|
|
87
|
-
}
|
|
88
|
-
return classified;
|
|
89
|
-
}
|
|
1
|
+
export * from '../packages/vault/src/memory-mode.mjs';
|