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/src/validate-core.mjs
CHANGED
|
@@ -1,181 +1 @@
|
|
|
1
|
-
|
|
2
|
-
// Ported from NutriGym-Vision's scripts/validate-brain-core.js to ESM:
|
|
3
|
-
// - cap 25 lines (hard), 22 (soft warning) — 1 durable item per line
|
|
4
|
-
// - 3 required sections
|
|
5
|
-
// - no secrets / no real-provider PII emails
|
|
6
|
-
// Plus the seeded skeleton and the protocol reference doc.
|
|
7
|
-
|
|
8
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
9
|
-
import { isAbsolute, join, resolve } from 'node:path';
|
|
10
|
-
|
|
11
|
-
const HARD_LIMIT = 25;
|
|
12
|
-
const SOFT_LIMIT = 22;
|
|
13
|
-
|
|
14
|
-
// Bilingual (0.8.0): a CORE is valid when it carries the COMPLETE section set of either
|
|
15
|
-
// locale — pt-BR or en. Mixed/partial sets fail (the 3 sections are one contract).
|
|
16
|
-
const SECTION_SETS = {
|
|
17
|
-
'pt-BR': [
|
|
18
|
-
{ label: 'Preferências do Usuário', regex: /^##\s+Prefer[êe]ncias\s+do\s+Usu[áa]rio\s*$/im },
|
|
19
|
-
{ label: 'Padrões Ativos', regex: /^##\s+Padr[õo]es\s+Ativos\s*$/im },
|
|
20
|
-
{ label: 'Pendências Abertas', regex: /^##\s+Pend[êe]ncias\s+Abertas\s*$/im },
|
|
21
|
-
],
|
|
22
|
-
en: [
|
|
23
|
-
{ label: 'User Preferences', regex: /^##\s+User\s+Preferences\s*$/im },
|
|
24
|
-
{ label: 'Active Patterns', regex: /^##\s+Active\s+Patterns\s*$/im },
|
|
25
|
-
{ label: 'Open Items', regex: /^##\s+Open\s+Items\s*$/im },
|
|
26
|
-
],
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
// Secret patterns reject only "real" values (length floor); abstract mentions like
|
|
30
|
-
// `sk_*` / `whsec_*` (trailing asterisk) are allowed.
|
|
31
|
-
const SECRET_PATTERNS = [
|
|
32
|
-
{ name: 'Stripe secret key', regex: /\bsk_(?:live|test)_[A-Za-z0-9]{20,}\b/ },
|
|
33
|
-
{ name: 'Stripe webhook secret', regex: /\bwhsec_[A-Za-z0-9]{20,}\b/ },
|
|
34
|
-
{ name: 'JWT token', regex: /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/ },
|
|
35
|
-
{ name: 'Bearer token', regex: /\bBearer\s+[A-Za-z0-9._-]{20,}\b/i },
|
|
36
|
-
{ name: 'OpenAI API key', regex: /\bsk-[A-Za-z0-9]{40,}\b/ },
|
|
37
|
-
{ name: 'Anthropic API key', regex: /\bsk-ant-[A-Za-z0-9_-]{40,}\b/ },
|
|
38
|
-
{ name: 'Google API key', regex: /\bAIza[0-9A-Za-z_-]{35}\b/ },
|
|
39
|
-
];
|
|
40
|
-
|
|
41
|
-
const PII_EMAIL_REGEX = /\b[A-Za-z0-9._%+-]+@(?!example\.(?:com|org|net)\b)(?:gmail|hotmail|yahoo|outlook|live|icloud|protonmail)\.[A-Za-z]{2,}\b/i;
|
|
42
|
-
|
|
43
|
-
// Validate CORE.md content. Returns { ok, errors, warnings, lineCount }.
|
|
44
|
-
export function validateCore(content) {
|
|
45
|
-
const text = String(content ?? '');
|
|
46
|
-
const lines = text.split('\n');
|
|
47
|
-
const lineCount = text.endsWith('\n') ? lines.length - 1 : lines.length;
|
|
48
|
-
const errors = [];
|
|
49
|
-
|
|
50
|
-
if (lineCount > HARD_LIMIT) {
|
|
51
|
-
errors.push(`Tamanho ${lineCount} > ${HARD_LIMIT} linhas (hard limit). Curar: remover itens resolvidos (detalhe vive no vault/git).`);
|
|
52
|
-
}
|
|
53
|
-
// Pick the locale set that matches best; require it to be complete.
|
|
54
|
-
const missingBySet = Object.values(SECTION_SETS).map((set) => set.filter(({ regex }) => !regex.test(text)));
|
|
55
|
-
const best = missingBySet.reduce((a, b) => (b.length < a.length ? b : a));
|
|
56
|
-
for (const { label } of best) errors.push(`Seção obrigatória ausente: ## ${label}`);
|
|
57
|
-
for (const { name, regex } of SECRET_PATTERNS) {
|
|
58
|
-
const m = text.match(regex);
|
|
59
|
-
if (m) errors.push(`Possível ${name} detectado: "${m[0].slice(0, 30)}..." — substituir por [REDACTED_SECRET].`);
|
|
60
|
-
}
|
|
61
|
-
const em = text.match(PII_EMAIL_REGEX);
|
|
62
|
-
if (em) errors.push(`Email real detectado: "${em[0]}" — usar user@example.com.`);
|
|
63
|
-
|
|
64
|
-
const warnings = [];
|
|
65
|
-
if (lineCount >= SOFT_LIMIT && lineCount <= HARD_LIMIT) {
|
|
66
|
-
warnings.push(`Tamanho ${lineCount}/${HARD_LIMIT} linhas — perto do limite; remover itens resolvidos (≥${SOFT_LIMIT}).`);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return { ok: errors.length === 0, errors, warnings, lineCount };
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// The seeded CORE.md (must pass validateCore). Bootstraps the 3 sections so the
|
|
73
|
-
// curated hot layer exists with the right shape from day one.
|
|
74
|
-
export function renderCoreSkeleton(localeId = 'pt-BR') {
|
|
75
|
-
if (localeId === 'en') {
|
|
76
|
-
return `# CORE — curated memory core (.brain)
|
|
77
|
-
|
|
78
|
-
> RULE #1 — the project's canonical memory. Hand-curated, 25-line cap (validate: \`wendkeep validate-memory\`). Volatile facts live in DIGEST.md (auto). Depth: /brain-recall <topic>.
|
|
79
|
-
|
|
80
|
-
## User Preferences
|
|
81
|
-
- (durable preferences: language, style, conventions)
|
|
82
|
-
|
|
83
|
-
## Active Patterns
|
|
84
|
-
- (active patterns/architecture another agent must know)
|
|
85
|
-
|
|
86
|
-
## Open Items
|
|
87
|
-
- (open items/decisions — remove when resolved)
|
|
88
|
-
`;
|
|
89
|
-
}
|
|
90
|
-
return `# CORE — núcleo curado da memória (.brain)
|
|
91
|
-
|
|
92
|
-
> REGRA #1 — memória canônica do projeto. Curado à mão, cap 25 linhas (valide: \`wendkeep validate-memory\`). Volátil vive no DIGEST.md (auto). Profundidade: /brain-recall <tópico>.
|
|
93
|
-
|
|
94
|
-
## Preferências do Usuário
|
|
95
|
-
- (preferências duráveis: idioma, estilo, convenções)
|
|
96
|
-
|
|
97
|
-
## Padrões Ativos
|
|
98
|
-
- (padrões/arquitetura ativos que outro agente precise saber)
|
|
99
|
-
|
|
100
|
-
## Pendências Abertas
|
|
101
|
-
- (pendências/decisões em aberto — remova quando resolvidas)
|
|
102
|
-
`;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// The compaction-protocol reference doc dropped into the vault.
|
|
106
|
-
export function renderCompactionProtocol() {
|
|
107
|
-
return `# Protocolo de Memória — núcleo curado + digest automático (.brain)
|
|
108
|
-
|
|
109
|
-
> Como cada agente recebe, consulta e persiste memória entre sessões no seu vault.
|
|
110
|
-
|
|
111
|
-
## 1. Duas camadas
|
|
112
|
-
|
|
113
|
-
- **QUENTE** (auto-injetada por sessão, budget ~45 linhas):
|
|
114
|
-
- \`.brain/CORE.md\` — curado à mão, **≤25 linhas** (1 item/linha): preferências, padrões, pendências.
|
|
115
|
-
- \`.brain/DIGEST.md\` — auto-gerado (0 token LLM, ≤15 linhas): decisões/sessões/bugs/aprendizados recentes.
|
|
116
|
-
- **FRIA** (sob demanda):
|
|
117
|
-
- \`.brain/index.jsonl\` — índice de todas as sessões (1/linha, frontmatter).
|
|
118
|
-
- Vault: \`02-Sessões/**\`, \`04-Decisões/**\`, \`05-Bugs/**\`, \`06-Aprendizados/**\`. Desce via \`/brain-recall <tópico>\`.
|
|
119
|
-
|
|
120
|
-
## 2. Compactação = regra de geração (sem trabalho manual)
|
|
121
|
-
|
|
122
|
-
- **DIGEST se auto-compacta**: caps determinísticos (5 decisões, 4 sessões, 2 bugs, 2 aprendizados + \`+N mais\`). O velho cai do quente sozinho e permanece no índice/vault. **NUNCA editar** \`DIGEST.md\`/\`index.jsonl\`.
|
|
123
|
-
- **CORE**: quando ≥22 linhas (soft warning), remover itens resolvidos/obsoletos — o detalhe já vive no vault e no histórico do git.
|
|
124
|
-
|
|
125
|
-
## 3. O que escrever no CORE
|
|
126
|
-
|
|
127
|
-
Só estado **durável** que outro agente precise saber — preferência, padrão ativo, pendência aberta. 1 linha por item. Nunca log de sessão (isso é automático no vault).
|
|
128
|
-
|
|
129
|
-
3 seções fixas (obrigatórias): \`## Preferências do Usuário\`, \`## Padrões Ativos\`, \`## Pendências Abertas\`.
|
|
130
|
-
|
|
131
|
-
## 4. Sem segredos / PII
|
|
132
|
-
|
|
133
|
-
\`CORE.md\` nunca contém tokens (\`sk_*\`, \`whsec_*\`, JWT, Bearer), API keys, senhas ou email/telefone real. Use \`[REDACTED_SECRET]\` / \`user@example.com\`.
|
|
134
|
-
|
|
135
|
-
## 5. Validação
|
|
136
|
-
|
|
137
|
-
\`\`\`bash
|
|
138
|
-
wendkeep validate-memory # valida <vault>/.brain/CORE.md
|
|
139
|
-
wendkeep validate-memory <path> # valida outro arquivo
|
|
140
|
-
\`\`\`
|
|
141
|
-
|
|
142
|
-
Checa: cap 25 (soft 22), 3 seções, sem segredos/PII. Exit 0 = OK, 1 = falha.
|
|
143
|
-
`;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// CLI entry for `wendkeep validate-memory [path]`. Resolves the target from an
|
|
147
|
-
// explicit path, else <vault>/.brain/CORE.md (--vault or OBSIDIAN_VAULT_PATH).
|
|
148
|
-
export function runValidateMemory(argv) {
|
|
149
|
-
let target;
|
|
150
|
-
let vault;
|
|
151
|
-
for (let i = 0; i < argv.length; i += 1) {
|
|
152
|
-
const a = argv[i];
|
|
153
|
-
if (a === '--vault') vault = argv[++i];
|
|
154
|
-
else if (a.startsWith('--vault=')) vault = a.slice(8);
|
|
155
|
-
else if (!a.startsWith('-')) target = a;
|
|
156
|
-
}
|
|
157
|
-
if (!target) {
|
|
158
|
-
const base = vault || process.env.OBSIDIAN_VAULT_PATH;
|
|
159
|
-
if (!base) {
|
|
160
|
-
process.stderr.write('wendkeep validate-memory: no target. Pass a path, --vault <path>, or set OBSIDIAN_VAULT_PATH.\n');
|
|
161
|
-
process.exit(2);
|
|
162
|
-
}
|
|
163
|
-
target = join(base, '.brain', 'CORE.md');
|
|
164
|
-
}
|
|
165
|
-
const abs = isAbsolute(target) ? target : resolve(process.cwd(), target);
|
|
166
|
-
if (!existsSync(abs)) {
|
|
167
|
-
process.stderr.write(`wendkeep validate-memory: not found: ${abs}\n`);
|
|
168
|
-
process.exit(2);
|
|
169
|
-
}
|
|
170
|
-
const res = validateCore(readFileSync(abs, 'utf8'));
|
|
171
|
-
if (!res.ok) {
|
|
172
|
-
process.stderr.write(`❌ CORE.md viola protocolo (${res.errors.length} erro${res.errors.length > 1 ? 's' : ''}):\n`);
|
|
173
|
-
for (const e of res.errors) process.stderr.write(` - ${e}\n`);
|
|
174
|
-
process.stderr.write('\nProtocolo: .brain/COMPACTION_PROTOCOL.md\n');
|
|
175
|
-
process.exit(1);
|
|
176
|
-
}
|
|
177
|
-
let msg = `✅ CORE.md OK (${res.lineCount} linhas, 3/3 seções, sem segredos).`;
|
|
178
|
-
for (const w of res.warnings) msg += `\n ⚠ ${w}`;
|
|
179
|
-
process.stdout.write(`${msg}\n`);
|
|
180
|
-
process.exit(0);
|
|
181
|
-
}
|
|
1
|
+
export * from '../packages/vault/src/validate-core.mjs';
|
package/src/validate-memory.mjs
CHANGED
|
@@ -1,128 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
import { validateMemoryEvent, validateSharedMemory } from '../hooks/memory-schema.mjs';
|
|
4
|
-
import { assertVaultPathSafe } from '../hooks/vault-path-safety.mjs';
|
|
5
|
-
import { validateCore } from './validate-core.mjs';
|
|
6
|
-
|
|
7
|
-
function failedComponent(errors, extra = {}) {
|
|
8
|
-
return { ok: false, errors: Array.isArray(errors) ? errors : [errors], warnings: [], ...extra };
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function readRequired(vaultBase, path, label) {
|
|
12
|
-
let checked;
|
|
13
|
-
try {
|
|
14
|
-
checked = assertVaultPathSafe(vaultBase, path, {
|
|
15
|
-
expectedType: 'file', label: `artefato ${label}`,
|
|
16
|
-
});
|
|
17
|
-
} catch (error) {
|
|
18
|
-
return { ok: false, error: `${label} inseguro: ${error?.message || error}` };
|
|
19
|
-
}
|
|
20
|
-
if (!checked.exists) return { ok: false, error: `${label} ausente: ${path}` };
|
|
21
|
-
try {
|
|
22
|
-
// Deliberately adjacent to the open performed by readFileSync.
|
|
23
|
-
checked = assertVaultPathSafe(vaultBase, checked.target, {
|
|
24
|
-
allowMissing: false, expectedType: 'file', label: `artefato ${label}`,
|
|
25
|
-
});
|
|
26
|
-
return { ok: true, content: readFileSync(checked.target, 'utf8') };
|
|
27
|
-
} catch (error) {
|
|
28
|
-
return { ok: false, error: `${label} ilegível: ${error?.message || error}` };
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function readProjectForValidation(vaultBase) {
|
|
33
|
-
const path = join(vaultBase, '.brain', 'PROJECT.json');
|
|
34
|
-
const read = readRequired(vaultBase, path, 'PROJECT.json');
|
|
35
|
-
if (!read.ok) return failedComponent(read.error, { projectId: '', path });
|
|
36
|
-
try {
|
|
37
|
-
const marker = JSON.parse(read.content);
|
|
38
|
-
if (!marker || typeof marker.projectId !== 'string' || !marker.projectId) {
|
|
39
|
-
return failedComponent('PROJECT.json inválido: projectId ausente.', { projectId: '', path });
|
|
40
|
-
}
|
|
41
|
-
return { ok: true, errors: [], warnings: [], projectId: marker.projectId, marker, path };
|
|
42
|
-
} catch (error) {
|
|
43
|
-
return failedComponent(`PROJECT.json contém JSON inválido: ${error?.message || error}`, { projectId: '', path });
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/** Read and validate the append-only JSONL authority without repairing or mutating it. */
|
|
48
|
-
export function readLedgerForValidation(vaultBase, { projectId } = {}) {
|
|
49
|
-
const path = join(vaultBase, '.brain', 'MEMORY_EVENTS.jsonl');
|
|
50
|
-
const read = readRequired(vaultBase, path, 'MEMORY_EVENTS.jsonl');
|
|
51
|
-
if (!read.ok) return failedComponent(read.error, { events: [], eventIds: new Set(), path });
|
|
52
|
-
|
|
53
|
-
const errors = [];
|
|
54
|
-
const warnings = [];
|
|
55
|
-
const events = [];
|
|
56
|
-
const eventIds = new Set();
|
|
57
|
-
const normalized = read.content.replace(/\r\n/g, '\n');
|
|
58
|
-
const lines = normalized.split('\n');
|
|
59
|
-
const logicalLines = normalized === '' ? [] : (normalized.endsWith('\n') ? lines.slice(0, -1) : lines);
|
|
60
|
-
logicalLines.forEach((line, index) => {
|
|
61
|
-
if (!line.trim()) {
|
|
62
|
-
errors.push(`MEMORY_EVENTS.jsonl linha ${index + 1} está vazia no meio do ledger.`);
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
let event;
|
|
66
|
-
try {
|
|
67
|
-
event = JSON.parse(line);
|
|
68
|
-
} catch (error) {
|
|
69
|
-
errors.push(`MEMORY_EVENTS.jsonl linha ${index + 1} contém JSON inválido/parcial: ${error?.message || error}`);
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
const validation = validateMemoryEvent(event, projectId ? { projectId } : {});
|
|
73
|
-
for (const error of validation.errors) errors.push(`MEMORY_EVENTS.jsonl linha ${index + 1}: ${error}`);
|
|
74
|
-
for (const warning of validation.warnings) warnings.push(`MEMORY_EVENTS.jsonl linha ${index + 1}: ${warning}`);
|
|
75
|
-
if (typeof event?.event_id === 'string' && event.event_id) {
|
|
76
|
-
if (eventIds.has(event.event_id)) errors.push(`MEMORY_EVENTS.jsonl event_id duplicado: ${event.event_id}.`);
|
|
77
|
-
eventIds.add(event.event_id);
|
|
78
|
-
}
|
|
79
|
-
events.push(event);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
return {
|
|
83
|
-
ok: errors.length === 0,
|
|
84
|
-
errors,
|
|
85
|
-
warnings,
|
|
86
|
-
events,
|
|
87
|
-
eventIds,
|
|
88
|
-
lineCount: logicalLines.length,
|
|
89
|
-
path,
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function validateCoreArtifact(vaultBase) {
|
|
94
|
-
const path = join(vaultBase, '.brain', 'CORE.md');
|
|
95
|
-
const read = readRequired(vaultBase, path, 'CORE.md');
|
|
96
|
-
if (!read.ok) return failedComponent(read.error, { lineCount: 0, path });
|
|
97
|
-
return { ...validateCore(read.content), path, content: read.content };
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function validateSharedArtifact(vaultBase, eventIds) {
|
|
101
|
-
const path = join(vaultBase, '.brain', 'SHARED_MEMORY.md');
|
|
102
|
-
const read = readRequired(vaultBase, path, 'SHARED_MEMORY.md');
|
|
103
|
-
if (!read.ok) return failedComponent(read.error, { path });
|
|
104
|
-
return { ...validateSharedMemory(read.content, { eventIds }), path, content: read.content };
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export function combineMemoryResults({ project, core, ledger, shared }) {
|
|
108
|
-
const components = { project, core, ledger, shared };
|
|
109
|
-
const errors = [];
|
|
110
|
-
const warnings = [];
|
|
111
|
-
for (const [name, result] of Object.entries(components)) {
|
|
112
|
-
for (const error of result?.errors || []) errors.push(`${name}: ${error}`);
|
|
113
|
-
for (const warning of result?.warnings || []) warnings.push(`${name}: ${warning}`);
|
|
114
|
-
}
|
|
115
|
-
return { ok: errors.length === 0, errors, warnings, ...components };
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Validate the v2 local memory bundle as a read-only composition. Missing/corrupt
|
|
120
|
-
* artifacts remain explicit failures; they are never silently treated as empty.
|
|
121
|
-
*/
|
|
122
|
-
export function validateMemoryBundle(vaultBase) {
|
|
123
|
-
const project = readProjectForValidation(vaultBase);
|
|
124
|
-
const core = validateCoreArtifact(vaultBase);
|
|
125
|
-
const ledger = readLedgerForValidation(vaultBase, { projectId: project.projectId });
|
|
126
|
-
const shared = validateSharedArtifact(vaultBase, ledger.eventIds);
|
|
127
|
-
return combineMemoryResults({ project, core, ledger, shared });
|
|
128
|
-
}
|
|
1
|
+
export * from '../packages/vault/src/validate-memory.mjs';
|